Download multiple files inside foreach loop issue - php

I have the following code to download some log files through code
$files = array( '../tmp/logs/debug.log',
'../tmp/logs/error.log');
foreach($files as $file) {
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$file");
header("Content-Type: text/html");
header("Content-Transfer-Encoding: binary");
// read the file from disk
readfile($file);
}
But only download the first element of the array. In this case debug.log , if i swap the elements then only error.log. Any help please ?

You can only download one file per HTTP request. Effectively once the first file is sent the browser will assume that's the end of the processing and stops talking to the server.
If you want to ensure that the user downloads multiple files, one solution might be to zip them all up on-the-fly at the server-side, and then send the zip file to the user to download instead.

You cannot download multiple files at once. The HTTP protocol was designed to send one file per one request.
Alternatively, you can zip all your log files and download it as a zip file.
You can use the ZipArchive class to create a ZIP file and stream it to the client. Something like:
$files = array(
'../tmp/logs/debug.log',
'../tmp/logs/error.log'
);
$zipname = 'logs.zip';
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
foreach ($files as $file) {
$zip->addFile($file);
}
$zip->close();
and to stream it:
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$zipname);
header('Content-Length: ' . filesize($zipname));
readfile($zipname);

Headers are setting up once in the same execution. If you put in a loop, the next headers will not send. You can make the loop in javascript and call with ajax, but the user will obtain multiple downloads at once, so it can crash browser and usability.

Related

PHPWord any way to not write file to disk when downloading file

I'm using PHPWord to generate Word documents, and I'm hoping someone can help on this. The way its set up now, an ajax request is sent to my server from the browser requesting the PHPWord document to be built. The document is built, and stored in a file on the server with this code:
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$fileName = $picklistDetails['file_name'] . ".docx";
$filePath = '../users/' . $userId . "_word_" . $fileName;
$objWriter->save($filePath);
To allow the user to download the file, once the ajax request succeeds i use window.location to send the user to a page that puts the word document together with this (allowing the download to commence):
$fileName = $this->session->word_file_name;
$filePath = $this->session->word_file_path;
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=" . $fileName);
header("Content-Transfer-Encoding: binary");
readfile($filePath);
This all works great, the only problem is i would really rather the file not have to be saved to the server before the download. I've been researching PHPWord and as per this post (Auto download the file attachment using PHPWord) theres some discussion of how you can send the file to php's output stream instead of writing it to disk, but because building the word document is done in ajax and then a separate page is used to download it, i don't think this would work. Am i wrong?
Also in this post (Save Generated File to database PHPWORD) there is a description of how to store it to a DB, but there's still the intermediate step of a file on the server, which i need to avoid.
Is there some way I can modify
$objWriter->save($filePath);
to not write it to disk and instead save it to a variable or something? Then I could maybe store in a database where I could encrypt it.
Any help would be appreciated. I'm kind of a beginner php programmer so detailed help would be much appreciated. have a good day.
Should be as simple as doing $objWriter->save("php://output");:
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$fileName = $picklistDetails['file_name'] . ".docx";
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=" . $fileName);
header("Content-Transfer-Encoding: binary");
$objWriter->save("php://output");
Skip the redirect entirely. Just window.location directly to the above code ^ and the file should download once it's done building.

PHP create and download ZIP on the fly

I'm creating a ZIP file with several scripts in it (for example: test.php, functions.js and style.css).
The scripts works just fine, the only problem is that the ZIP file gets placed on my webserver. Is there a way to prevent this? I've read multiple similar questions: this one seems to work, but I can't figure it out how to use that.
So, I wan't to delete the file after it has been placed (even if user aborts it) or (even better) that my scripts never puts the file on the webserver.
download.php
$scriptId = checkNumeric($_GET['sid']);
//Check if user has access to the script
if(isLoggedIn() && hasScriptAccess($scriptId)) {
//Create ZIP
$zip = new ZipArchive();
$zipName = "script.zip";
if ($zip->open($zipName, ZIPARCHIVE::CREATE)!== TRUE) {
exit(); //Something went wrong while creating the ZIP
}
//Get associated codes
$query = $mysqli->query("SELECT * FROM code WHERE script_id = '{$scriptId}'");
while($code = $query->fetch_assoc()) {
$filename = $code['title'];
$content = $code['code'];
//Add file to ZIP
$zip->addFromString($filename, $content);
}
$zip->close();
//Set headers
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename='" . $zipName . "'");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($zipName));
clearstatcache(); //Make sure the file size isn't cached
readfile($zipName); //Output the file
$zip->deleteName($zipName);
}
From my understanding the zip file must be saved, it can not be stored in memory. $zip->close(); is what actually triggers the file creation. I am sure someone smarter than I will figure out how to write it to memory but for now there is a simple work around.
I just did something similar. The trick is to use:
// Keep script running even if user aborts
ignore_user_abort(true);
Add this as the first line of your script. What this does is allow your download script to run even if the user aborts the download. That will ensure your delete command gets called.
I am not sure if you are saying if the file is or is not deleting properly even if the user is not aborting. But if your current delete command is not working as expected you could use a simple:
unlink( $zipName);
Hope this helps.

