Blocks can only contain strings error when saving to disk - php

Background
I'm generating an Excel sheet that get's emailed to a certain address. It works perfectly on localhost (saves perfectly, can't test the mailing as I have no local mailserver running). When I host it, I run into some problems.
What problems
I've narrowed it down to this line:
$this->PhpExcel->saveToDisk($path);
$path is a custom path that is set at runtime. When I run that, it saves the file to disk but it gives me this:
This is a function in a custom helper class. Below is the code for the function:
public function saveToDisk($path) {
// set layout
$this->_View->layout = '';
// headers
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="'.$filename.'"');
header('Cache-Control: max-age=0');
// writer
$objWriter = PHPExcel_IOFactory::createWriter($this->xls, 'Excel2007');
ob_end_clean();
$objWriter->save($path);
// clear memory
$this->xls->disconnectWorksheets();
}
But here is the catch. When I output it to browser, by changing $objWriter->save($path); to $objWriter->save('php://output'); it gives me no error and exports the file perfectly.
Question
I've googled the error and tried my best to find the solution, but to no avail. The amount of info available on this error is staggeringly scarce or outdated. Could someone please tell me what it is that I'm missing or doing wrong?

When you've saved the file to disk, CakePHP still expects you to give it something to return to the browser that made the request, even if it's just a message to say that the file has been saved successfully, or that the email has been sent successfully.
If you're saving to disk, you don't want to send the headers because the server isn't going to be returning an Excel file, so no headers.

I fixed it by removing this line:
ob_end_clean();

Related

Readfile is best solution to download external files?

I need to get a remote file and give it to user without saving it to my server disk (for hiding original URL) and found a lot of posts about download external files with various functions like file_get_contents or readfile. Already I'm using this one:
function startDownload($url){
if($this->url_exists($url))
{
//get filename from url
$name=$this->getFileName($url);
//first flush clear almost output
ob_end_flush();
//final clear
ob_clean();
//set headers
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . $name . "\"");
//send file to client;
readfile($url);
//exit command is important
exit;
}
else JFactory::getApplication()->enqueueMessage(JText::_('URL_NOT_FOUND'), 'error');
}
And that's working but there is a problem! For a file with 200 MB size it takes ~ 10 seconds to start download in client browser. I think it's because readfile first downloads whole file to my server buffer and then give it to user. Is that right?
And is it possible to make it faster? for example download be started before fetch ended or it isn't possible technically?
In fact I don't know that this method is optimised or not. Any technical advice would be appreciated.
Note :
I know that this function should be changed for big files and that's not my concern now.
I consider to buy the external server in the same datacenter to make this download faster.
Target is that [File server] be separate than the file [online shop].
I tested curl method that mentioned by #LawrenceCherone. It worked nicely but when moved it to my project the result was the same as readfile (white screen for a few seconds).
So suspect to readfile() function. Separate my previous code to a single PHP file and result was amazing! Download starts immediately.
So I think my guess wasn't right and problem was not related to readfile function.
After a little search found a minor modification. I added below line :
while (ob_get_level()) ob_end_clean();
before the :
readfile($url);
And now download starts before whole file fetched in my server.

What does the "PK¿¿¿" response means in PHP

Hi I'm downloading a file to an app on iOS using the function readfile() on a PHP web service and I want to know if the file is downloaded correctly but I don't know how I can do that.
So what I'm trying is to do some echo to know if the file has been downloaded like this:
echo "before";
readfile($file);
echo "after";
But the response I get is this:
beforePK¿¿¿
Any one knows what does this mean or how can I know if the file is downloaded correctly?
UPDATE:
Yes it's a zip file, here are my headers
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$ticket");
header("Content-Type: application/zip");
header("Content-Transfer-Encoding: binary");
You're trying to output the contents of a zip file aren't you?
readfile($file) works the same as echo file_get_contents($file). If you're trying to present someone a file to download, do not add any additional output else you risk breaking the file.
I would also recommend reading up on the header function. That way you can explicitly tell the browser that you're sending a file, not an HTML page that has file-like contents. (See the examples involving Content-Type)
PHP should be setting the correct headers prior to readfile() - this LITERALLY reads the file out to the browser/app... but the browser/app needs to know what to do with it...
Usually you just assume that once the connection has closed that the data is done being transferred. If you want to validate that the file has been transferred fully, and without corruption you'll need to use a data structure like XML or JSON which will:
Delimit the data fields and cause the XML/JSON parser to throw an error if one is omitted, aka the transfer was cut off before it finished.
Allow you to embed more than one piece of data with the response, eg. an MD5 hash of the file that can be re-calculated client-side to verify that the data is intact.
eg:
$file = 'myfile.zip';
$my_data = array(
'file' => base64_encode(file_get_contents($file)),
'hash' => md5_file($file)
)
//header calls
header(...)
echo json_encode($my_data);
exit;

PHPExcel error when opening created File in EXCEL

I create with a php-script which uses the PHPExcel Library an simple .xlsx file. But when I want to open it in MS Excel 2010 on Win7, I get an error message that file format is wrong or the file is damaged. Tried several possible solutions from the internet but nothing worked for me.
public function createControllingFile($suffix){
$this->PHPExcel = new PHPExcel();
$year = date('y');
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Cache-Control: max-age=0');
$this->objWriter = PHPExcel_IOFactory::createWriter($this->PHPExcel, 'excel5');
$this->objWriter->save('tmp/controlling_'.$year.'_'.$suffix.'.xlsx');
$_SESSION['counter'] = 0;
exit();
}
Hope you can help me, the session thing is to count up something
$this->objWriter->save('tmp/controlling_'.$year.'_'.$suffix.'.xlsx');
saves to a file on your servers filesystem, and nothing is sent to the client browser.
If you want to send to the client's browser, change that line to:
$this->objWriter->save('php://output');
just like in 01simple-download-xlsx.php in the /Tests or /Examples directory

