Create a zip file and download it - php

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.

Related

Download multiple files inside foreach loop issue

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.

PHP Download - File emty [duplicate]

The end goal is for the user to download a .csv file. Right now I'm just testing trying to download a simple text file: test.txt. The only thing in this file is the word "test".
Here is the HTML code for files_to_download.php
Test file: <a href='test.php?file=test.txt'>Test.txt</a>
Code for test.php:
if(!(empty($_GET["file"])))
{
$file_name = $_GET["file"];
$path = "path/to/file";
$fullPath = $path . $file_name;
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: inline; attachment; filename=\"$file_name\"");
header("Content-Type: text/plain");
header("Content-Transfer-Encoding: binary");
readfile($fullPath);
}
I've tried variations of the headers above, adding more and removing others. The above seem to be the most common recommended on this site.
I've also tried changing
header("Content-Type: text/plain");
to
header("Content-Type: text/csv");
and get same results: empty .txt or .csv file.
The files are not empty when I open them directly (file browser) from the server. I've checked the permissions of the files and they're both 644, so the entire world can at least read the files. The directory is 777.
Is there a configuration on the Apache server I need to specify that may not be or am I missing something above.
Thanks for looking!
In most cases the path is wrong
Read the text file, then echo the text out after your header() calls.
Here's how I have my csv download set up:
//downloads an export of the user DB
$csv = User::exportUsers();
header('Content-disposition: attachment; filename=userdb.csv');
header('Content-type: text/csv');
echo $csv;
Where exportUsers() creates the csv data. You can easily just replace $csv with the contents of your text file, then echo it out.
And as far as your text file, you can use file_get_contents() to get the contents of your file into a string. Then echo that string.
Try setting the content length of the file:
header('Content-Length: ' . filesize($file));
Also, please have this in mind: file inclusion
In my case, the path was correct. And the download-forcing was working on windows, but not mac.
I figured out after few tests that the header Content-Length was failing. I was using the function filesize on a full url, like :
$url_my_file = "http://my-website.com/folders/file.ext";
header('Content-Length: '.(filesize($url_my_file)));
I replace it by
$url_my_file = "http://my-website.com/folders/file.ext";
$headers = get_headers($url_my_file, 1);
header('Content-Length: '.($headers['Content-Length']));
And ... It's working now :)

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?

Force download file with PHP giving empty file

The end goal is for the user to download a .csv file. Right now I'm just testing trying to download a simple text file: test.txt. The only thing in this file is the word "test".
Here is the HTML code for files_to_download.php
Test file: <a href='test.php?file=test.txt'>Test.txt</a>
Code for test.php:
if(!(empty($_GET["file"])))
{
$file_name = $_GET["file"];
$path = "path/to/file";
$fullPath = $path . $file_name;
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: inline; attachment; filename=\"$file_name\"");
header("Content-Type: text/plain");
header("Content-Transfer-Encoding: binary");
readfile($fullPath);
}
I've tried variations of the headers above, adding more and removing others. The above seem to be the most common recommended on this site.
I've also tried changing
header("Content-Type: text/plain");
to
header("Content-Type: text/csv");
and get same results: empty .txt or .csv file.
The files are not empty when I open them directly (file browser) from the server. I've checked the permissions of the files and they're both 644, so the entire world can at least read the files. The directory is 777.
Is there a configuration on the Apache server I need to specify that may not be or am I missing something above.
Thanks for looking!
In most cases the path is wrong
Read the text file, then echo the text out after your header() calls.
Here's how I have my csv download set up:
//downloads an export of the user DB
$csv = User::exportUsers();
header('Content-disposition: attachment; filename=userdb.csv');
header('Content-type: text/csv');
echo $csv;
Where exportUsers() creates the csv data. You can easily just replace $csv with the contents of your text file, then echo it out.
And as far as your text file, you can use file_get_contents() to get the contents of your file into a string. Then echo that string.
Try setting the content length of the file:
header('Content-Length: ' . filesize($file));
Also, please have this in mind: file inclusion
In my case, the path was correct. And the download-forcing was working on windows, but not mac.
I figured out after few tests that the header Content-Length was failing. I was using the function filesize on a full url, like :
$url_my_file = "http://my-website.com/folders/file.ext";
header('Content-Length: '.(filesize($url_my_file)));
I replace it by
$url_my_file = "http://my-website.com/folders/file.ext";
$headers = get_headers($url_my_file, 1);
header('Content-Length: '.($headers['Content-Length']));
And ... It's working now :)

Categories