Downloading a csv as a zip

Im using Php (Zend Framework) to download a datagrid as CSV..the csv generation works fine.I just want to know is it possible to download the csv file within a zip file
My current code
public function downloadCsvAction(){
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=answers_csv.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo "stuff1" . ",";
echo "stuff2".",";
//----bla bla
exit;
}
By calling the url controller/downloadCsv im getting a popup to save the csv.I want the csv to be in a zip file
Here is a simple code to zip and output csv as a zip file
//here is your csv file
$csvFile = "test.csv";
//location for php to create zip file.
$zipPath = "/tmp/$csvFile.zip";
$zip = new ZipArchive();
if ($zip->open($zipPath,ZipArchive::CREATE)) {
$zip->addFile($csvFile, $csvFile);
$zip->close();
//Make sure the zip file created and output it.
if(is_file($zipPath)){
header('Content-type: application/octet-stream; charset=utf-8');
header('Content-Disposition: attachment; filename="'.$csvFile.'.zip"');
header('Content-Length: ' . filesize($zipPath));
readfile($zipPath,false);
}
}
I just want to know is it possible to download the csv file within a zip file
Yes it is possible. Just create a ZIP-file and put the CSV-file in there. Then deliver the ZIP-file.
If you're looking for more technical info, please read into the related Q&A material as the concrete code differs highly on what you need to achieve:
Generating ZIP files with PHP + Apache on-the-fly in high speed?

how to make zip file downloadable with headers php without saving it on the server

I have this code
<?php
$files = array('includes/s.xlsx', 'includes/test.html');
$zipname = 'file.zip';
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
foreach ($files as $file) {
$zip->addFile($file);
}
$zip->close();
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename=filename.zip');
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
But i don't want to make the zip file saved to the server i just want it to be served to the users.
ZIP is not a streamable format (the target needs to support seeking, i.e. a ZIP file cannot be simply written to a target where the previously-written data cannot be re-read and modified) what you are trying to do can't work. So the best solution (if your zip files won't be huge) is creating it in a temporary location, readfile() it and then delete the temporary file.
See the following Stack Overflow questions for further reference. They contain some workarounds that might be fine for you:
Zip Stream in PHP
LAMP: How to create .Zip of large files for the user on the fly, without disk/CPU thrashing
Try adding this after readfile($zipname); :
// delete the file from the server
unlink($zipname);
exit;
You're using the object $zip to open and modify the zip file, but filesize() and readfile() only work on file names, so you should have this:
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
Btw, you can use tempnam() to generate a "temporary" file that will be used to hold the zip contents; once readfile() is done you still have to unlink() it though.

Create a zip file and download it

