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).
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 am working on a PHP app on Google App Engine standard environment.
I upload an image to the app's associated bucket, then use this code to serve the image:
use \google\appengine\api\cloud_storage\CloudStorageTools;
// ...
$path = "gs://my-app.appspot.com/the_image_name.jpg"
$imageUrl = CloudStorageTools::getImageServingUrl($path, ['secure_url' => true, 'size' => 0]);
header("Location: $imageUrl");
There is of course a redirection to a page like this:
https://lh3.googleusercontent.com/<some_hash>
The image displays correctly, but in the title of the browser it says "unnamed.jpg", and when I try to save the image to my computer the default name appears as "unnamed.jpg". This happens every time I upload an image, no matter its actual name, or if I do upload it through my app using the API or if I upload it directly to the bucket through the web cloud console.
Is there any way in which I can specify what name is used when serving the image?
Did you set the headers: 'Content-disposition', 'Content-Transfer-Encoding', & 'Content-Type'
CloudStorageTools::getImageServingUrl uses googleusercontent.com which is a "sandbox" and therefore you can't change the name of the file served.
I suggest you to take a look in CloudStorageTools::serve method where you will be able to set the name of the file with save_as parameter. Please note that the type is string and not boolean as it's mentioned in the documentation and serve will take some app engine compute time.
The code should look like this:
use \google\appengine\api\cloud_storage\CloudStorageTools;
// ...
$file_name = "gs://my-app.appspot.com/the_image_name.jpg"
$options = ['save_as' => 'the_image_name.jpg'];
CloudStorageTools::serve($file_name, $options);
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'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.
I'm using GAE version 1.9.0 and I want to delete an image from the data storage and upload another image to its location. This is how I'm doing it right now.
unlink("gs://my_storage/images/test.jpg");
move_uploaded_file($_FILES['image']['tmp_name'],'gs://my_storage/images/test.jpg');
And then I want to get the Image serving URL of the latest uploaded image, and I do it like this.
$image_link = CloudStorageTools::getImageServingUrl("gs://my_storage/images/test.jpg");
The issue is, when the name of the deleted image("test.jpg") and the uploaded image("test.jpg") is the same, the old file is served when I call for the newly uploaded file(I think it is cached.)
Is there anyway I can permanently delete this file without caching it?
You should probably delete the original serving URL before creating another with the same name.
There's a deleteImageServingUrl() method in CloudStorageTools that you can use to do this.
Here it is how to do in php laravel.
$object = $post_media->media_cloud;
$objectname = substr($object,48,100);
$bucket = Storage::disk('gcs')->delete($objectname);
Here in $object i get google cloud image url from db
Then we take only object name from that url, by substr.
Since you have given in your config Storage class as Storage::disk('gcs')
so this will call the function delete by taking the objectname.
Hope it helps anyone.
Note : For multiple images either pass an array of objects, or repeat it foreach loop.