Broken Images within phpwkhtmltopdf - php

Things to note, I have tested this on two different servers, Debian 9 and Ubuntu 14.04 and the same error persists. Right now I am using Ubuntu 14.04 with PHP 5, I have installed composer, I have installed both wkhtmltopdf and phpwkhtmltopdf correctly. How do I know this? Well wkhtmltopdf/image works via CLI, phpwkhtmltopdf also works via PHP however when I attempt to send the image to the client as an inline display or download the image corrupts. For example;
Visit desired url, for us its test.php
Phpwkhtmltopdf will send commands that hook up with the CLI version wkhtmltopdf
Once the page loads it will visit google.com, save a screenshot on the disk /var/www/html/tmp/page.jpg and that image opens/displays fine, however when I attempt to use $image->send('page.jpg'); the sent image is corrupt/wont open.
I have made two changes to the system, I have disabled mod_deflate within apache2 and I have also increased the max_filesize options within apache2's php.ini config file.
Dependencies
wkhtmltopdf - https://wkhtmltopdf.org/
phpwkhtmltopdf - https://github.com/mikehaertl/phpwkhtmltopdf
Live Example
http://155.254.35.63/test.php // Generate the image
http://155.254.35.63/tmp/page.png // The image file generated
test.php
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
?>
<?php
$loader = require __DIR__ . '/vendor/autoload.php';
?>
<?php
use mikehaertl\wkhtmlto\Image;
$image = new \mikehaertl\wkhtmlto\Image('https://www.google.co.uk/search?q=what+is+the+time&oq=what+is+the+time&aqs=chrome.0.69i59j69i60l3j0l2.2977j0j4&sourceid=chrome&ie=UTF-8');
$image->setOptions(array(
'binary' => '/usr/local/bin/wkhtmltoimage',
'type' => 'png'
));
$image->saveAs('/var/www/html/tmp/page.png');
header('Content-Type: image/png');
echo file_get_contents('/var/www/html/tmp/page.jpg');
?>
File.php (Lines 72 to 83)
<?php
namespace mikehaertl\tmp;
/**
* File
*
* A convenience class for temporary files.
*
* #author Michael Härtl <haertl.mike#gmail.com>
* #version 1.1.0
* #license http://www.opensource.org/licenses/MIT
*/
class File
{
/**
* #var bool whether to delete the tmp file when it's no longer referenced or when the request ends.
* Default is `true`.
*/
public $delete = true;
/**
* #var string the name of this file
*/
protected $_fileName;
/**
* Constructor
*
* #param string $content the tmp file content
* #param string|null $suffix the optional suffix for the tmp file
* #param string|null $prefix the optional prefix for the tmp file. If null 'php_tmpfile_' is used.
* #param string|null $directory directory where the file should be created. Autodetected if not provided.
*/
public function __construct($content, $suffix = null, $prefix = null, $directory = null)
{
if ($directory===null) {
$directory = self::getTempDir();
}
if ($prefix===null) {
$prefix = 'php_tmpfile_';
}
$this->_fileName = tempnam($directory,$prefix);
if ($suffix!==null) {
$newName = $this->_fileName.$suffix;
rename($this->_fileName, $newName);
$this->_fileName = $newName;
}
file_put_contents($this->_fileName, $content);
}
/**
* Delete tmp file on shutdown if `$delete` is `true`
*/
public function __destruct()
{
if ($this->delete) {
unlink($this->_fileName);
}
}
/**
* Send tmp file to client, either inline or as download
*
* #param string|null $filename the filename to send. If empty, the file is streamed inline.
* #param string $contentType the Content-Type header
* #param bool $inline whether to force inline display of the file, even if filename is present.
*/
public function send($filename = null, $contentType, $inline = false)
{
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: image/png');
header('Content-Transfer-Encoding: binary');
if ($filename!==null || $inline) {
$disposition = $inline ? 'inline' : 'attachment';
header("Content-Disposition: $disposition; filename=\"$filename\"");
}
readfile($this->_fileName);
}
/**
* #param string $name the name to save the file as
* #return bool whether the file could be saved
*/
public function saveAs($name)
{
return copy($this->_fileName, $name);
}
/**
* #return string the full file name
*/
public function getFileName()
{
return $this->_fileName;
}
/**
* #return string the path to the temp directory
*/
public static function getTempDir()
{
if (function_exists('sys_get_temp_dir')) {
return sys_get_temp_dir();
} elseif ( ($tmp = getenv('TMP')) || ($tmp = getenv('TEMP')) || ($tmp = getenv('TMPDIR')) ) {
return realpath($tmp);
} else {
return '/tmp';
}
}
/**
* #return string the full file name
*/
public function __toString()
{
return $this->_fileName;
}
}
php.ini
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
; Whether to allow HTTP file uploads.
; http://php.net/file-uploads
file_uploads = On
; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
; http://php.net/upload-tmp-dir
;upload_tmp_dir = /var/www/html/tmp
; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 50M
; Maximum number of files that can be uploaded via a single request
max_file_uploads = 20
There are none (0) errors in the apache log file, which is really putting me off what the issue could be. I have attempted to find a resolution with the dev but no look;
https://github.com/mikehaertl/phpwkhtmltopdf/issues/278
Id appreciate the help on this one.

