So I installed Laravel Debugbar today in an attempt to make my local dev a better experience, and for the most part, it has. An issue has arisen though when I try to download an Excel generated from PhpOffice\PhpSpreadsheet (https://phpspreadsheet.readthedocs.io/en/latest/) Here's a snippet of the code in question:
$excelFile = new Spreadsheet();
// Load stuff from DB in Sheets, etc. etc.
$writer = new Xlsx($excelFile);
$filename = 'testing.xlsx';
$writer->save('php://output');
This works great, and the content is loaded into and downloaded as an .xlsx file without issue. When I open the file, I get this alert:
We found a problem with some content in 'testing.xlsx'. Do you want us to try to recover as much as we can? If you trust the source of this workbook, click Yes.
I click "Yes", and then I get this alert:
Excel was able to open the file by repairing or removing the unreadable content.
I click "Delete" and my file is opened, with nothing deleted (compared file against previous commit on master branch, no change).
Now here's the fun part, I only get this alert when Debugbar is enabled. In .env, I have DEBUGBAR_ENABLED=true, and if I set that to DEBUGBAR_ENABLED=false and reload/redownload, I don't get the alert. Has anyone seen this issue before? Why would Debugbar be messing with this? Is it an issue with $writer->save('php://output'); being polluted by Debugbar's injection?
Sidenote, this will be a non-issue in production, as Debugbar is a require-dev dependence, but I'm just curious if I can avoid this during local development or not.
Apparently, this has been asked and answered on the official documentation; was just a little tricky to find. I was correct about object pollution via debugbar, and the simply soultion was to add exit(); after $writer->save();:
$excelFile = new Spreadsheet();
// Load stuff from DB in Sheets, etc. etc.
$writer = new Xlsx($excelFile);
$filename = 'testing.xlsx';
$writer->save('php://output');
exit();
There were a couple more notes about ob_clean() and/or ob_end_clean(), ob_flush(), etc etc., but none of those worked with Laravel + Debugbar. For reference:
https://github.com/PHPOffice/PhpSpreadsheet/issues/217
Sidenote, this prevents Debugbar from handling the GET or POST request associated with the Excel download, so there's some give and take required. Simply commenting out the exit() locally will allow debugging, but the Excel will be marked as corrupt. Simple repeat action with exit() on will handle.
Related
I am trying to generate an archive on-the-fly in PHP and send it to the user immediately (without saving it). I figured that there would be no need to create a file on disk as the data I'm sending isn't persistent anyway, however, upon searching the web, I couldn't find out how. I also don't care about the file format.
So, the question is:
Is it possible to create and manipulate a file archive in memory within a php script without creating a tempfile along the way?
I had the same problem but finally found a somewhat obscure solution and decided to share it here.
I came accross the great zip.lib.php/unzip.lib.php scripts which come with phpmyadmin and are located in the "libraries" directory.
Using zip.lib.php worked as a charm for me:
require_once(LIBS_DIR . 'zip.lib.php');
...
//create the zip
$zip = new zipfile();
//add files to the zip, passing file contents, not actual files
$zip->addFile($file_content, $file_name);
...
//prepare the proper content type
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=my_archive.zip");
header("Content-Description: Files of an applicant");
//get the zip content and send it back to the browser
echo $zip->file();
This script allows downloading of a zip, without the need of having the files as real files or saving the zip itself as a file.
It is a shame that this functionality is not part of a more generic PHP library.
Here is a link to the zip.lib.php file from the phpmyadmin source:
https://github.com/phpmyadmin/phpmyadmin/blob/RELEASE_4_5_5_1/libraries/zip.lib.php
UPDATE:
Make sure you remove the following check from the beginning of zip.lib.php as otherwise the script just terminates:
if (! defined('PHPMYADMIN')) {
exit;
}
UPDATE:
This code is available on the CodeIgniter project as well:
https://github.com/patricksavalle/CodeIgniter/blob/439ac3a87a448ae6c2cbae0890c9f672efcae32d/system/helpers/zip_helper.php
what are you using to generate the archive? You might be able to use the stream php://temp or php://memory to read and write to/from the archive.
See http://php.net/manual/en/wrappers.php.php
Regarding your comment that php://temp works for you except when you close it, try keeping it open, flushing the output, then rewind it back to 0 and read it.
Look here for more examples: http://us.php.net/manual/en/function.tmpfile.php
Also research output buffering and capturing: http://us.php.net/manual/en/function.ob-start.php
You need to use ZipArchive::addFromString - if you use addFile() the file is not actually added until you go to close it. (Horrible bug IMHO, what if you are trying to move files into a zip and you delete them before you close the zip...)
The addFromString() method adds it to the archive immediately.
Is there really a performance issue here, or does it just offend your sense of rightness? A lot of processes write temporary files and delete them, and often they never hit the disk due to caching.
A tempfile is automatically deleted when closed. That's it's nature.
There are only two ways I can think of to create a zip file in memory and serve it and both are probably more trouble than they are worth.
use a ram disk.
modify the ziparchive class to add a method that does everything the close() method does, except actually close the file. (Or add a leave-open parameter to close()).
This might not even be possible depending on the underlying C libraries.
I am having trouble with the download helper in ie..basically I built a site that dynamically creates pdf invoices and pdf proofs in both cases the force download works great in firefox, chrome and opera. In IE it fails everytime and I get the following error:
Unable to download $filename from mysite.com
Unable to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later.
To begin the force_download I have a anchor target _blank with a url that directs to the following controller:
function view_uploaded_file($order = 0, $name = NULL){
$this->load->helper('directory');
$params['where'] = array('id' => id_clean($order));
$data['order'] = $this->MOrders->get($params);
if($data['order']->id < 1){
redirect('admin/orders');
}
$name = db_clean(urldecode($name));
$map = directory_map('./uploads/customer_order_uploads/'.$data['order']->user_id.'/'.$data['order']->id, 1);
if(is_array($map) && in_array($name, $map)){
$this->load->helper('download');
$data = file_get_contents('./uploads/customer_order_uploads/'.$data['order']->user_id.'/'.$data['order']->id.'/'.urldecode($name));
force_download($name, $data);
} else {
redirect('admin/orders');
}
}
Originally I thought maybe a problem with MY IE but I am able to download PDFs on other sites. I then thought that it could be a problem with codeigniters download helper but I see they already made special provisions for IE in the helper.
If you have any ideas please let me know. Thank you.
Frankly I am not sure why we bothered with a helper for downloads in code igniter.
It's not entirely hard to do in pure php:
This Wonderful Question/Answer outlines how to do it quite nicely.
The real thing to remember is the content-disposition: attachment part of the headers. It's what tells the browser that the file should be downloaded & saved vs. trying to show it in the browser.
All browsers handle things differently, maybe you have something in your IE install that's overriding the behaviour but if you follow the instructions in the linked article, you should get files downloaded correctly in all browsers.
Essentially there are 3 things we need to tell the browser:
Content Type
File Name
How to treat the incoming data
(Optional Fourth, if you have it) File Size (Content-Length)
Then you just dump that data right out to the output buffer.
Response
In response to your replies, it's probably a security feature to not automatically download something in a popup window, probably one of the new things IE introduced to combat their previous security holes.
Well I have found atleast a temporary fix for the problem. All my links for force downloads were target _blank..once I created standard non pop out links the file downloads worked in IE. There is probably some type of work around but I just also realized there is really no need for a pop up window for the download anyway..the download dialog box already serves that purpose.
I have a script which uploads files into an online directory and stores the file details in a database. The files when stored are renamed to the id of the entry in the database. Whenever a user requests a download, a simple SQL statement retrieves the file details from the database, the contents of the file are read from the database, and the file is prompted for download. The following is my code:
$one_file = $FILE_OBJECT->get($_GET['id']); // this is an object which just grabs the file details from the database
header("Content-type: ".$one_file['type']); // add here more headers for diff. extensions
header("Content-Disposition: attachment; filename=\"".$one_file["filename"]."\""); // use 'attachment' to force a download
header("Content-type: application/octet-stream");
header("Content-Disposition: filename=\"".$one_file["filename"]."\"");
readfile(_config('files_path').$_GET['id']);// reading the actual raw file stored in my online directory
Problem is that Im testing using a word document and its uploading perfectly - I've even checked the raw file being uploaded by manually changing its extension and it's uploading perfectly. The problem is that when it's downloaded using the code above, the Word file seems corrupted or something, because when I try to open it, it's all mumbled and jumbled. What's happening? I've used this snippet on a few other sites I've worked on, and they work perfectly fine... Help please!
By default PHP's header function will replace previous headers with the same name, so your first two headers are being overwritten by the second two. Delete the second two and see if that works.
See if this helps:
Webkit and Excel file(PHPexcel)
I was having the same problem: every time I downloaded a file, it was supposedly "corrupt". Turns out I had made a stupid directory path mistake, but the php error was being written into the downloaded file. Which, of course, made it "corrupt".
Actually I solved by reading Ian Wetherbee's comment about testing with a plain text file. Thanks Ian!
Today I started experimenting with PHP-based PDF generators. I tried TCPDF and it works fine for the most part, although it seems to be a little slow. But when I load the PHP file that generates my PDF in Internet Explorer 8, I see lines and lines of weird characters. Chrome however recognizes it as a PDF.
I'm assuming that I have to set a special MIME type to tell IE that it should interpret the page output as a PDF file. If yes, how can I do this?
putting "application/pdf" or "application/octet-stream" mime types might help. keep in mind that "application/octet-stream" will force download of the file and might prevent it from opening in the browser..
in case you wonder, you can do it like that:
header('Content-type: application/octet-stream');
I had this problem also but what I did to get it work is I added
exit();
at the end of pdf output.
You need to handle IE differently for dynamic-generated content. See this article,
http://support.microsoft.com/default.aspx?scid=kb;en-us;293792
In my code, I do this,
if(isset($_SERVER['HTTP_USER_AGENT']) AND ($_SERVER['HTTP_USER_AGENT']=='contype')) {
header('Content-Type: application/pdf');
exit;
}
This problem may also explain slowness you mentioned because your page actually sends the whole PDF multiple times without this logic.
#Pieter: I was experiencing the same issue using tcpdf (with fpdi), and loading the page that was generating the pdf using an ajax call. I changed the javascript to load the page using window.location instead, and the issue went away and the performance was much better. I believe that the other two posters are correct in the idea that the document header is causing the issue. In my case, because of the ajax call, the header was not being applied to the whole document, and causing the issue. Hope this helps.
I found this to be a problem too, and for me this all hinged on the code:
if (php_sapi_name( != 'cli') {
on line 7249 of the tcpdf.php file.
I commented this 'if' statement (and related '}')and all works fine for my other browser and ie8
Hope this helps
I am trying to generate an archive on-the-fly in PHP and send it to the user immediately (without saving it). I figured that there would be no need to create a file on disk as the data I'm sending isn't persistent anyway, however, upon searching the web, I couldn't find out how. I also don't care about the file format.
So, the question is:
Is it possible to create and manipulate a file archive in memory within a php script without creating a tempfile along the way?
I had the same problem but finally found a somewhat obscure solution and decided to share it here.
I came accross the great zip.lib.php/unzip.lib.php scripts which come with phpmyadmin and are located in the "libraries" directory.
Using zip.lib.php worked as a charm for me:
require_once(LIBS_DIR . 'zip.lib.php');
...
//create the zip
$zip = new zipfile();
//add files to the zip, passing file contents, not actual files
$zip->addFile($file_content, $file_name);
...
//prepare the proper content type
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=my_archive.zip");
header("Content-Description: Files of an applicant");
//get the zip content and send it back to the browser
echo $zip->file();
This script allows downloading of a zip, without the need of having the files as real files or saving the zip itself as a file.
It is a shame that this functionality is not part of a more generic PHP library.
Here is a link to the zip.lib.php file from the phpmyadmin source:
https://github.com/phpmyadmin/phpmyadmin/blob/RELEASE_4_5_5_1/libraries/zip.lib.php
UPDATE:
Make sure you remove the following check from the beginning of zip.lib.php as otherwise the script just terminates:
if (! defined('PHPMYADMIN')) {
exit;
}
UPDATE:
This code is available on the CodeIgniter project as well:
https://github.com/patricksavalle/CodeIgniter/blob/439ac3a87a448ae6c2cbae0890c9f672efcae32d/system/helpers/zip_helper.php
what are you using to generate the archive? You might be able to use the stream php://temp or php://memory to read and write to/from the archive.
See http://php.net/manual/en/wrappers.php.php
Regarding your comment that php://temp works for you except when you close it, try keeping it open, flushing the output, then rewind it back to 0 and read it.
Look here for more examples: http://us.php.net/manual/en/function.tmpfile.php
Also research output buffering and capturing: http://us.php.net/manual/en/function.ob-start.php
You need to use ZipArchive::addFromString - if you use addFile() the file is not actually added until you go to close it. (Horrible bug IMHO, what if you are trying to move files into a zip and you delete them before you close the zip...)
The addFromString() method adds it to the archive immediately.
Is there really a performance issue here, or does it just offend your sense of rightness? A lot of processes write temporary files and delete them, and often they never hit the disk due to caching.
A tempfile is automatically deleted when closed. That's it's nature.
There are only two ways I can think of to create a zip file in memory and serve it and both are probably more trouble than they are worth.
use a ram disk.
modify the ziparchive class to add a method that does everything the close() method does, except actually close the file. (Or add a leave-open parameter to close()).
This might not even be possible depending on the underlying C libraries.