PHP Download not working with path coming from DB

I have a simple form that, when posted back, calls a function to initiate a download. The path and file name are pulled from the database then I'm using headers to start the download. My code for the download is:
//START DOWNLOAD
header('Content-type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$FILE_PATH.$FILE_NAME.'"');
header("Content-Transfer-Encoding: binary");
header("Connection: close")
In the example above, the $FILE_PATH variable is /downloads/software/ and the $FILE_NAME variable is client-installer.exe. So, what I would expect is a file called client-installer.exe (approximately 70MB) to be downloaded to the client. Instead, I get a file called _downloads_software_client-installer.exe and approximately 10KB.
I thought maybe I needed to urlencode the file path/name but that didn't fix the issue either. So I'm left thinking perhaps I have something wrong with the header but can't seem to find it.
Thank you!
The filename header just denotes what the file should be called. It must contain only a filename, not a path. The internal path on the server's hard disk is irrelevant and of no interest to the client. Your server will have to output the actual file data in the response, the client can't take it from the server given the path.
See readfile.

PHP zip download bug

I have a script that lets users download a zip file on request. It works 100% on computer browsers but does not work on Android/mobile browsers, except for Opera (mobile) only.
Here's my script.
$leads = new Packer($zip_name);
$index = 1;
$count = 0;
foreach($cLeads->lastUnitInfo['leads'] as $lead)
{
// build a request string
$export = 'export_lead_'.$index;
$req = $_POST[$export];
// add it to the zip file
if(isset($req) && $req == '1')
{
// debug only
//echo 'adding lead: '.$lead['file_name'].'<br />';
$leads->addLead('leads/'.$lead['file_name'],$lead['item_name']);
$count++;
//echo 'count: '.$count.'<br/>';
}
$index++;
}
// debug
//exit('count: '.$count); // displays same results on all browsers.
// we got anything packed ?
if($count <= 0) //// <-------- BLOCK OF BUG ON MOBILE PHONE
{
if(file_exists($zip_name))
unlink($zip_name); // delete the zip file created.
exit('<h1>Nothing to export</h1>');
} ///// <---------------------- END BLOCK
// download the leads here.
$leads->purge();
exit;
Here's my purge() function
public function purge($zip_name = 'leads.zip')
{
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="'.$zip_name.'"');
ob_clean();
flush();
readfile($this->zip_name);
// errors will be disabled here
unlink($this->zip_name);
}
On my Android phone, the zip file gets downloaded but contains <h1>Nothing to export</h1> which renders it as an invalid zip file.
So my problem is, How is it that the block only executes on mobile browsers (except Opera) and then continues to download the zip when it should have exited if $count was zero at all?
I debugged it using Fiddler and the requests are all the same but the output is different, why ?
Is this a bug in PHP? Because if you look at my code the function purge() should output errors saying headers have already been sent but it just continues to download the zip file.
Browsers:
Dolphin (+Beta)
FireFox
Default Android browser
Boat Browser
PHP versions tested:
5.3.13 (Production, shared server)
5.1.4
This is driving me nuts now.
#Alix This is the weirdest bug I've ever seen. I don't seriously see any logical errors here. For the script to initiate a download, there actually has to be files added to the zip. Now on my phone, it says no files are added but there's a zip file in the temp folder. Moreover if there are no files added ($count = 0) then the script should terminate (hence the exit() function) with just a message <h1>Nothing to export</h1>. But it goes on to download the zip file (which at this time does not exist, but in the temp folder it does). The zip file ends up being corrupt as it contains <h1>Nothing to export</h1>
Alix wrote:
*> what happens if you comment out the exit call before serving the file?
It says readfile error then purges the zip file in gibberish UNICODE chars. I can tell it's the zip file because it starts with PK and contains the names of images being exported.
Alix wrote:
If this doesn't work, you may wanna change exit('<h1>Nothing to export</h1>'); to exit(var_dump($_REQUEST));, that way you may check for possible bugs in the form submission by inspecting the Zip file.
Interesting. It prints nothing but the cookie and a $_GET parameter only. If I put the code in your suggestion at the beginning on the script and in the block that adds the files to the zip it prints all the $_POST variables.
There's clearly a bug on PHP's part here. It should terminate when exit is called but it doesn't. And mind you this only happens on mobile browsers except Opera. I'm going to cry blood now.
Are you sure the other browsers are setting $_POST[$export] to the correct value?
Also, you should first ob_[end_]clean() and then output the headers, not the other way around:
public function purge($zip_name = 'leads.zip')
{
ob_end_clean();
flush();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="'.$zip_name.'"');
readfile($this->zip_name);
// errors will be disabled here
unlink($this->zip_name);
}
Additionally, you may wanna set these headers:
header('Content-Length: ' . intval(filesize($zip_name)));
header('Content-Transfer-Encoding: binary');
If this doesn't work, you may wanna change exit('<h1>Nothing to export</h1>'); to exit(var_dump($_REQUEST));, that way you may check for possible bugs in the form submission by inspecting the Zip file.

Categories