PHP Classes

File: engine/class.www-state.php

Recommend this page to a friend!
  Classes of Kristo Vaher  >  Wave Framework  >  engine/class.www-state.php  >  Download  
File: engine/class.www-state.php
Role: Class source
Content type: text/plain
Description: State Class
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: 8 years ago
Size: 67,678 bytes
 

Contents

Class file image Download
<?php

/**
 * Wave Framework <http://www.waveframework.com>
 * State Class
 *
 * State is always required by Wave Framework. It is used by API and some handlers. State is used 
 * to keep track of system state, configuration and its changes, such as relevant PHP settings. 
 * It allows changing these settings, and thus affecting API or PHP configuration. State also 
 * includes functionality for State Messenger, sessions, cookies, translations and sitemap data. 
 * State is assigned in API and is accessible in MVC objects through Factory wrapper methods. 
 * Multiple different states can be used by the same request, but usually just one is used per 
 * request. State is only kept for the duration of the request processing and is not stored 
 * beyond its use in the request.
 *
 * @package    State
 * @author     Kristo Vaher <kristo@waher.net>
 * @copyright  Copyright (c) 2012, Kristo Vaher
 * @license    GNU Lesser General Public License Version 3
 * @tutorial   /doc/pages/state.htm
 * @since      1.0.0
 * @version    3.7.0
 */

class WWW_State	{

	/**
	 * This is the primary variable of State class and it carries representation of the system 
	 * configuration, both loaded from /config.ini file as well as initialized from environmental 
	 * server variables.
	 */
	public $data=array();
	
	/**
	 * This should hold Database class and connection data, if used.
	 */
	public $databaseConnection=false;
	
	/**
	 * This holds the session handler for session management. This is a
	 * WWW_Sessions class from /resources/sessions.php file.
     */	 
	public $sessionHandler=false;
	
	/**
	 * This is an internal variable that State uses to check if it has already validated sessions 
	 * or not.
	 */
	private $sessionStarted=false;
	
	/**
	 * This holds the 'keyword' or 'passkey' of currently used State messenger.
	 */
	private $messenger=false;
	
	/**
	 * This holds state messenger data as an array.
	 */
	private $messengerData=array();
	
	/**
	 * This holds system Tool Class object, if it exists.
	 */
	private $tools=false;
	
