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.
Related
I am saving a pdf file, and then attempting to download it using php.
The script seemed to work fine, but all of the sudden not anymore.
Can anybody see what I am missing?
PS: the file I am downloading is only 4.3kb big, so I assume that would be because it is not downloading at all. The actual file size should be bigger than this.
$pdf->output(ROOTDIR.'/modules/addons/statement_generator/reports/statement.pdf');
if($action=='print'){
$file_name = 'statement.pdf';
$file_url = "http://".$_SERVER['SERVER_NAME']."/modules/addons/statement_generator/reports/" . $file_name;
header('Content-Type: application/pdf');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"".$file_name."\"");
readfile($file_url);
exit;
}
The $pdf->output() call will already send the PDF to the client. The file will not be saved to your local folder (Didn't you checked at least this?) because you have to pass "F" as the snd parameter.
After that you try to read from an URL (!!!!) that does not exists and which maybe return a nicely styled 404 html response. Two issues here:
Why are you using http when you have the local path used some lines above? Use the local path only!
The content returned by the URL is append to the already send PDF which ends in a document mixed of PDF and HTML (the 404 response) -> corrupted PDF
Conclusion: Use "F" as the 2nd parameter and use the same path for both writing and reading and not a mix of local path and URL.
In a current PHP project, I need to bundle up a bunch of PDF files in some sort of archive so the user can download them all together. Because zip is most common and even the most basic non-IT-Windows guys know about it, I was going for a zip archive.
My code looks like the following
$invoices = getRequestedInvoices(); // load all requested invoices, this function is just for demonstration
// Create a temporary zip archive
$filename = tempnam("tmp", "zip");
$zip = new \ZipArchive();
$zip->open($filename, \ZipArchive::OVERWRITE);
foreach($invoices as $invoice)
{
// create the pdf file and add it to the archive
$pdf = new InvoicePdf($invoice); // this is derived from \ZendPdf\PdfDocument
$zip->addFromString($pdf->getFilename(), $pdf->render()); // for clarification: the getFilename method creates a filename for the PDF based on the invoice's id
}
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($filename));
header('Content-Disposition: attachment; filename="invoices.zip"');
readfile($filename);
unlink($filename);
exit;
This script works fine if the server has enough memory. Unfortunately our productive system is very limited so the script only works with a few PDF files, but most of the time it runs out of memory and aborts. Adding unlink($pdf) at the end of the foreach loop didn't help, so my guess is the ZipArchive object is using up the memory.
I am trying to add as little dependencies as possible to the project, so I would love to be able to solve this with PHPs (PHP 5.4) own functions or functions from Zend Framework 2. I was looking for some way of directly streaming the archive (the zip:// stream wrapper looked good at first, but it's read-only), but that seems to be impossible with zip archives.
Does anyone have an idea? Maybe a different but also widely known type of archive that allows streaming? Compression is not a must
I had to find a quick solution to this problem, so despite trying to avoid it, I had to use an external dependency.
I found the ZipStream class from the PHPZip project (https://github.com/Grandt/PHPZip) which does the job just fine.
$zip = new \ZipStream("invoices.zip");
foreach($invoices as $invoice)
{
$pdf = new InvoicePdf($invoice);
$zip->addFile($pdf->render(), $pdf->getFilename());
}
$zip->finalize();
exit;
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);
?>
First post. I'm working on a project for a client where they have pdf files uploaded to a file structure (LAMP Stack) but the files have no extensions on them. Under the assumption that those files have to be PDF how would I get the browsers to understand that, and open them accordingly? Obviously with adding the file extensions this would suddenly work but I can't change the way their system works, it would result in too many changes and they are on a tight deadline. As for saving a temporary copy somewhere, I could do that, but I was hoping for a better solution. Is there a way to suggest to the browsers that they open a file a certain way?
Any thoughts guys/gals?
You just set the application type and file name in the headers, like so:
// This points to the file in question, note that it doesn't
// care whether it has an extension on the name or not.
$filePathOnDisk = '/path/to/your/pdffile';
// You can make this whatever you like, it doesn't have to
// be the same as the file name on the disk! This is the name of the file your end
// user will see when they are asked if they want to save. open, etc in the browser.
$fileName = 'file.pdf';
$data = file_get_contents($filePathOnDisk);
header("Content-type: application/pdf");
header("Content-disposition: attachment;filename=$fileName");
echo $data;
See PHP: stream remote pdf to client browser and Proper MIME media type for PDF files for reference as well.
Tested
You can use the following which will prompt the user to save the (PDF) file on their computer.
Notice the different file names.
One is the file that will be uploaded/prompted to the user download_example.pdf, while the other is the file without an extension as set in readfile('example');
<?php
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="download_example.pdf"');
readfile('example');
?>
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.