I have examined the page.jpg file your test site generates. The file itself is intact. This means there is nothing wrong with your plumbing.
The file header shows that instead of a standard JPEG file, yours is a JFIF variant. See if you can set the library to generate a PNG file to workaround this issue.
Edit: now that I see the generated file is correct, see if you can just stream the content instead of using $image->send. Send it youself:
header('Content-Type: image/png');
echo file_get_contents('/var/www/html/tmp/page.jpg');

Related

XenForo Avatar upload fails, $image == false - PHP7

I'm stumped on a XenForo 1.5.7 / php7 issue. I've read that tempnam() was changed as of php7 (based on temp dir permissions), but I've chmod'd the directory as that link states, still to no avail.
I printed out $newTempFile which returns /var/www/forum/internal_data/temp/xfJ9FLyG (looks correct). It's the next line, the $image variable, that does not get set, and then throws the error in the if() below.
$newTempFile = tempnam(XenForo_Helper_File::getTempDir(), 'xf');
$image = XenForo_Image_Abstract::createFromFile($fileName, $imageType);
if (!$image)
{
throw new XenForo_Exception(new XenForo_Phrase('image_could_be_processed_try_another_contact_owner'), true);
}
Here is the code for createFromFile() in Image\Abstract.php:
/**
* Creates an image from an existing file.
*
* #param string $fileName
* #param integer $inputType IMAGETYPE_XYZ constant representing image type
*
* #return XenForo_Image_Abstract|false
*/
public static function createFromFileDirect($fileName, $inputType)
{
throw new XenForo_Exception('Must be overridden');
}
...
public static function createFromFile($fileName, $inputType)
{
$class = self::_getDefaultClassName();
return call_user_func(array($class, 'createFromFileDirect'), $fileName, $inputType);
}
Because it looks like createFromFileDirect() is called from createFromFile(), my thought was that a "Must be overriden" error would be thrown, but this doesn't appear to be the case.
Any ideas?

Continue php script after connection close

