<?php/**
* Cache subsystem library
*
* @package API - Cache
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/defined('COT_CODE') or die('Wrong URL');/**
* Stores the list of advanced cachers provided by the host
* @var array
*/$cot_cache_drivers=array();/**
* Default cache realm
*/define('COT_DEFAULT_REALM','cot');/**
* Default time to live for temporary cache objects
*/define('COT_DEFAULT_TTL',3600);/**
* Default cache type, uneffective
*/define('COT_CACHE_TYPE_ALL',0);/**
* Disk cache type
*/define('COT_CACHE_TYPE_DISK',1);/**
* Database cache type
*/define('COT_CACHE_TYPE_DB',2);/**
* Shared memory cache type
*/define('COT_CACHE_TYPE_MEMORY',3);/**
* Page cache type
*/define('COT_CACHE_TYPE_PAGE',4);/**
* Default cache type
*/define('COT_CACHE_TYPE_DEFAULT', COT_CACHE_TYPE_DB);/**
* Abstract class containing code common for all cache drivers
*/abstractclass Cache_driver
{/**
* Clears all cache entries served by current driver
* @param string $realm Cache realm name, to clear specific realm only
* @return bool
*/abstractpublicfunction clear($realm= COT_DEFAULT_REALM);/**
* Checks if an object is stored in cache
* @param string $id Object identifier
* @param string $realm Cache realm
* @return bool
*/abstractpublicfunction exists($id,$realm= COT_DEFAULT_REALM);/**
* Returns value of cached image
* @param string $id Object identifier
* @param string $realm Realm name
* @return mixed Cached item value or NULL if the item was not found in cache
*/abstractpublicfunction get($id,$realm= COT_DEFAULT_REALM);/**
* Removes object image from cache
* @param string $id Object identifier
* @param string $realm Realm name
* @return bool
*/abstractpublicfunction remove($id,$realm= COT_DEFAULT_REALM);}/**
* Static cache is used to store large amounts of rarely modified data
*/abstractclass Static_cache_driver
{/**
* Stores data as object image in cache
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @return bool
*/abstractpublicfunction store($id,$data,$realm= COT_DEFAULT_REALM);}/**
* Dynamic cache is used to store data that is not too large
* and is modified more or less frequently
*/abstractclass Dynamic_cache_driver
{/**
* Stores data as object image in cache
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @param int $ttl Time to live, 0 for unlimited
* @return bool
*/abstractpublicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL);}/**
* Persistent cache driver that writes all entries back on script termination.
* Persistent cache drivers work slower but guarantee long-term data consistency.
*/abstractclass Writeback_cache_driver extends Dynamic_cache_driver
{/**
* Values for delayed writeback to persistent cache
* @var array
*/protected$writeback_data=array();/**
* Keys that are to be removed
*/protected$removed_data=array();/**
* Writes modified entries back to persistent storage
*/abstractpublicfunctionflush();/**
* Removes cache image of the object from the database
* @param string $id Object identifier
* @param string $realm Realm name
*/publicfunction remove($id,$realm= COT_DEFAULT_REALM){$this->removed_data[]=array('id'=>$id,'realm'=>$realm);}/**
* Removes item immediately, avoiding writeback.
* @param string $id Item identifier
* @param string $realm Cache realm
* @return bool
* @see Cache_driver::remove()
*/abstractpublicfunction remove_now($id,$realm= COT_DEFAULT_REALM);/**
* Stores data as object image in cache
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @param int $ttl Time to live, 0 for unlimited
* @return bool
* @see Cache_driver::store()
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl=0){$this->writeback_data[]=array('id'=>$id,'data'=>$data,'realm'=>$realm,'ttl'=>$ttl);returntrue;}/**
* Writes item to cache immediately, avoiding writeback.
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @param int $ttl Time to live, 0 for unlimited
* @return bool
* @see Cache_driver::store()
*/abstractpublicfunction store_now($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL);}/**
* Query cache drivers are driven by database
*/abstractclass Db_cache_driver extends Writeback_cache_driver
{/**
* Loads all variables from a specified realm(s) into the global scope
* @param mixed $realm Realm name or array of realm names
* @return int Number of items loaded
*/abstractpublicfunction get_all($realm= COT_DEFAULT_REALM);}/**
* Temporary cache driver is fast in-memory cache. It usually works faster and provides
* automatic garbage collection, but it doesn't save data if PHP stops whatsoever.
* Use it for individual frequently modified variables.
*/abstractclass Temporary_cache_driver extends Dynamic_cache_driver
{/**
* Increments counter value
* @param string $id Counter identifier
* @param string $realm Realm name
* @param int $value Increment value
* return int Result value
*/publicfunction inc($id,$realm= COT_DEFAULT_REALM,$value=1){$res=$this->get($id,$realm);$res+=$value;$this->store($id,$res,$realm);return$res;}/**
* Decrements counter value
* @param string $id Counter identifier
* @param string $realm Realm name
* @param int $value Increment value
* return int Result value
*/publicfunction dec($id,$realm= COT_DEFAULT_REALM,$value=1){$res=$this->get($id,$realm);$res-=$value;$this->store($id,$res,$realm);return$res;}/**
* Returns information about memory usage if available.
* Possible keys: available, occupied, max.
* If the driver cannot provide a value, it sets it to -1.
* @return array Associative array containing information
*/abstractpublicfunction get_info();/**
* Gets a size limit from php.ini
* @param string $name INI setting name
* @return int Number of bytes
*/protectedfunction get_ini_size($name){$ini=ini_get($name);$suffix=strtoupper(substr($ini,-1));$prefix=substr($ini,0,-1);switch($suffix){case'K':return((int)$prefix)*1024;break;case'M':return((int)$prefix)*1048576;break;case'G':return((int)$prefix)*1073741824;break;default:return(int)$ini;}}}/**
* A persistent cache using local file system tree. It does not use multilevel structure
* or lexicograph search, so it may slow down when your cache grows very big.
* But normally it is very fast reads.
*/class File_cache extends Static_cache_driver
{/**
* Cache root directory
* @var string
*/private$dir;/**
* Cache storage object constructor
* @param string $dir Cache root directory. System default will be used if empty.
* @throws Exception
* @return File_cache
*/publicfunction __construct($dir=''){global$cfg;if(empty($dir))$dir=$cfg['cache_dir'];if(!empty($dir)&&!file_exists($dir))mkdir($dir,0755,true);if(file_exists($dir)&&is_writeable($dir)){$this->dir=$dir;}else{thrownew Exception('Cache directory '.$dir.' is not writeable!');// TODO: Need translate}}/**
* @see Cache_driver::clear()
*/publicfunction clear($realm= COT_DEFAULT_REALM){if(empty($realm)){if(is_dir($this->dir)){$dp=opendir($this->dir);while($f=readdir($dp)){$dname=$this->dir.'/'.$f;if($f[0]!='.'&&is_dir($dname)){$this->clear($f);}}closedir($dp);}}else{if(is_dir($this->dir.'/'.$realm)){$dp=opendir($this->dir.'/'.$realm);while($f=readdir($dp)){$fname=$this->dir.'/'.$realm.'/'.$f;if(is_file($fname)){unlink($fname);}}closedir($dp);}}returnTRUE;}/**
* Checks if an object is stored in disk cache
* @param string $id Object identifier
* @param string $realm Cache realm
* @param int $ttl Lifetime in seconds, 0 means unlimited
* @return bool
*/publicfunction exists($id,$realm= COT_DEFAULT_REALM,$ttl=0){$filename=$this->dir.'/'.$realm.'/'.$id;returnfile_exists($filename)&&($ttl==0||time()-filemtime($filename)<$ttl);}/**
* Gets an object directly from disk
* @param string $id Object identifier
* @param string $realm Realm name
* @param int $ttl Lifetime in seconds, 0 means unlimited
* @return mixed Cached item value or NULL if the item was not found in cache
*/publicfunction get($id,$realm= COT_DEFAULT_REALM,$ttl=0){if($this->exists($id,$realm,$ttl)){returnunserialize(file_get_contents($this->dir.'/'.$realm.'/'.$id));}else{returnNULL;}}/**
* Removes cache image of the object from disk
* @param string $id Object identifier
* @param string $realm Realm name
*/publicfunction remove($id,$realm= COT_DEFAULT_REALM){if($this->exists($id,$realm)){unlink($this->dir.'/'.$realm.'/'.$id);returntrue;}elsereturnfalse;}/**
* Stores disk cache entry
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @return bool
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM){if(!file_exists($this->dir.'/'.$realm)){mkdir($this->dir.'/'.$realm);}file_put_contents($this->dir.'/'.$realm.'/'.$id,serialize($data));returntrue;}}/**
* A cache that stores entire page outputs. Disk-based.
*/class Page_cache
{/**
* Cache root
*/private$dir;/**
* Relative page (item) path
*/private$path;/**
* Short file name
*/private$name;/**
* Parameters to exclude
*/private$excl;/**
* Filename extension
*/private$ext;/**
* Full path to page cache image
*/private$filename;/**
* Directory permissions
*/private$perms;/**
* Constructs controller object and sets basic configuration
* @param string $dir Cache directory
* @param int $perms Octal permission mask for cache directories
*/publicfunction __construct($dir,$perms=0777){$this->dir=$dir;$this->perms=$perms;}/**
* Removes an item and all contained items and cache files
* @param string $path Item path
* @return int Number of files removed
*/publicfunction clear($path){return$this->rm_r($this->dir.'/'.$path);}/**
* Initializes actual page cache
* @param string $path Page path string
* @param string $name Short name for the cache file
* @param array $exclude A list of GET params to be excluded from consideration
* @param string $ext File extension
*/publicfunction init($path,$name,$exclude=array(),$ext=''){$this->path=$path;$this->name=$name;$this->excl=$exclude;$this->ext=$ext;}/**
* Reads the page cache object from disk and sends it to output.
* If the cache object does not exist, then just calculates the path
* for a following write() call.
*/publicfunction read(){$filename=$this->dir.'/'.$this->path.'/'.$this->name;$args=array();foreach($_GETas$key=>$val){if(!in_array($key,$this->excl)){$args[$key]=$val;}}ksort($args);if(count($args)>0){$hashkey=serialize($args);$filename.='_'.md5($hashkey).sha1($hashkey);}if(!empty($this->ext)){$filename.='.'.$this->ext;}if(file_exists($filename)){// Browser cache headers$filemtime=filemtime($filename);$etag=md5($filename.filesize($filename).$filemtime);if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){// convert to unix timestamp$if_modified_since=strtotime(preg_replace('#;.*$#','',stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])));}else{$if_modified_since=false;}$if_none_match=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);if($if_none_match==$etag&&$if_modified_since>=$filemtime){$protocol=(isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL']:'HTTP/1.1';header($protocol.' 304 Not Modified');header("Etag: $etag");exit;}header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T',$filemtime));header("ETag: $etag");header('Expires: Mon, 01 Apr 1974 00:00:00 GMT');header('Cache-Control: must-revalidate, proxy-revalidate');// Page outputheader('Content-Type: text/html; charset=UTF-8');if(@strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip')===FALSE){readgzfile($filename);}else{header('Content-Encoding: gzip');echofile_get_contents($filename);}exit;}$this->filename=$filename;}/**
* Writes output buffer contents to a cache image file
*/publicfunction write(){if(!empty($this->filename)){if(!file_exists($this->dir.'/'.$this->path)){mkdir($this->dir.'/'.$this->path,$this->perms,true);}file_put_contents($this->filename,gzencode(cot_outputfilters(ob_get_contents())));}}/**
* Removes a directory with all its contents recursively
* @param string $path Directory path
* @return int Number of items removed
*/privatefunction rm_r($path){$cnt=0;if(is_dir($path)){$dp=opendir($path);while($f=readdir($dp)){$fpath=$path.'/'.$f;if(is_dir($fpath)&&$f!='.'&&$f!='..'){$cnt+=$this->rm_r($fpath);}elseif(is_file($fpath)){unlink($fpath);++$cnt;}}closedir($dp);rmdir($path);}return++$cnt;}}/**
* A very popular caching solution using MySQL as a storage. It is quite slow compared to
* File_cache but may be considered more reliable.
*/class MySQL_cache extends Db_cache_driver
{/**
* Prefetched data to avoid duplicate queries
* @var array
*/private$buffer=array();/**
* Performs pre-load actions
*/publicfunction __construct(){// 10% GC probabilityif(mt_rand(1,10)==5){$this->gc();}}/**
* Enforces flush()
*/publicfunction __destruct(){$this->flush();}/**
* Saves all modified data with one query
*/publicfunctionflush(){global$db,$db_cache,$sys;if(count($this->removed_data)>0){$q="DELETE FROM $db_cache WHERE";$i=0;foreach($this->removed_dataas$entry){$c_name=$db->quote($entry['id']);$c_realm=$db->quote($entry['realm']);$or=$i==0 ? '':' OR';$q.=$or." (c_name = $c_name AND c_realm = $c_realm)";$i++;}$this->removed_data=array();$db->query($q);}if(count($this->writeback_data)>0){$q="INSERT INTO $db_cache (c_name, c_realm, c_expire, c_value) VALUES ";$i=0;foreach($this->writeback_dataas$entry){$c_name=$db->quote($entry['id']);$c_realm=$db->quote($entry['realm']);$c_expire=$entry['ttl']>0 ? $sys['now']+$entry['ttl']:0;$c_value=$db->quote(serialize($entry['data']));$comma=$i==0 ? '':',';$q.=$comma."($c_name, $c_realm, $c_expire, $c_value)";$i++;}$this->writeback_data=array();$q.=" ON DUPLICATE KEY UPDATE c_value=VALUES(c_value), c_expire=VALUES(c_expire)";$db->query($q);}}/**
* @see Cache_driver::clear()
*/publicfunction clear($realm=''){global$db,$db_cache;if(empty($realm)){$db->query("TRUNCATE $db_cache");}else{$db->query("DELETE FROM $db_cache WHERE c_realm = ".$db->quote($realm));}$this->buffer=array();returnTRUE;}/**
* @see Cache_driver::exists()
*/publicfunction exists($id,$realm= COT_DEFAULT_REALM){global$db,$db_cache;if(isset($this->buffer[$realm][$id])){returntrue;}$sql=$db->query("SELECT c_value FROM $db_cache WHERE c_realm = ".$db->quote($realm)." AND c_name = ".$db->quote($id));$res=$sql->rowCount()==1;if($res){$this->buffer[$realm][$id]=unserialize($sql->fetchColumn());}return$res;}/**
* Garbage collector function. Removes cache entries which are not valid anymore.
* @return int Number of entries removed
*/privatefunction gc(){global$db,$db_cache,$sys;$db->query("DELETE FROM $db_cache WHERE c_expire > 0 AND c_expire < ".$sys['now']);return$db->affectedRows;}/**
* @see Cache_driver::get()
*/publicfunction get($id,$realm= COT_DEFAULT_REALM){if($this->exists($id,$realm)){return$this->buffer[$realm][$id];}else{returnnull;}}/**
* @see Db_cache_driver::get_all()
*/publicfunction get_all($realms= COT_DEFAULT_REALM){global$db,$db_cache;if(is_array($realms)){$r_where="c_realm IN(";$i=0;foreach($realmsas$realm){$glue=$i==0 ? "'":",'";$r_where.=$glue.$db->prep($realm)."'";$i++;}$r_where.=')';}else{$r_where="c_realm = '".$db->prep($realms)."'";}$sql=$db->query("SELECT c_name, c_value FROM `$db_cache` WHERE c_auto=1 AND $r_where");$i=0;while($row=$sql->fetch()){global ${$row['c_name']}; ${$row['c_name']}=unserialize($row['c_value']);$i++;}$sql->closeCursor();return$i;}/**
* @see Writeback_cache_driver::remove_now()
*/publicfunction remove_now($id,$realm= COT_DEFAULT_REALM){global$db,$db_cache;$db->query("DELETE FROM $db_cache WHERE c_realm = ".$db->quote($realm)." AND c_name = ".$db->quote($id));unset($this->buffer[$realm][$id]);return$db->affectedRows==1;}/**
* Stores data as object image in cache
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @param int $ttl Time to live, 0 for unlimited
* @return bool
* @see Cache_driver::store()
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl=0){global$db;// Check data lengthif($data){if(strlen($db->prep(serialize($data)))>16777215)// MySQL max MEDIUMTEXT size{returnfalse;}}return parent::store($id,$data,$realm,$ttl);}/**
* Writes item to cache immediately, avoiding writeback.
* @param string $id Object identifier
* @param mixed $data Object value
* @param string $realm Realm name
* @param int $ttl Time to live, 0 for unlimited
* @return bool
* @see Cache_driver::store()
*/publicfunction store_now($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL){global$db,$db_cache,$sys;$c_name=$db->quote($id);$c_realm=$db->quote($realm);$c_expire=$ttl>0 ? $sys['now']+$ttl:0;$c_value=$db->quote(serialize($data));$db->query("INSERT INTO $db_cache (c_name, c_realm, c_expire, c_value)
VALUES ($c_name, $c_realm, $c_expire, $c_value)");$this->buffer[$realm][$id]=$data;return$db->affectedRows==1;}}if(extension_loaded('memcache')){$cot_cache_drivers[]='Memcache_driver';/**
* Memcache distributed persistent cache driver implementation. Give it a higher priority
* if a cluster of webservers is used and Memcached is running via TCP/IP between them.
* In other circumstances this only should be used if no APC/XCache available,
* keeping in mind that File_cache might be still faster.
* @author Cotonti Team
*/class Memcache_driver extends Temporary_cache_driver
{/**
* PHP Memcache instance
* @var Memcache
*/protected$memcache=NULL;/**
* Creates an object and establishes Memcached server connection
* @param string $host Memcached host
* @param int $port Memcached port
* @param bool $persistent Use persistent connection
* @return Memcache_driver
*/publicfunction __construct($host='localhost',$port=11211,$persistent=true){if(empty($host))$host='localhost';if(empty($port))$port=11211;$this->memcache=new Memcache;$this->memcache->addServer($host,$port,$persistent);}/**
* Make unique key for one of different sites on one memcache pool
* @param $key
* @return string
*/public static function createKey($key){if(is_array($key))$key=serialize($key);returnmd5(cot::$cfg['site_id'].$key);}/**
* @see Cache_driver::clear()
*/publicfunction clear($realm=''){if(empty($realm)){return$this->memcache->flush();}else{// FIXME implement exact realm cleanup (not yet provided by Memcache)return$this->memcache->flush();}}/**
* @see Temporary_cache_driver::dec()
*/publicfunction dec($id,$realm= COT_DEFAULT_REALM,$value=1){$id=self::createKey($id);return$this->memcache->decrement($realm.'/'.$id,$value);}/**
* @see Cache_driver::exists()
*/publicfunction exists($id,$realm= COT_DEFAULT_REALM){$id=self::createKey($id);return$this->memcache->get($realm.'/'.$id)!==FALSE;}/**
* @see Cache_driver::get()
*/publicfunction get($id,$realm= COT_DEFAULT_REALM){$id=self::createKey($id);return$this->memcache->get($realm.'/'.$id);}/**
* @see Temporary_cache_driver::get_info()
*/publicfunction get_info(){$info=$this->memcache->getstats();returnarray('available'=>$info['limit_maxbytes']-$info['bytes'],'max'=>$info['limit_maxbytes'],'occupied'=>$info['bytes']);}/**
* @see Temporary_cache_driver::inc()
*/publicfunction inc($id,$realm= COT_DEFAULT_REALM,$value=1){$id=self::createKey($id);return$this->memcache->increment($realm.'/'.$id,$value);}/**
* @see Cache_driver::remove()
*/publicfunction remove($id,$realm= COT_DEFAULT_REALM){$id=self::createKey($id);return$this->memcache->delete($realm.'/'.$id);}/**
* @see Dynamic_cache_driver::store()
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL){$id=self::createKey($id);return$this->memcache->set($realm.'/'.$id,$data,0,$ttl);}}}if(extension_loaded('apc')){$cot_cache_drivers[]='APC_driver';/**
* Accelerated PHP Cache driver implementation. This should be used as default cacher
* on APC-enabled hosts.
* @author Cotonti Team
*/class APC_driver extends Temporary_cache_driver
{/**
* @see Cache_driver::clear()
*/publicfunction clear($realm=''){if(empty($realm)){return apc_clear_cache();}else{// TODO implement exact realm cleanupreturnFALSE;}}/**
* @see Cache_driver::exists()
*/publicfunction exists($id,$realm= COT_DEFAULT_REALM){return apc_fetch($realm.'/'.$id)!==FALSE;}/**
* @see Cache_driver::get()
*/publicfunction get($id,$realm= COT_DEFAULT_REALM){returnunserialize(apc_fetch($realm.'/'.$id));}/**
* @see Temporary_cache_driver::get_info()
*/publicfunction get_info(){$info= apc_sma_info();$max=ini_get('apc.shm_segments')*ini_get('apc.shm_size')*1024*1024;$occupied=$max-$info['avail_mem'];returnarray('available'=>$info['avail_mem'],'max'=>$max,'occupied'=>$occupied);}/**
* @see Cache_driver::remove()
*/publicfunction remove($id,$realm= COT_DEFAULT_REALM){return apc_delete($realm.'/'.$id);}/**
* @see Dynamic_cache_driver::store()
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL){// Protect from exhausted memory$info=$this->get_info();if($info['available']<$info['max']*0.2){$this->clear();}return apc_store($realm.'/'.$id,serialize($data),$ttl);}}}if(extension_loaded('xcache')){$cot_cache_drivers[]='Xcache_driver';/**
* XCache variable cache driver. It should be used on hosts that use XCache for
* PHP acceleration and variable cache.
* @author Cotonti Team
*/class Xcache_driver extends Temporary_cache_driver
{/**
* @see Cache_driver::clear()
*/publicfunction clear($realm=''){if(!function_exists('xcache_unset_by_prefix')){function xcache_unset_by_prefix($prefix){// Since we can't clear targetted cache, we'll clear all. :( xcache_clear_cache(XC_TYPE_VAR,0);}}if(empty($realm)){ xcache_unset_by_prefix('');}else{ xcache_unset_by_prefix($realm.'/');}returntrue;}/**
* @see Cache_driver::exists()
*/publicfunction exists($id,$realm= COT_DEFAULT_REALM){return xcache_isset($realm.'/'.$id);}/**
* @see Temporary_cache_driver::dec()
*/publicfunction dec($id,$realm= COT_DEFAULT_REALM,$value=1){return xcache_dec($realm.'/'.$id,$value);}/**
* @see Cache_driver::get()
*/publicfunction get($id,$realm= COT_DEFAULT_REALM){returnunserialize(xcache_get($realm.'/'.$id));}/**
* @see Temporary_cache_driver::get_info()
*/publicfunction get_info(){returnarray('available'=>-1,'max'=>$this->get_ini_size('xcache.var_size'),'occupied'=>-1);}/**
* @see Temporary_cache_driver::inc()
*/publicfunction inc($id,$realm= COT_DEFAULT_REALM,$value=1){return xcache_inc($realm.'/'.$id,$value);}/**
* @see Cache_driver::remove()
*/publicfunction remove($id,$realm= COT_DEFAULT_REALM){return xcache_unset($realm.'/'.$id);}/**
* @see Dynamic_cache_driver::store()
*/publicfunction store($id,$data,$realm= COT_DEFAULT_REALM,$ttl= COT_DEFAULT_TTL){return xcache_set($realm.'/'.$id,serialize($data),$ttl);}}}/**
* Multi-layer universal cache controller for Cotonti
*
* @property-read bool $mem_available Memory storage availability flag
*/class Cache
{/**
* Persistent cache underlayer driver.
* Stores disk-only cache entries. Use it for large objects, which you don't want to put
* into memory cache.
* @var Static_cache_driver
*/public$disk;/**
* Intermediate database cache driver.
* It is recommended to use memory cache for particular objects rather than DB cache.
* @var Db_cache_driver
*/public$db;/**
* Mutable top-layer shared memory driver.
* Is FALSE if memory cache is not available
* @var Temporary_cache_driver
*/public$mem;/**
* Page cache driver.
* Is FALSE if page cache is disabled
* @var Page_cache
*/public$page;/**
* Event bindings
* @var array
*/private$bindings;/**
* A flag to apply binding changes before termination
* @var bool
*/private$resync_on_exit=false;/**
* Selected memory driver
* @var string
*/private$selected_drv='';/**
* Initializes Page cache for early page caching
*/publicfunction __construct(){global$cfg;$this->page=new Page_cache($cfg['cache_dir'],$cfg['dir_perms']);}/**
* Performs actions before script termination
*/publicfunction __destruct(){if($this->resync_on_exit){$this->resync_bindings();}}/**
* Property handler
* @param string $name Property name
* @return mixed Property value
*/publicfunction __get($name){switch($name){case'mem_available':return$this->mem!==FALSE;break;case'mem_driver':return$this->selected_drv;break;default:returnnull;break;}}/**
* Initializes the rest Cache components when the sources are available
*/publicfunction init(){global$cfg,$cot_cache_autoload,$cot_cache_drivers,$cot_cache_bindings,$env;$this->disk=new File_cache($cfg['cache_dir']);$this->db=new MySQL_cache();$cot_cache_autoload=is_array($cot_cache_autoload) ? array_merge(array('system','cot',$env['ext']),$cot_cache_autoload):array('system','cot',$env['ext']);$this->db->get_all($cot_cache_autoload);$cfg['cache_drv'].='_driver';if(in_array($cfg['cache_drv'],$cot_cache_drivers)){$selected=$cfg['cache_drv'];}if(!empty($selected)){$cfg['cache_drv_host']=!empty($cfg['cache_drv_host']) ? $cfg['cache_drv_host']:null;$cfg['cache_drv_port']=!empty($cfg['cache_drv_port']) ? $cfg['cache_drv_port']:null;/** @var Temporary_cache_driver $mem */$mem=new$selected($cfg['cache_drv_host'],$cfg['cache_drv_port']);// Some drivers may be enabled but without variable cache$info=$mem->get_info();if($info['max']>1024){$this->mem=$mem;$this->selected_drv=$selected;}}else{$this->mem=false;}if(!$cot_cache_bindings){$this->resync_bindings();}else{unset($cot_cache_bindings);}}/**
* Rereads bindings from database
*/privatefunction resync_bindings(){// global $db, $db_cache_bindings;$this->bindings=array();// $sql = $db->query("SELECT * FROM `$db_cache_bindings`");// while ($row = $sql->fetch())// {// $this->bindings[$row['c_event']][] = array('id' => $row['c_id'], 'realm' => $row['c_realm']);// }// $sql->closeCursor();// $this->db->store('cot_cache_bindings', $this->bindings, 'system');}/**
* Binds an event to automatic cache field invalidation
* @param string $event Event name
* @param string $id Cache entry id
* @param string $realm Cache realm name
* @param int $type Storage type, one of COT_CACHE_TYPE_* values
* @return bool TRUE on success, FALSE on error
*/publicfunction bind($event,$id,$realm= COT_DEFAULT_REALM,$type= COT_CACHE_TYPE_DEFAULT){global$db,$db_cache_bindings;$c_event=$db->quote($event);$c_id=$db->quote($id);$c_realm=$db->quote($realm);$c_type=(int)$type;$db->query("INSERT INTO `$db_cache_bindings` (c_event, c_id, c_realm, c_type)
VALUES ($c_event, $c_id, $c_realm, $c_type)");$res=$db->affectedRows==1;if($res){$this->resync_on_exit=true;}return$res;}/**
* Binds multiple cache fields to events, all represented as an associative array
* Binding keys:
* event - name of the event the field is binded to
* id - cache object id
* realm - cache realm name
* type - cache storage type, one of COT_CACHE_TYPE_* constants
* @param array $bindings An indexed array of bindings.
* Each binding is an associative array with keys: event, realm, id, type.
* @return int Number of bindings added
*/publicfunction bind_array($bindings){global$db,$db_cache_bindings;$q="INSERT INTO `$db_cache_bindings` (c_event, c_id, c_realm, c_type) VALUES ";$i=0;foreach($bindingsas$entry){$c_event=$db->prep($entry['event']);$c_id=$db->prep($entry['id']);$c_realm=$db->prep($entry['realm']);$c_type=(int)$entry['type'];$comma=$i==0 ? '':',';$q.=$comma."('$c_event', '$c_id', '$c_realm', $c_type)";}$db->query($q);$res=$db->affectedRows;if($res>0){$this->resync_on_exit=true;}return$res;}/**
* Clears all cache entries
* @param int $type Cache storage type:
* COT_CACHE_TYPE_ALL, COT_CACHE_TYPE_DB, COT_CACHE_TYPE_DISK, COT_CACHE_TYPE_MEMORY.
* @return bool
*/publicfunction clear($type= COT_CACHE_TYPE_ALL){$res=true;switch($type){case COT_CACHE_TYPE_DB:$res=$this->db->clear();break;case COT_CACHE_TYPE_DISK:$res=$this->disk->clear();break;case COT_CACHE_TYPE_MEMORY:if($this->mem){$res=$this->mem->clear();}break;case COT_CACHE_TYPE_PAGE:$res=$this->disk->clear();break;default:if($this->mem){$res&=$this->mem->clear();}$res&=$this->db->clear();$res&=$this->disk->clear();}return$res;}/**
* Clears cache in specific realm
* @param string $realm Realm name
* @param int $type Cache storage type:
* COT_CACHE_TYPE_ALL, COT_CACHE_TYPE_DB, COT_CACHE_TYPE_DISK, COT_CACHE_TYPE_MEMORY.
*/publicfunction clear_realm($realm= COT_DEFAULT_REALM,$type= COT_CACHE_TYPE_ALL){switch($type){case COT_CACHE_TYPE_DB:$this->db->clear($realm);break;case COT_CACHE_TYPE_DISK:$this->disk->clear($realm);break;case COT_CACHE_TYPE_MEMORY:if($this->mem){$this->mem->clear($realm);}break;case COT_CACHE_TYPE_PAGE:$this->page->clear($realm);break;default:if($this->mem){$this->mem->clear($realm);}$this->db->clear($realm);$this->disk->clear($realm);$this->page->clear($realm);}}/**
* Returns information about memory driver usage
* @return array Usage information
*/publicfunction get_info(){if($this->mem){return$this->mem->get_info();}else{returnarray();}}/**
* Invalidates cache cells which were binded to the event.
* @param string $event Event name
* @return int Number of cells cleaned
*/publicfunction trigger($event){$cnt=0;if(isset($this->bindings[$event])&&count($this->bindings[$event])>0){foreach($this->bindings[$event]as$cell){switch($cell['type']){case COT_CACHE_TYPE_DISK:$this->disk->remove($cell['id'],$cell['realm']);break;case COT_CACHE_TYPE_DB:$this->db->remove($cell['id'],$cell['realm']);break;case COT_CACHE_TYPE_MEMORY:if($this->mem){$this->mem->remove($cell['id'],$cell['realm']);}break;case COT_CACHE_TYPE_PAGE:$this->page->clear($cell['realm'].'/'.$cell['id']);break;default:if($this->mem){$this->mem->remove($cell['id'],$cell['realm']);}$this->disk->remove($cell['id'],$cell['realm']);$this->db->remove($cell['id'],$cell['realm']);$this->page->clear($cell['realm'].'/'.$cell['id']);}$cnt++;}}return$cnt;}/**
* Removes event/cache bindings
* @param string $realm Realm name (required)
* @param string $id Object identifier. Optional, if not specified, all bindings from the realm are removed.
* @return int Number of bindings removed
*/publicfunction unbind($realm,$id=''){global$db,$db_cache_bindings;$c_realm=$db->quote($realm);$q="DELETE FROM `$db_cache_bindings` WHERE c_realm = $c_realm";if(!empty($id)){$c_id=$db->quote($id);$q.=" AND c_id = $c_id";}$db->query($q);$res=$db->affectedRows;if($res>0){$this->resync_on_exit=true;}return$res;}}