Login   Register  
PHP Classes
elePHPant
Icontem

File: engine/handler.resource.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.resource.php  >  Download  
File: engine/handler.resource.php
Role: Application script
Content type: text/plain
Description: Resource Handler
Class: Wave Framework
MVC framework for building Web sites and APIs
Author: By
Last change: It is now possible to return picture files and resource files as BASE64 encoded if you provide 'base64' parameter as a dynamic URL parameter. Added 'url-absolute' and 'url-relative' to the already existing 'request-url' variables that the default URL Controller returns to the View. Added 'output' return type that simply returns the whatever is stored in response arrays 'www-data' key. When using getState() method on a variable that does not exist, then it generates a notice in debugger log. Added an option to restrict URL's based on user agent being matched, this allows to create mobile specific redirects and other user agent specific features. Some State variable names have been changed to clean up the State namespace, this can break backwards compatibility. Chech the full list at update documentation. Updated jQuery to 1.9.0 release. Added isDeveloper() call and developer-ip and developer-user-agent configuration settings. Fixed a bug with Javascript API Wrapper parallel form requests.
Date: 2013-01-30 05:38
Size: 16,968 bytes
 

Contents

Class file image Download
<?php

/**
 * Wave Framework <http://www.waveframework.com>
 * Resource Handler
 *
 * Resource Handler is used to return files that are considered web resources that are not 
 * media. This includes things like JavaScript, CSS stylesheets, XML, HTML and other file 
 * formats (this is based on configuration). Resource Handler uses Wave Frameworks on-demand 
 * resource loader, which allows to combine multiple files to a single resource file or 
 * minify contents of the scripts. 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_resource.htm
 * @since      1.5.0
 * @version    3.5.0
 */

// 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']);
	}

	// Dynamic resource loading can be turned off in configuration
	if(!isset($config['dynamic-resource-loading']) || $config['dynamic-resource-loading']==true){
		// Comma separated filenames will mean that the result will be unified
		$parameters=array_unique(explode('&',$resourceFile));
	} else {
		// If dynamic resource loading was turned off, then the entire 'first parameter' is considered to be the full string for parsing purposes
		$parameters=array();
		$parameters[0]=$resourceFile;
	}

	// Storing last modified time here
	$lastModified=false;
	
	// This flag stores whether cache was used
	$cacheUsed=false;

	// If cache is not defined in configuration file then pre-set is used
	if(!isset($config['resource-cache-timeout'])){
		$config['resource-cache-timeout']=31536000; // A year
	}

	// Web root is the subfolder on public site
	$webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']);
	
	// Files from /resources/content/ cannot be accessed directly
	if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\/content\//',$_SERVER['REQUEST_URI'])){
		header('HTTP/1.1 403 Forbidden');
		die();
	}

	// If minification is used for CSS and JS
	$minify=false;

	// Default cache flag
	$noCache=false;
	
	// Default BASE64 flag
	$base64=false;

	// Checking if the file might be loaded from overrides folder
	$overridesFolder=false;
	if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){
		// Solving possible overrides folder
		$overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder);
	}
	
