1<?php 2 3namespace dokuwiki\Cache; 4 5/** 6 * Generic handling of caching 7 */ 8class Cache 9{ 10 public $key = ''; // primary identifier for this item 11 public $ext = ''; // file ext for cache data, secondary identifier for this item 12 public $cache = ''; // cache file name 13 public $depends = array(); // array containing cache dependency information, 14 // used by makeDefaultCacheDecision to determine cache validity 15 16 protected $event = ''; // event to be triggered during useCache 17 protected $time; 18 protected $nocache = false; // if set to true, cache will not be used or stored 19 20 /** 21 * @param string $key primary identifier 22 * @param string $ext file extension 23 */ 24 public function __construct($key, $ext) 25 { 26 $this->key = $key; 27 $this->ext = $ext; 28 $this->cache = getCacheName($key, $ext); 29 } 30 31 /** 32 * @deprecated since 2019-02-02 use the respective getters instead! 33 */ 34 public function __get($key) 35 { 36 if ($key === '_event') { 37 trigger_error( 38 '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()', 39 E_USER_DEPRECATED 40 ); 41 return $this->getEvent(); 42 } 43 44 if ($key === '_time') { 45 trigger_error( 46 '\dokuwiki\Cache\Cache::_time is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getTime()', 47 E_USER_DEPRECATED 48 ); 49 return $this->getTime(); 50 } 51 return $this->$$key; 52 } 53 54 public function __set($name, $value) 55 { 56 if ($name === '_event') { 57 trigger_error( 58 '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()', 59 E_USER_DEPRECATED 60 ); 61 $this->setEvent($value); 62 } 63 $this->$$name = $value; 64 } 65 66 public function getTime() 67 { 68 return $this->time; 69 } 70 71 public function getEvent() 72 { 73 return $this->event; 74 } 75 76 public function setEvent($event) 77 { 78 $this->event = $event; 79 } 80 81 /** 82 * public method to determine whether the cache can be used 83 * 84 * to assist in centralisation of event triggering and calculation of cache statistics, 85 * don't override this function override makeDefaultCacheDecision() 86 * 87 * @param array $depends array of cache dependencies, support dependecies: 88 * 'age' => max age of the cache in seconds 89 * 'files' => cache must be younger than mtime of each file 90 * (nb. dependency passes if file doesn't exist) 91 * 92 * @return bool true if cache can be used, false otherwise 93 */ 94 public function useCache($depends = array()) 95 { 96 $this->depends = $depends; 97 $this->addDependencies(); 98 99 if ($this->event) { 100 return $this->stats(trigger_event($this->event, $this, array($this, 'makeDefaultCacheDecision'))); 101 } else { 102 return $this->stats($this->makeDefaultCacheDecision()); 103 } 104 } 105 106 /** 107 * internal method containing cache use decision logic 108 * 109 * this function processes the following keys in the depends array 110 * purge - force a purge on any non empty value 111 * age - expire cache if older than age (seconds) 112 * files - expire cache if any file in this array was updated more recently than the cache 113 * 114 * Note that this function needs to be public as it is used as callback for the event handler 115 * 116 * can be overridden 117 * 118 * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead! 119 * 120 * @return bool see useCache() 121 */ 122 public function makeDefaultCacheDecision() 123 { 124 125 if ($this->nocache) { 126 return false; 127 } // caching turned off 128 if (!empty($this->depends['purge'])) { 129 return false; 130 } // purge requested? 131 if (!($this->time = @filemtime($this->cache))) { 132 return false; 133 } // cache exists? 134 135 // cache too old? 136 if (!empty($this->depends['age']) && ((time() - $this->time) > $this->depends['age'])) { 137 return false; 138 } 139 140 if (!empty($this->depends['files'])) { 141 foreach ($this->depends['files'] as $file) { 142 if ($this->time <= @filemtime($file)) { 143 return false; 144 } // cache older than files it depends on? 145 } 146 } 147 148 return true; 149 } 150 151 /** 152 * add dependencies to the depends array 153 * 154 * this method should only add dependencies, 155 * it should not remove any existing dependencies and 156 * it should only overwrite a dependency when the new value is more stringent than the old 157 */ 158 protected function addDependencies() 159 { 160 global $INPUT; 161 if ($INPUT->has('purge')) { 162 $this->depends['purge'] = true; 163 } // purge requested 164 } 165 166 /** 167 * retrieve the cached data 168 * 169 * @param bool $clean true to clean line endings, false to leave line endings alone 170 * @return string cache contents 171 */ 172 public function retrieveCache($clean = true) 173 { 174 return io_readFile($this->cache, $clean); 175 } 176 177 /** 178 * cache $data 179 * 180 * @param string $data the data to be cached 181 * @return bool true on success, false otherwise 182 */ 183 public function storeCache($data) 184 { 185 if ($this->nocache) { 186 return false; 187 } 188 189 return io_savefile($this->cache, $data); 190 } 191 192 /** 193 * remove any cached data associated with this cache instance 194 */ 195 public function removeCache() 196 { 197 @unlink($this->cache); 198 } 199 200 /** 201 * Record cache hits statistics. 202 * (Only when debugging allowed, to reduce overhead.) 203 * 204 * @param bool $success result of this cache use attempt 205 * @return bool pass-thru $success value 206 */ 207 protected function stats($success) 208 { 209 global $conf; 210 static $stats = null; 211 static $file; 212 213 if (!$conf['allowdebug']) { 214 return $success; 215 } 216 217 if (is_null($stats)) { 218 $file = $conf['cachedir'] . '/cache_stats.txt'; 219 $lines = explode("\n", io_readFile($file)); 220 221 foreach ($lines as $line) { 222 $i = strpos($line, ','); 223 $stats[substr($line, 0, $i)] = $line; 224 } 225 } 226 227 if (isset($stats[$this->ext])) { 228 list($ext, $count, $hits) = explode(',', $stats[$this->ext]); 229 } else { 230 $ext = $this->ext; 231 $count = 0; 232 $hits = 0; 233 } 234 235 $count++; 236 if ($success) { 237 $hits++; 238 } 239 $stats[$this->ext] = "$ext,$count,$hits"; 240 241 io_saveFile($file, join("\n", $stats)); 242 243 return $success; 244 } 245 246 /** 247 * @return bool 248 */ 249 public function isNoCache() 250 { 251 return $this->nocache; 252 } 253} 254