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