10db5771eSMichael Große<?php 20db5771eSMichael Große 30db5771eSMichael Großenamespace dokuwiki\Cache; 40db5771eSMichael Große 50db5771eSMichael Große/** 60db5771eSMichael Große * Generic handling of caching 70db5771eSMichael Große */ 80db5771eSMichael Großeclass Cache 90db5771eSMichael Große{ 100db5771eSMichael Große public $key = ''; // primary identifier for this item 110db5771eSMichael Große public $ext = ''; // file ext for cache data, secondary identifier for this item 120db5771eSMichael Große public $cache = ''; // cache file name 130db5771eSMichael Große public $depends = array(); // array containing cache dependency information, 1472c2bae8SMichael Große // used by makeDefaultCacheDecision to determine cache validity 150db5771eSMichael Große 16*d2f1d7a1SMichael Große protected $event = ''; // event to be triggered during useCache 17*d2f1d7a1SMichael Große protected $time; 18*d2f1d7a1SMichael Große protected $nocache = false; // if set to true, cache will not be used or stored 190db5771eSMichael Große 200db5771eSMichael Große /** 210db5771eSMichael Große * @param string $key primary identifier 220db5771eSMichael Große * @param string $ext file extension 230db5771eSMichael Große */ 240db5771eSMichael Große public function __construct($key, $ext) 250db5771eSMichael Große { 260db5771eSMichael Große $this->key = $key; 270db5771eSMichael Große $this->ext = $ext; 280db5771eSMichael Große $this->cache = getCacheName($key, $ext); 290db5771eSMichael Große } 300db5771eSMichael Große 310db5771eSMichael Große /** 32*d2f1d7a1SMichael Große * @deprecated since 2019-02-02 use the respective getters instead! 33*d2f1d7a1SMichael Große */ 34*d2f1d7a1SMichael Große public function __get($key) 35*d2f1d7a1SMichael Große { 36*d2f1d7a1SMichael Große if ($key === '_event') { 37*d2f1d7a1SMichael Große trigger_error( 38*d2f1d7a1SMichael Große '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()', 39*d2f1d7a1SMichael Große E_USER_DEPRECATED 40*d2f1d7a1SMichael Große ); 41*d2f1d7a1SMichael Große return $this->getEvent(); 42*d2f1d7a1SMichael Große } 43*d2f1d7a1SMichael Große 44*d2f1d7a1SMichael Große if ($key === '_time') { 45*d2f1d7a1SMichael Große trigger_error( 46*d2f1d7a1SMichael Große '\dokuwiki\Cache\Cache::_time is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getTime()', 47*d2f1d7a1SMichael Große E_USER_DEPRECATED 48*d2f1d7a1SMichael Große ); 49*d2f1d7a1SMichael Große return $this->getTime(); 50*d2f1d7a1SMichael Große } 51*d2f1d7a1SMichael Große return $this->$$key; 52*d2f1d7a1SMichael Große } 53*d2f1d7a1SMichael Große 54*d2f1d7a1SMichael Große public function __set($name, $value) 55*d2f1d7a1SMichael Große { 56*d2f1d7a1SMichael Große if ($name === '_event') { 57*d2f1d7a1SMichael Große trigger_error( 58*d2f1d7a1SMichael Große '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()', 59*d2f1d7a1SMichael Große E_USER_DEPRECATED 60*d2f1d7a1SMichael Große ); 61*d2f1d7a1SMichael Große $this->setEvent($value); 62*d2f1d7a1SMichael Große } 63*d2f1d7a1SMichael Große $this->$$name = $value; 64*d2f1d7a1SMichael Große } 65*d2f1d7a1SMichael Große 66*d2f1d7a1SMichael Große public function getTime() 67*d2f1d7a1SMichael Große { 68*d2f1d7a1SMichael Große return $this->time; 69*d2f1d7a1SMichael Große } 70*d2f1d7a1SMichael Große 71*d2f1d7a1SMichael Große public function getEvent() 72*d2f1d7a1SMichael Große { 73*d2f1d7a1SMichael Große return $this->event; 74*d2f1d7a1SMichael Große } 75*d2f1d7a1SMichael Große 76*d2f1d7a1SMichael Große public function setEvent($event) 77*d2f1d7a1SMichael Große { 78*d2f1d7a1SMichael Große $this->event = $event; 79*d2f1d7a1SMichael Große } 80*d2f1d7a1SMichael Große 81*d2f1d7a1SMichael Große /** 820db5771eSMichael Große * public method to determine whether the cache can be used 830db5771eSMichael Große * 840db5771eSMichael Große * to assist in centralisation of event triggering and calculation of cache statistics, 8572c2bae8SMichael Große * don't override this function override makeDefaultCacheDecision() 860db5771eSMichael Große * 870db5771eSMichael Große * @param array $depends array of cache dependencies, support dependecies: 880db5771eSMichael Große * 'age' => max age of the cache in seconds 890db5771eSMichael Große * 'files' => cache must be younger than mtime of each file 900db5771eSMichael Große * (nb. dependency passes if file doesn't exist) 910db5771eSMichael Große * 920db5771eSMichael Große * @return bool true if cache can be used, false otherwise 930db5771eSMichael Große */ 940db5771eSMichael Große public function useCache($depends = array()) 950db5771eSMichael Große { 960db5771eSMichael Große $this->depends = $depends; 9742c00b45SMichael Große $this->addDependencies(); 980db5771eSMichael Große 99*d2f1d7a1SMichael Große if ($this->event) { 100*d2f1d7a1SMichael Große return $this->stats(trigger_event($this->event, $this, array($this, 'makeDefaultCacheDecision'))); 1010db5771eSMichael Große } else { 10272c2bae8SMichael Große return $this->stats($this->makeDefaultCacheDecision()); 1030db5771eSMichael Große } 1040db5771eSMichael Große } 1050db5771eSMichael Große 1060db5771eSMichael Große /** 10743ff1056SMichael Große * internal method containing cache use decision logic 1080db5771eSMichael Große * 1090db5771eSMichael Große * this function processes the following keys in the depends array 1100db5771eSMichael Große * purge - force a purge on any non empty value 1110db5771eSMichael Große * age - expire cache if older than age (seconds) 1120db5771eSMichael Große * files - expire cache if any file in this array was updated more recently than the cache 1130db5771eSMichael Große * 1140db5771eSMichael Große * Note that this function needs to be public as it is used as callback for the event handler 1150db5771eSMichael Große * 1160db5771eSMichael Große * can be overridden 1170db5771eSMichael Große * 11842c00b45SMichael Große * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead! 11942c00b45SMichael Große * 1200db5771eSMichael Große * @return bool see useCache() 1210db5771eSMichael Große */ 12272c2bae8SMichael Große public function makeDefaultCacheDecision() 1230db5771eSMichael Große { 1240db5771eSMichael Große 125*d2f1d7a1SMichael Große if ($this->nocache) { 1260db5771eSMichael Große return false; 1270db5771eSMichael Große } // caching turned off 1280db5771eSMichael Große if (!empty($this->depends['purge'])) { 1290db5771eSMichael Große return false; 1300db5771eSMichael Große } // purge requested? 131*d2f1d7a1SMichael Große if (!($this->time = @filemtime($this->cache))) { 1320db5771eSMichael Große return false; 1330db5771eSMichael Große } // cache exists? 1340db5771eSMichael Große 1350db5771eSMichael Große // cache too old? 136*d2f1d7a1SMichael Große if (!empty($this->depends['age']) && ((time() - $this->time) > $this->depends['age'])) { 1370db5771eSMichael Große return false; 1380db5771eSMichael Große } 1390db5771eSMichael Große 1400db5771eSMichael Große if (!empty($this->depends['files'])) { 1410db5771eSMichael Große foreach ($this->depends['files'] as $file) { 142*d2f1d7a1SMichael Große if ($this->time <= @filemtime($file)) { 1430db5771eSMichael Große return false; 1440db5771eSMichael Große } // cache older than files it depends on? 1450db5771eSMichael Große } 1460db5771eSMichael Große } 1470db5771eSMichael Große 1480db5771eSMichael Große return true; 1490db5771eSMichael Große } 1500db5771eSMichael Große 1510db5771eSMichael Große /** 1520db5771eSMichael Große * add dependencies to the depends array 1530db5771eSMichael Große * 1540db5771eSMichael Große * this method should only add dependencies, 1550db5771eSMichael Große * it should not remove any existing dependencies and 1560db5771eSMichael Große * it should only overwrite a dependency when the new value is more stringent than the old 1570db5771eSMichael Große */ 15842c00b45SMichael Große protected function addDependencies() 1590db5771eSMichael Große { 1600db5771eSMichael Große global $INPUT; 1610db5771eSMichael Große if ($INPUT->has('purge')) { 1620db5771eSMichael Große $this->depends['purge'] = true; 1630db5771eSMichael Große } // purge requested 1640db5771eSMichael Große } 1650db5771eSMichael Große 1660db5771eSMichael Große /** 1670db5771eSMichael Große * retrieve the cached data 1680db5771eSMichael Große * 1690db5771eSMichael Große * @param bool $clean true to clean line endings, false to leave line endings alone 1700db5771eSMichael Große * @return string cache contents 1710db5771eSMichael Große */ 1720db5771eSMichael Große public function retrieveCache($clean = true) 1730db5771eSMichael Große { 1740db5771eSMichael Große return io_readFile($this->cache, $clean); 1750db5771eSMichael Große } 1760db5771eSMichael Große 1770db5771eSMichael Große /** 1780db5771eSMichael Große * cache $data 1790db5771eSMichael Große * 1800db5771eSMichael Große * @param string $data the data to be cached 1810db5771eSMichael Große * @return bool true on success, false otherwise 1820db5771eSMichael Große */ 1830db5771eSMichael Große public function storeCache($data) 1840db5771eSMichael Große { 185*d2f1d7a1SMichael Große if ($this->nocache) { 1860db5771eSMichael Große return false; 1870db5771eSMichael Große } 1880db5771eSMichael Große 1890db5771eSMichael Große return io_savefile($this->cache, $data); 1900db5771eSMichael Große } 1910db5771eSMichael Große 1920db5771eSMichael Große /** 1930db5771eSMichael Große * remove any cached data associated with this cache instance 1940db5771eSMichael Große */ 1950db5771eSMichael Große public function removeCache() 1960db5771eSMichael Große { 1970db5771eSMichael Große @unlink($this->cache); 1980db5771eSMichael Große } 1990db5771eSMichael Große 2000db5771eSMichael Große /** 2010db5771eSMichael Große * Record cache hits statistics. 2020db5771eSMichael Große * (Only when debugging allowed, to reduce overhead.) 2030db5771eSMichael Große * 2040db5771eSMichael Große * @param bool $success result of this cache use attempt 2050db5771eSMichael Große * @return bool pass-thru $success value 2060db5771eSMichael Große */ 20742c00b45SMichael Große protected function stats($success) 2080db5771eSMichael Große { 2090db5771eSMichael Große global $conf; 2100db5771eSMichael Große static $stats = null; 2110db5771eSMichael Große static $file; 2120db5771eSMichael Große 2130db5771eSMichael Große if (!$conf['allowdebug']) { 2140db5771eSMichael Große return $success; 2150db5771eSMichael Große } 2160db5771eSMichael Große 2170db5771eSMichael Große if (is_null($stats)) { 2180db5771eSMichael Große $file = $conf['cachedir'] . '/cache_stats.txt'; 2190db5771eSMichael Große $lines = explode("\n", io_readFile($file)); 2200db5771eSMichael Große 2210db5771eSMichael Große foreach ($lines as $line) { 2220db5771eSMichael Große $i = strpos($line, ','); 2230db5771eSMichael Große $stats[substr($line, 0, $i)] = $line; 2240db5771eSMichael Große } 2250db5771eSMichael Große } 2260db5771eSMichael Große 2270db5771eSMichael Große if (isset($stats[$this->ext])) { 2280db5771eSMichael Große list($ext, $count, $hits) = explode(',', $stats[$this->ext]); 2290db5771eSMichael Große } else { 2300db5771eSMichael Große $ext = $this->ext; 2310db5771eSMichael Große $count = 0; 2320db5771eSMichael Große $hits = 0; 2330db5771eSMichael Große } 2340db5771eSMichael Große 2350db5771eSMichael Große $count++; 2360db5771eSMichael Große if ($success) { 2370db5771eSMichael Große $hits++; 2380db5771eSMichael Große } 2390db5771eSMichael Große $stats[$this->ext] = "$ext,$count,$hits"; 2400db5771eSMichael Große 2410db5771eSMichael Große io_saveFile($file, join("\n", $stats)); 2420db5771eSMichael Große 2430db5771eSMichael Große return $success; 2440db5771eSMichael Große } 245*d2f1d7a1SMichael Große 246*d2f1d7a1SMichael Große /** 247*d2f1d7a1SMichael Große * @return bool 248*d2f1d7a1SMichael Große */ 249*d2f1d7a1SMichael Große public function isNoCache() 250*d2f1d7a1SMichael Große { 251*d2f1d7a1SMichael Große return $this->nocache; 252*d2f1d7a1SMichael Große } 2530db5771eSMichael Große} 254