Login   Register  
PHP Classes
elePHPant
Icontem

File: engine/handler.image.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  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: Re-implemented system version number and updated versioning documentation. You can also limit API version numbers with API profiles now.
Date: 1 year ago
Size: 16,085 bytes
 

Contents

Class file image Download
<?php

/**
 * Wave Framework <http://www.waveframework.com>
 * 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();
	}

?>