I am trying to download a 2 files by creating the zip file on local-server.the file is downloaded in zip format but when i try to extract it.it gives error:
End-of-central-directory signature not found. Either this file is not
a zip file, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zip file comment will be found on
the last disk(s) of this archive.
the following code i am using for this:
<?php
$file_names = array('iMUST Operating Manual V1.3a.pdf','iMUST Product Information Sheet.pdf');
//Archive name
$archive_file_name=$name.'iMUST_Products.zip';
//Download Files path
$file_path=$_SERVER['DOCUMENT_ROOT'].'/Harshal/files/';
zipFilesAndDownload($file_names,$archive_file_name,$file_path);
function zipFilesAndDownload($file_names,$archive_file_name,$file_path)
{
//echo $file_path;die;
$zip = new ZipArchive();
//create the file and throw the error if unsuccessful
if ($zip->open($archive_file_name, ZIPARCHIVE::CREATE )!==TRUE) {
exit("cannot open <$archive_file_name>\n");
}
//add each files of $file_name array to archive
foreach($file_names as $files)
{
$zip->addFile($file_path.$files,$files);
//echo $file_path.$files,$files."
}
$zip->close();
//then send the headers to force download the zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
exit;
}
?>
i checked the values of all variables which are passing into the function,all are fine.so please look this.Thanks in advance.
Add Content-length header describing size of zip file in bytes.
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header("Content-length: " . filesize($archive_file_name));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
Also make sure that there is absolutely no white space before <? and after ?>. I see a space here:
↓
<?php
$file_names = array('iMUST Operating Manual V1.3a.pdf','iMUST Product Information Sheet.pdf');
$zip = new ZipArchive;
$tmp_file = 'assets/myzip.zip';
if ($zip->open($tmp_file, ZipArchive::CREATE)) {
$zip->addFile('folder/bootstrap.js', 'bootstrap.js');
$zip->addFile('folder/bootstrap.min.js', 'bootstrap.min.js');
$zip->close();
echo 'Archive created!';
header('Content-disposition: attachment; filename=files.zip');
header('Content-type: application/zip');
readfile($tmp_file);
} else {
echo 'Failed!';
}
// http headers for zip downloads
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$filename."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filepath.$filename));
ob_end_flush();
#readfile($filepath.$filename);
I found this soludtion here and it work for me
One of the error could be that the file is not read as 'archive' format. check out ZipArchive not opening file - Error Code: 19. Open the downloaded file in text editor, if you have any html tags or debug statements at the starting, clear the buffer before reading the file.
ob_clean();
flush();
readfile("$archive_file_name");
I just ran into this problem. For me the issue was with:
readfile("$archive_file_name");
It was resulting in a out of memory error.
Allowed memory size of 134217728 bytes exhausted (tried to allocate 292982784 bytes)
I was able to correct the problem by replacing readfile() with the following:
$handle = fopen($zipPath, "rb");
while (!feof($handle)){
echo fread($handle, 8192);
}
fclose($handle);
Not sure if this is your same issue or not seeing that your file is only 1.2 MB. Maybe this will help someone else with a similar problem.
I have experienced exactly the same problem. In my case, the source of it was the permissions of the folder in which I wanted to create the zip file that were all set to read only. I changed it to read and write and it worked.
If the file is not created on your local-server when you run the script, you most probably have the same problem as I did.
but the file i am getting from server after download it gives the size
of 226 bytes
This is the size of a ZIP header. Apparently there is no data in the downloaded ZIP file. So, can you verify that the files to be added into the ZIP file are, indeed, there (relative to the path of the download PHP script)?
Consider adding a check on addFile too:
foreach($file_names as $file)
{
$inputFile = $file_path . $file;
if (!file_exists($inputFile))
trigger_error("The input file $inputFile does not exist", E_USER_ERROR);
if (!is_readable($inputFile))
trigger_error("The input file $inputFile exists, but has wrong permissions or ownership", E_USER_ERROR);
if (!$zip->addFile($inputFile, $file))
trigger_error("Could not add $inputFile to ZIP file", E_USER_ERROR);
}
The observed behaviour is consistent with some problem (path error, permission problems, ...) preventing the files from being added to the ZIP file. On receiving an "empty" ZIP file, the client issues an error referring to the ZIP central directory missing (the actual error being that there is no directory, and no files).
Make sure that the path inside $archive_file_name is correct and does exist. In your code, you updated the $archive_file_name variable by putting an undeclared prefix variable - $name.
$archive_file_name = $name.'iMUST_Products.zip';
If the path is not correct and cannot be found, the created zipped file have nowhere to go to and the two files have nowhere to store to to begin with. The path to the files to be stored should also exist.
Let's assume that you just want to customize the name of the zipped folder to be downloaded that is why there is a $name variable to prevent redundant file name. But you have to consider the URI where you run your script.
For example, you're using URI routing, and you load the script at https://sample.co/file-management/download-zipped-111. With your current script, the zipped folder will be stored at file-management folder. If it does not exist, you will mostly encounter the error you are experiencing right now.
The solution is to explicitly declare the path to the folder where the zipped file will be stored.
For example, you want to store the zipped file at Harshal/files/zipped/. Make sure that this folder exist by creating a folder named zipped inside files folder and that you have access to write on this folder.
$file_names = array('iMUST Operating Manual V1.3a.pdf', 'iMUST Product Information Sheet.pdf');
// $name should be declared before this line
$archive_file_name = $name.'iMUST_Products.zip';
//Download Files path
$file_path = $_SERVER['DOCUMENT_ROOT'].'/Harshal/files/';
zipFilesAndDownload($file_names, $archive_file_name, $file_path);
function zipFilesAndDownload($file_names, $archive_file_name, $file_path)
{
$zip = new ZipArchive();
// WE REUSED THE $file_path VARIABLE HERE THEN ADDED zipped FOLDER
if ($zip->open($file_path.'zipped/'.$archive_file_name, ZIPARCHIVE::CREATE )!==TRUE) {
exit('cannot open <'.$archive_file_name.'>');
}
//add each files of $file_name array to archive
foreach($file_names as $files)
{
$zip->addFile($file_path.$files, $files);
}
$zip->close();
//then send the headers to force download the zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header('Content-length: '.filesize($file_path.'zipped/'.$archive_file_name));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
exit;
}
People may suggest - "Why not directly put the full path to the $archive_file_name variable?
$archive_file_name = $file_path.'zipped/'.$name.'iMUST_Products.zip';
If we put it directly to $archive_file_name variable, the file that will be downloaded will mostly likely be named /var/www/Harshal/files/zipped/xxx-iMUST_Products.zip.

Categories