PHP Classes

File: engine/handler.image.php

Recommend this page to a friend!
  Classes of Kristo Vaher   Wave Framework   engine/handler.image.php   Download  
File: engine/handler.image.php
Role: Application script
Content type: text/plain
Description: Image Handler
Class: Wave Framework
MVC framework for building Web sites and APIs
Author: By
Last change: Update of engine/handler.image.php
Date: 9 months ago
Size: 16,101 bytes
 

Contents

Class file image Download
<?php /** * Wave Framework <http://github.com/kristovaher/Wave-Framework> * Image Handler * * Image Handler is used by Index Gateway to return all image files to the user agent HTTP * requests. Handler adds proper cache headers as well as supports on-demand image-editing, * where it is possible to load an image file with specific resize algorithms and even image * filtering. It also checks for files from overrides folder, which can be returned instead * of the actual file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_image.htm * @since 1.5.0 * @version 3.6.4 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // Web root is the subfolder on public site $systemRoot=str_replace('index.php','',$_SERVER['SCRIPT_FILENAME']); // Dynamic resource loading can be turned off in configuration if(!isset($config['dynamic-image-loading']) || $config['dynamic-image-loading']==true){ // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); } else { $parameters=array(); $parameters[0]=$resourceFile; } // True filename is the last string in the string separated by & character $resourceFile=array_pop($parameters); // Current true file position $resource=$resourceFolder.$resourceFile; // Files from /resources/ folder can be overwritten if file with the same name is placed to /overrides/resources/ if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources/',$resourceRequest)){ //Checking if file of the same name exists in overrides folder $overrideFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overrideFolder.$resourceFile)){ // System will use an override as a resource, since it exists $resource=$overrideFolder.$resourceFile; } } // RESOURCE EXISTENCE CHECK // If file does not exist then 404 is thrown if(!file_exists($resource) && (!isset($config['404-image-placeholder']) || $config['404-image-placeholder']==true) && (file_exists(__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg') || file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'))){ // It's possible to overwrite the default image used for 404 placeholder if(file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg')){ $resource=__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } else { $resource=__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } // 404 header header('HTTP/1.1 404 Not Found'); // Notifying Logger of 404 response code if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404)); } // This variable is used by cache to calculate cache filename, but since system is returning a placeholder instead, it is overwritten // This allows system to keep all 404 placeholder image cache in the same cache file $tmp=explode('/',str_replace($resourceFile,'placeholder.jpg',$resourceRequest)); $resourceRequest=array_pop($tmp); } elseif(!file_exists($resource)){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // CACHE AND BASE64 SETTINGS // Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // Last-modified time of the original resource $lastModified=filemtime($resource); // This flag stores whether cache was used $cacheUsed=false; // No cache flag $noCache=array_search('nocache',$parameters); if($noCache!==false){ // Unsetting the key for nocache parameter unset($parameters[$noCache]); } // Base64 flag $base64=array_search('base64',$parameters); if($base64!==false){ // Unsetting the key for base64 parameter unset($parameters[$base64]); } // GENERATION BASED ON PARAMETERS // If file seems to carry additional configuration options, then it is generated or loaded from cache if(empty($parameters)){ // Pure image file request is considered 'cache used' due to it not needing any processing $cacheUsed=true; // IF NOT MODIFIED // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header, since it is often ignored) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Solving cache folders and directory $cacheFilename=md5($lastModified.'&'.$config['version-system'].'&'.$config['version-api'].'&'.$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // IF NOT MODIFIED // If cache file exists then cache modified is considered that time if(!$noCache && file_exists($cacheDirectory.$cacheFilename)){ // Getting last modified time from cache file $lastModified=filemtime($cacheDirectory.$cacheFilename); // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING RESOURCE // If resource cannot be found from cache, it is generated with Imager class if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){ // LOADING THE IMAGE FOR DYNAMIC MANIPULATION // Requiring WWW_Imager class that is used to do basic image manipulation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-imager.php'); // New Imager object, this is a wrapper around GD library $picture=new WWW_Imager(); // Current image file is loaded into Imager if(!$picture->input($resource)){ trigger_error('Cannot load image from '.$resource,E_USER_ERROR); } // FINDING SETTINGS FOR MANIPULATION FROM PARAMETERS // This applies parameters that were found from file request to the Imager $settings=$picture->parseParameters($parameters); // If settings encountered a problem (such as incorrect parameters string) if(!$settings){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // IMAGE SETTING VALIDATION TO PROTECT THE SYSTEM FROM MALICIOUS REQUESTS // System checks for legality of the entered values // Whitelists allow to protect the server better from possible abuse and denial of service attacks $allowed=true; // If configuration file has not been set for dynamic max size, then it is defaulted to 1000x1000 maximum if(!isset($config['dynamic-max-size'])){ // Default maximum image dimension height/width $config['dynamic-max-size']=4096; } // Checking if image is within allowed parameters if($settings['width']>$config['dynamic-max-size'] || $settings['height']>$config['dynamic-max-size'] || $settings['height']==0 || $settings['width']==0){ // If image dimensions are beyond allowed values $allowed=false; } elseif(isset($config['dynamic-size-whitelist']) && $config['dynamic-size-whitelist']!='' && !in_array($settings['width'].'x'.$settings['height'],explode(' ',$config['dynamic-size-whitelist']))){ // For size whitelist check // If resolution has been changed and this resolution is not found in whitelist $allowed=false; } elseif(isset($config['dynamic-color-whitelist']) && $config['dynamic-color-whitelist']!='' && !in_array($settings['red'].','.$settings['green'].','.$settings['blue'],explode(' ',$config['dynamic-color-whitelist']))){ // For RGB whitelist check // If RGB values are not defaults and this setting is not found in color whitelist $allowed=false; } elseif(isset($config['dynamic-quality-whitelist']) && $config['dynamic-quality-whitelist']!='' && $settings['quality'] && !in_array('@'.$settings['quality'],explode(' ',$config['dynamic-quality-whitelist']))){ // For quality whitelist check // If quality values are not defaults and this setting is not found in quality whitelist $allowed=false; } elseif(isset($config['dynamic-position-whitelist']) && $config['dynamic-position-whitelist']!='' && !in_array($settings['top'].'-'.$settings['left'],explode(' ',$config['dynamic-position-whitelist']))){ // For position whitelist check // If position values are not defaults and this setting is not found in position whitelist $allowed=false; } elseif(isset($config['dynamic-filter-whitelist']) && $config['dynamic-filter-whitelist']!='' && !empty($settings['filters'])){ // Making sure that dynamic image filters are allowed if(isset($config['dynamic-image-filters']) && $config['dynamic-image-filters']==false){ // Filters are set, but dynamic image filters are not enabled $allowed=false; } else { // For filter whitelist check foreach($settings['filters'] as $filter){ // If filters are not in filter whitelist then processing is canceled if($allowed && !in_array($filter['type'].'@'.$filter['alpha'].','.implode(',',$filter['settings']),explode(' ',$config['dynamic-filter-whitelist']))){ $allowed=false; } } } } // IMAGE GENERATION BASED ON SETTINGS // If whitelist checks did not fail and image dimensions are good if($allowed){ // This applies settings to the image through Imager class if(!$picture->applyParameters($settings)){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // If cache folder does not exist, it is created // This is done even if cache itself is not used due to dynamic image having to exist if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Resulting image is saved to cache if(!$picture->output($cacheDirectory.$cacheFilename,$settings['quality'],$settings['format'])){ trigger_error('Cannot output image file',E_USER_ERROR); } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } else { // To notify logger that cache was used $cacheUsed=true; } // File URL is set to cache address $resource=$cacheDirectory.$cacheFilename; } // HEADERS // Serving up the correct content type header if($base64){ // BASE64 text string header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: base64'); } else { // Proper content-type is set based on file extension if(isset($resourceExtension)){ switch($resourceExtension){ case 'jpg': header('Content-Type: image/jpeg;'); break; case 'jpeg': header('Content-Type: image/jpeg;'); break; case 'png': header('Content-Type: image/png;'); break; } } } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Robots header if(isset($config['image-robots'])){ // If image-specific robots setting is defined header('Robots-Tag: '.$config['image-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // Returning image resource if(!$base64){ // Getting current output length $contentLength=filesize($resource); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resource); } else { // Getting file contents and converting it to BASE64 string $contents=base64_encode(file_get_contents($resource)); // Getting current output length $contentLength=strlen($contents); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning data to user agent echo $contents; } // Cache resource is deleted if cache was requested to be off if($noCache){ unlink($resource); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>$cacheUsed,'content-length'=>$contentLength,'category'=>'image')); // Writing log entry $logger->writeLog(); } ?>