// GETTING RESOURCE CONTENTS

	// If file does not carry any additional parameters, then there is no need to dynamically generate it
	if(!isset($parameters[1])){
	
		// FILE EXISTENCE CHECK
		
			// Testing file name
			$fileName=explode('.',$parameters[0]);

			// If file does not exist in regular nor overrides folder
			if(file_exists($overridesFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])){
				// Getting the last modified time from overrides folder
				$lastModified=filemtime($overridesFolder.$parameters[0]);
				// This is the loaded resource
				$parameters[0]=$overridesFolder.$resourceFile;
			} elseif((file_exists($resourceFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){
				// Wave Framework files can be loaded unified together with files from /resources/ folders
				if($parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){
					// Wave Framework specific file is stored in engine folder
					$parameters[0]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameters[0];
					// Last modified time of the file stored in overrides folder
					$lastModified=filemtime($parameters[0]);
				} else {
					// This is the loaded resource
					$parameters[0]=$resourceFolder.$parameters[0];
					// Getting the last modified time from the expected resource folder
					$lastModified=filemtime($parameters[0]);
				}
			} else {
				// Adding log entry	
				if(isset($logger)){
					$logger->setCustomLogData(array('category'=>'resource','response-code'=>'404'));
					$logger->writeLog();
				}
				// Returning 404 header
				header('HTTP/1.1 404 Not Found');
				die();
			}
			
		// NOT MODIFIED CHECK

			// 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('category'=>'resource','cache-used'=>true,'response-code'=>'304'));
					$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 {

		// Newest last-modified file is considered for the last modified time
		foreach($parameters as $key=>$parameter){
		
			// PARAMETER CONDITIONS

				// Cache can be turned off and minification can be turned on with parameters.
				// Full stop including parameters are also entirely ignored for parameter considerations
				if($parameter!='nocache' && $parameter!='minify' && $parameter!='base64'){
					
					// MULTIPLE REQUESTED FILES
					
						// Possible version number
						if(is_numeric($parameter)){
							// This is probably just a version number
							unset($parameters[$key]);
						} else {
					
							// Making sure that parent folders are not requested
							$parameters[$key]=str_replace('..','',$parameter);
							// Testing file name
							$fileName=explode('.',$parameters[$key]);
					
							// Overrides can be used if file with the same name is stored in same folder under /overrides/ folder
							if($overridesFolder && file_exists($overridesFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])){
								// File was found and the filename will be replaced by file location for later processing
								$parameters[$key]=$overridesFolder.$parameter;
								// Last modified time of the file stored in overrides folder
								$thisLastModified=filemtime($overridesFolder.$parameter);
								// Only the newest last modified time will be used for output headers
								if($lastModified==false || $lastModified<$thisLastModified){
									$lastModified=$thisLastModified;
								}
							} elseif((file_exists($resourceFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){
								if($parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){
									// Wave Framework specific file is stored in engine folder
									$parameters[$key]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameter;
									// Last modified time of the file stored in overrides folder
									$thisLastModified=filemtime($parameters[$key]);
								} else {
									// File was found and the filename will be replaced by file location for later processing
									$parameters[$key]=$resourceFolder.$parameter;
									// Last modified time of the file stored in overrides folder
									$thisLastModified=filemtime($parameters[$key]);
								}
								// Only the newest last modified time will be used for output headers
								if($lastModified==false || $lastModified<$thisLastModified){
									$lastModified=$thisLastModified;
								}
							} else {
								// Adding log entry	
								if(isset($logger)){
									$logger->setCustomLogData(array('category'=>'resource','response-code'=>'404'));
									$logger->writeLog();
								}
								// Returning 404 header
								header('HTTP/1.1 404 Not Found');
								die();
							}
							
						}
					
				} elseif($parameter=='minify'){
					// This will use minify for CSS and JS files
					$minify=true;
					// Unsetting the parameter as it will not be used later
					unset($parameters[$key]);
				} elseif($parameter=='nocache'){
					// Caching
					$noCache=true;
					// Unsetting the parameter as it will not be used later
					unset($parameters[$key]);
				} elseif($parameter=='base64'){
					// Caching
					$base64=true;
					// Unsetting the parameter as it will not be used later
					unset($parameters[$key]);
				} else {
					// Adding log entry	
					if(isset($logger)){
						$logger->setCustomLogData(array('category'=>'resource','response-code'=>'404'));
						$logger->writeLog();
					}
					// Returning 404 header
					header('HTTP/1.1 404 Not Found');
					die();
				}
			
		}

	}
	
// COMPRESSION SETTINGS

	// This stores currently used compression mode
	$compression='';

	// If output compression is turned on then the content is compressed
	if((isset($config['output-compression']) && $config['output-compression']!=false) && extension_loaded('Zlib')){
		// Different compression options can be used
		switch($config['output-compression']){
			case 'deflate':
				$compression='deflate';
				break;
			case 'gzip':
				$compression='gzip';
				break;
		}
	} elseif(extension_loaded('Zlib')){
		// User agent accepted methods are checked when compression is not set in configuration itself
		if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])){
			if(in_array('deflate',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){
				$compression='deflate';
			} elseif(in_array('gzip',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){
				$compression='gzip';
			}
		}
	}

// CACHE AND NOT MODIFIED SETTINGS

	// Solving cache folders and directory
	$cacheFilename=md5($lastModified.$config['version'].$_SERVER['REQUEST_URI']).(($compression!='')?'_'.$compression:'').'.tmp';
	$cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR;

	// If cache file exists then cache modified is considered that time
	if(file_exists($cacheDirectory.$cacheFilename)){
		$lastModified=filemtime($cacheDirectory.$cacheFilename);
	} else {
		// Otherwise it is server request time
		$lastModified=$_SERVER['REQUEST_TIME'];
	}
	
	// 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('category'=>'resource','cache-used'=>true,'response-code'=>'304'));
			$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();
	}
	
// GENERATING RESOURCE

	// If resource cannot be found from cache, it is generated
	if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){
	
		// LOADING CONTENTS

			// Resource data is stored as a string
			$data='';
			// All requested files are appended
			foreach($parameters as $parameter){
				// Loading data into string.
				$data.=file_get_contents($parameter)."\n";
			}
			
		// MINIFICATION AND COMPRESSION
						
			// Minification of data for smaller filesize and less clutter.
			if($minify){
				// Including minification class
				require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-minifier.php');
				// Minification is based on the type of class
				switch($resourceExtension){
					case 'js':
						$data=WWW_Minifier::minifyJS($data);
						break;
					case 'css':
						$data=WWW_Minifier::minifyCSS($data);
						break;
					case 'xml':
						$data=WWW_Minifier::minifyXML($data);
						break;
					case 'htm':
						$data=WWW_Minifier::minifyHTML($data);
						break;
					case 'html':
						$data=WWW_Minifier::minifyHTML($data);
						break;
					case 'rss':
						$data=WWW_Minifier::minifyXML($data);
						break;
				}
			}
			
			// If data is requested in BASE64 encoding
			if($base64){
				$data=base64_encode($data);
			}

			// Data is compressed based on current compression settings
			switch($compression){
				case 'deflate':
					$data=gzdeflate($data,9);
					break;
				case 'gzip':
					$data=gzencode($data,9);
					break;
			}
			
		// STORING IN CACHE
		
			// Resource cache is cached in subdirectories, if directory does not exist then it is created
			if(!is_dir($cacheDirectory)){
				if(!mkdir($cacheDirectory,0755)){
					trigger_error('Cannot create cache folder',E_USER_ERROR);
				}
			}
			
			// Data is written to cache file
			if(!file_put_contents($cacheDirectory.$cacheFilename,$data)){
				trigger_error('Cannot create resource cache',E_USER_ERROR);
			}
		
		// Unsetting the variable due to memory reasons
		unset($data);

	} else {
		// Logger is notified that cache was used
		$cacheUsed=true;
	}
	
