I need to download a zip file from a website as I am going to require to coalesce multiple files in a single download (up to 100 individual files-ish).
When attempting to create the zip file, it downloads as intended, the file name appears in the format "YYYY.MM.DD - HH.MM.SS" also as intended. My issue occurs when attempting to open the zip file in windows 7 (or winzip) - I get the error message below. This happens repeatedly from multiple attempts.
I assume that I have made an error while coding the creation or download of the zip file, rather than the zip file format itself being an issue as I can open different zip files - can anyone see the mistake I have probably made? (code included below error image)
I have attempted to use Download multiple files as a zip-file using php as a reference.
//backup file name based on current date and time
$filename = date("Y.m.j - H.i.s");
//name of zip file used when downloading
$zipname = 'temp.zip';
//create new zip file
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
//yes, I know the zip file is empty currently - I've cut the code from here for
//now as the zip file doesn't function with / without it currently
$zip->close();
//download file from temporary file on server as '$filename.zip'
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$filename.'.zip');
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
Try to open the zip file with a text editor.
This way you can check if were is a php error in your code (during the compression step).
Check that the web server user has write permission to the folder where you're creating the ZIP file. Notwithstanding the documentation, ZipArchive::open() will fail silently and return true (i.e. success) if it cannot create the ZIP file. Further, ZipArchive::addFile() will seemingly add as many files as you wish to this non-existent archive, also without reporting an error. The first point at which an error appears is when ZipArchive::close() returns `false'. No error messages appear in the error logs, either.
Readfile() will report an error to the logs and fail, so the result is a zero-length ZIP file on your local hard disk.
The reason seems to be that the ZipArchive class is only assembling a list of files in memory until it's closed, at which point it assembles all the files into the Zip file. If this can't be done then ZipArchive::close() returns false.
Note: if the zip file is empty, it might not be created at all! Your download will proceed, but readfile() will fail and you'll get a zero-length ZIP file downloaded.
What to do?
Add a little error checking to your code to report some of this:
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
// Add your files here
if ($zip->close() === false) {
exit("Error creating ZIP file");
};
//download file from temporary file on server as '$filename.zip'
if (file_exists($zipname)) {
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$filename.'.zip');
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
} else {
exit("Could not find Zip file to download");
}
I had this issue, but this solution fixed it for me
Add ob_clean(); just before your new output headers.
I am using an older version of Silverstripe and my zip files were constantly being corrupted even though the data was visibly in there.
The solution of this obscure comment above to use ob_clean(); was helpful, and I wanted to pull this out as an Answer to this question.
From PHP docs:
ob_clean(); discards the contents of the output buffer.
ob_clean(); does not destroy the output buffer like ob_end_clean() does.
try to add
ob_end_clean();
before headers
<?php
$zip = new ZipArchive();
$zip_name = $name.".zip"; // Zip name
$tmpFile=APPPATH.'../'.$zip_name;
$zip->open($zip_name, ZipArchive::CREATE);
foreach ($images as $files) {
if(!empty($files->cloudinary_img_url)){
$zip->addFromString(basename($files->car_images), file_get_contents(getImagesDropbox($files->cloudinary_img_url)));
}
else{
echo"file does not exist";
}
}
$zip->close();
ob_end_clean();
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename="'.$zip_name.'"');
header('Content-Length: ' . filesize($tmpFile));
readfile($tmpFile);
unlink($tmpFile);
?>
Related
Have a php function to generate a zip file "on fly". I have the files on one server (AWS S3) but the PHP function to generate the zip-file is on another server/web hosting. I have noticed that it takes long time to generate the zip-file and I get a corrupt zip file if there are many files when I create zip file. I want to troubleshoot/debug where it takes "stop", what the missing link is if there are many files (the limit seems to be 20 files, which is not many).
How can I find out where in my function it fails if I have more than 20 files to generate my zip-file from?
Can I add a timer to every row?
Can I find out if it is memory or something else on my shared hosting (where I have the php function)? Or if it is something with S3.
My php function to generate the zip file from files on AWS S3
<?php
$imageQueryResult = $this->getUserImages('download', array('i.image_name'));
if(!empty($imageQueryResult)) {
$imageUrl = $this->getFromAmazon('download', $imageQueryResult);
$imageNumber = 1;
$zipName = 'tt-'.date("Y-m-d").'.zip';
//create new zip object
$zip = new ZipArchive();
//create a temp file & open it
$tmp_file = tempnam('.','');
$zip->open($tmp_file, ZipArchive::CREATE);
//loop through each file
foreach($imageUrl as $image){
//Get extension
$ext = pathinfo(parse_url($image, PHP_URL_PATH), PATHINFO_EXTENSION);
//download file
$download_file = file_get_contents($image);
//add it to the zip
$zip->addFromString(basename($imageNumber.'-tt.'.$ext),$download_file);
$imageNumber++;
}
//close zip
$zip->close();
//send the file to the browser as a download
header('Content-disposition: attachment; filename='.$zipName);
header('Content-Length: ' . filesize($tmp_file));
header('Content-type: application/zip');
readfile($tmp_file);
ignore_user_abort(true);
unlink($tmp_file);
?>
You can try to use
http://php.net/manual/en/ziparchive.getstatusstring.php
ZipArchive::getStatusString — Returns the status error message, system and/or zip messages
You don't know how long it will take to compress all your files. So you'll have to check the maximal execution time and the memory that consumes that script.
If the problem is with the time, the solution might be to do this in chunks:
open the archive for writing
add N files per iteration
close the archive
Repeat until you you have files
Keep in mind that with this approach the more files you have, the more memory you need to store the temporary results
I'm trying to generate OpenDocument Streadsheets using PHP.
This is my code:
$content_xml = '<?xml version="1.0" encoding="UTF-8"?><office:document-content ...';
$file = 'spreadsheet.ods'; // this one is beeing generated. It is not existent yet...
$zipfile = 'container.zip'; // this one already exists. It's in the same directory
// this script is in. I created the container.zip by unziping a usual .ods file,
// removing the content.xml and ziping again
$handle1 = fopen($zipfile, "r");
$handle2 = fopen($file, "w");
// writing the content of the `container.zip` to the `spreadsheet.ods`
fwrite($handle2, fread($handle1, filesize($zipfile)));
fclose($handle1);
fclose($handle2);
$zip = new ZipArchive();
$zip->open($file, ZIPARCHIVE::CREATE);
// adding the xml string to the zip file
$zip->addFromString('content.xml',$content_xml);
$zip->close();
header('Content-disposition: attachment; filename="'.$file.'"');
header('Content-Type: application/vnd.oasis.opendocument.spreadsheet');
header('Content-Length: ' . filesize($file));
readfile($file);
unlink($file);
If I open the generated file with OpenOffice - everything looks fine. But MS Excel for some reason is not able to open the file. I think the XML string in $content_xml is corrupt. I don't want to bother anybody with that string - it is huge! I got it from a simple ods spreadsheet. I just edited the <office:body>...</office:body> part.
I wondering whether this is a good way to generate spreadsheets. Does anybody know a tutorial for this task:
do all that zip stuff
structure of a simple content.xml in an ods file
Regarding "1. do all that zip stuff":
what I do here is generating the content.xml and adding it to the root of the .ods structure
Configurations2 // Folder
META-INF // Folder
Thumbnails // Folder
meta.xml
mimetype
settings.xml
styles.xml
By doing
$zip->addFromString('content.xml',$content_xml);
But how can I add a file NOT to the root of the structure but (let' say) to Configurations2/images/Bitmamps?
You can find full details of the OpenOffice (OASIS) format on the OASIS technical pages while The Open Office website provides DTDs and some tutorials
I am needing to parse a series of php files to output .PDFs and .PNGs files before zipping them using zipArchive. What I would like to do is something like
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);
//If you access qr_gen.php on a browser it creates a QR PNG file.
$zip->addFile('qr_gen.php?criteria=1', 'alpha.png');
$zip->addFile('qr_gen.php?criteria=2', 'beta.png');
//If you access pdf_gen.php on a browser it creates a PDF file.
$zip->addFile('pdf_gen.php?criteria=A', 'instructions.pdf');
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');
readfile($file);
unlink($file);
This obviously does not work. How can I accomplish my goal?
The following line will not work as you provide and url as filename:
$zip->addFile('qr_gen.php?criteria=1', 'alpha.png');
Instead you'll have to download the pngs first and store them locally. Then add them to the zip archive. Like this:
file_put_contents('alpha.png',
file_get_contents('http://yourserver.com/qr_gen.php?criteria=1');
$zip->addFile('alpha.png');
You'll find more information at the documentation page of ZipArchive::addFile()
What you will need to do is to get the files locally first. This can be (trivially) achieved using file_get_contents if you have URL fopen mappers set up, or failing that, cURL calls.
This is a sample way to do it:
$zip = new ZipArchive();
$zip->open("zipfile.zip",ZipArchive::OVERWRITE);
$URLs = array(
"alpha.png" => "http://my.url/qr_gen.php?criteria=1",
"beta.png" => "http://my.url/qr_gen.php?criteria=2",
"instructions.pdf" => "http://my.url/pdf_gen.php?criteria=A");
foreach ($URLs as $file => $URL) {
$f = #file_get_contents($URL);
if (empty($f)) throw new Exception("File not found: ".$URL);
$zip->addFromString($file, $f);
}
Your zip is then available as $zip for further processing.
First of all, execute all files into browser and put that content (png.pdf) into one folder and then create zip of it by fetching one by one.
hope it helps
I'm trying to optimize dompdf to do something a bit strange. As it is on my server, dompdf is generating a pdf file (for the client requesting the file) from a php/html file stored somewhere on the server. This is cool because it doesn't bog the server down with pdf files, but the problem I have is that I want someone to be able to export a group of PDFs and receive them in a zip file or something similar.
Is there a way to make dompdf export a group of PDF files, based on the filenames of the php/html files, to a zip file or something so the person requesting it can download it?
Let me know if more information is needed.
Thank you!!
DOMPDF only handles a single file at a time. But you could write a PHP script to accept the list of files and then use DOMPDF to parse each one separately. Since DOMPDF can return the rendered PDF as a string you could write each file out to a temporary directory then archive the results when you're done. Or, if you're using dompdf.php you could use exec() to process each HTML document in a similar loop.
Here's a simple example showing one way to do what you want:
$files_html = array('docs/file1.html','docs/file2.html');
foreach ($files_html as $file) {
exec('dompdf.php ' . $file);
}
$zip = new ZipArchive();
$zip->open('docs/pdfs.zip', ZipArchive::CREATE);
$files_pdf = glob('docs/*.pdf');
foreach ($files_pdf as $file) {
$zip->addFile($file);
}
$zip->close();
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=pdfs.zip);
header('Content-Transfer-Encoding: binary');
readfile('docs/pdfs.zip');
There are some discussions of using DOMPDF to batch process files in the forum and issue tracker.
What's the easiest way to zip, say 2 files, from a folder on the server and force download? Without saving the "zip" to the server.
$zip = new ZipArchive();
//the string "file1" is the name we're assigning the file in the archive
$zip->addFile(file_get_contents($filepath1), 'file1'); //file 1 that you want compressed
$zip->addFile(file_get_contents($filepath2), 'file2'); //file 2 that you want compressed
$zip->addFile(file_get_contents($filepath3), 'file3'); //file 3 that you want compressed
echo $zip->file(); //this sends the compressed archive to the output buffer instead of writing it to a file.
Can someone verify:
I have a folder with test1.doc, test2.doc, and test3.doc
with the above example - file1 (file2 and file3) might just be test1.doc, etc.
do I have to do anything with "$filepath1"? Is that the folder directory that holds the 3 docs?
Sorry for my basic question..
Unfortunately w/ PHP 5.3.4-dev and Zend Engine v2.3.0 on CentOS 5.x I couldn't get the code above to work. An "Invalid or unitialized Zip object" error message was all I could get. So, in order to make it work, I had to use following snippet (taken from the example by Jonathan Baltazar on PHP.net manual, at the ZipArchive::open page):
// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);
// Stuff with content
$zip->addFromString('file_name_within_archive.ext', $your_string_data);
$zip->addFile('file_on_server.ext', 'second_file_name_within_archive.ext');
// Close and send to users
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');
readfile($file);
unlink($file);
I know this is different than working w/ memory only - unless you have your tmp area in ram ;-) - but maybe this can help out someone else, who's struggling with the solution above, like I was; and for which performance penalty is not an issue.
Your code is very close. You need to use the file name instead of the file contents.
$zip->addFile(file_get_contents($filepath1), 'file1');
should be
$zip->addFile($filepath1, 'file1');
http://us3.php.net/manual/en/function.ziparchive-addfile.php
If you need to add files from a variable instead of a file you can use the addFromString function.
$zip->addFromString( 'file1', $data );
This works for me (nothing is written to disk)
$tmp_file = tmpfile(); //temp file in memory
$tmp_location = stream_get_meta_data($tmp_file)['uri']; //"location" of temp file
$zip = new ZipArchive;
$res = $zip->open($tmp_location, ZipArchive::CREATE);
$zip->addFile($filepath1, 'file1');
$zip->close();
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
echo(file_get_contents($tmp_location));
If you have access to the zip commandline utility you can try
<?php
$zipped_data = `zip -q - files`;
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
echo $zipped_data;
?>
where files is the things you want to zip and zip the location to the zip executable.
This assumes Linux or similar, of course. In Windows you might be able to do similar with another compression tool, I guess.
There's also a zip extension, usage shown here.
maraspin's Answer is what I tried. Strangely, I got it working by removing the header line that references the file size:
header('Content-Length: ' . filesize($file));
With the above change, the code works like a breeze!
Without this change, I used to get the following error message:
End-of-central-directory signature not found. Either this file is not a zipfile, or it constitutes one disk of a multi-part archive. In the latter case the central directory and zipfile comment will be found on the last disk(s) of this archive.
Test environment:
OS: Ubuntu 10.10
Browser: Firefox
And the default LAMP stack.
To create ZIP files on the fly (in memory), you can use ZipFile class from phpMyAdmin:
PMA\libraries\ZipFile.php
An example of how to use it in your own application:
https://stackoverflow.com/a/9648562/767871
Note: Your ZIP files will be limited by PHP's memory limit, resulting in corrupted archive if you exceed the limit.
itsols
If you want to insert the 'Content-Length' do it like this:
$length = filesize($file);
header('Content-Length: ' . $length);
I don't know why, but it crashes if you put it in the same line.
On Unix systems (and maybe others),
you can apply a simple trick to #maraspin's answer by deleting the file entry for the file ("unlinking" it from its inode), and send its data using a handle opened previously. This is basically the same thing tmpfile() does; this way you can "temporarify" any file.
The code is the same as maraspin's up to the very last lines:
// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);
// Stuff with content
$zip->addFromString('file_name_within_archive.ext', $your_string_data);
$zip->addFile('file_on_server.ext', 'second_file_name_within_archive.ext');
// Close and send to users
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');
// open a handle to the zip file.
$fp = fopen($file, 'rb');
// unlink the file. The handle will stay valid, and the disk space will remain occupied, until the script ends or all file readers have terminated and closed.
unlink($file);
// Pass the file descriptor to the Web server.
fpassthru($fp);
As soon as the script finishes, or terminates abnormally, or the application pool is cycled, or the Apache child gets recycled -- the "disappearance" of the file will be formalized and its disk space released.