I'm trying to generate a PDF with KnpSnappyBundle in Symfony, but whenever I attempt to run the action to do so, it exceeds the maximum 60 second execution time in PHP.
Here is the action:
/**
* #Route("/download-agreement", name="download_agreement")
*/
public function downloadAgreementAction()
{
$session = new Session();
$html = $this->renderView('client-representation.html.twig', array(
'clientAgreementData' => $session->get("sessionClientAgreementData"),
"pdfStatus" => true
));
return new Response(
$this->get('knp_snappy.pdf')->getOutputFromHtml($html),
200,
array(
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="error.pdf"'
)
);
}
It also only seems to exceed the execution time when I use absolute urls for assets in the twig template, like absolute_url(asset('css/agreement.css')). If I use relative urls, then the css will just get ignored and the PDF will generate, but of course I need the styling.
Any ideas?
EDIT: For anyone who has this problem, using absolute urls SHOULD work on a production server; on localhost however, you may get the problem I had. Thank you to chalasr.
Ok, the solution is really simple actually, the problem is that it wont work on dev(local) environment because for some reason the wkhtmltopdf doesn't like localhost:8000... whatever.
From this issue (and many other) opened for this problem in the laravel package.
Another on the bundle https://github.com/KnpLabs/KnpSnappyBundle/issues/66
The first time I worked with KnpSnappyBundle, I used a lot of alternatives, unsuccessfully.
The 'timeout exceeded' occurs when trying to generate pdf using a view (same for all related methods of the Pdf class) which contains one or more absolute url(s).
To deal with this bug, I use a specific template used only for Pdf generation, where I put the css directly in a <style></style> block.
Like this, the style is correctly applied.
Related
Users upload PDF files using my PHP application and I store them on S3. At some later point other users view the files, which I display 'inline' in their browser.
The problem is that the 'Title' attribute of the PDF is displayed in teh browser tab where the web site title would normally be displayed. As it is set by the user who did the original upload, it is arbitrary, and I therefore need to change it. How do I do this?
I thought that Title was an extended attribute of the file, however installed Ubuntu's xattr, and when I run it on the file, it returns nothing, so perhaps I am mistaken.
When I view the object metadata on S3, there is no mention of a Title attribute, so I don't know where/how it is stored on S3.
My preference would be to rewrite the Title attirbute using an OS call, rather than installing another PHP extension (such as xattr).
EDIT: Here is the Laravel controller method which returns the response
public function displayFile($id)
{
$headers = ['Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline'];
return response(Storage::disk('private')->get("files/{$id}.pdf"), 200, $headers);
}
When you say 'inline' what exactly do you mean? What you are describing seems more like you are pointing them to the document url. In this case, the title will be the one contained in the PDF, which only some PDF editor could change. I know of none that will not break files (especially ones with interactive content) BADLY for PHP. If you have access to native apps, you can try using exiftool: https://askubuntu.com/questions/27381/how-to-edit-pdf-metadata-from-command-line
What you might want to do is actually display the document inside a HTML file, like this:
https://www.w3docs.com/snippets/html/how-to-embed-pdf-in-html.html
Note: do extensive testing for various browsers, especially mobile; PDF embedding is notoriously quirky in mobile browsers.
You should be able to add an arbitrary filename to Content-Disposition for inline viewing, just as you could if you were downloading. Try something like this:
$headers = ['Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline; filename=\"WhateverYouWantTheUsersToSee\"'];
(Don't actually know whether you need to escape those quotation marks; if not, take out the backslashes.)
The problem is that to set the title, the title must set into the pdf, but there is a workaround ( see explanation )
Explanation:
The page with target "_blank" attribute, set the file names base on the last part of the url. So if the url is my.site/32/55 , the html title is 55.
The workaround is to set the file name as the last part of the url
1) Web.php
This is the most important part. To give the page the pdf title, set the title you want as the last param
Route::get('/pdfSee/{fileID}/{htmlTitle}', 'FileController#viewInTheBrowser')->name('file.see');
2) Controller
public function viewInTheBrowser(File $file){
$basePath = 'public/myFolder/';
return response()->download(storage_path("app/".$this->basePath. $file->file_system_path), $file->file_name, [], 'inline');
}
3) View
Download
As you can see, we pass in the route file.see the actual file name as last param, so wen the html page is open, it takes the last param ( the file name ) as html page title
I have a website with images upload/show functionality on it. All images are saved into filesystem on a specific path.
I use Yii2 framework in the project. There isn't straight way to the images and all of them requested by specific URL. ImageController proceses the URL and takes decision about image resizing. ImageModel does the job. The user get image content.
Here the code snippet:
$file = ... // full path to image
...
$ext = pathinfo($file)['extension'];
if (file_exists($file)) {
// return original
return Imagine::getImagine()
->open($file)
->show($ext, []);
}
preg_match("/(.*)_(\d+)x(\d+)\.{$ext}/", $file, $matches);
if (is_array($matches) && count($matches)) {
if (!file_exists("{$matches[1]}.{$ext}")) {
throw new NotFoundHttpException("Image doen't exist!");
}
$options = array(
'resolution-units' => ImageInterface::RESOLUTION_PIXELSPERINCH,
'resolution-x' => $matches[2],
'resolution-y' => $matches[3],
'jpeg_quality' => 100,
);
return Imagine::resize("{$matches[1]}.{$ext}", $matches[2], $matches[3])
->show($ext, $options);
} else {
throw new NotFoundHttpException('Wrong URL params!');
}
We don't discuss data caching in this topic.
So, I wonder about efficient of this approach. Is it ok to return all images by PHP even they aren't changed at all? Will it increase the server load?
Or, maybe, I should save images to another public directory and redirect browser to it? How long does it take to so many redirects on a single page (there are can be plenty images). What about SEO?
I need an advice. What is the best practice to solve such tasks?
You should consider using sendFile() or xSendFile() for sending files - it should be much faster than loading image using Imagine and displaying it by show(). But for that you need to have a final image saved on disk, so we're back to:
We don't discuss data caching in this topic.
Well, this is actually the first thing that you should care about. Sending image by PHP will be significantly less efficient (but still pretty fast, although this may depend on your server configuration) than doing that by webserver. Involving framework into this will be much slower (bootstrapping framework takes time). But this is all irrelevant if you will resize the image on every request - this will be the main bottleneck here.
As long as you're not having some requirement which will make it impossible (like you need to check if the user has rights to see this image before displaying it) I would recommend saving images to public directory and link to them directly (without any redirection). It will save you much pain with handling stuff that webserver already do for static files (handling cache headers, 304 responses etc) and it will be the most efficient solution.
If this is not possible, create a simple PHP file which will only send file to the user without bootstrapping the whole framework.
If you really need the whole framework, use sendFile() or xSendFile() for sending file.
The most important things are:
Do not use Imagine to other things than generating an image thumbnail (which should be generated only once and cached).
Do not link to PHP page which will always only redirect to real image served by webserver. It will not reduce server load comparing to serving image by PHP (you already paid the price of handling request by PHP) and your website will work slower for clients (which may affect SEO) due to additional request required to get actual image.
If you need to serve image by PHP, make sure that you set cache headers and it works well with browser cache - you don't want to download the same images on every website refresh.
I have an issue with a view and generated content to produce a PDF. At the moment, I've been working with niklasravnsborg\LaravelPdf (a wrapper for mPDF, because of an issue with another PDF writer I was working with beforehand), which turns out nice PDFs that are of the quality that I want.
I have never had an issue with images inside of the view before with this PDF writer though, but I must admit that they were with images that had been set-up inside the view already (like a logo, rather than say an employees photo).
My issue arises in a way that has at least allowed me to track down the issue a little better.
From my controller, I get the following:
$employeeMedCert = $employee->attachments()->where('category','Medical Examiners Certificate')->orderBy('endDate','desc')->limit(1)->get();
And then in my blade I have the following:
#foreach($employeeMedCert as $med){
{{Storage::url($med->attachment)}}
#endforeach
Now, with this current setup, I get the public path of the attachment, without any issue at all.
However, if I do the following:
#foreach($employeeMedCert as $med){
<img src="{{Storage::url($med->attachment)}}">
#endforeach
It stalls my Laravel to a point where I have to reset the server and hasn't generated anything.
I'm not sure what the issue is, like I said, I've had no issues with images before and the images I am referencing dynamically aren't large by any means (300 - 600 kB), so I am not sure where the issue actually is.
Upload controller action:
$path = Storage::putFile('public/employees', new File(request('file')));
employeeAttachment::create([
'attachment' => $path,
'attachmentType' => Storage::mimeType($path),
'category' => $request->type,
'endDate' => $request->dueDate,
'date' => $request->date,
'employeeID' => $employee,
'createdBy' => Auth::id()
]);
The big clue for this problem is that in html <img> tags do a get request when they are run in html. This means you could be having a permission error.
You can check this by opening your devtools in chrome and checking out your server requests in any website that has an image tag.
Following things to check (assuming you are using a basic storage e.g. not using AWS):
Is image in your public folder or have you correctly created your sym link: https://laravel.com/docs/5.6/filesystem.
If your files are public can you navigate to it in the browser?
Have you tried using the url instead of going through storage? e.g. <img src="{{base_url() . $med->attachment)}}"> (assuming $med->attachment is a file path).
Assuming it's a public file you shouldn't have to go through the Storage facade to access it. Let me know if this points you in the right direction.
The purpose of the Storage facade is to save files and get files and load them into a php variable. Currently you are loading the file from the server and then pointing your image tag to the file and not to the url of where the image is stored.
Putting in a valid url into the img tag should solve your problem (shown in 3 above).
I am working on a Symfony 1.4 project. I need to make a PDF download link for a (yet to be) generated voucher and I have to say, I am a bit confused. I already have the HTML/CSS for the voucher, I created the download button in the right view, but I don't know where to go from there.
Use Mpdf to create the pdf file
http://www.mpdf1.com/
+1 with wkhtmltopdf
I'd even recommand the snappy library.
If you use composer, you can even get the wkhtmltopdf binaries automatically
Having used wkhtmltopdf for a while I've moved off it as 1) it has some serious bugs and 2) ongoing development has slowed down. I moved over to PhantomJS which is proving to be much better in terms of functionality and effectiveness.
Once you've got something like wkhtmltopdf or PhantomJS on your machine you need to generate the HTML page and pass that along to it. I'll give you an example assuming you use PhantomJS.
Initially set what every request parameters you need to for the template.
$this->getRequest->setParamater([some parameter],[some value]);
Then call the function getPresentation() to generate the HTML from a template. This will return the resulting HTML for a specific module and action.
$html = sfContext::getInstance()->getController()->getPresentation([module],[action]);
You'll need to replace the relative CSS paths with a absolute CSS path in the HTML file. For example by running preg_replace.
$html_replaced = preg_replace('/"\/css/','"'.sfConfig('sf_web_dir').'/css',$html);
Now write the HTML page to file and convert to a PDF.
$fp = fopen('export.html','w+');
fwrite($fp,$html_replaced);
fclose($fp)
exec('/path/to/phantomjs/bin/phantomjs /path/to/phantomjs/examples/rasterize.js /path/to/export.html /path/to/export.pdf "A3");
Now send the PDF to the user:
$this->getResponse()->clearHttpHeaders();
$this->getResponse()->setHttpHeader('Content-Description','File Transfer');
$this->getResponse()->setHttpHeader('Cache-Control','public, must-revalidate, max-age=0');
$this->getResponse()->setHttpHeader('Pragma: public',true);
$this->getResponse()->setHttpHeader('Content-Transfer-Encoding','binary');
$this->getResponse()->setHttpHeader('Content-length',filesize('/path/to/export.pdf'));
$this->getResponse()->setContentType('application/pdf');
$this->getResponse()->setHttpHeader('Content-Disposition','attachment; filename=export.pdf');
$this->getResponse()->setContent(readfile('/path/to/export.pdf'));
$this->getResponse()->sendContent();
You do need to set the headers otherwise the browser does odd things. The filename for the generated HTML file and export should be unique to avoid the situation of two people generating PDF vouchers at the same time clashing. You can use something like sha1(time()) to add a randomised hash to a standard name e.g. 'export_'.sha1(time());
Use wkhtmltopdf, if possible. It is by far the best html2pdf converter a php coder can use.
And then do something like this (not tested, but should be pretty close):
public function executeGeneratePdf(sfWebRequest $request)
{
$this->getContext()->getResponse()->clearHttpHeaders();
$html = '*your html content*';
$pdf = new WKPDF();
$pdf->set_html($html);
$pdf->render();
$pdf->output(WKPDF::$PDF_EMBEDDED, 'whatever_name.pdf');
throw new sfStopException();
}
I am trying to make a download action that downloads a Word doc generated in the 'download' controller using PHPDOCX. So far PHPDOCX is able to save the desired .docx file in the correct folder, but something goes wrong when I try to download it. Since Media Views were deprecated, I must use the CakeResponse file method as suggested in the CakePHP 2.x Cookbook:
// In the controller:
$this->response->file($file['path'], array('download' => true, 'name' => $filename));
return $this->response;
I was able to use this method for to export an RTF with no problem (the RTF was generated using PHPRTFLite), but when I use the method for a .docx file using PHPDOCX I receive the following error in Firefox:
The character encoding declaration of the HTML document was not found
when prescanning the first 1024 bytes of the file. When viewed in a
differently-configured browser, this page will reload automatically.
The encoding declaration needs to be moved to be within the first 1024
bytes of the file.
I would like to use a document generator that accepts HTML, which is why I chose PHPDOCX. Considering the above error, I set off to define the headers and content-type using the following method:
$this->response->header(array('Content-type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document'));
But I still receive the same error in CakePHP:
The requested file APP/files/info_sheets/filename.docx was not found or not readable
One thing I was thinking is that PHPDOCX sends many errors when it generates the document and this is interfering with the Mime-type or encoding. But according to the 2.x Cookbook:
Headers are not sent when CakeResponse::header() is called either.
They are just buffered until the response is actually sent.
Another idea is that I need to set the character encoding in the header right after the content-type:
$this->response->header(array('Content-type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8'));
But this results in garbled text.
Does anyone have any ideas how to resolve this? The "download.ctp" view file is currently blank. Please let me know if you need additional information about this issue.
Thanks!
Chris
First of all, you might try to disable autoRender, otherwise CakePHP might still try to render your view and layout;
$this->autoRender = false;
Also, haven't tested it, but have you tried this to set the header:
// register the type
$this->response->type(array('docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'));
// set the type for the response
$this->response->type('docx');
See the documentation:
http://book.cakephp.org/2.0/en/controllers/request-response.html#dealing-with-content-types
You can modify the media.php file in the core of the framework and add the mime-type to the array that have the types.
Eg:
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'