I am writing a PHP export script for my copy of magento. For some reason, the following code gives me a "Headers already sent" error:
//Load magento and set to match frontend
require_once '../../app/Mage.php';
umask(0);
Mage::app();
Mage::app()->loadArea(Mage_Core_Model_App_Area::AREA_FRONTEND);
//Send headers to browser to prep for csv file download
header('Content-Type: text/csv');
header('Content-Disposition: attachment;filename=exportSKUs.csv');
//Load only product collection details that we need.
$product = Mage::getModel('catalog/product');
$product->getCollection()->getSelect()->limit(5);
$products = $product->getCollection()
->addFieldToFilter('status','1')
->addAttributeToSelect('name')
->addAttributeToSelect('sku')
->addAttributeToSelect('upc')
->addAttributeToSelect('status')
->addAttributeToSelect('price')
->addAttributeToSelect('special_price')
->addAttributeToSelect('description')
->addAttributeToSelect('category_ids')
->addAttributeToSelect('short_description');
//Open current output to fputcsv
$fp = fopen('php://output', 'w');
//CSV headers
$headerRow = array(
'name',
'sku',
'upc',
'status',
'price',
'special_price',
'description',
'category_ids',
'short_description'
);
fputcsv($fp, $headerRow);
$count = 0;
//CSV Rows
foreach($products as &$product){
$categoryIds = implode(',', $product->getCategoryIds());
$row = array(
$product->getName(),
$product->getSku(),
$product->getUpc(),
$product->getStatus(),
$product->getPrice(),
$product->getSpecialPrice(),
$product->getDescription(),
$categoryIds,
$product->getShortDescription()
);
fputcsv($fp, $row);
$count++;
if($count>5){
//Close current output (save csv)
fclose($fp);
exit;
}
}
The line of code here that is causing me problems is this: fputcsv($fp, $headerRow);
For some reason, when this line is commented out the script runs fine. However, when this line is run with the script, it shoots the header already sent error. I don't understand why I am able to run fputcsv INSIDE my foreach loop any number of times (fputcsv($fp, $row);)but I can not run it before the foreach loop at all.
I have ways around this issue, so it's not super critical, but I really wish I could understand what was going on here to cause this.
Thanks for your time!
i have change your code ... check this.I have using magento process to export...
<?php
//Load magento and set to match frontend
require_once '../../app/Mage.php';
umask(0);
Mage::app();
Mage::app()->loadArea(Mage_Core_Model_App_Area::AREA_FRONTEND);
//Send headers to browser to prep for csv file download
//header('Content-Type: text/csv');
//header('Content-Disposition: attachment;filename=exportSKUs.csv');
//
$filename="exportSKUs.csv";
//Load only product collection details that we need.
$product = Mage::getModel('catalog/product');
$product->getCollection()->getSelect()->limit(5);
$products = $product->getCollection()
->addFieldToFilter('status','1')
->addAttributeToSelect('name')
->addAttributeToSelect('sku')
->addAttributeToSelect('upc')
->addAttributeToSelect('status')
->addAttributeToSelect('price')
->addAttributeToSelect('special_price')
->addAttributeToSelect('description')
->addAttributeToSelect('category_ids')
->addAttributeToSelect('short_description');
$io = new Varien_Io_File();
$path = Mage::getBaseDir('var') . DS . 'export' . DS;
$name = md5(microtime());
$file = $path . DS . $name . '.csv';
$io->setAllowCreateFolders(true);
$io->open(array('path' => $path));
$io->streamOpen($filename, 'w+');
$io->streamLock(true);
$headerRow = array(
'name',
'sku',
'upc',
'status',
'price',
'special_price',
'description',
'category_ids',
'short_description'
);
$io->streamWriteCsv($headerRow);
foreach($products as &$product){
$categoryIds = implode(',', $product->getCategoryIds());
$row = array(
$product->getName(),
$product->getSku(),
$product->getUpc(),
$product->getStatus(),
$product->getPrice(),
$product->getSpecialPrice(),
$product->getDescription(),
$categoryIds,
$product->getShortDescription()
);
$io->streamWriteCsv($row);
}
?>
Related
I need to generate an Excel file with extension .xlsx.
Here is my simple code:
$file = "test.xlsx";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1\tCol2\tCol3\t\n";
$content .= "test1\ttest1\ttest3\t\n";
$content .= "testtest1\ttesttest2\ttesttest3\t\n";
echo $content;
But I get this error when I open the generated file:
Excel cannot open the file 'test.xlsx' because the file format or file extension is not valid.
Any ideas?
SimpleXLSXGen
$books = [
['ISBN', 'title', 'author', 'publisher', 'ctry' ],
[618260307, 'The Hobbit', 'J. R. R. Tolkien', 'Houghton Mifflin', 'USA'],
[908606664, 'Slinky Malinki', 'Lynley Dodd', 'Mallinson Rendel', 'NZ']
];
$xlsx = Shuchkin\SimpleXLSXGen::fromArray( $books );
$xlsx->saveAs('books.xlsx');
// $xlsx->downloadAs('books.xlsx');
After trying a few options, I found that PHP_XLSX_Writer suited my needs.
PHP_XLSX_Writer
-
...designed to be lightweight, minimal memory usage, generates an
Excel-compatible workbook in XLSX format, with basic features supported:
- supports PHP 5.2.1+
- takes 'UTF-8' characters (or encoded input)
- multiple worksheets
- supports currency/date/numeric cell formatting, simple formulas
- supports basic cell styling
- supports writing huge 100K+ row spreadsheets
(Adapted from the library's GitHub repository)
Here's an working example demonstrating a few features on 3 worksheets (tabs):
First create a file called xlsxwriter.class.php containing the code found here.
Create another PHP file (in the same folder) containing:
require('xlsxwriter.class.php');
$fname='my_1st_php_excel_workbook.xlsx';
$header1 = [ 'create_date' => 'date',
'quantity' => 'string',
'product_id' => 'string',
'amount' => 'money',
'description' => 'string' ];
$data1 = [ ['2021-04-20', 1, 27, '44.00', 'twig'],
['2021-04-21', 1, '=C1', '-44.00', 'refund'] ];
$data2 = [ ['2','7','ᑌᑎIᑕᗝᗪᗴ ☋†Ϝ-➑'],
['4','8','😁'] ];
$styles2 = array( ['font-size'=>6],['font-size'=>8],['font-size'=>10],['font-size'=>16] );
$writer = new XLSXWriter();
$writer->setAuthor('Your Name Here');
$writer->writeSheet($data1,'MySheet1', $header1); // with headers
$writer->writeSheet($data2,'MySheet2'); // no headers
$writer->writeSheetRow('MySheet2', $rowdata = array(300,234,456,789), $styles2 );
$writer->writeToFile($fname); // creates XLSX file (in current folder)
echo "Wrote $fname (".filesize($fname)." bytes)<br>";
// ...or instead of creating the XLSX you can just trigger a
// download by replacing the last 2 lines with:
// header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
// header('Content-Disposition: attachment;filename="'.$fname.'"');
// header('Cache-Control: max-age=0');
// $writer->writeToStdOut();
More Information:
GitHub repository
author's website
lots more examples
As others have mentioned, PhpSpreadsheet provides a nice library for this. Assuming you have it installed via composer and the vendor/autoload.php has been included in your project, you can use the function below to generate an xlsx from an array of arrays. I've included extensive comments here to help teach beginners about how PhpSpreadsheet works and what the code is doing:
function writeXLSX($filename, $rows, $keys = [], $formats = []) {
// instantiate the class
$doc = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $doc->getActiveSheet();
// $keys are for the header row. If they are supplied we start writing at row 2
if ($keys) {
$offset = 2;
} else {
$offset = 1;
}
// write the rows
$i = 0;
foreach($rows as $row) {
$doc->getActiveSheet()->fromArray($row, null, 'A' . ($i++ + $offset));
}
// write the header row from the $keys
if ($keys) {
$doc->setActiveSheetIndex(0);
$doc->getActiveSheet()->fromArray($keys, null, 'A1');
}
// get last row and column for formatting
$last_column = $doc->getActiveSheet()->getHighestColumn();
$last_row = $doc->getActiveSheet()->getHighestRow();
// autosize all columns to content width
for ($i = 'A'; $i <= $last_column; $i++) {
$doc->getActiveSheet()->getColumnDimension($i)->setAutoSize(TRUE);
}
// if $keys, freeze the header row and make it bold
if ($keys) {
$doc->getActiveSheet()->freezePane('A2');
$doc->getActiveSheet()->getStyle('A1:' . $last_column . '1')->getFont()->setBold(true);
}
// format all columns as text
$doc->getActiveSheet()->getStyle('A2:' . $last_column . $last_row)->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT);
if ($formats) {
// if there are user supplied formats, set each column format accordingly
// $formats should be an array with column letter as key and one of the PhpOffice constants as value
// https://phpoffice.github.io/PhpSpreadsheet/1.2.1/PhpOffice/PhpSpreadsheet/Style/NumberFormat.html
// EXAMPLE:
// ['C' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00, 'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00]
foreach ($formats as $col => $format) {
$doc->getActiveSheet()->getStyle($col . $offset . ':' . $col . $last_row)->getNumberFormat()->setFormatCode($format);
}
}
// write and save the file
$writer = new PhpOffice\PhpSpreadsheet\Writer\Xlsx($doc);
$writer->save($filename);
}
EXAMPLE USAGE:
$rows = [
['sku' => 'A123', 'price' => '99'],
['sku' => 'B456', 'price' => '5.35'],
['sku' => 'C789', 'price' => '17.7']
];
$keys = array_keys(current($rows));
$formats = ['B' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00];
writeXLSX('pricelist.xlsx', $rows, $keys, $formats);
You can still use previous versions of PHPSpreadsheet if you are using old versions of PHP.
You have to include the version number of PHPSpreadsheet while installing it in your project directory.
This worked for me:
composer require phpoffice/phpspreadsheet:1.8.0
May be the file is not compatible with the version of Excel that is used.
Try with change your file extension 'xlsx' to 'xls and check it's working or not.
if it's working then this file extension(.xlsx) is not compatible with the version of Excel that, you used.
The content is not a valid xlsx content. Try sending your content as a comma separated value content and use xls to open that
As a suggestion, If u can change your content, May be you could try this?
$file = "test.csv";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1,Col2,Col3\n";
$content .= "test1,test1,test3\n";
$content .= "testtest,ttesttest2,testtest3\n";
echo $content;
See my code, here I have made simple xlsx file
<?php
include 'PHPExcel/PHPExcel.php';
include 'PHPExcel/PHPExcel/Writer/Excel2007.php';
$objPHPExcel = new PHPExcel();
$objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->SetCellValue('A1', 'Hello');
$objPHPExcel->getActiveSheet()->SetCellValue('B1', 'Trudeau');
$objPHPExcel->getActiveSheet()->SetCellValue('C1', 'Fernandes');
$objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel);
$objWriter->save(str_replace('.php', '.xlsx', __FILE__));
echo " Click here for gerate xlsx file <a href='test.xlsx'>clicking me</a>";
?>
and for me, it's working fine.
Found a handful of questions on here about this with no answer, so hopefully, someone can point me in the right direction...
I'm trying to create and save a csv file to storage, then update the DB in Laravel. I can create the file successfully, and I can update the DB successfully... but I'm stuck on putting them both together. In my controller, I have this for creating the file (taken from here):
public function updatePaymentConfirm(Request $request) {
$users = User::all();
$fileName = 'test.csv';
$headers = array(
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=$fileName",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
);
$columns = array('First Name', 'Email');
$callback = function() use($users, $columns) {
$file = fopen('php://output', 'w');
fputcsv($file, $columns);
foreach ($users as $user) {
$row['First Name'] = $user->first_name;
$row['Email'] = $user->email;
fputcsv($file, array($row['First Name'], $row['Email']));
}
fclose($file);
};
// return response()->stream($callback, 200, $headers);
}
When the function completes, the last line (that's commented out) prompts the user to download the newly created file (which is not the functionality I'm looking for). I tried adding this to my controller in its place for saving to storage and also updating the database:
$fileModel = new UserDocument;
if($callback) {
$filePath = $callback->storeAs('uploads', $fileName, 'public');
$fileModel->name = $fileName;
$fileModel->file_path = '/storage/' . $filePath;
$fileModel->save();
return back()
->with('success','File has been uploaded.')
->with('file', $fileName);
}
It saves a row to the db, albeit incorrectly, but it doesn't save the file to storage. I've reworked the $filePath line a million times, but I keep getting this error Call to a member function storeAs() on resource or something similar. I'm relatively new to working with Laravel, so I'm not sure what I should be looking for. Thoughts?
Removed everything and started over... got it! And for anyone else running into the same issue: just calling for a file that doesn't exist creates the file (unless the file exists - then it updates it), so you don't have to create a temp file or use $file = fopen('php://output', 'w'); to create the file. It'll automatically "save" the newly generated file in the file path you specified when you fclose() out of the file.
The only thing I'll note is that the file path has to exist (the file doesn't, but the file path does). In my instance, the file path already exists, but if yours doesn't or if you're not sure if it does, check to see if it exists, and then make the directory.
public function updatePaymentConfirm(Request $request) {
$user = Auth::user();
$path = storage_path('app/public/docs/user_docs/'.$user->id);
$fileName = $user->ein.'.csv';
$file = fopen($path.$fileName, 'w');
$columns = array('First Name', 'Email Address');
fputcsv($file, $columns);
$data = [
'First Name' => $user->first_name,
'Email Address' => $user->email,
];
fputcsv($file, $data);
fclose($file);
$symlink = 'public/docs/user_docs/'.$user->id.'/';
$fileModel = new UserDocument;
$fileModel->name = 'csv';
$fileModel->file_path = $symlink.$fileName;
$fileModel->save();
return redirect()->route('completed');
}
** UPDATE **
Everything worked perfectly locally, and when I pushed this to production, I received this error 🙄:
fopen(https://..../12-3456789.csv): failed to open stream: HTTP wrapper does not support writeable connections.
I'm saving to an s3 bucket, and I had to rework the entire process. You can't create and/or write to a file in the directory. I had to create a temp file first. Here's where I landed:
$user = Auth::user();
$s3 = Storage::disk('s3');
$storage = Storage::disk('s3')->url('/');
$path = 'public/docs/user_docs/'.$user->id.'/';
$csvFile = tmpfile();
$csvPath = stream_get_meta_data($csvFile)['uri'];
$fd = fopen($csvPath, 'w');
$columns = array('First Name', 'Email Address');
$data = array(
'First Name' => $user->first_name,
'Email Address' => $user->email,
);
fputcsv($fd, $columns);
fputcsv($fd, $data);
fclose($fd);
$s3->putFileAs('', $csvPath, $path.$user->ein.'.csv');
Today I have fixed it with this snipe:
// output up to 5MB is kept in memory, if it becomes bigger it will
// automatically be written to a temporary file
$csv = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
fputcsv($csv, array('blah','blah'));
rewind($csv);
$output = stream_get_contents($csv);
// Put the content directly in file into the disk
Storage::disk('myDisk')->put("report.csv", $output);
This code is easy and functional, use Laravel Storage Class
https://laravel.com/docs/9.x/filesystem#main-content
use Illuminate\Support\Facades\Storage;
// data array
$results = [
['id' => 0, 'name' => 'David', 'parent' => 1],
['id' => 1, 'name' => 'Ron', 'parent' => 0],
['id' => 2, 'name' => 'Mark', 'parent' => 1]
];
// create a variable to store data
$pages = "id,name,parent\n"; // use " not ' or \n not working
// use foreach to data
foreach ($results as $where) {
$pages .= "{$where['id']},{$where['name']},{$where['parent']}\n";
}
// use Fecades Laravel Storage
Storage::disk('local')->put('file.csv', $pages);
I need to generate an Excel file with extension .xlsx.
Here is my simple code:
$file = "test.xlsx";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1\tCol2\tCol3\t\n";
$content .= "test1\ttest1\ttest3\t\n";
$content .= "testtest1\ttesttest2\ttesttest3\t\n";
echo $content;
But I get this error when I open the generated file:
Excel cannot open the file 'test.xlsx' because the file format or file extension is not valid.
Any ideas?
SimpleXLSXGen
$books = [
['ISBN', 'title', 'author', 'publisher', 'ctry' ],
[618260307, 'The Hobbit', 'J. R. R. Tolkien', 'Houghton Mifflin', 'USA'],
[908606664, 'Slinky Malinki', 'Lynley Dodd', 'Mallinson Rendel', 'NZ']
];
$xlsx = Shuchkin\SimpleXLSXGen::fromArray( $books );
$xlsx->saveAs('books.xlsx');
// $xlsx->downloadAs('books.xlsx');
After trying a few options, I found that PHP_XLSX_Writer suited my needs.
PHP_XLSX_Writer
-
...designed to be lightweight, minimal memory usage, generates an
Excel-compatible workbook in XLSX format, with basic features supported:
- supports PHP 5.2.1+
- takes 'UTF-8' characters (or encoded input)
- multiple worksheets
- supports currency/date/numeric cell formatting, simple formulas
- supports basic cell styling
- supports writing huge 100K+ row spreadsheets
(Adapted from the library's GitHub repository)
Here's an working example demonstrating a few features on 3 worksheets (tabs):
First create a file called xlsxwriter.class.php containing the code found here.
Create another PHP file (in the same folder) containing:
require('xlsxwriter.class.php');
$fname='my_1st_php_excel_workbook.xlsx';
$header1 = [ 'create_date' => 'date',
'quantity' => 'string',
'product_id' => 'string',
'amount' => 'money',
'description' => 'string' ];
$data1 = [ ['2021-04-20', 1, 27, '44.00', 'twig'],
['2021-04-21', 1, '=C1', '-44.00', 'refund'] ];
$data2 = [ ['2','7','ᑌᑎIᑕᗝᗪᗴ ☋†Ϝ-➑'],
['4','8','😁'] ];
$styles2 = array( ['font-size'=>6],['font-size'=>8],['font-size'=>10],['font-size'=>16] );
$writer = new XLSXWriter();
$writer->setAuthor('Your Name Here');
$writer->writeSheet($data1,'MySheet1', $header1); // with headers
$writer->writeSheet($data2,'MySheet2'); // no headers
$writer->writeSheetRow('MySheet2', $rowdata = array(300,234,456,789), $styles2 );
$writer->writeToFile($fname); // creates XLSX file (in current folder)
echo "Wrote $fname (".filesize($fname)." bytes)<br>";
// ...or instead of creating the XLSX you can just trigger a
// download by replacing the last 2 lines with:
// header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
// header('Content-Disposition: attachment;filename="'.$fname.'"');
// header('Cache-Control: max-age=0');
// $writer->writeToStdOut();
More Information:
GitHub repository
author's website
lots more examples
As others have mentioned, PhpSpreadsheet provides a nice library for this. Assuming you have it installed via composer and the vendor/autoload.php has been included in your project, you can use the function below to generate an xlsx from an array of arrays. I've included extensive comments here to help teach beginners about how PhpSpreadsheet works and what the code is doing:
function writeXLSX($filename, $rows, $keys = [], $formats = []) {
// instantiate the class
$doc = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $doc->getActiveSheet();
// $keys are for the header row. If they are supplied we start writing at row 2
if ($keys) {
$offset = 2;
} else {
$offset = 1;
}
// write the rows
$i = 0;
foreach($rows as $row) {
$doc->getActiveSheet()->fromArray($row, null, 'A' . ($i++ + $offset));
}
// write the header row from the $keys
if ($keys) {
$doc->setActiveSheetIndex(0);
$doc->getActiveSheet()->fromArray($keys, null, 'A1');
}
// get last row and column for formatting
$last_column = $doc->getActiveSheet()->getHighestColumn();
$last_row = $doc->getActiveSheet()->getHighestRow();
// autosize all columns to content width
for ($i = 'A'; $i <= $last_column; $i++) {
$doc->getActiveSheet()->getColumnDimension($i)->setAutoSize(TRUE);
}
// if $keys, freeze the header row and make it bold
if ($keys) {
$doc->getActiveSheet()->freezePane('A2');
$doc->getActiveSheet()->getStyle('A1:' . $last_column . '1')->getFont()->setBold(true);
}
// format all columns as text
$doc->getActiveSheet()->getStyle('A2:' . $last_column . $last_row)->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT);
if ($formats) {
// if there are user supplied formats, set each column format accordingly
// $formats should be an array with column letter as key and one of the PhpOffice constants as value
// https://phpoffice.github.io/PhpSpreadsheet/1.2.1/PhpOffice/PhpSpreadsheet/Style/NumberFormat.html
// EXAMPLE:
// ['C' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00, 'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00]
foreach ($formats as $col => $format) {
$doc->getActiveSheet()->getStyle($col . $offset . ':' . $col . $last_row)->getNumberFormat()->setFormatCode($format);
}
}
// write and save the file
$writer = new PhpOffice\PhpSpreadsheet\Writer\Xlsx($doc);
$writer->save($filename);
}
EXAMPLE USAGE:
$rows = [
['sku' => 'A123', 'price' => '99'],
['sku' => 'B456', 'price' => '5.35'],
['sku' => 'C789', 'price' => '17.7']
];
$keys = array_keys(current($rows));
$formats = ['B' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00];
writeXLSX('pricelist.xlsx', $rows, $keys, $formats);
You can still use previous versions of PHPSpreadsheet if you are using old versions of PHP.
You have to include the version number of PHPSpreadsheet while installing it in your project directory.
This worked for me:
composer require phpoffice/phpspreadsheet:1.8.0
May be the file is not compatible with the version of Excel that is used.
Try with change your file extension 'xlsx' to 'xls and check it's working or not.
if it's working then this file extension(.xlsx) is not compatible with the version of Excel that, you used.
The content is not a valid xlsx content. Try sending your content as a comma separated value content and use xls to open that
As a suggestion, If u can change your content, May be you could try this?
$file = "test.csv";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1,Col2,Col3\n";
$content .= "test1,test1,test3\n";
$content .= "testtest,ttesttest2,testtest3\n";
echo $content;
See my code, here I have made simple xlsx file
<?php
include 'PHPExcel/PHPExcel.php';
include 'PHPExcel/PHPExcel/Writer/Excel2007.php';
$objPHPExcel = new PHPExcel();
$objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->SetCellValue('A1', 'Hello');
$objPHPExcel->getActiveSheet()->SetCellValue('B1', 'Trudeau');
$objPHPExcel->getActiveSheet()->SetCellValue('C1', 'Fernandes');
$objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel);
$objWriter->save(str_replace('.php', '.xlsx', __FILE__));
echo " Click here for gerate xlsx file <a href='test.xlsx'>clicking me</a>";
?>
and for me, it's working fine.
Please help, I'm new in Yii. I want to generate and export CSV file from checked rows in CGridView. When I use static SQL query it works normally, but when I use implode function in WHERE clause - controller returns an error.
My button that called controller action:
$this->widget('bootstrap.widgets.TbButtonGroup', array(
'type' => 'primary',
'size'=>'mini',
'buttons' => array(
array(
'label' => 'Export',
'type' => 'success',
'buttonType'=>'ajaxLink',
'encodeLabel'=>true,
'icon'=> 'th white',
'url'=>Yii::app()->createUrl('/propertyPurchaseSale/ExportChecked'),
'ajaxOptions'=>array(
"type" => "post",
"data" => "js:{ids:$.fn.yiiGridView.getSelection('property-purchase-sale-grid')}",
"update" => '#', 'success'=>"js:function(data) {window.location.assign('/propertyPurchaseSale/ExportChecked');}"),
array( //htmlOptions
)
),
array(
...
),
),
));
My controller action:
public function actionExportchecked() {
header('Content-type: text/csv');
header('Content-type: multipart/form-data');
header('Content-Disposition: attachment; filename="Export_(' . date('H-i_d.m.Y') .').csv"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Description: File Transfer');
$fp = fopen('php://output', 'w');
if(Yii::app()->request->isAjaxRequest)
{
if(isset($_POST['ids']))
{
$idx = $_POST['ids'];
$count=Yii::app()->db->createCommand('SELECT COUNT(*) FROM property')->queryScalar();
// $sql='SELECT * FROM property WHERE id IN (981, 982, 985)'; --> when I use static values - all work
$sql="SELECT * FROM property WHERE id IN('".implode("', '",$idx)."')"; // --> when I use join or implode function - data exist in firebug, but page return error 500
$dataProvider=new CSqlDataProvider($sql, array(
'totalItemCount'=>$count,
'sort'=>array(
'attributes'=>array(
'fullname', 'address', 'phone', 'db_number', 'created_date'
),
),
'pagination'=>false,
));
fputs($fp, $bom =( chr(0xEF) . chr(0xBB) . chr(0xBF) ));
if ($fp)
{
echo PropertyPurchaseSale::model()->getAttributeLabel("id").";".
PropertyPurchaseSale::model()->getAttributeLabel("fullname").";".
PropertyPurchaseSale::model()->getAttributeLabel("address").";".
PropertyPurchaseSale::model()->getAttributeLabel("phone").";".
PropertyPurchaseSale::model()->getAttributeLabel("db_number").";".
PropertyPurchaseSale::model()->getAttributeLabel("created_date").
" \r\n";
foreach ($dataProvider->getData() as $data) {
echo $data['id'] . '; ' . $data['fullname'] . '; ' . $data['address'] . '; ' . $data['phone'] . '; ' . $data['db_number'] . '; ' . $data['created_date'] . '; ' . "\r\n";
}
}
exit;
}
}}
Please, help, what am I doing incorrectly?
My knee-jerk reaction is that there is nothing confirming that $_POST['ids'] is an array. If it is just a string, then implode will fail, the SQL will be improperly formatted, and that would lead to a 500 level error.
Perhaps this might work:
// check for empty, that way invalid entry won't go through
if(!empty($_POST['ids'])) {
// Check if it is an array
$input_idx = is_array($_POST['ids'])?
// if so, then use it as an array
$_POST['ids']:
// If not, you need to turn it into an array. I'm only guessing that this should
// be a ','. It could be a " " or some other character(s)
explode(',',$_POST['ids']);
// Remove all non-numeric items in the array.
$idx = array_filter($input_idx, 'is_numeric');
if(!$idx) {
// Do something with bad data.
}
// continue with the line $count = ...
You should also look to making sure that the data is sanitary as those IDs could include SQL injection. (Perhaps this answer)
I would like to output a csv file with a drupal 6 module. This is the code I have, but it's hacked together with some of it in my custom theme and some of it in my module. Is there anyway I can move all of it into my module?
///////////////////////////
// csv.module <- a custom module
///////////////////////////
function csv_menu() {
$items = array();
$items['csv/results'] = array (
'page callback' => 'csv_results_page',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
function csv_theme() {
$items = array();
$items['csv_results'] = array(
'arguments' => array(),
);
return $items;
}
function csv_results_page() {
return generate_csv_results();
}
function theme_csv_results() {
return generate_csv_results();
}
function generate_csv_results() {
return "header1,header2,header3,header4\nval1,val2,val3,val4\n";
}
//////////////////////////////
// page-csv-results.tpl.php <- in my theme. I would like it all contained in the module.
//////////////////////////////
<?php
//!TODO: Change Content Type Header
print theme('csv_results');
EDIT
Below is an updated version for anyone with a similar question. Thanks goes to chx!
///////////////////////////
// csv.module <- a custom module
///////////////////////////
function csv_menu() {
$items = array();
$items['csv/results'] = array (
'page callback' => 'csv_results_page',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
function csv_results_page() {
//Take a look at the Nikit's response on header stuff. This might be incorrect.
header('Content-Type: text/x-comma-separated-values');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private',false); // required for certain browser
print generate_csv_results();
}
function generate_csv_results() {
return "header1,header2,header3,header4\nval1,val2,val3,val4\n";
}
This is my simplified version of exporting csv file, add this code into csv_results_page(). Here used query, but can be other thing (like arrays, file listing, etc):
/**
* Export to cvs
*/
function _YOURMODULENAME_csv_export() {
$delimiter = "\t"; // This is tab delimiter, can be other
$temp = file_directory_temp();
$file = tempnam(realpath($temp), 'csv');
if (!$fp = fopen($file, 'a')) {
drupal_set_message(t('The file for exported could not be created. Refer to the site administrator'), 'error');
return;
}
$title = "Some title for csv\n"; // you can remove it, if don't want title in csv
$title .= implode($delimiter, array('Column name 1','Column name 2')). "\n"; // Add all columns titles here, it should mutch query result fields below
fwrite($fp, $title);
$query = 'WRITE HERE SOME CODE THAT SHOULD RESULT QUERY AND RETURN fields in order described in $title above';
$result = db_query($query);
while ($data = db_fetch_array($result)) {
$rows = implode($delimiter, $data);
fwrite($fp, $rows."\n");
}
fclose($fp);
$header = array();
$header[] = 'Pragma: no-cache';
$header[] = 'Cache-Control: no-cache, must-revalidate';
$header[] = 'Content-Disposition: attachment; filename="exort_'.date('Ymd',time()).'.csv";'; // This is file name
$header[] = 'Content-Type: text/x-comma-separated-values';
$header[] = 'Content-Length: '.filesize($file);
$header[] = 'Connection: close';
file_transfer($file, $header);
file_delete($file);
return;
}
Outputting CSV should be done by just printing stuff in the page callback, if you return as you do it will be in the current theme, surrounded by HTML and you dont need that.