	/**
	 * Construction of State object initializes the defaults for $data variable. A lot of the 
	 * data is either loaded from /config.ini file or initialized based on server environment 
	 * variables. Fingerprint string is also created during construction as well as input data 
	 * loaded from XML or JSON strings, if sent with POST directly.
	 *
	 * @param array $config configuration array
	 * @return WWW_State
	 */
	final public function __construct($config=array()){
	
		// PRE-DEFINED STATE VALUES
		
			// Required constants
			if(!defined('__IP__')){
				define('__IP__',$_SERVER['REMOTE_ADDR']);
			}
			if(!defined('__ROOT__')){
				define('__ROOT__',__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
			}
	
			// A lot of default State variables are loaded from PHP settings, others are simply pre-defined
			// Every setting from core configuration file is also listed here
			$this->data=array(
				'404-image-placeholder'=>true,
				'404-view'=>'404',
				'access-control'=>false,
				'apc'=>0,
				'api-logging'=>array('*','!public'),
				'api-permissions'=>array('*'),
				'api-profile'=>'public',
				'api-public-profile'=>'public',
				'api-public-token'=>false,
				'api-versions'=>array('v1'),
				'cache-database'=>false,
				'cache-database-address-column'=>'address',
				'cache-database-data-column'=>'data',
				'cache-database-errors'=>true,
				'cache-database-host'=>'localhost',
				'cache-database-name'=>'',
				'cache-database-password'=>'',
				'cache-database-persistent'=>false,
				'cache-database-table-name'=>'cache',
				'cache-database-timestamp-column'=>'timestamp',
				'cache-database-type'=>false,
				'cache-database-username'=>'',
				'client-ip'=>__IP__,
				'client-user-agent'=>((isset($_SERVER['HTTP_USER_AGENT']))?$_SERVER['HTTP_USER_AGENT']:''),
				'content-security-policy'=>false,
				'database-errors'=>true,
				'database-host'=>'localhost',
				'database-name'=>'',
				'database-password'=>'',
				'database-persistent'=>false,
				'database-type'=>'mysql',
				'database-username'=>'',
				'developer'=>false,
				'developer-ip'=>'',
				'developer-user-agent'=>'',
				'directory-data'=>false,
				'directory-filesystem'=>false,
				'directory-keys'=>false,
				'directory-static'=>false,
				'directory-system'=>str_replace('engine'.DIRECTORY_SEPARATOR.'class.www-state.php','',__FILE__),
				'directory-tmp'=>false,
				'directory-user'=>false,
				'dynamic-color-whitelist'=>'',
				'dynamic-filter-whitelist'=>'',
				'dynamic-image-filters'=>true,
				'dynamic-image-loading'=>true,
				'dynamic-max-size'=>1000,
				'dynamic-position-whitelist'=>'',
				'dynamic-quality-whitelist'=>'',
				'dynamic-resource-loading'=>true,
				'dynamic-size-whitelist'=>'',
				'enforce-first-language-url'=>true,
				'enforce-url-end-slash'=>true,
				'errors-reporting'=>'full',
				'errors-trace'=>false,
				'errors-verbose'=>false,
				'file-robots'=>'noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',
				'fingerprint'=>false,
				'forbidden-extensions'=>array('tmp','log','ht','htaccess','pem','crt','db','sql','version','conf','ini','empty'),
				'frame-permissions'=>'',
				'headers-set'=>array(),
				'headers-unset'=>array(),
				'home-view'=>'home',
				'http-accept'=>((isset($_SERVER['HTTP_ACCEPT']))?explode(',',$_SERVER['HTTP_ACCEPT']):''),
				'http-accept-charset'=>((isset($_SERVER['HTTP_ACCEPT_CHARSET']))?explode(',',$_SERVER['HTTP_ACCEPT_CHARSET']):array()),
				'http-accept-encoding'=>((isset($_SERVER['HTTP_ACCEPT_ENCODING']))?explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']):array()),
				'http-accept-language'=>((isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))?explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']):array()),
				'http-authentication-ip'=>'*',
				'http-authentication-password'=>'',
				'http-authentication-username'=>'',
				'http-do-not-track'=>((isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT']==1)?true:false),
				'http-content-length'=>((isset($_SERVER['CONTENT_LENGTH']))?$_SERVER['CONTENT_LENGTH']:false),
				'http-content-type'=>((isset($_SERVER['CONTENT_TYPE']))?$_SERVER['CONTENT_TYPE']:false),
				'http-host'=>$_SERVER['HTTP_HOST'],
				'http-if-modified-since'=>false,
				'http-input'=>false,
				'http-referrer'=>((isset($_SERVER['HTTP_REFERER']))?$_SERVER['HTTP_REFERER']:false),
				'http-request-method'=>$_SERVER['REQUEST_METHOD'],
				'https-mode'=>(((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']==1 || $_SERVER['HTTPS']=='on')) || (isset($_SERVER['SCRIPT_URI']) && strpos($_SERVER['SCRIPT_URI'],'https://')!==false))?true:false),
				'image-extensions'=>array('jpeg','jpg','png'),
				'image-robots'=>'noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',
				'index-url-cache-timeout'=>0,
				'index-view-cache-timeout'=>0,
				'internal-logging'=>array('*','!input-data','!output-data'),
				'language'=>false,
				'languages'=>array('en'),
				'limiter'=>false,
				'limiter-authentication'=>false,
				'limiter-blacklist'=>false,
				'limiter-https'=>false,
				'limiter-load'=>false,
				'limiter-referrer'=>'*',
				'limiter-request'=>false,
				'limiter-whitelist'=>false,
				'locale'=>setlocale(LC_ALL,0),
				'memcache'=>false,
				'memcache-host'=>'localhost',
				'memcache-port'=>11211,
				'memory-limit'=>false,
				'output-compression'=>'deflate',
				'project-author'=>false,
				'project-copyright'=>false,
				'project-title'=>false,
				'request-id'=>((isset($_SERVER['UNIQUE_ID']))?$_SERVER['UNIQUE_ID']:false),
				'request-time'=>$_SERVER['REQUEST_TIME'],
				'request-true'=>false,
				'request-uri'=>$_SERVER['REQUEST_URI'],
				'resource-cache-timeout'=>31536000,
				'resource-extensions'=>array('css','js','txt','csv','xml','html','htm','rss','vcard'),
				'resource-robots'=>'noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',
				'robots'=>'noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',
				'robots-cache-timeout'=>14400,
				'server-ip'=>((isset($_SERVER['SERVER_ADDR']))?$_SERVER['SERVER_ADDR']:false),
				'server-name'=>((isset($_SERVER['SERVER_NAME']))?$_SERVER['SERVER_NAME']:false),
				'server-port'=>((isset($_SERVER['SERVER_PORT']))?$_SERVER['SERVER_PORT']:false),
				'session-data'=>array(),
				'session-domain'=>false,
				'session-fingerprint'=>array(),
				'session-fingerprint-key'=>'www-fingerprint',
				'session-http-only'=>true,
				'session-id'=>false,
				'session-lifetime'=>0,
				'session-name'=>'WWW'.crc32(__ROOT__),
				'session-original-data'=>array(),
				'session-path'=>false,
				'session-permissions-key'=>'www-permissions',
				'session-regenerate'=>0,
				'session-secure'=>false,
				'session-timestamp-key'=>'www-timestamp',
				'session-token-key'=>'www-public-token',
				'session-user-key'=>'www-user',
				'sitemap'=>array(),
				'sitemap-cache-timeout'=>14400,
				'sitemap-raw'=>array(),
				'storage'=>array(),
				'test-database-host'=>false,
				'test-database-name'=>false,
				'test-database-password'=>false,
				'test-database-type'=>false,
				'test-database-username'=>false,
				'testing'=>false,
				'time-limit'=>false,
				'timezone'=>false,
				'translations'=>array(),
				'trusted-proxies'=>array('*'),
				'url-base'=>false,
				'url-web'=>str_replace('index.php','',$_SERVER['SCRIPT_NAME']),
				'user-data'=>false,
				'user-permissions'=>false,
				'version-api'=>'v1',
				'version-system'=>'1.0.0',
				'version-www'=>'1.0.0',
				'view'=>array(),
				'view-headers'=>array()
			);			
			
		// ASSIGNING STATE FROM CONFIGURATION FILE
					
			// If array of configuration data is set during object creation, it is used
			// This loops over all the configuration options from /config.ini file through setState() function
			// That function has key-specific functionality that can be tied to some internal commands and PHP functions
			if(!empty($config)){
				$this->setState(array($config));
			}
			
		// CHECKING FOR SERVER OR PHP SPECIFIC CONFIGURATION OPTIONS AND ASSIGNING UNSET CONFIGURATIONS
		
			// Removing full stop from the beginning of both directory URL's
			if($this->data['url-web'][0]=='.'){
				$this->data['url-web'][0]='';
			}
			
			// If request ID is not set
			if(!$this->data['request-id']){
				$this->data['request-id']=md5(uniqid('www',true).microtime().$this->data['request-uri'].$this->data['request-time'].$this->data['client-user-agent'].$this->data['client-ip']);
			}
			
			// If default session cookie domain is not set
			if(!$this->data['session-domain']){
				$this->data['session-domain']=$this->data['http-host'];
			}
			
			// If default session cookie path is not set
			if(!$this->data['session-path']){
				$this->data['session-path']=$this->data['url-web'];
			}
			
			// Finding base URL
			if(!$this->data['url-base']){
				$this->data['url-base']=(($this->data['https-mode'])?'https://':'http://').$this->data['http-host'].$this->data['url-web'];
			}
			
			// Defining default user root folder
			if(!$this->data['directory-filesystem']){
				$this->data['directory-filesystem']=$this->data['directory-system'].'filesystem'.DIRECTORY_SEPARATOR;
			}
			
			// Defining default user root folder
			if(!$this->data['directory-user']){
				$this->data['directory-user']=$this->data['directory-filesystem'].'userdata'.DIRECTORY_SEPARATOR;
			}
			
			// Defining default user root folder
			if(!$this->data['directory-data']){
				$this->data['directory-data']=$this->data['directory-filesystem'].'data'.DIRECTORY_SEPARATOR;
			}
			
			// Defining default static folder
			if(!$this->data['directory-static']){
				$this->data['directory-static']=$this->data['directory-filesystem'].'static'.DIRECTORY_SEPARATOR;
			}
			
			// Defining temporary files root folder
			if(!$this->data['directory-tmp']){
				$this->data['directory-tmp']=$this->data['directory-filesystem'].'tmp'.DIRECTORY_SEPARATOR;
			}
			
			// Defining certificates and keys folder
			if(!$this->data['directory-keys']){
				$this->data['directory-keys']=$this->data['directory-filesystem'].'keys'.DIRECTORY_SEPARATOR;
			}
				
			// If timezone is still set to false, then system attempts to set the currently set timezone
			// Some systems throw deprecated warning if this value is not set
			if($this->data['timezone']==false){
				// Some systems throw a deprecated warning without implicitly re-setting default timezone
				date_default_timezone_set('Europe/London');
				$this->data['timezone']='Europe/London';
			}
			
			// Setting if modified since, if it happens to be set
			if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $this->data['http-if-modified-since']==false){
				$this->data['http-if-modified-since']=strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
			}
			
			// If default API profile has been changed by configuration, we assign current API profile to default profile as well
			if($this->data['api-public-profile']!='public'){
				$this->data['api-profile']=$this->data['api-public-profile'];
			}
			
			// If first language is not defined then first node from languages array is used
			if($this->data['language']==false){
				$this->data['language']=$this->data['languages'][0];
			}
			
			// Making sure that boundary is not part of the content type definition
			if($this->data['http-content-type']){
				$tmp=explode(';',$this->data['http-content-type']);
				$this->data['http-content-type']=array_shift($tmp);
			}
			
			// Compressed output is turned off if the requesting user agent does not support it
			// This is also turned off if PHP does not support Zlib compressions
			if($this->data['output-compression']!=false){
				if(!in_array($this->data['output-compression'],$this->data['http-accept-encoding']) || !extension_loaded('Zlib')){
					$this->data['output-compression']=false;
				}
			}
			
			// If configuration has not sent a request string then State solves it using request-uri
			if(!$this->data['request-true']){
				// If install is at www.example.com/w/ subfolder and user requests www.example.com/w/en/page/ then this would be parsed to 'en/page/'
				$this->data['request-true']=preg_replace('/(^'.preg_quote($this->data['url-web'],'/').')/ui','',$this->data['request-uri']);
			}
			
			// This sets the developer flag to either true or false depending on configuration
			if($this->data['developer-ip']!='' && $this->data['developer-user-agent']!=''){
				// IP addresses can be comma-separated list
				$developers=explode(',',$this->data['developer-ip']);
				// User agent is matched by finding user agent string within the configuration
				if(in_array($this->data['client-ip'],$developers) && strpos(str_replace(';','',$this->data['client-user-agent']),$this->data['developer-user-agent'])!==false){
					$this->data['developer']=true;
				} 
			}
		
		// FINGERPRINTING
		
			// Fingerprint is created based on data sent by user agent, this can be useful for light detection without cookies
			$fingerprint='';
			
			// If IP is set for session fingerprinting
			if(in_array('ip',$this->data['session-fingerprint'])){
				$fingerprint.=$this->data['client-ip'];
			}
			
			// If browser is used for session fingerprinting
			if(in_array('browser',$this->data['session-fingerprint'])){
				$fingerprint.=$this->data['client-user-agent'];
				$fingerprint.=(isset($_SERVER['HTTP_ACCEPT']))?$_SERVER['HTTP_ACCEPT']:'';
				$fingerprint.=(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))?$_SERVER['HTTP_ACCEPT_LANGUAGE']:'';
				$fingerprint.=(isset($_SERVER['HTTP_ACCEPT_ENCODING']))?$_SERVER['HTTP_ACCEPT_ENCODING']:'';
				$fingerprint.=(isset($_SERVER['HTTP_ACCEPT_CHARSET']))?$_SERVER['HTTP_ACCEPT_CHARSET']:'';
				$fingerprint.=(isset($_SERVER['HTTP_KEEP_ALIVE']))?$_SERVER['HTTP_KEEP_ALIVE']:'';
				$fingerprint.=(isset($_SERVER['HTTP_CONNECTION']))?$_SERVER['HTTP_CONNECTION']:'';
			}
			
			// If fingerprint is not an empty string then it overwrites the State value
			if($fingerprint!=''){
				// Fingerprint is hashed with MD5 using session name for salt
				$this->data['fingerprint']=md5($this->data['session-name'].$fingerprint);
			}
			
		// JSON OR XML BASED INPUT
		
			// PHP Input data is ignored for input if form submit is done
			if(!in_array($this->data['http-content-type'],array(false,'','application/x-www-form-urlencoded','multipart/form-data'))){
				// Gather sent input
				$phpInput=file_get_contents('php://input');
				// For custom content types, when data is sent as an XML or JSON string
				if($phpInput!=''){
					// Parsing method depends on content type header
					if($this->data['http-content-type']=='application/json'){
						// JSON string is converted to associative array
						$this->data['http-input']=json_decode($phpInput,true);
					} elseif(extension_loaded('SimpleXML') && ($this->data['http-content-type']=='application/xml')){
						// This is not supported in earlier versions of LibXML
						if(defined('LIBXML_PARSEHUGE')){
							$tmp=simplexml_load_string($phpInput,'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE | LIBXML_PARSEHUGE);
						} else {
							$tmp=simplexml_load_string($phpInput,'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE);
						}
						// Data is converted to array only if an object was created
						if($tmp){
							$this->data['http-input']=json_decode(json_encode($tmp),true);
						}
					} else {
						// Storing the entire stream directly
						$this->data['http-input']=$phpInput;
					}
				} 
			}
			
			// If special input file is set as XML
			if(isset($_FILES['www-xml']) || isset($_REQUEST['www-xml'])){
				// If this is a file upload or not
				if(isset($_FILES['www-xml'])){
					// This is not supported in earlier versions of LibXML
					if(defined('LIBXML_PARSEHUGE')){
						$tmp=simplexml_load_file($_FILES['www-xml']['tmp_name'],'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE | LIBXML_PARSEHUGE);
					} else {
						$tmp=simplexml_load_file($_FILES['www-xml']['tmp_name'],'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE);
					}
				} else {
					// This is not supported in earlier versions of LibXML
					if(defined('LIBXML_PARSEHUGE')){
						$tmp=simplexml_load_string($_REQUEST['www-xml'],'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE | LIBXML_PARSEHUGE);
					} else {
						$tmp=simplexml_load_string($_REQUEST['www-xml'],'SimpleXMLElement',LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_ERR_NONE);
					}
				}
				// Data is converted to array only if an object was created
				if($tmp){
					$this->data['http-input']=json_decode(json_encode($tmp),true);
				}
			} 
			
			// If special input file is set as JSON
			if(isset($_FILES['www-json']) || isset($_REQUEST['www-json'])){
				if(isset($_FILES['www-json'])){
					// JSON string is converted to associative array
					$this->data['http-input']=json_decode(file_get_contents($_FILES['www-json']['tmp_name']),true);
				} else {
					// JSON string is converted to associative array
					$this->data['http-input']=json_decode($_REQUEST['www-json'],true);
				}
			}
		
	}
	
	/**
	 * When State class is not used anymore, then state messenger data - if set - is written 
	 * to filesystem based on the State messenger key. This method also deletes session cookie, 
	 * if sessions have been used but the session variable itself is empty.
	 *
	 * @return null
	 */
	final public function __destruct(){
	
		// Only applies if request messenger actually holds data
		if($this->messenger){
			// This stores messenger data in filesystem
			$this->storeMessenger();
		}
		
		// This will commit session to the session storage
		if(!headers_sent()){
			$this->commitHeaders();
		}
		
	}
	
	// STATE MANIPULATION
	
		/**
		 * This is the basic call to return a State variable from the object. If this method is 
		 * called without any variables, then the entire State array is returned. You can send 
		 * one or more key variables to this method to return a specific key from State. If you 
		 * send multiple parameters then this method attempts to find keys of a key in case the 
		 * State variable is an array itself. $input variable is only used within State class 
		 * itself. Method returns null for keys that have not been set.
		 *
		 * @param array $input input variables sent to the function
		 * @return mixed
		 */
		final public function getState($input){
			// Returning the entire array, if entire state was requested
			if(!$input){
				return $this->data;
			}
			// Finding out the specific requested variable
			$return=&$this->data;
			// Looping over each of the input parameters sent to the function
			foreach($input as $key){
				if(!isset($return[$key])){
					trigger_error('State variable '.implode(',',$input).' does not exist',E_USER_NOTICE);
					return null;
				}
				$return=&$return[$key];
			}
			return $return;
		}
		
		/**
		 * This method is used to set a $data variable value in State object. $variable can 
		 * also be an array of keys and values, in which case multiple variables are set at 
		 * once. This method uses stateChanged() for variables that carry additional 
		 * functionality, such as setting timezone. $input variable is only used within 
		 * State class itself.
		 *
		 * @param mixed $input input variables sent to the function
		 * @return boolean
		 */
		final public function setState($input){
				
			// If an array is sent as key-value pair
			if($input && count($input)==1 && is_array($input[0])){
			
				foreach($input[0] as $key=>$val){
					// Setting the state variable (note that the stateChanged may change this)
					$this->data[$key]=$val;
					// Checking for system specific changes due to the changed State
					$this->stateChanged($key,$val);
				}
				
			} elseif(count($input)>1){
			
				// The last element in the sent parameters is the value to be set
				$value=array_pop($input);
				// Setting the proper address of State to the newly set variable
				$pointer=&$this->data;
				foreach($input as $key){
					$pointer=&$pointer[$key];
				}
				$pointer=$value;
				
				// Checking for system specific changes due to the changed State
				return $this->stateChanged($input[0],$value);
				
			} else {
			
				// Only one parameter was sent, meaning that the setting fails
				trigger_error('setState() method requires two variables if not sending an array',E_USER_NOTICE);
				return false;
				
			}
			
			// State value has been set
			return true;
			
		}
		
		/**
		 * This is a private method used internally whenever configuration is changed. It has 
		 * checks for cases when a variable is changed that carries additional functionality 
		 * such as when changing the timezone or output compression. For example, if output 
		 * compression is set, but not supported by user agent that is making the request, 
		 * then output supression is turned off.
		 *
		 * @param string $variable variable name that is changed
		 * @param mixed $value new value of the variable
		 * @return boolean
		 */
		final private function stateChanged($variable,$value=true){
			
			// Certain variables are checked that might change system flags
			switch ($variable) {
				case 'timezone':
					// Attempting to set default timezone
					if(!date_default_timezone_set($value)){
						trigger_error('Cannot set timezone to '.$value,E_USER_WARNING);
						return false;
					}
					break;
				case 'memory-limit':
					if($value){
						if(!function_exists('ini_set') || !ini_set('memory_limit',$value)){
							trigger_error('Cannot set memory limit to '.$value,E_USER_WARNING);
							return false;
						}
					}
					break;
				case 'time-limit':
					if($value){
						set_time_limit($value);
					}
					break;
				case 'locale':
					if($value){
						if(!setlocale(LC_ALL,$value.'.UTF-8')){
							trigger_error('Cannot set locale to '.$value.'.UTF-8',E_USER_WARNING);
							return false;
						}
					}
					break;
				case 'output-compression':
					// If user agent does not expect compressed data and PHP extension is not loaded, then this value cannot be turned on
					if($value==false || !in_array($value,$this->data['http-accept-encoding']) || !extension_loaded('Zlib')){
						$this->data[$variable]=false;
						return false;
					}
					break;
			}
			
			// State has been changed
			return true;
			
		}
		
		/**
		 * This function is called before output is pushed to browser by the API or when State 
		 * object is not used anymore. This method is not accessible to Factory class, but it 
		 * is not private.
		 *
		 * @return boolean
		 */
		final public function commitHeaders(){
		
			// If sessions have been started
			if($this->sessionStarted){
				
				// If session data has changed in any way or session is assigned to be regenerated
				if($this->sessionHandler->regenerateId || $this->data['session-original-data']!=$this->data['session-data']){
					// Checking if there are more data in sessions than just internal keys
					$sessionSize=count($this->data['session-data']);
					if($sessionSize==0 || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-timestamp-key']])) || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-fingerprint-key']])) || ($sessionSize==2 && isset($this->data['session-data'][$this->data['session-timestamp-key']]) && isset($this->data['session-data'][$this->data['session-fingerprint-key']]))){
						// This will make the session handler destroy the session
						$this->data['session-data']=array();
					}
					// Sending State session data to session handler
					$this->sessionHandler->sessionData=$this->data['session-data'];
					// Closing sessions
					$this->sessionHandler->sessionCommit();
				}
			
			}
			
			// Commiting headers for added headers
			if(!empty($this->data['headers-set'])){
				foreach($this->data['headers-set'] as $header=>$replace){
					header($header,$replace);
				}
			}
			
			// Removing headers if set for removal
			if(!empty($this->data['headers-unset'])){
				foreach($this->data['headers-unset'] as $header){
					header_remove($header);
				}
			}
			
			// Headers have been commited
			return true;
			
		}
		
	// SITEMAP AND TRANSLATIONS

		/**
		 * This method returns an array of currently active translations, or for a language set 
		 * with $language variable. If $keyword is also set, then it returns a specific translation 
		 * with that keyword from $language translations. If $keyword is an array, then $subkeyword
		 * can be used to return specific translation from that keyword.
		 *
		 * @param boolean|string $language language keyword, if this is not set then returns current language translations
		 * @param boolean|string $keyword if only single keyword needs to be returned
		 * @param boolean|string $subkeyword if the $keyword is an array, then $subkeyword is the actual translation that is requested
		 * @return array, string or false if failed
		 */
		final public function getTranslations($language=false,$keyword=false,$subkeyword=false){
		
			// If language is not set, then assuming current language
			if(!$language){
				$language=$this->data['language'];
			}
			
			// If translations data is already stored in state
			if(!isset($this->data['translations'][$language])){
			
				// Translations can be loaded from overrides folder as well
				if(file_exists($this->data['directory-system'].'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.translations.ini')){
					$sourceUrl=$this->data['directory-system'].'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.translations.ini';
				} elseif(file_exists($this->data['directory-system'].'resources'.DIRECTORY_SEPARATOR.$language.'.translations.ini')){
					$sourceUrl=$this->data['directory-system'].'resources'.DIRECTORY_SEPARATOR.$language.'.translations.ini';
				} else {
					return false;
				}
				
				// This data can also be stored in cache
				$cacheUrl=$this->data['directory-system'].'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.translations.tmp';
				// Including the translations file
				if(!file_exists($cacheUrl) || filemtime($sourceUrl)>filemtime($cacheUrl)){
					// Translations are parsed from INI file in the resources folder
					$this->data['translations'][$language]=parse_ini_file($sourceUrl,true,INI_SCANNER_RAW);
					if(!$this->data['translations'][$language]){
						trigger_error('Cannot parse INI file: '.$sourceUrl,E_USER_ERROR);
					}
					// Cache of parsed INI file is stored for later use
					if(!file_put_contents($cacheUrl,serialize($this->data['translations'][$language]))){
						trigger_error('Cannot store INI file cache at '.$cacheUrl,E_USER_ERROR);
					}
				} else {
					// Since INI file has not been changed, translations are loaded from cache
					$this->data['translations'][$language]=unserialize(file_get_contents($cacheUrl));
				}
				
			}
			
			// Returning keyword, if it is requested
			if($keyword){
				// If $keyword is an array and subkeyword is requested
				if($subkeyword){
					if(isset($this->data['translations'][$language][$keyword],$this->data['translations'][$language][$keyword][$subkeyword])){
						return $this->data['translations'][$language][$keyword][$subkeyword];
					} else {
						return false;
					}
				} else {
					if(isset($this->data['translations'][$language][$keyword])){
						return $this->data['translations'][$language][$keyword];
					} else {
						return false;
					}
				}
			} else {
				// If keyword was not set, then returning entire array
				return $this->data['translations'][$language];
			}
			
		}
		
		/**
		 * This method includes or reads in a file from '/resources/content/' folder $name is the 
		 * modified filename that can also include subfolders, but without the language prefix and 
		 * without extension in the filename itself. If $language is not defined then currently 
		 * active language is used.
		 *
		 * @param string $name filename without language prefix
		 * @param boolean|string $language language keyword
		 * @return string
		 */
		final public function getContent($name,$language=false){
		
			// If language is not defined then default language is used
			if(!$language){
				$language=$this->data['language'];
			}
			
			// Initial content folder
			$fileDestination='./resources/content/';
			
			// Folder can be defined in the name
			$components=explode('/',$name);
			$count=count($components);
			
			// If a folder is defined
			if($count>1){
				for($i=0;$i<$count;$i++){
					// If the last node is used
					if($count==($i+1)){
						$fileDestination.=$language.'.'.$components[$i];
					} else {
						$fileDestination.=$components[$i].'/';
					}
				}
			} else {
				$fileDestination.=$language.'.'.$name;
			}
			
			// Adding the extension
			$fileDestination.='.htm';
			
			// Making sure that the file itself exists
			if(file_exists($fileDestination)){
				// Echoing out the contents to output buffer
				return file_get_contents($fileDestination);
			} else {
				trigger_error('Cannot find a file in '.$fileDestination,E_USER_WARNING);
				return false;
			}
			
		}
		
		/**
		 * This method returns an array of currently active sitemap, or a sitemap for a language 
		 * set with $language variable. If $keyword is also set, then it returns a specific 
		 * sitemap node with that keyword from $language sitemap file. This method returns the 
		 * original, non-modified sitemap that has not been parsed for use with URL controller.
		 *
		 * @param boolean|string $language language keyword, if this is not set then returns current language sitemap
		 * @param boolean|string $keyword if only a single URL node needs to be returned
		 * @return array or false if failed
		 */
		final public function getSitemapRaw($language=false,$keyword=false){
		
			// If language is not set, then assuming current language
			if(!$language){
				$language=$this->data['language'];
			}
			
			// If translations data is already stored in state
			if(!isset($this->data['sitemap-raw'][$language])){
			
				// Translations can be loaded from overrides folder as well
				if(file_exists($this->data['directory-system'].'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){
					$sourceUrl=$this->data['directory-system'].'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini';
				} elseif(file_exists($this->data['directory-system'].'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){
					$sourceUrl=$this->data['directory-system'].'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini';
				} else {
					return false;
				}
				
				// This data can also be stored in cache
				$cacheUrl=$this->data['directory-system'].'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.tmp';
				// Including the sitemap file
				if(!file_exists($cacheUrl) || filemtime($sourceUrl)>filemtime($cacheUrl)){
					// Sitemap is parsed from INI file in the resources folder
					$this->data['sitemap-raw'][$language]=parse_ini_file($sourceUrl,true,INI_SCANNER_RAW);
					if(!$this->data['sitemap-raw'][$language]){
						trigger_error('Cannot parse INI file: '.$sourceUrl,E_USER_ERROR);
					}
					// Parsing the sitemap
					foreach($this->data['sitemap-raw'][$language] as $key=>$settings){
						$this->data['sitemap-raw'][$language][$key]['nodes']=explode('/',$key);
					}
					// Cache of parsed INI file is stored for later use
					if(!file_put_contents($cacheUrl,serialize($this->data['sitemap-raw'][$language]))){
						trigger_error('Cannot store INI file cache at '.$cacheUrl,E_USER_ERROR);
					}
				} else {
					// Since INI file has not been changed, translations are loaded from cache
					$this->data['sitemap-raw'][$language]=unserialize(file_get_contents($cacheUrl));
				}
				
			}
			
			// Returning keyword, if it is requested
			if($keyword){
				if(isset($this->data['sitemap-raw'][$language][$keyword])){
					return $this->data['sitemap-raw'][$language][$keyword];
				} else {
					return false;
				}
			} else {
				// If keyword was not set, then returning entire array
				return $this->data['sitemap-raw'][$language];
			}
			
		}
		
		/**
		 * This returns sitemap array that is modified for use with View controller and other 
		 * parts of the system. It returns sitemap for current language or a language set with 
		 * $language variable and can return a specific sitemap node based on $keyword.
		 *
		 * @param boolean|string $language language keyword, if this is not set then returns current language sitemap
		 * @param boolean|string $keyword if only a single URL node needs to be returned
		 * @return array or false if failed
		 */
		final public function getSitemap($language=false,$keyword=false){
		
			// If language is not set, then assuming current language
			if(!$language){
				$language=$this->data['language'];
			}
			
			// If translations data is already stored in state
			if(!isset($this->data['sitemap'][$language])){
			
				// Getting raw sitemap data
				$siteMapRaw=$this->getSitemapRaw($language);
				if(!$siteMapRaw){
					return false;
				}
				
				// This is output array
				$this->data['sitemap'][$language]=array();
				
				// System builds usable URL map for views
				foreach($siteMapRaw as $key=>$node){
					// Only sitemap nodes with set view will be assigned to reference
					if(isset($node['view'])){
						// Since the same view can be referenced in multiple locations
						if(isset($node['subview'])){
							$node['view']=$node['view'].'/'.$node['subview'];
						}
						// This is used only if view has not yet been defined
						if(!isset($this->data['sitemap'][$language][$node['view']])){
							$this->data['sitemap'][$language][$node['view']]=$key;
						}
						// Home views do not need a URL node
						if($node['view']!=$this->data['home-view']){
							if(strpos($key,':')!==false){
								$url='';
								$count=0;
								$bits=explode('/',$key);
								foreach($bits as $b){
									if($b[0]!=':'){
										$url.=$b.'/';
									} else {
										$url.=':'.$count.':/';
										$count++;
									}
								}
							} else {
								$url=$key.'/';
							}
						} else {
							$url='';
						}
						// Storing data from Sitemap file
						$this->data['sitemap'][$language][$node['view']]=$siteMapRaw[$key];
						// If first language URL is not enforced, then this is taken into account
						if($language==$this->data['languages'][0] && $this->data['enforce-first-language-url']==false){
							$this->data['sitemap'][$language][$node['view']]['url']=$this->data['url-web'].$url;
						} else {
							$this->data['sitemap'][$language][$node['view']]['url']=$this->data['url-web'].$language.'/'.$url;
						}
					}
				}
				
			}
			
			// Returning keyword, if it is requested
			if($keyword){
				if(isset($this->data['sitemap'][$language][$keyword])){
					return $this->data['sitemap'][$language][$keyword];
				} else {
					return false;
				}
			} else {
				// If keyword was not set, then returning entire array
				return $this->data['sitemap'][$language];
			}
			
		}
		
	// STATE MESSENGER
	
		/**
		 * This method initializes State messenger by giving it an address and assigning the file 
		 * that State messenger will be stored under. If the file already exists and $overwrite is 
		 * not turned on, then it automatically loads contents of that file from filesystem.
		 *
		 * @param string $address key that messenger data will be saved under
		 * @param boolean $overwrite if this is set then existing state messenger file will be overwritten
		 * @return boolean
		 */
		final public function stateMessenger($address,$overwrite=false){
		
			// If messenger is already set
			if($this->messenger){
				// This stores data to filesystem
				$this->storeMessenger();
				// New messenger data will be empty
				$this->messengerData=array();
			}
			
			// New messenger address is hashed
			$this->messenger=md5($address);
			// Messenger data is stored in a filesystem subfolder
			$dataAddress=$this->data['directory-system'].'filesystem'.DIRECTORY_SEPARATOR.'messenger'.DIRECTORY_SEPARATOR.substr($this->messenger,0,2).DIRECTORY_SEPARATOR.$this->messenger.'.tmp';
			// If this state messenger address already stores data, then it is loaded as the base data
			if(!$overwrite && file_exists($dataAddress)){
				$this->messengerData=unserialize(file_get_contents($dataAddress));
			}
			
			// Messenger is always started
			return true;
			
		}
		
		/**
		 * This writes data to State messenger. $data is the key and $value is the value of the 
		 * key. $data can also be an array of keys and values, in which case multiple values are 
		 * set at the same time.
		 *
		 * @param array|string $data key or data array
		 * @param mixed $value value, if data is a key
		 * @return boolean
		 */
		final public function setMessengerData($data,$value=false){
		
			// If messenger address is set
			if($this->messenger){
				// If data is an array, then it adds data recursively
				if(is_array($data)){
					foreach($data as $key=>$value){
						// Setting messenger data
						$this->messengerData[$key]=$value;
					}
				} else {
					// Setting messenger data
					$this->messengerData[$data]=$value;
				}
				return true;
			} else {
				return false;
			}
			
		}
		
		/**
		 * This method removes key from State messenger based on value of $key. If $key is not 
		 * set, then the entire State messenger data is cleared.
		 *
		 * @param boolean|string $key key that will be removed, if set to false then removes the entire data
		 * @return boolean
		 */
		final public function unsetMessengerData($key=false){
		
			// If messenger address is set
			if($this->messenger && $key){
				if(isset($this->messengerData[$key])){
					unset($this->messengerData[$key]);
					return true;
				} else {
					return false;
				}
			} elseif($this->messenger){
				$this->messengerData=array();	
				return true;
			} else {
				return false;
			}
			
		}
		
		/**
		 * This method returns data from State messenger. It either returns all the data from 
		 * initialized state messenger, or just a $key from it. If $remove is set, then data is 
		 * also set for deletion after it has been accessed.
		 *
		 * @param boolean|string $key data keyword
		 * @param boolean $remove true or false flag whether to delete the request data after returning it
		 * @return mixed or false if failed
		 */
		final public function getMessengerData($key=false,$remove=true){
			
			// If messenger address is set
			if($this->messenger && $key){
				if(isset($this->messengerData[$key])){
					$return=$this->messengerData[$key];
					// If key is set for removal, then unsetting it
					if($remove){
						unset($this->messengerData[$key]);
					}
					return $return;
				} else {
					return false;
				}
			} elseif($this->messenger){
				$return=$this->messengerData;
				// If data is set for removal, then removing the whole data array
				if($remove){
					$this->messengerData=array();
				}
				return $return;
			} else {
				return false;
			}
			
		}
		
		/**
		 * This method simply stores messenger data in filesystem, if there is data to store.
		 * 
		 * @return boolean
		 */
		final private function storeMessenger(){
		
			// Data is only stored if messenger is set
			if($this->messenger){
				// Finding data folder
				$dataFolder=$this->data['directory-system'].'filesystem'.DIRECTORY_SEPARATOR.'messenger'.DIRECTORY_SEPARATOR.substr($this->messenger,0,2).DIRECTORY_SEPARATOR;
				// If there is data to store in messenger
				if(!empty($this->messengerData)){
					// Testing if state messenger folder exists
					if(!is_dir($dataFolder)){
						if(!mkdir($dataFolder,0755)){
							trigger_error('Cannot create messenger folder',E_USER_ERROR);
						}
					}
					// Writing messenger data to file
					if(!file_put_contents($dataFolder.$this->messenger.'.tmp',serialize($this->messengerData))){
						trigger_error('Cannot write messenger data',E_USER_ERROR);
					}
				} elseif(file_exists($dataFolder.$this->messenger.'.tmp')){
					unlink($dataFolder.$this->messenger.'.tmp');
				}
				// Processing complete
				return true;
			} else {
				// Messenger was not set
				return false;
			}
			
		}
		
	// SESSION USER AND PERMISSIONS
	
		/**
		 * This method sets user data array in session. This is a simple helper function used 
		 * for holding user-specific data for a web service. $data is an array of user data.
		 *
		 * @param boolean|array $data data array set to user
		 * @return boolean
		 */
		final public function setUser($data=false){
			
			// If user is not being unset
			if($data){
				// Setting the session
				$this->setSession($this->data['session-user-key'],$data);
				// Setting the state variable
				$this->data['user-data']=$data;
				return true;
			} else {
				// Unsetting the user
				return $this->unsetUser();
			}
			
		}
		
		/**
		 * This either returns the entire user data array or just a specific $key of user data 
		 * from the session.
		 *
		 * @param boolean|string $key element returned from user data, if not set then returns the entire user data
		 * @return mixed
		 */
		final public function getUser($key=false){
		
			// Testing if permissions state has been populated or not
			if(!$this->data['user-data']){
				$this->data['user-data']=$this->getSession($this->data['session-user-key']);
				// If this session key did not exist, then returning false
				if(!$this->data['user-data']){
					return false;
				}
			}
			
			// If key is set
			if($key){
				if(isset($this->data['user-data'][$key])){
					// Returning key data
					return $this->data['user-data'][$key];
				} else {
					return false;
				}
			} else {
				// Returning entire user array
				return $this->data['user-data'];
			}
			
		}
		
		/**
		 * This unsets user data and removes the session of user data.
		 *
		 * @return boolean
		 */
		final public function unsetUser(){
		
			// Unsetting the session
			$this->unsetSession($this->data['session-user-key']);
			// Regenerating session cookie
			$this->regenerateSession(true,true);
			// Unsetting the state variable
			$this->data['user-data']=false;
			return true;
			
		}
		
		/**
		 * This method sets an array of $permissions or a comma-separated string of permissions 
		 * for the current user permissions session.
		 *
		 * @param boolean|array|string $permissions an array or a string of permissions
		 * @return boolean
		 */
		final public function setPermissions($permissions=false){
		
			// If permissions are set
			if($permissions){
				// If permissions were sent as a string
				if(!is_array($permissions)){
					$permissions=explode(',',$permissions);
				}
				// Setting the session variable
				$this->setSession($this->data['session-permissions-key'],$permissions);
				// Setting the state variable
				$this->data['user-permissions']=$permissions;
				return true;
			} else {
				// Unsetting permissions
				return $this->unsetPermissions();
			}
			
		}
		
		/**
		 * This method returns an array of currently set user permissions from the session.
		 *
		 * @return array
		 */
		final public function getPermissions(){
		
			// Testing if permissions state has been populated or not
			if(!$this->data['user-permissions']){
				$this->data['user-permissions']=$this->getSession($this->data['session-permissions-key']);
			}
			return $this->data['user-permissions'];
			
		}
	
		/**
		 * This checks for an existence of permissions in the user permissions session array 
		 * or in the API profile permissions setting. $permissions is either a comma-separated 
		 * string of permissions to be checked, or an array. This method returns false when one 
		 * of those permission keys is not set in the permissions session. Method returns true, 
		 * if $permissions exist in the permissions session array.
		 *
		 * @param string|array $permissions comma-separated string or an array that is checked against permissions array
		 * @return boolean
		 */
		final public function checkPermissions($permissions){
		
			// Public profile permissions are stored in the session
			if($this->data['api-profile']==$this->data['api-public-profile']){
				// Testing if permissions state has been populated or not
				if(!$this->data['user-permissions']){
					$this->data['user-permissions']=$this->getSession($this->data['session-permissions-key']);
					// If this session key did not exist, then returning false
					if(!$this->data['user-permissions']){
						return false;
					}
				}
				$permissionsMatch=$this->data['user-permissions'];
			} else {
				// Private API profile permissions are stored separately
				$permissionsMatch=$this->data['api-permissions'];
			}
			
			// Permissions are handled as an array
			if(!is_array($permissions)){
				$permissions=explode(',',$permissions);
			}

			// If all permissions are set, then permissions will not be separately validated and true is assumed
			if(!in_array('*',$permissionsMatch)){
				foreach($permissions as $p){
					// Returning true or false depending on whether this key exists or not
					if(!in_array($p,$permissionsMatch)){
						return false;
					}
				}
			} else {
				foreach($permissions as $p){
					// Returning false, if one of the permissions is disallowed
					if(in_array('!'.$p,$permissionsMatch)){
						return false;
					}
				}
			}
			
			// Permission check passed
			return true;
			
		}
		
		/**
		 * This unsets permissions data from session similarly to how unsetUser() method unsets 
		 * user data from session.
		 *
		 * @return boolean
		 */
		final public function unsetPermissions(){
		
			// Unsetting the session
			$this->unsetSession($this->data['session-permissions-key']);
			// Regenerating session cookie
			$this->regenerateSession(true,true);
			// Unsetting the state variable
			$this->data['user-permissions']=false;
			return true;
			
		}
		
		/**
		 * This method returns the currently active public token that is used to increase security 
		 * against cross-site-request-forgery attacks. This method returns false if user session 
		 * is not populated, in which case public token is not needed. $regenerate sets if the token 
		 * should be regenerated if it already exists, this invalidates forms when Back button is 
		 * used after submitting data, but is more secure. $forced is used to force token generation 
		 * even if no user session is active.
		 *
		 * @param boolean $regenerate if public token should be regenerated
		 * @param boolean $forced if token is generated even when there is no actual user session active
		 * @return string or boolean if no user session active
		 */
		final public function getPublicToken($regenerate=false,$forced=false){
		
			// This is only required to protect users with active sessions
			if($forced || $this->getUser()){
				// Public token is stored in the session
				$token=$this->getSession('www-public-token');
				if($token && $token!='' && !$regenerate){
					// Returning a token
					return $token;
				} else {
					// Generating a new token
					$token=sha1($this->data['client-ip'].$this->data['client-user-agent'].$this->data['request-id'].microtime());
					$this->setSession($this->data['session-token-key'],$token);
					// Setting it also in sessions
					$this->data['api-public-token']=$token;
					// Returning a token
					return $token;
				}
			} else {
				return false;
			}
			
		}
		
		/**
		 * This method is useful when 'api-public-token' setting is off in configuration, but you
		 * still want to protect your API method from public API requests from XSS and other attacks.
		 * This returns false if the provided public API token is incorrect.
		 *
		 * @return boolean
		 */
		final public function checkPublicToken(){
		
			// This is only checked if the token actually exists and public API profile is used
			if($this->data['api-public-token'] && $this->data['api-public-profile']==$this->data['api-profile']){
				if($this->data['api-public-token']==$this->getSession($this->data['session-token-key'])){
					return true;
				} else {
					return false;
				}
			} else {
				return true;
			}
			
		}
		
	// SESSION AND COOKIES
	
		/**
		 * This method validates current session data and checks for lifetime as well as 
		 * session fingerprint. It notifies session handler if any changes have to be made.
         * Configuration array can have these keys:
         * 'name' - session cookie name
         * 'lifetime' - session cookie lifetime
         * 'path' - URL path for session cookie
         * 'domain' - domain for session cookie
         * 'secure' - whether session cookie is for secure connections only
         * 'http-only' - whether session cookie is for HTTP requests only and unavailable for scripts
         * 'user-key' - session key for storing user data
         * 'permissions-key' - session key for storing user permissions
         * 'fingerprint-key' - session key for storing session fingerprint
         * 'timestamp-key' - session key for storing session timestamp
         * 'token-key' - session key for public request tokens for protecting against replay attacks
         * 'fingerprint' - session key for session fingerprinting for protecting against replay attacks
		 *
		 * @param boolean|array $configuration list of session configuration options
		 * @return boolean
		 */
		final public function startSession($configuration=false){
		
			// If configuration options are sent
			if($configuration && is_array($configuration) && !empty($configuration)){
				// Making sure that only session variables are overwritten
				$sessionConfigurationKeys=array('name','lifetime','path','domain','secure','http-only','user-key','permissions-key','fingerprint-key','timestamp-key','token-key','fingerprint');
				foreach($sessionConfigurationKeys as $key){
					if(isset($configuration[$key])){
						$this->data['session-'.$key]=$configuration[$key];
					}
				}
			}
		
			// Starting sessions if session ID does not exist
			if(!$this->sessionHandler->sessionId){
				// Opening sessions and initializing session data
				$this->sessionHandler->sessionOpen((isset($configuration['session-name']))?$configuration['session-name']:$this->data['session-name']);
			}
			
			// Setting session cookie variables from Configuration
			$this->sessionHandler->sessionCookie(array(
				'lifetime'=>$this->data['session-lifetime'],
				'path'=>$this->data['session-path'],
				'domain'=>$this->data['session-domain'],
				'secure'=>$this->data['session-secure'],
				'http-only'=>$this->data['session-http-only'],
			));
			
			// This can regenerate the session ID, if enough time has passed
			if($this->data['session-regenerate']){
				if(!isset($this->data['session-data'][$this->data['session-timestamp-key']])){
					// Storing a session creation time in sessions
					$this->data['session-data'][$this->data['session-timestamp-key']]=$this->data['request-time'];
				} elseif($this->data['request-time']>($this->data['session-data'][$this->data['session-timestamp-key']]+$this->data['session-regenerate'])){
					// Regenerating the session ID
					$this->sessionHandler->regenerateId=true;
					$this->sessionHandler->regenerateRemoveOld=true;
					// Storing a session creation time in sessions
					$this->data['session-data'][$this->data['session-timestamp-key']]=$this->data['request-time'];
				}
			}
			
			// If session fingerprinting is used
			if($this->data['session-fingerprint'] && $this->data['fingerprint']){
				if(!isset($this->data['session-data'][$this->data['session-fingerprint-key']])){
					// Storing session fingerprint in sessions
					$this->data['session-data'][$this->data['session-fingerprint-key']]=$this->data['fingerprint'];
				} elseif($this->data['session-data'][$this->data['session-fingerprint-key']]!=$this->data['fingerprint']){
					// Regenerating the session ID
					$this->sessionHandler->regenerateId=true;
					$this->sessionHandler->regenerateRemoveOld=false;
					// Emptying the session array
					$this->data['session-data']=array();
				}
			}
			
			// Session validation complete
			$this->sessionStarted=true;
			return true;
			
		}
		
		/**
		 * This method notifies the session handler that the session data should be
		 * stored under a new ID.
		 *
		 * @param boolean $regenerate to regenerate or not
		 * @param boolean $deleteOld deletes the previous one, if set to true
		 * @return boolean
		 */
		final public function regenerateSession($regenerate=true,$deleteOld=true){
		
			// Making sure that sessions have been started
			if(!$this->sessionStarted){
				$this->startSession();
			}
			$this->sessionHandler->regenerateId=$regenerate;
			$this->sessionHandler->regenerateRemoveOld=$deleteOld;
			return true;
			
		}
		
		/**
		 * This method sets a session variable $key with a $value. If $key is an array of 
		 * keys and values, then multiple session variables are set at once.
		 *
		 * @param boolean|array|string $key key of the variable or an array of keys and values
		 * @param mixed $value value to be set
		 * @return boolean
		 */
		final public function setSession($key=false,$value=false){
		
			// Making sure that sessions have been started
			if(!$this->sessionStarted){
				$this->startSession();
			}
			// Multiple values can be set if key is an array
			if(is_array($key)){
				foreach($key as $k=>$v){
					// setting value based on key
					$this->data['session-data'][$k]=$v;
				}
			} elseif($key){
				// Setting value based on key
				$this->data['session-data'][$key]=$value;
			} else {
				return false;
			}
			return true;
			
		}
		
		/**
		 * This method returns $key value from session data. If $key is an array of keys, then 
		 * it can return multiple variables from session at once. If $key is not set, then entire 
		 * session array is returned. Method returns null for keys that have not been set.
		 *
		 * @param boolean|string|array $key key to return or an array of keys
		 * @return mixed
		 */
		final public function getSession($key=false){
		
			// Making sure that sessions have been started
			if(!$this->sessionStarted){
				$this->startSession();
			}
			
			// Multiple keys can be returned
			if(is_array($key)){
				// This array will hold multiple values
				$return=array();
				foreach($key as $val){
					// Getting value based on key
					if(isset($this->data['session-data'][$val])){
						$return[$val]=$this->data['session-data'][$val];
					} else {
						$return[$val]=null;
					}
				}
				return $return;
			} elseif($key){
				// Return data from specific key
				if(isset($this->data['session-data'][$key])){
					return $this->data['session-data'][$key];
				} else {
					return null;
				}
			} else {
				// Return entire session data, if key was not set
				return $this->data['session-data'];
			}
			
		}
		
		/**
		 * This method unsets $key value from current session. If $key is an array of keys, then 
		 * multiple variables can be unset at once. If $key is not set at all, then this simply 
		 * destroys the entire session.
		 *
		 * @param boolean|string|array $key key of the value to be unset, or an array of keys
		 * @return boolean
		 */
		final public function unsetSession($key=false){
		
			// Making sure that sessions have been started
			if(!$this->sessionStarted){
				$this->startSession();
			}
			
			// Can unset multiple values
			if(is_array($key)){
				foreach($key as $value){
					unset($this->data['session-data'][$value]);
				}
			} elseif($key){
				unset($this->data['session-data'][$key]);
			} else {
				// Entire session data is unset
				$this->data['session-data']=array();
				// Session cookie will be regenerated if new data is stored
				$this->sessionHandler->regenerateId=true;
				$this->sessionHandler->regenerateRemoveOld=true;
			}
			return true;
			
		}
		
		/**
		 * This method sets a cookie with $key and a $value. $configuration is an array of 
		 * cookie parameters that can be set.
		 *
		 * @param string|array $key key of the variable, or an array of keys and values
		 * @param string|array $value value to be set, can also be an array
		 * @param array $configuration cookie configuration options, list of keys below
		 *  'expire' - timestamp when the cookie is set to expire
		 *  'timeout' - alternative to 'expire', this sets how long the cookie can survive
		 *  'path' - cookie limited to URL path
		 *  'domain' - cookie limited to domain
		 *  'secure' - whether cookie is for secure connections only
		 *  'http-only' - if the cookie is only available for HTTP requests
		 * @return boolean
		 */
		final public function setCookie($key,$value,$configuration=array()){
		
			// Checking for configuration options
			if(!isset($configuration['expire'])){
				if(isset($configuration['timeout'])){
					$configuration['expire']=$this->data['request-time']+$configuration['timeout'];
				} else {
					$configuration['expire']=2147483647;
				}
			}
			if(!isset($configuration['path'])){
				$configuration['path']=$this->data['url-web'];
			}
			if(!isset($configuration['domain'])){
				$configuration['domain']=$this->data['http-host'];
			}
			if(!isset($configuration['secure'])){
				$configuration['secure']=false;
			}
			if(!isset($configuration['http-only'])){
				$configuration['http-only']=true;
			}
			
			// Can set multiple values
			if(is_array($key)){
				// Value can act as a configuration
				if(is_array($value)){
					$configuration=$value;
				}
				foreach($key as $k=>$v){
					// Value can be an array, in which case the values set will be an array
					if(is_array($v)){
						foreach($v as $index=>$val){
							setcookie($k.'['.$index.']',$val,$configuration['expire'],$configuration['path'],$configuration['domain'],$configuration['secure'],$configuration['http-only']);
							// Cookie values can be accessed immediately after they are set
							$_COOKIE[$k][$index]=$val;
						}
					} else {
						// Setting the cookie
						setcookie($k,$v,$configuration['expire'],$configuration['path'],$configuration['domain'],$configuration['secure'],$configuration['http-only']);
						// Cookie values can be accessed immediately after they are set
						$_COOKIE[$k]=$v;
					}
				}
			} else {
				// Value can be an array, in which case the values set will be an array
				if(is_array($value)){
					foreach($value as $index=>$val){
						setcookie($key.'['.$index.']',$val,$configuration['expire'],$configuration['path'],$configuration['domain'],$configuration['secure'],$configuration['http-only']);
						// Cookie values can be accessed immediately after they are set
						$_COOKIE[$key][$index]=$val;
					}
				} else {
					// Setting the cookie
					setcookie($key,$value,$configuration['expire'],$configuration['path'],$configuration['domain'],$configuration['secure'],$configuration['http-only']);
					// Cookie values can be accessed immediately after they are set
					$_COOKIE[$key]=$value;
				}
			}
			
		}
		
		/**
		 * This method returns a cookie value with the set $key. $key can also be an array of 
		 * keys, in which case multiple cookie values are returned in an array. Method returns null 
		 * for keys that have not been set.
		 *
		 * @param string $key key of the value to be returned, can be an array
		 * @return mixed
		 */
		final public function getCookie($key){
		
			// Multiple keys can be returned
			if(is_array($key)){
				// This array will hold multiple values
				$return=array();
				foreach($key as $val){
					if(isset($_COOKIE[$val])){
						$return[$val]=$_COOKIE[$val];
					} else {
						$return[$val]=false;
					}
				}
				return $return;
			} else {
				// Returning single cookie value
				if(isset($_COOKIE[$key])){
					return $_COOKIE[$key];
				} else {
					return null;
				}
			}
			
		}
		
		/**
		 * This method unsets a cookie with the set key of $key. If $key is an array, then 
		 * it can remove multiple cookies at once.
		 *
		 * @param string|array $key key of the value to be unset or an array of keys
		 * @return boolean
		 */
		final public function unsetCookie($key){
		
			// Can set multiple values
			if(is_array($key)){
				foreach($key as $value){
					if(isset($_COOKIE[$value])){
						// Removes cookie by setting its duration to 0
						setcookie($value,'',($this->data['request-time']-3600));
						// Unsets the cookie value
						unset($_COOKIE[$value]);
					}
				}
			} else {
				if(isset($_COOKIE[$key])){
					// Removes cookie by setting its duration to 0
					setcookie($key,'',($this->data['request-time']-3600));
					// Unsets the cookie value
					unset($_COOKIE[$value]);
				} else {
					return false;
				}
			}
			return true;
			
		}
		
	// HEADERS
	
		/**
		 * This method adds a header to the array of headers to be added before data is pushed 
		 * to the client, when headers are sent. $header is the header string to add and $replace 
		 * is a true/false setting for whether previously sent header like this is replaced or not.
		 *
		 * @param string|array $header header string to add or an array of header strings
		 * @param boolean $replace whether the header should be replaced, if previously set
		 * @return boolean
		 */
		final public function setHeader($header,$replace=true){
		
			// Multiple headers can be set at once
			if(is_array($header)){
				foreach($header as $h){
					if($h!=''){
						// Removing the header from unset array
						unset($this->data['headers-unset'][$h]);
						// Assigning the setting to headers array
						$this->data['headers-set'][$h]=$replace;
					}
				}
			} else {
				if($header!=''){
					// Removing the header from unset array
					unset($this->data['headers-unset'][$header]);
					// Assigning the setting to headers array
					$this->data['headers-set'][$header]=$replace;
				}
			}
			return true;
			
		}
	
		/**
		 * This method adds a header to the array of headers to be removed before data is pushed 
		 * to the client, when headers are sent. $header is the header string to remove.
		 *
		 * @param string|array $header header string to add or an array of header strings
		 * @return boolean
		 */
		final public function unsetHeader($header){
		
			// Multiple headers can be unset at once
			if(is_array($header)){
				foreach($header as $h){
					if($h!=''){
						// Removing the header from unset array
						unset($this->data['headers-set'][$h]);
						// Assigning the setting to headers array
						$this->data['headers-unset'][$h]=true;
					}
				}
			} else {
				if($header!=''){
					// Unsetting the header, if previously set
					unset($this->data['headers-set'][$header]);
					// Assigning the setting to headers array
					$this->data['headers-unset'][$header]=true;
				}
			}
			return true;
			
		}
		
	// TOOLS
	
		/**
		 * This method loads tools object if it doesn't exist yet and then allows to 
		 * call various methods of the tool. You can call filesystem cleaner, indexer
		 * or a file-size calculator (and each work recursively).
		 *
		 * @param string $type the type of tool to be loaded
		 * @param mixed $arg1 additional parameter for the tool
		 * @param mixed $arg2 additional parameter for the tool
		 * @return mixed based on the tool
		 */
		final public function callTool($type,$arg1=false,$arg2=false){
		
			// If tool does not exist yet
			if(!$this->tools){
		
				// Loading the Imager class
				if(!class_exists('WWW_Tools',false)){
					require($this->data['directory-system'].'engine'.DIRECTORY_SEPARATOR.'class.www-tools.php');
				}
				
				// Creating a new tool object
				$this->tools=new WWW_Tools();
				
			}
		
			// Calling the method based on selected tool
			switch($type){
				case 'cleaner':
					// This variable defines the cut-off time of the cleaner
					if($arg2){
						return $this->tools->cleaner($arg1,$arg2);
					} else {
						return $this->tools->cleaner($arg1);
					}
					break;
				case 'indexer':
					// This variable defines the mode of the index method
					if($arg2){
						return $this->tools->indexer($arg1,$arg2);
					} else {
						return $this->tools->indexer($arg1);
					}
					break;
				case 'sizer':
					// Takes just the directory variable
					return $this->tools->sizer($arg1);
					break;
				case 'counter':
					// Takes just the directory variable
					return $this->tools->counter($arg1);
					break;
				default:
					trigger_error('This tool does not exist: '.$type,E_USER_WARNING);
					return false;
					break;
			}
		
		}
	
	// TERMINAL
	
		/**
		 * This method is wrapper function for making terminal calls. It attempts to detect 
		 * what terminal is available on the system, if any, and then execute the call and 
		 * return the results of the call.
		 *
		 * @param string $command command to be executed
		 * @return mixed
		 */
		final public function terminal($command){
		
			// Status variable
			$status=1;
		
			// Checking all possible terminal functions
			if(function_exists('system')){
				ob_start();
				system($command,$status);
				$output=ob_get_contents();
				ob_end_clean();
			} elseif(function_exists('passthru')){
				ob_start();
				passthru($command,$status);
				$output=ob_get_contents();
				ob_end_clean();
			} elseif(function_exists('exec')){
				exec($command,$output,$status);
				$output=implode("\n",$output);
			} elseif(function_exists('shell_exec')){
				$output=shell_exec($command);
			} else {
				// No function was available, returning false
				return false;
			}

			// Returning result
			return array('output'=>$output,'status'=>$status);
			
		}
	
}
	
?>
For more information send a message to info at phpclasses dot org.