I am trying to continue a PHP Script after the page/connection is closed.
Users will POLL the script in every 1 hour, I want to return some json output and want to continue the script in the background. I am using a shared host and I cannot use cron job.
Here is what I've tried.
ob_start();
ignore_user_abort();
echo "JSON_OUTPUT GOES HERE";
$ob_length = ob_get_length();
header("Content-Type : text/plain",TRUE);
header("Content-Length : $ob_length",TRUE);
header("Connection : Close",TRUE);
flush();
ob_flush();
ob_end_flush();
sleep(3);
echo "You cant see me..";
exit();
I am using Codeigniter framework, But its not working on my live server. It waits 3 seconds and then outputting You cant see me.. too.
Please help me.
Note
Project is hosted in LINUX/WINDOWS/WAMP-SERVER shared hosts.
After some research i got it work, Sometime it may be useful to some others.
function closeOutput($stringToOutput){
set_time_limit(0);
ignore_user_abort(true);
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
ob_start();
echo $stringToOutput;
$size = ob_get_length();
header("Content-Length: $size",TRUE);
ob_end_flush();
ob_flush();
flush();
}
You can use it like
$outputContent = 'Contentent Goes Here...';
closeOutput( $outputContent );
sleep(5);
//do some background works ...
exit();
First, don't use space after Connection and before : it should be Header: value not Header : value. Second, Connection: close don't force browser to stop getting current response and display blank page. Here http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html chapter 14.10 it states: Connection: close in either the request or the response header fields indicates that the connection SHOULD NOT be considered 'persistent' (section 8.1) after the current request/response is complete
So how can you try if your code works:
ignore_user_abort();
header("Content-Type: text/plain; charset=UTF-8");
// just to try show following echo immediately, working depends on server configuration
while (#ob_end_flush());
echo date('Y-m-d H:i:s'), PHP_EOL;
echo "JSON_OUTPUT GOES HERE", PHP_EOL;
sleep(10); // 10 seconds so you can close browser tab before
// this file should be created after 10 seconds, even after you closed browser tab
// also check if permissions to write to __DIR__ are set for apache.
file_put_contents(__DIR__ . '/tmp.txt', "Text after 10 sec");
exit;
Open this php file in browser and after 2-3 seconds close tab (even if you don't see anything on screen), wait a little longer and check if file is created. It's working on my linux machine.
Because of this cool possibility, which Red posted, I've written a small utility class which provides a queue where you can add Closures for later execution:
<?php
namespace Company\Project\Utilities;
/**
* Class ContinueUtility
*
* #package Company\Project\Utilities
*/
class ContinueUtility {
/**
* Stored tasks
* #var array
*/
static protected $tasks = array();
/** Constant for new line in HTTP Header */
const HEADER_NEW_LINE = "\r\n";
/**
* Add task (closure/function) to queue, with set arguments
*
* #param \Closure $task
* #param array $arguments
* #return void
*/
public static function addTask(\Closure $task, array $arguments = array()) {
self::$tasks[] = array(
'closure' => $task,
'arguments' => $arguments
);
}
/**
* Returns TRUE if tasks has been set, otherwise FALSE
*
* #return boolean
*/
public static function hasTasks() {
return !empty(self::$tasks);
}
/**
* Clear all previous set tasks
*
* #return void
*/
protected static function clearTasks() {
self::$tasks = array();
}
/**
* Execute all previous set tasks
*
* #return void
*/
protected static function executeTasks() {
foreach (self::$tasks as $task) {
call_user_func_array($task['closure'], $task['arguments']);
}
}
/**
* Execute and clear all previous set tasks
*
* #return void
*/
public static function executeAndClearTasks() {
self::executeTasks();
self::clearTasks();
}
/**
* Closes the HTTP connection to client immediately and outputs given string.
*
* #param string $instantOutput
* #return void
*/
public static function closeConnection($instantOutput = '') {
set_time_limit(0);
ignore_user_abort(TRUE);
header('Connection: close' . self::HEADER_NEW_LINE);
header('Content-Encoding: none' . self::HEADER_NEW_LINE);
ob_start();
echo $instantOutput;
$size = ob_get_length();
header('Content-Length: ' . $size, TRUE);
ob_end_flush();
ob_flush();
flush();
}
}
This is how you add new tasks to queue:
use Company\Project\Utilities\ContinueUtility;
$a = 4;
$b = 5;
ContinueUtility::addTask(function($a, $b){
sleep(5);
$c = a + b;
file_put_contents(__DIR__ . '/whatever.log', $a . '+' . $b . '=' . $c);
}, array(
$a, $b
));
And this is how you trigger execution of all previous added tasks:
ContinueUtility::closeConnection('Ready.');
ContinueUtility::executeAndClearTasks();
If you are using PHP-FPM, a cleaner and more versatile solution would be to simply execute fastcgi_finish_request();
From PHP.net's documentation
This function flushes all response data to the client and finishes the request. This allows for time consuming tasks to be performed without leaving the connection to the client open.
This is how Symfony handles its onTerminate.

How to use Minify PHP with YUI compressor?

I would like to use YUI compressor with minify PHP rather than the default JSmin. Does anyone have experience setting this up?
Right now I am using the groupsConfig.php to combine the JS.
return array(
'jsAll' => array('//contenido/themes/bam/assets/js/jquery.js', '//contenido/themes/bam/assets/js/modernizr.js','//contenido/themes/bam/assets/js/imgpreload.js', '//contenido/themes/bam/assets/js/imgpreload.js', '//contenido/themes/bam/assets/js/history.js','//contenido/themes/bam/assets/js/ajaxify.js', '//contenido/themes/bam/assets/js/isotope.js'),
'jsHome' => array('//contenido/themes/bam/assets/js/easing.js','//contenido/themes/bam/assets/js/scrollable.js', '//contenido/themes/bam/assets/js/home.js'),
'cssAll' => array('//contenido/themes/bam/bam.css'),
);
As it says on the homepage:
Uses an enhanced port of Douglas Crockford's JSMin library and custom classes to minify CSS and HTML
I have the following code in config.php, but I get a 500 error when trying to view the combined js file:
function yuiJs($js) {
require_once '/lib/Minify/YUICompressor.php';
Minify_YUICompressor::$jarFile = '/lib/yuicompressor-2.4.2.jar';
Minify_YUICompressor::$tempDir = '/temp';
return Minify_YUICompressor::minifyJs($js);
}
$min_serveOptions['minifiers']['application/x-javascript'] = 'yuiJs';
It also appears that there are several lines in lib/Minify/YUICompressor.php that need to be configured, and I'm not sure if I'm doing it right:
class Minify_YUICompressor {
/**
* Filepath of the YUI Compressor jar file. This must be set before
* calling minifyJs() or minifyCss().
*
* #var string
*/
public static $jarFile = '../yuicompressor-2.4.2.jar';
/**
* Writable temp directory. This must be set before calling minifyJs()
* or minifyCss().
*
* #var string
*/
public static $tempDir = '../../temp/';
/**
* Filepath of "java" executable (may be needed if not in shell's PATH)
*
* #var string
*/
public static $javaExecutable = 'java';
I had the same problem on windows. It seems jar file needs to be executable in order to run yui compressor. So, i have to remove excutable check from YUICompressor.php
#132
private static function _prepare()
{
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
}
// if (! is_executable(self::$jarFile)) {
// throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
// }
if (! is_dir(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
}
if (! is_writable(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
}
}
and that works fine.

How to check if gzip compression is enabled with PHP?

Is (function_exists('ob_gzhandler') && ini_get('zlib.output_compression')) enough ?
I want to check if the host is serving compressed pages within one of the pages :)
For PHP, they'll do fine.
However, if your referring to compression of pages back to clients, you'll also need to check it's enabled in apache (assuming your using apache you'll need the mod_gzip.c OR mod_deflate.c modules).
For instance:
# httpd -l (apache 2)
Ive also seen mention of needing to implement .htaccess overrides in the past:
#compress all text & html:
AddOutputFilterByType DEFLATE text/html text/plain text/xml
# Or, compress certain file types by extension:
<Files *.html>
SetOutputFilter DEFLATE
</Files>
With
<?php
phpinfo();
?>
You could find if the module is loaded
or
This website https://www.giftofspeed.com/gzip-test/
You can check if the compression on a certain page is enabled.
With those you'll see if the compression is enough for you.
you can do this programmatically from php:
if (count(array_intersect(['mod_deflate', 'mod_gzip'], apache_get_modules())) > 0) {
echo 'compression enabled';
}
This is of course not super reliable, because there might be other compression modules...
I have a class that checks based on a get request to css or js file if gzip compression is enabled and then tries to output corresponding compressed css or js file if it's enabled or a regular file if it's not.
If you are looking only at how to check if gszip is enabled then isPhpGzCompressionInProcess method of these class can help you but in most cases, you need to process some output base on it and I am pretty sure that the rest of the class can help someone too.
<?php
/**
* Class AssetBundleController
*/
class AssetBundleCompressionController
{
/**
* AssetBundleController constructor.
*/
public function __construct()
{
$this->outputCompression();
}
/**
* Trying to output compression bundle.
*/
protected function outputCompression()
{
// here request to css or js file
if (empty($_GET['vcv-script']) && empty($_GET['vcv-style'])) {
return;
}
error_reporting(0);
$mimeType = $this->getMimeType();
header('Content-Type: ' . $mimeType);
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === false) {
// browser cannot accept compressed content, so need output standard JS/CSS
echo file_get_contents($this->getBundlePath());
} else {
if ($this->isPhpGzCompressionInProcess()) {
// let 3 party app gzip our content.
echo file_get_contents($this->getBundlePath());
} else {
// output our gzip content.
header("Content-Encoding: gzip");
echo file_get_contents($this->getBundlePath(true));
}
}
exit;
}
/**
* Get current requested bundle path.
*
* #param bool $isCompress
*
* #return string
*/
protected function getBundlePath($isCompress = false)
{
$assetType = $this->getAssetType();
$name = $this->getCompressionRequestName($assetType);
$path = VCV_PLUGIN_DIR_PATH . 'public/dist/' . $name . '.bundle.' . $assetType;
if ($isCompress) {
$path .= '.gz';
}
return $path;
}
/**
* Check if php compression is already enabled.
*
* #return bool
*/
protected function isPhpGzCompressionInProcess()
{
if (in_array('ob_gzhandler', ob_list_handlers())) {
return true;
}
// check if zlib php exention is working
if (extension_loaded('zlib')) {
#ini_set('zlib.output_compression_level', 1);
if (ini_get('zlib.output_compression_level') === '1') {
return true;
}
}
return false;
}
/**
* Get compression request name.
*
* #return string
*/
protected function getCompressionRequestName($assetType)
{
$name = '';
$compressList = [
'editor',
'wp',
'vendor',
'runtime',
];
$searchKey = $assetType === 'js' ? $_GET['vcv-script'] : $_GET['vcv-style'];
$key = array_search($searchKey, $compressList);
if ($key !== false) {
$name = $compressList[$key];
}
return $name;
}
/**
* Check current requested asset type
*
* #return string
*/
protected function getAssetType()
{
$type = 'js';
if (!empty($_GET['vcv-style'])) {
$type = 'css';
}
return $type;
}
/**
* Set current request asset mine type.
*/
protected function getMimeType()
{
$type = 'application/javascript';
if (!empty($_GET['vcv-style'])) {
$type = 'text/css';
}
return $type;
}
}

PHP Thumbnail Image Generator Caching: How to set If-Last-Modified/Max-Age/Last-Modified HEADERS correctly in PHP?

Even after a very high score of Google PageSpeed(97) & Yahoo! YSlow(92) the PHP generated thumbnails don't seem to be coming passively from an old cache: they seem to be generated every time again...and again... freshly baked consuming lots of waisted time.
This question will focus only & specifically on how to solve the CACHE problem of the PHP Code that generates the thumbs:
Just have a look at these tiny puny little thumbnails measuring only 3 ~ 5 kb each!
Waterfall in detail: http://www.webpagetest.org/result/110328_AM_8T00/1/details/
Any & all suggestons are +1 help to me and warmly welcome, for I have grown quite desperate on this issue for the last months. Thanx a Thousand!
Using or not Modrewrite does not influence speed: both are same. I use these rewrite conditions: RewriteCond %{REQUEST_URI} ^/IMG-.*$ & RewriteCond %{REQUEST_FILENAME} !-f
Both the original default URL as well as the beautified rewritten URL produce the same delays!! So let us not point the fault to the lightning fast Apache: its the PHP Cache / headers that are somehow wrongly coded...
Warning by webpagetest.org: Leverage browser caching of static assets: 69/100
FAILED - (No max-age or expires): http://aster.nu/imgcpu?src=aster_bg/124.jpg&w=1400&h=100&c=p
After each refresh, you will see either of these two warnings appear on random at REDbot.org
Relevant Portions of The Code:
// Script is directly called
if(isset($_GET['src']) && (isset($_GET['w']) || isset($_GET['h']) || isset($_GET['m']) || isset($_GET['f']) || isset($_GET['q']))){
$ImageProcessor = new ImageProcessor(true);
$ImageProcessor->Load($_GET['src'], true);
$ImageProcessor->EnableCache("/var/www/vhosts/blabla.org/httpdocs/tmp/", 345600);
$ImageProcessor->Parse($quality);
}
/* Images processing class
* - create image thumbnails on the fly
* - Can be used with direct url imgcpu.php?src=
* - Cache images for efficiency
*/
class ImageProcessor
{
private $_image_path; # Origninal image path
protected $_image_name; # Image name string
private $_image_type; # Image type int
protected $_mime; # Image mime type string
private $_direct_call = false; # Is it a direct url call? boolean
private $_image_resource; # Image resource var Resource
private $_cache_folder; # Cache folder strig
private $_cache_ttl; # Cache time to live int
private $_cache = false; # Cache on boolean
private $_cache_skip = false; # Cache skip var boolean
private function cleanUrl($image){ # Cleanup url
$cimage = str_replace("\\", "/", $image);
return $cimage;
}
/** Get image resource
* #access private, #param string $image, #param string $extension, #return resource */
private function GetImageResource($image, $extension){
switch($extension){
case "jpg":
#ini_set('gd.jpeg_ignore_warning', 1);
$resource = imagecreatefromjpeg($image);
break;
}
return $resource;
}
/* Save image to cache folder
* #access private, #return void */
private function cacheImage($name, $content){
# Write content file
$path = $this->_cache_folder . $name;
$fh = fopen($path, 'w') or die("can't open file");
fwrite($fh, $content);
fclose($fh);
# Delete expired images
foreach (glob($this->_cache_folder . "*") as $filename) {
if(filemtime($filename) < (time() - $this->_cache_ttl)){
unlink( $filename );
}
}
}
/* Get an image from cache
* #access public, #param string $name, #return void */
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
/* Get name of the cache file
* #access private, #return string */
private function generateCacheName(){
$get = implode("-", $_GET);
return md5($this->_resize_mode . $this->_image_path . $this->_old_width . $this->_old_height . $this->_new_width . $this->_new_height . $get) . "." . $this->_extension;
}
/* Check if a cache file is expired
* #access private, #return bool */
private function cacheExpired(){
$path = $this->_cache_folder . $this->generateCacheName();
if(file_exists($path)){
$filetime = filemtime($path);
return $filetime < (time() - $this->_cache_ttl);
}else{
return true;
}
}
/* Lazy load the image resource needed for the caching to work
* #return void */
private function lazyLoad(){
if(empty($this->_image_resource)){
if($this->_cache && !$this->cacheExpired()){
$this->_cache_skip = true;
return;
}
$resource = $this->GetImageResource($this->_image_path, $this->_extension);
$this->_image_resource = $resource;
}
}
/* Constructor
* #access public, #param bool $direct_call, #return void */
public function __construct($direct_call=false){
# Check if GD extension is loaded
if (!extension_loaded('gd') && !extension_loaded('gd2')) {
$this->showError("GD is not loaded");
}
$this->_direct_call = $direct_call;
}
/* Resize
* #param int $width, #param int $height, #param define $mode
* #param bool $auto_orientation houd rekening met orientatie wanneer er een resize gebeurt */
public function Resize($width=100, $height=100, $mode=RESIZE_STRETCH, $auto_orientation=false){
// Validate resize mode
$valid_modes = array("f", "p");
}
// .... omitted .....
// Set news size vars because these are used for the
// cache name generation
// .... omitted .....
$this->_old_width = $width;
$this->_old_height = $height;
// Lazy load for the directurl cache to work
$this->lazyLoad();
if($this->_cache_skip) return true;
// Create canvas for the new image
$new_image = imagecreatetruecolor($width, $height);
imagecopyresampled($new_image, $this->_image_resource, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
// .... omitted .....
$this->_image_resource = $new_image;
}
/* Create image resource from path or url
* #access public, #param string $location, #param bool $lazy_load, #return */
public function Load($image,$lazy_load=false){
// Cleanup image url
$image = $this->cleanUrl($image);
// Check if it is a valid image
if(isset($mimes[$extension]) && ((!strstr($image, "http://") && file_exists($image)) || strstr($image, "http://")) ){
// Urlencode if http
if(strstr($image, "http://")){
$image = str_replace(array('http%3A%2F%2F', '%2F'), array('http://', '/'), urlencode($image));
}
$image = str_replace("+", "%20", $image);
$this->_extension = $extension;
$this->_mime = $mimes[$extension];
$this->_image_path = $image;
$parts = explode("/", $image);
$this->_image_name = str_replace("." . $this->_extension, "", end($parts));
// Get image size
list($width, $height, $type) = getimagesize($image);
$this->_old_width = $width;
$this->_old_height = $height;
$this->_image_type = $type;
}else{
$this->showError("Wrong image type or file does not exists.");
}
if(!$lazy_load){
$resource = $this->GetImageResource($image, $extension);
$this->_image_resource = $resource;
}
}
/* Save image to computer
* #access public, #param string $destination, #return void */
public function Save($destination, $quality=60){
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource,$destination, $quality); break;
case "gif": imagegif($this->_image_resource,$destination); break;
default: $this->showError('Failed to save image!'); break;
}
}
/* Print image to screen
* #access public, #return void */
public function Parse($quality=60){
$name = $this->generateCacheName();
$content = "";
if(!$this->_cache || ($this->_cache && $this->cacheExpired())){
ob_start();
header ("Content-type: " . $this->_mime);
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource, "", $quality); break;
case "gif": imagegif($this->_image_resource); break;
default: $this->showError('Failed to save image!'); break;
}
$content = ob_get_contents();
ob_end_clean();
}else{
if (isset ($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
if (strtotime ($_SERVER['HTTP_IF_MODIFIED_SINCE']) < strtotime('now')) {
header ('HTTP/1.1 304 Not Modified');
die ();
}
}
// change the modified headers
$gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
$gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
header ("Content-type: " . $this->_mime);
header ('Accept-Ranges: bytes');
header ('Last-Modified: ' . $gmdate_modified);
header ('Cache-Control: max-age=864000, must-revalidate');
header ('Expires: ' . $gmdate_expires);
echo $this->cachedImage($name);
exit();
}
// Save image content
if(!empty($content) && $this->_cache){
$this->cacheImage($name, $content);
}
// Destroy image
$this->Destroy();
echo $content;
exit();
}
/* Destroy resources
* #access public, #return void */
public function Destroy(){
imagedestroy($this->_image_resource);
}
/* Get image resources
* #access public, #return resource */
public function GetResource(){
return $this->_image_resource;
}
/* Set image resources
* #access public, #param resource $image, #return resource */
public function SetResource($image){
$this->_image_resource = $image;
}
/* Enable caching
* #access public, #param string $folder, #param int $ttl, * #return void */
public function EnableCache($folder="/var/www/vhosts/blabla.org/httpdocs/tmp/", $ttl=345600){
if(!is_dir($folder)){
$this->showError("Directory '" . $folder . "' does'nt exist");
}else{
$this->_cache = true;
$this->_cache_folder = $folder;
$this->_cache_ttl = $ttl;
}
return false;
}
}
The original author granted me permission for placing parts of code in here for solving this issue.
If I'm understanding the question correctly, this is entirely to be expected. Image manipulation is slow.
The yellow is your browser sending the request. The green is your browser waiting on the server to actually create the thumbnail, which takes a very significant amount of time, no matter what library the server is using. The blue is the server sending the response, which, unlike the previous steps, is affected by filesize.
There's not much to be done about the inherent slowness of image manipulation. It would be wise to cache these thumbnails so that they are only generated once and are then served statically. That way, very few of your users will ever have to sit through that green delay, and your server will be happy, too.
EDIT: If the issue is that the files exist at those URLs, but your RewriteRule is kicking in anyway, bear in mind that, by default, rules run without checking if the file exists.
Use the following condition above your RewriteRule to make sure the file exists.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule # ...etc...
imgcpu.php?src=foo/foo.jpg&w=100&h=100
so imgcpu.php is running for every image request?
In that case, if you are worried about performance,:
the script needs to do some caching of the thumbnails it creates. If it resizes stuff on every request, that's your problem right there.
the script needs to send some caching headers to the browser - a pure PHP script won't do that, and will be refreshed on every page load
a session_start() call inside the PHP script could lead to concurrency issues because of session locking.
you will need to show some PHP code. Maybe in a separate question, though.
Apache can serve up files from your hard disk a lot faster than PHP can, and it appears that you're doing the latter to handle caching:
/**
* Get an image from cache
*
* #access public
* #param string $name
* #return void
*/
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
There's a better way of doing what that function is doing (passthru), but the best option is to setup a regex that'll only rewrite a request to your thumbnailing script if the file doesn't already exist:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^images/.*$ - [NC,L]
RewriteRule ^images/(.*)$ /imgcpu.php/$1 [NC,L]
And then introduce logic to parse the request to an image and format it accordingly. For example, you could say thumbs should be named after the original file and have the W x H dimensions appended like "stackoverflow_logo_100x100.jpg".
Make sense?
Per request (in the comment), the description of the "s", "l", and "d" flags is as follows (quoting the docs):
'-d' (is directory) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
directory.
'-s' (is
regular file, with size) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
regular file with size greater than
zero.
'-l' (is symbolic link) Treats
the TestString as a pathname and tests
whether or not it exists, and is a
symbolic link.
Your checking your HTTP_IF_MODIFIED_SINCE header and cache AFTER you generate the image so the image is getting generated and cached every time you load the page. You would get a considerable decrease in time if you move these checks closer to the start of execution, before you start processing the image.
Matchu gave you answer why. If you want to fix it, save the created thumbnails, so they are not recreated on each request. I use simple 404 page that catches request to thumbnails that haven't been created, that script figures out the required dimensions and file from url - eg /thumbs/100x100/cat.png means create 100x100 thumbnail from /images/cat.png.

Categories