<?php
//Initialize variables for downloads
$report_name = $_POST['herd-reports-download-name'];
$filetype = $_POST['group1'];
$farm_id = $_POST['farm_id'];
$username = $_POST['user_name'];
$json_array = json_decode($_POST['table_data'], true);
$wksDescription = "Report generated for farm";
//Branch casing depending on filetype
switch($filetype) {
case 'PDF':
generate_pdf();
break;
case 'Excel':
generate_excel();
break;
}
//Functions for downloads here.
function generate_pdf() {
//echo 'pdf';
}
function generate_excel() {
//echo 'excel';
require '../includes/libraries/PHPExcel/Classes/PHPExcel.php';
$objPHPExcel = new PHPExcel(); //Spreadsheet generated, one worksheet is always added to it.
$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_in_memory_serialized;
PHPExcel_Settings::setCacheStorageMethod($cacheMethod); //Serializes to cut memory usage slightly at a minor performance hit.
$objPHPExcel->getProperties()->setCreator("myself")
->setLastModifiedBy("myself")
->setTitle("Report for a farm")
->setSubject("A report")
->setDescription($wksDescription)
->setKeywords("office 2007 etc")
->setCategory("Report File");
$objPHPExcel->setActiveSheetIndex(0)
->setCellValue('A1', 'Snoop Dawg');
//Once it's done, begin prep to output to browser
$objPHPExcel->setActiveSheetIndex(0);
header('Content-Type: application/vnd.ms-excel');
header("Content-Disposition: attachment;filename=vroom");
header('Cache-Control: max-age=1');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
$objWriter->save('php://output');
}
?>
My question is on setDescription() and setting the file name, why won't php variables work? They are currently returning nothing in those respective areas. It's probably a matter of manipulating them somehow, as setDescription() takes a string as an argument, and I'm passing a string, but it doesn't work. Nothing is being returned.
The file downloads fine, but properties that aren't strictly a string being passed aren't being set.
try making that variable global within the function.
function generate_excel() {
global $wksDescription;
The variable doesn't exist within the scope of the function unless you pull it in from the outside.
Related
I have a button on a modal and when clicked I want to generate an Excel workbook based off of a xls template file and then prompt it for the user to download/open.
The button uses javascript to open a new blank window
The click event simply does
var tsUrl = baseUrl + 'index.php?r=office/gen-t-s-xls&proj='+projNo+'&leg='+legNo+'&driver='+driverId;
var win = window.open(tsUrl, '_blank');
and calls the following controller Action
public function actionGenTSXls($proj, $leg, $driver){
// https://phpspreadsheet.readthedocs.io/en/develop/topics/recipes/
if(!is_numeric($proj) || !is_numeric($leg) || !is_numeric($driver)){
echo 'Invalid entry.';
die();
}
//Get the Project Model
$model = Projects::find()
->where(['=', 'ProjNo', $proj])
->one();
if (!$model) {
echo "There was a problem with the submitted request. (1)";
die();
}
//Get the Project Leg Model
$modelLeg = ProjectsLegs::find()
->where(['=', 'ProjId', $model->ProjId])
->andWhere(['=', 'LegNo', $leg])
->andWhere(['=', 'DriverId', $driver])
->one();
if (!$modelLeg) {
echo "There was a problem with the submitted request. (2)";
die();
}
$timestamp = date('Ymd\THis'); // Date/Time stamp
$filename = 'TripSheet_'.$model->ProjNo.'_'.$modelLeg->LegId.'_'.$timestamp.'.xls';
if(YII_ENV_DEV){
$filepath = Yii::$app->basePath.'\web\files\excel\\';
}else{
$filepath = \Yii::getAlias('#webroot').'/files/excel/';
}
$file = $filepath . $filename;
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="'.$filename.'"');
header('Cache-Control: max-age=0');
$sFile = Yii::$app->basePath.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.'TS_Tmplt.xlsx';
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load( $sFile );
$spreadsheet->getProperties()
->setCreator("TS")
->setLastModifiedBy(User::findOne(Yii::$app->user->getId())->Dispatcher)
->setTitle("TS ".$model->ProjNo.'_'.$modelLeg->LegId)
->setSubject("TS ".$model->ProjNo.'_'.$modelLeg->LegId)
->setDescription("TS ".$model->ProjNo.'_'.$modelLeg->LegId);
//working on something with the spreadsheet/worksheet
$sheet = $spreadsheet->getSheetByName('Sheet1');
//Date
$sheet->setCellValue('K3', date("n/j/Y"));
$sheet->getStyle('K3')
->getNumberFormat()
->setFormatCode('m/d/yy');
//Order
if(!is_null($model->ProjNo)){$sheet->setCellValue('K4', $model->ProjNo);}
//Carrier Order
if(!is_null($model->CompTripNo)){
$sheet->setCellValue('K5', $model->CompTripNo);
$sheet->getStyle('K5')->getAlignment()->setShrinkToFit(true);
}
//Dispatcher
if(!is_null($model->DispatcherId)){$sheet->setCellValue('J11', User::findOne($modelLeg->DispatcherId)->Dispatcher);}
//Company Name
if(!is_null($model->ClientId)){
$sheet->setCellValue('E4', Clients::findOne($model->ClientId)->Company);
$sheet->getStyle('E4')->getAlignment()->setShrinkToFit(true);
}
//Start Date
if(!is_null($modelLeg->StartDt)){
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $modelLeg->StartDt);
$cellVal = $date->format("n/j/Y g:i A");
$sheet->setCellValue('E8', $cellVal);
}
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls');
$writer->save('php://output');
}
Now, in a general sense, it works. It create the xls, the user is prompted to open/download. But it generates an error in my yii log.
[error][yii\web\HeadersAlreadySentException] exception
'yii\web\HeadersAlreadySentException' with message 'Headers already
sent
I've looked at countless similar thread, and tried all sorts of permutations involving
ob_start();
if (ob_get_length()) ob_end_clean();
if (ob_get_contents()) ob_end_clean();
//etc...
nothing seems to get me around my current issue.
I've been turning in circles for quite some time (not a lack of trying, doing research, ...) and thought I'd get some experts advice and learn. I am a novice, so if you can explain so I can learn it would be greatly appreciated.
Update 1
I decided to simplify my code to see what the issue was, so I generated the xls file, so I could temporarily eliminate that code and even so, with only 7 lines of code, I get the same error!
public function actionDownload(){
$filename = "TS.xls";
$file = "C:\Users\Dev\Downloads\TS.xls";
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load( $file );
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter( $spreadsheet, 'Xls');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="'.$filename.'"');
$writer->save("php://output");
}
Update 2 / Workaround
Did more Googling, and found a posting that I tried. By adding die(); at the very end of my action, the error goes away?! Can someone explain. I'm assuming this is not a proper solution though even if it does appear to work.
Update 3 / Solution
This ended up being the proper way to handle this
replace the following part of the code
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="'.$filename.'"');
$writer->save("php://output");
with
$response = Yii::$app->getResponse();
$headers = $response->getHeaders();
$headers->set('Content-Type', 'application/vnd.ms-excel');
$headers->set('Content-Disposition', 'attachment;filename="'.$filename.'"');
$headers->set('Cache-Control: max-age=0');
ob_start();
$writer->save("php://output");
$content = ob_get_contents();
ob_clean();
return $content;
and a big thank you to all of you for taking the time to try and help me. Your time and efforts are greatly appreciated!
Right before your first header call add:
ob_end_clean(); // clear out anything that may have already been output
Your code would look something like:
$file = $filepath . $filename;
ob_end_clean(); // clear out anything that may have already been output
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="'.$filename.'"');
header('Cache-Control: max-age=0');
I was working on an Yii2 API where i need to upload a .csv or .xlsx file and read from it using PHPExcel(DEPRECATED now , but i am stuck with it as new one PhpSpreadsheet requires PHP version 5.6 or newer) and return the array of data .
This was the code used in the API function
public function actionUpload()
{
$params = $_FILES['uploadFile'];
if($params)
{
$data = array();
$model = new UploadForm();
$model->uploadFile = $_FILES['uploadFile'];
$file = UploadedFile::getInstanceByname('uploadFile');
$inputFileName = $model->getpath($file,$data);
// Read your Excel workbook
try
{
$inputFileType = \PHPExcel_IOFactory::identify($inputFileName['link']);
$objReader = \PHPExcel_IOFactory::createReader($inputFileType);
if($inputFileType == 'CSV')
{
if (mb_check_encoding(file_get_contents($inputFileName['link']), 'UTF-8'))
{
$objReader->setInputEncoding('UTF-8');
}
else
{
$objReader->setInputEncoding('Windows-1255');
//$objReader->setInputEncoding('ISO-8859-8');
}
}
$objPHPExcel = $objReader->load($inputFileName['link']);
}
catch(Exception $e)
{
die('Error loading file "'.pathinfo($inputFileName['link'],PATHINFO_BASENAME).'": '.$e->getMessage());
}
// Get worksheet dimensions
$sheet = $objPHPExcel->getSheet(0);
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
$fileData = array();
// Loop through each row of the worksheet in turn
for ($row = 1; $row <= $highestRow; $row++)
{
// Read a row of data into an array
$rowData = $sheet->rangeToArray('A' . $row . ':' . $highestColumn . $row,
NULL,
TRUE,
FALSE);
array_push($fileData,$rowData[0]);
// Insert row data array into your database of choice here
}
return $fileData;
}
}
But there are encoding issues when we upload a excel file containing hebrew data in it . As you can see the code below from the above code was used to address this issue
if (mb_check_encoding(file_get_contents($inputFileName['link']), 'UTF-8'))
{
$objReader->setInputEncoding('UTF-8');
}
else
{
$objReader->setInputEncoding('Windows-1255');
}
Later i found that UTF-8 and Windows-1255 are not the only possible encoding for the flies that may be uploaded but other encoding like UTF-16 or other ones depending upon the Operating System of user. Is there any better way to find the encoding other than using mb_check_encoding
The common error that occur during the process of reading the data in file is :
iconv(): Detected an illegal character in input string
As you can see the above error occurs due to the inability to detect the appropriate encoding of the file. Is there any workaround ?
You can attempt to use mb_detect_encoding to detect the file encoding but I find that results vary. You might have to manually specify a custom match order of encodings to get proper results. Here is an example substitute for the if statement in question:
if(inputFileType == 'CSV')
{
// Try to detect file encoding
$encoding = mb_detect_encoding(file_get_contents($inputFileName['link']),
// example of a manual detection order
'ASCII,UTF-8,ISO-8859-15');
$objReader->setInputEncoding($encoding);
}
Make sure the first clean the output buffer in your page:
ob_end_clean();
header( "Content-type: application/vnd.ms-excel" );
header('Content-Disposition: attachment; filename="uploadFile.xls"');
header("Pragma: no-cache");
header("Expires: 0");
ob_end_clean();
I need to clone the first worksheet a few times, accordingly to the amount of rows, but something may be wrong.
The code is:
public function downloadFile()
{
date_default_timezone_set('America/Sao_Paulo');
if(file_exists("xpto.xlsx")){
$objPHPExcel = PHPExcel_IOFactory::load("xpto.xlsx");
$sheets = 3;//3 is enough to throw the error
for($i = 0; $i<$sheets; $i++){
$objClonedWorksheet = clone $objPHPExcel->getSheet(0);
$objClonedWorksheet->setTitle('Sheet ' . $i);
$objClonedWorksheet->setCellValue('A1', 'Test ' . $i);
$objPHPExcel->addSheet($objClonedWorksheet);
}
$objPHPExcel->setActiveSheetIndex(0);
$filename = 'file.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="'.$filename.'"');
header('Cache-Control: max-age=0');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
ob_end_clean();
$ret = $objWriter->save('php://output');
exit;
}
}
But I got an exhausted memory error. Than I tried the most commented solution (that is actually an workaround) that is to add
ini_set('memory_limit', '-1');
I added this line just after the load function and it worked, but I don't think it is a good solution to use on a SaaS application. I don't even think most hosts (AWS, for example) will allow me to use that.
I also tried to clone the sheet before the for loop, but when use addSheet, I realized that this function doesn't create a new object and when I change the name of the sheet (by the second iteration of the for loop), it changes the last sheet created, throwing an "already existing sheet with the same name" error.
Trying to use one of the links #rhazen listed, I changed the for loop to:
$objFromSheet = $objPHPExcel->getSheet(0);
$sheets = 3;
for($i = 1; $i<=$sheets; $i++){
$objToSheet = $objPHPExcel->createSheet($i);
foreach($objFromSheet->getRowIterator() as $row){
$cellIterator = $row->getCellIterator();
$cellFrom = $cellIterator->current();
$cellTo = $objToSheet->getCell($cellFrom->getCoordinate());
$cellTo->setXfIndex($cellFrom->getXfIndex());
$cellTo->setValue($cellFrom->getValue());
}
}
But it seems not to work either. Is there a misunderstanding about Iterator or XfIndex?
The solution is in the edited question. Thanks for those who helped.
The help I expect is written at the end as Question. Please refer that so that you might be able to skip the whole explanation and save time if you already know the solution.
I am using FPDF library. By default, the library lets me open the PDF in the browser itself.
I want the file to be downloaded instead of opening in the browser, so I made a button and gave it a download attribute as the following.
<a download href="pdf_conversion/actionpdf.php?mypdf=<?php echo $mypdf ?>"><button>Download PDF</button></a>
Now, the download works fine, and the file that is being saved gets the name doc.pdf, which if I understand correctly, is the default name given to it by the library in the following Output function.
function Output($name='', $dest='')
{
// Output PDF to some destination
if($this->state<3)
$this->Close();
$dest = strtoupper($dest);
if($dest=='')
{
if($name=='')
{
$name = 'doc.pdf';
$dest = 'I';
}
else
$dest = 'F';
}
switch($dest)
{
case 'I':
// Send to standard output
$this->_checkoutput();
if(PHP_SAPI!='cli')
{
// We send to a browser
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="'.$name.'"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
}
echo $this->buffer;
break;
case 'D':
// Download file
$this->_checkoutput();
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="'.$name.'"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->buffer;
break;
case 'F':
// Save to local file
$f = fopen($name,'wb');
if(!$f)
$this->Error('Unable to create output file: '.$name);
fwrite($f,$this->buffer,strlen($this->buffer));
fclose($f);
break;
case 'S':
// Return as a string
return $this->buffer;
default:
$this->Error('Incorrect output destination: '.$dest);
}
return '';
}
The function Output is being called at the end of actionpdf.php as
$pdf->Output();
Now, I need the file name to be the value of mypdf which is being fetched from the URL using the GET method. I believe that I should be passing $mypdf as an argument in the Output function in the actionpdf.php page. I'm forced to believe that my entire logic is terribly wrong or the way I pass the variable is terribly wrong, because passing the variable as $pdf->Output($mypdf); result in a save-file pop up window which has actionpdf.htm as the file.
Question : How do I take the value of a variable from the URL using GET method, and make that value as the name of the PDF file that is being downloaded. I am making use of the FPDF library.
If you just want to grab a parameter in your URL path using GET do the following :
$myparameter = $_GET["myparameter"];
As here $myparameter will contain the value passed in URL as : http://url/myfile.php?myparameter=myvalue
Now all you have to do is pass that to the Output method:
$pdf->Output($myparameter);
Just for the record, you'd better test if the parameter you're looking for exists, let's say your default PDF file should be "pdf.pdf" :
$mypdf = "pdf";
if(isset($_GET["filename"]) && is_string($_GET["filename"])) {
$mypdf = $_GET["filename"];
}
//add the following line, NOTICE THE DOT BEFORE THE EQUAL (.=)
$mypdf .= ".pdf";
$pdf->Output($mypdf);
I figured out what the problem was. All I had to do was, pass the argument just like I was doing, and then make a small change in the Output function as the following.
if($dest=='')
{
if($name=='')
{
$name = 'doc.pdf';
$dest = 'I';
}
else
$dest = 'I';
}
The original code had $dest = 'F' in the else condition, which caused the issue.
I've been looking everywhere on how to do this with two existing files, looks like all documentation is on creating new files. I'd like to take one of the files and add the second file to it as a new worksheet then save it to the server.
I've been trying with no avail like this:
$file="test.xls";
$file2="test2.xls";
$outputFile = "final.xls";
$phpExcel = new PHPExcel($file);
$phpExcel->getActiveSheet();
$phpExcel->setActiveSheetIndex(0);
$phpExcel->addSheet($file2);
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=$outputFile");
header("Cache-Control: max-age=0");
$objWriter = PHPExcel_IOFactory::createWriter($phpExcel, "Excel5");
file_put_contents($outputFile, $objWriter);
Any help would be greatly appreciated. Very new to PHP.
Doesn't anybody ever read documentation these days? There's a whole document in the folder called /Documentation about reading files to PHPExcel objects (it's called PHPExcel User Documentation - Reading Spreadsheet Files), together with dozens of examples (the /Documentation/Examples/Reader folder is a good place to look), and none of them use new PHPExcel($file). Nor do any of the examples or any of the documents say to use file_put_contents() when saving.
$file1="test.xls";
$file2="test2.xls";
$outputFile = "final.xls";
// Files are loaded to PHPExcel using the IOFactory load() method
$objPHPExcel1 = PHPExcel_IOFactory::load($file1);
$objPHPExcel2 = PHPExcel_IOFactory::load($file2);
// Copy worksheets from $objPHPExcel2 to $objPHPExcel1
foreach($objPHPExcel2->getAllSheets() as $sheet) {
$objPHPExcel1->addExternalSheet($sheet)
}
// Save $objPHPExcel1 to browser as an .xls file
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel1, "Excel5");
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=$outputFile");
header("Cache-Control: max-age=0");
$objWriter->save('php://output');
// update from office site
$filenames = array('doc1.xlsx', 'doc2.xlsx');
$bigExcel = new PHPExcel();
$bigExcel->removeSheetByIndex(0);
$reader = PHPExcel_IOFactory::createReader($input_file_type);
foreach ($filenames as $filename) {
$excel = $reader->load($filename);
foreach ($excel->getAllSheets() as $sheet) {
$bigExcel->addExternalSheet($sheet);
}
foreach ($excel->getNamedRanges() as $namedRange) {
$bigExcel->addNamedRange($namedRange);
}
}
$writer = PHPExcel_IOFactory::createWriter($bigExcel, 'Excel5');
$file_creation_date = date("Y-m-d");
// name of file, which needs to be attached during email sending
$saving_name = "Report_Name" . $file_creation_date . '.xls';
// save file at some random location
$writer->save($file_path_location . $saving_name);
// More Detail : with different object:
Merge multiple xls file into single one is explained here:
I'm going to describe a bit different:
http://rosevinod.wordpress.com/2014/03/15/combine-two-or-more-xls-files-as-worksheets-phpexcel/
// Combine all .csv files into one .xls file,
$cache_dir = "/home/user_name/public_html/";
$book1 = $cache_dir . "book1.csv";
$book2 = $cache_dir . "book2.csv";
$outputFile = $cache_dir . "combined.xls";
$inputFileType = 'CSV';
$inputFileNames = array($book1,$book2);
$objReader = new PHPExcel_Reader_CSV();
/** Extract the first named file from the array list **/
$inputFileName = array_shift($inputFileNames);
/** Load the initial file to the first worksheet in a PHPExcel Object **/
$objPHPExcel = $objReader->load($inputFileName);
/** Set the worksheet title (to the filename that we've loaded) **/
$objPHPExcel->getActiveSheet()
->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME));
/** Loop through all the remaining files in the list **/
foreach($inputFileNames as $sheet => $inputFileName) {
/** Increment the worksheet index pointer for the Reader **/
$objReader->setSheetIndex($sheet+1);
/** Load the current file into a new worksheet in PHPExcel **/
$objReader->loadIntoExisting($inputFileName,$objPHPExcel);
/** Set the worksheet title (to the filename that we've loaded) **/
$objPHPExcel->getActiveSheet()->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME));
}
$objWriter = new PHPExcel_Writer_Excel5($objPHPExcel);
$objWriter->save( $outputFile );
$objPHPExcel->disconnectWorksheets();
unset($objPHPExcel);
echo "DONE";