1*4b5f4f4eSchris<?php 2*4b5f4f4eSchris/** 3*4b5f4f4eSchris * Generic class to handle caching 4*4b5f4f4eSchris * 5*4b5f4f4eSchris * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6*4b5f4f4eSchris * @author Chris Smith <chris@jalakai.co.uk> 7*4b5f4f4eSchris */ 8*4b5f4f4eSchris 9*4b5f4f4eSchrisif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/'); 10*4b5f4f4eSchris 11*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/io.php'); 12*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/pageutils.php'); 13*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/parserutils.php'); 14*4b5f4f4eSchris 15*4b5f4f4eSchrisclass cache { 16*4b5f4f4eSchris var $key = ''; // primary identifier for this item 17*4b5f4f4eSchris var $ext = ''; // file ext for cache data, secondary identifier for this item 18*4b5f4f4eSchris var $cache = ''; // cache file name 19*4b5f4f4eSchris 20*4b5f4f4eSchris var $_event = ''; // event to be triggered during useCache 21*4b5f4f4eSchris 22*4b5f4f4eSchris function cache($key,$ext) { 23*4b5f4f4eSchris $this->key = $key; 24*4b5f4f4eSchris $this->ext = $ext; 25*4b5f4f4eSchris $this->cache = getCacheName($key,$ext); 26*4b5f4f4eSchris } 27*4b5f4f4eSchris 28*4b5f4f4eSchris /** 29*4b5f4f4eSchris * public method to determine whether the cache can be used 30*4b5f4f4eSchris * 31*4b5f4f4eSchris * to assist in cetralisation of event triggering and calculation of cache statistics, 32*4b5f4f4eSchris * don't override this function override _useCache() 33*4b5f4f4eSchris * 34*4b5f4f4eSchris * @param array $depends array of cache dependencies, support dependecies: 35*4b5f4f4eSchris * 'age' => max age of the cache in seconds 36*4b5f4f4eSchris * 'files' => cache must be younger than mtime of each file 37*4b5f4f4eSchris * 38*4b5f4f4eSchris * @return bool true if cache can be used, false otherwise 39*4b5f4f4eSchris */ 40*4b5f4f4eSchris function useCache($depends=array()) { 41*4b5f4f4eSchris $this->depends = $depends; 42*4b5f4f4eSchris 43*4b5f4f4eSchris if ($this->_event) { 44*4b5f4f4eSchris return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache'))); 45*4b5f4f4eSchris } else { 46*4b5f4f4eSchris return $this->_stats($this->_useCache()); 47*4b5f4f4eSchris } 48*4b5f4f4eSchris } 49*4b5f4f4eSchris 50*4b5f4f4eSchris /* 51*4b5f4f4eSchris * private method containing cache use decision logic 52*4b5f4f4eSchris * 53*4b5f4f4eSchris * this function can be overridden 54*4b5f4f4eSchris * 55*4b5f4f4eSchris * @return bool see useCache() 56*4b5f4f4eSchris */ 57*4b5f4f4eSchris function _useCache() { 58*4b5f4f4eSchris 59*4b5f4f4eSchris if (isset($_REQUEST['purge'])) return false; // purge requested? 60*4b5f4f4eSchris if (!($this->_time = @filemtime($this->cache))) return false; // cache exists? 61*4b5f4f4eSchris 62*4b5f4f4eSchris // cache too old? 63*4b5f4f4eSchris if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false; 64*4b5f4f4eSchris 65*4b5f4f4eSchris if (!empty($this->depends['files'])) { 66*4b5f4f4eSchris foreach ($this->depends['files'] as $file) { 67*4b5f4f4eSchris if ($this->_time < @filemtime($file)) return false; // cache older than files it depends on? 68*4b5f4f4eSchris } 69*4b5f4f4eSchris } 70*4b5f4f4eSchris 71*4b5f4f4eSchris return true; 72*4b5f4f4eSchris } 73*4b5f4f4eSchris 74*4b5f4f4eSchris /** 75*4b5f4f4eSchris * retrieve the cached data 76*4b5f4f4eSchris * 77*4b5f4f4eSchris * @param bool $clean true to clean line endings, false to leave line endings alone 78*4b5f4f4eSchris * @return string cache contents 79*4b5f4f4eSchris */ 80*4b5f4f4eSchris function retrieveCache($clean=true) { 81*4b5f4f4eSchris return io_readFile($this->cache, $clean); 82*4b5f4f4eSchris } 83*4b5f4f4eSchris 84*4b5f4f4eSchris /** 85*4b5f4f4eSchris * cache $data 86*4b5f4f4eSchris * 87*4b5f4f4eSchris * @param string $data the data to be cached 88*4b5f4f4eSchris * @return none 89*4b5f4f4eSchris */ 90*4b5f4f4eSchris function storeCache($data) { 91*4b5f4f4eSchris io_savefile($this->cache, $data); 92*4b5f4f4eSchris } 93*4b5f4f4eSchris 94*4b5f4f4eSchris /** 95*4b5f4f4eSchris * remove any cached data associated with this cache instance 96*4b5f4f4eSchris */ 97*4b5f4f4eSchris function removeCache() { 98*4b5f4f4eSchris @unlink($this->cache); 99*4b5f4f4eSchris } 100*4b5f4f4eSchris 101*4b5f4f4eSchris /** 102*4b5f4f4eSchris * record cache hits statistics 103*4b5f4f4eSchris * 104*4b5f4f4eSchris * @param bool $success result of this cache use attempt 105*4b5f4f4eSchris * @return bool pass-thru $success value 106*4b5f4f4eSchris */ 107*4b5f4f4eSchris function _stats($success) { 108*4b5f4f4eSchris global $conf; 109*4b5f4f4eSchris static $stats = NULL; 110*4b5f4f4eSchris static $file; 111*4b5f4f4eSchris 112*4b5f4f4eSchris if (is_null($stats)) { 113*4b5f4f4eSchris $file = $conf['cachedir'].'/cache_stats.txt'; 114*4b5f4f4eSchris $lines = explode("\n",io_readFile($file)); 115*4b5f4f4eSchris 116*4b5f4f4eSchris foreach ($lines as $line) { 117*4b5f4f4eSchris $i = strpos($line,','); 118*4b5f4f4eSchris $stats[substr($line,0,$i)] = $line; 119*4b5f4f4eSchris } 120*4b5f4f4eSchris } 121*4b5f4f4eSchris 122*4b5f4f4eSchris if (isset($stats[$this->ext])) { 123*4b5f4f4eSchris list($ext,$count,$successes) = explode(',',$stats[$this->ext]); 124*4b5f4f4eSchris } else { 125*4b5f4f4eSchris $ext = $this->ext; 126*4b5f4f4eSchris $count = 0; 127*4b5f4f4eSchris $successes = 0; 128*4b5f4f4eSchris } 129*4b5f4f4eSchris 130*4b5f4f4eSchris $count++; 131*4b5f4f4eSchris $successes += $success ? 1 : 0; 132*4b5f4f4eSchris $stats[$this->ext] = "$ext,$count,$successes"; 133*4b5f4f4eSchris 134*4b5f4f4eSchris io_saveFile($file,join("\n",$stats)); 135*4b5f4f4eSchris 136*4b5f4f4eSchris return $success; 137*4b5f4f4eSchris } 138*4b5f4f4eSchris} 139*4b5f4f4eSchris 140*4b5f4f4eSchrisclass cache_parser extends cache { 141*4b5f4f4eSchris 142*4b5f4f4eSchris var $file = ''; // source file for cache 143*4b5f4f4eSchris var $mode = ''; // input mode (represents the processing the input file will undergo) 144*4b5f4f4eSchris 145*4b5f4f4eSchris var $_event = 'PARSER_CACHE_USE'; 146*4b5f4f4eSchris 147*4b5f4f4eSchris function cache_parser($id, $file, $mode) { 148*4b5f4f4eSchris if ($id) $this->page = $id; 149*4b5f4f4eSchris $this->file = $file; 150*4b5f4f4eSchris $this->mode = $mode; 151*4b5f4f4eSchris 152*4b5f4f4eSchris parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode); 153*4b5f4f4eSchris } 154*4b5f4f4eSchris 155*4b5f4f4eSchris function _useCache() { 156*4b5f4f4eSchris global $conf; 157*4b5f4f4eSchris 158*4b5f4f4eSchris if (!@file_exists($this->file)) return false; // source exists? 159*4b5f4f4eSchris 160*4b5f4f4eSchris if (!isset($this->depends['age'])) $this->depends['age'] = $conf['cachetime']; 161*4b5f4f4eSchris 162*4b5f4f4eSchris // parser cache file dependencies ... 163*4b5f4f4eSchris $files = array($this->file, // ... source 164*4b5f4f4eSchris DOKU_CONF.'dokuwiki.php', // ... config 165*4b5f4f4eSchris DOKU_CONF.'local.php', // ... local config 166*4b5f4f4eSchris DOKU_INC.'inc/parser/parser.php', // ... parser 167*4b5f4f4eSchris DOKU_INC.'inc/parser/handler.php', // ... handler 168*4b5f4f4eSchris ); 169*4b5f4f4eSchris 170*4b5f4f4eSchris $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; 171*4b5f4f4eSchris return parent::_useCache($depends); 172*4b5f4f4eSchris } 173*4b5f4f4eSchris 174*4b5f4f4eSchris} 175*4b5f4f4eSchris 176*4b5f4f4eSchrisclass cache_renderer extends cache_parser { 177*4b5f4f4eSchris 178*4b5f4f4eSchris function _useCache() { 179*4b5f4f4eSchris global $conf; 180*4b5f4f4eSchris 181*4b5f4f4eSchris // renderer cache file dependencies ... 182*4b5f4f4eSchris $files = array( 183*4b5f4f4eSchris# $conf['cachedir'].'/purgefile', // ... purgefile - time of last add 184*4b5f4f4eSchris DOKU_INC.'inc/parser/'.$this->mode.'.php', // ... the renderer 185*4b5f4f4eSchris ); 186*4b5f4f4eSchris 187*4b5f4f4eSchris if (isset($this->page)) { $files[] = metaFN($this->page,'.meta'); } 188*4b5f4f4eSchris 189*4b5f4f4eSchris $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; 190*4b5f4f4eSchris if (!parent::_useCache($depends)) return false; 191*4b5f4f4eSchris 192*4b5f4f4eSchris // for wiki pages, check for internal link status changes 193*4b5f4f4eSchris if (isset($this->page)) { 194*4b5f4f4eSchris $links = p_get_metadata($this->page,"relation references"); 195*4b5f4f4eSchris 196*4b5f4f4eSchris if (!empty($links)) { 197*4b5f4f4eSchris foreach ($links as $id => $exists) { 198*4b5f4f4eSchris if ($exists != @file_exists(wikiFN($id,'',false))) return false; 199*4b5f4f4eSchris } 200*4b5f4f4eSchris } 201*4b5f4f4eSchris } 202*4b5f4f4eSchris 203*4b5f4f4eSchris return true; 204*4b5f4f4eSchris } 205*4b5f4f4eSchris} 206*4b5f4f4eSchris 207*4b5f4f4eSchrisclass cache_instructions extends cache_parser { 208*4b5f4f4eSchris 209*4b5f4f4eSchris function cache_instructions($id, $file) { 210*4b5f4f4eSchris parent::cache_parser($id, $file, 'i'); 211*4b5f4f4eSchris } 212*4b5f4f4eSchris 213*4b5f4f4eSchris function retrieveCache() { 214*4b5f4f4eSchris $contents = io_readFile($this->cache, false); 215*4b5f4f4eSchris return !empty($contents) ? unserialize($contents) : array(); 216*4b5f4f4eSchris } 217*4b5f4f4eSchris 218*4b5f4f4eSchris function storeCache($instructions) { 219*4b5f4f4eSchris io_savefile($this->cache,serialize($instructions)); 220*4b5f4f4eSchris } 221*4b5f4f4eSchris} 222