// 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 {
		// System returns proper content type based on file extension
		if(isset($resourceExtension)){
			switch($resourceExtension){
				case 'js':
					header('Content-Type: application/javascript;charset=utf-8;');
					break;
				case 'css':
					header('Content-Type: text/css;charset=utf-8;');
					break;
				case 'xml':
					header('Content-Type: text/xml;charset=utf-8;');
					break;
				case 'txt':
					header('Content-Type: text/plain;charset=utf-8;');
					break;
				case 'csv':
					header('Content-Type: text/csv;charset=utf-8;');
					break;
				case 'html':
					header('Content-Type: text/html;charset=utf-8;');
					break;
				case 'htm':
					header('Content-Type: text/html;charset=utf-8;');
					break;
				case 'rss':
					header('Content-Type: application/rss+xml;charset=utf-8;');
					break;
				case 'vcard':
					header('Content-Type: text/vcard;charset=utf-8;');
					break;
				default:
					header('Content-Type: text/plain;charset=utf-8;');
					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');
	}

	// This tells proxies to store both compressed and uncompressed version
	header('Vary: Accept-Encoding');

	// Proper compression header
	if($compression!=''){
		header('Content-Encoding: '.$compression);
	}

	// Robots header
	if(isset($config['resource-robots'])){
		// If resource-specific robots setting is defined
		header('Robots-Tag: '.$config['resource-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
	
	// Getting current output length
	$contentLength=filesize($cacheDirectory.$cacheFilename);
	// Content length is defined that can speed up website requests, letting user agent to determine file size
	header('Content-Length: '.$contentLength);  
	// Returning the file contents to user agent
	readfile($cacheDirectory.$cacheFilename);
	
	// File is deleted if cache was requested to be off
	if($noCache){
		unlink($cacheDirectory.$cacheFilename);
	}
	
// 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,'category'=>'resource','content-length'=>$contentLength));
		// Writing log entry
		$logger->writeLog();
	}

?>