Mereged updates from DokuWiki 38
[sudaraka-org:dokuwiki-mods.git] / inc / cache.php
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
9 if(!defined('DOKU_INC')) die('meh.');
10
11 class cache {
12     var $key = '';          // primary identifier for this item
13     var $ext = '';          // file ext for cache data, secondary identifier for this item
14     var $cache = '';        // cache file name
15     var $depends = array(); // array containing cache dependency information,
16     //   used by _useCache to determine cache validity
17
18     var $_event = '';       // event to be triggered during useCache
19
20     function cache($key,$ext) {
21         $this->key = $key;
22         $this->ext = $ext;
23         $this->cache = getCacheName($key,$ext);
24     }
25
26     /**
27      * public method to determine whether the cache can be used
28      *
29      * to assist in cetralisation of event triggering and calculation of cache statistics,
30      * don't override this function override _useCache()
31      *
32      * @param  array   $depends   array of cache dependencies, support dependecies:
33      *                            'age'   => max age of the cache in seconds
34      *                            'files' => cache must be younger than mtime of each file
35      *                                       (nb. dependency passes if file doesn't exist)
36      *
37      * @return bool    true if cache can be used, false otherwise
38      */
39     function useCache($depends=array()) {
40         $this->depends = $depends;
41         $this->_addDependencies();
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 processes the following keys in the depends array
54      *   purge - force a purge on any non empty value
55      *   age   - expire cache if older than age (seconds)
56      *   files - expire cache if any file in this array was updated more recently than the cache
57      *
58      * can be overridden
59      *
60      * @return bool               see useCache()
61      */
62     function _useCache() {
63
64         if (!empty($this->depends['purge'])) return false;              // purge requested?
65         if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
66
67         // cache too old?
68         if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
69
70         if (!empty($this->depends['files'])) {
71             foreach ($this->depends['files'] as $file) {
72                 if ($this->_time <= @filemtime($file)) return false;         // cache older than files it depends on?
73             }
74         }
75
76         return true;
77     }
78
79     /**
80      * add dependencies to the depends array
81      *
82      * this method should only add dependencies,
83      * it should not remove any existing dependencies and
84      * it should only overwrite a dependency when the new value is more stringent than the old
85      */
86     function _addDependencies() {
87         global $INPUT;
88         if ($INPUT->has('purge')) $this->depends['purge'] = true;   // purge requested
89     }
90
91     /**
92      * retrieve the cached data
93      *
94      * @param   bool   $clean   true to clean line endings, false to leave line endings alone
95      * @return  string          cache contents
96      */
97     function retrieveCache($clean=true) {
98         return io_readFile($this->cache, $clean);
99     }
100
101     /**
102      * cache $data
103      *
104      * @param   string $data   the data to be cached
105      * @return  bool           true on success, false otherwise
106      */
107     function storeCache($data) {
108         return io_savefile($this->cache, $data);
109     }
110
111     /**
112      * remove any cached data associated with this cache instance
113      */
114     function removeCache() {
115         @unlink($this->cache);
116     }
117
118     /**
119      * Record cache hits statistics.
120      * (Only when debugging allowed, to reduce overhead.)
121      *
122      * @param    bool   $success   result of this cache use attempt
123      * @return   bool              pass-thru $success value
124      */
125     function _stats($success) {
126         global $conf;
127         static $stats = null;
128         static $file;
129
130         if (!$conf['allowdebug']) { return $success; }
131
132         if (is_null($stats)) {
133             $file = $conf['cachedir'].'/cache_stats.txt';
134             $lines = explode("\n",io_readFile($file));
135
136             foreach ($lines as $line) {
137                 $i = strpos($line,',');
138                 $stats[substr($line,0,$i)] = $line;
139             }
140         }
141
142         if (isset($stats[$this->ext])) {
143             list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
144         } else {
145             $ext = $this->ext;
146             $count = 0;
147             $hits = 0;
148         }
149
150         $count++;
151         if ($success) $hits++;
152         $stats[$this->ext] = "$ext,$count,$hits";
153
154         io_saveFile($file,join("\n",$stats));
155
156         return $success;
157     }
158 }
159
160 class cache_parser extends cache {
161
162     var $file = '';       // source file for cache
163     var $mode = '';       // input mode (represents the processing the input file will undergo)
164
165     var $_event = 'PARSER_CACHE_USE';
166
167     function cache_parser($id, $file, $mode) {
168         if ($id) $this->page = $id;
169         $this->file = $file;
170         $this->mode = $mode;
171
172         parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
173     }
174
175     function _useCache() {
176
177         if (!@file_exists($this->file)) return false;                   // source exists?
178         return parent::_useCache();
179     }
180
181     function _addDependencies() {
182         global $conf, $config_cascade;
183
184         $this->depends['age'] = isset($this->depends['age']) ?
185             min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
186
187         // parser cache file dependencies ...
188         $files = array($this->file,                                     // ... source
189                 DOKU_INC.'inc/parser/parser.php',                // ... parser
190                 DOKU_INC.'inc/parser/handler.php',               // ... handler
191                 );
192         $files = array_merge($files, getConfigFiles('main'));           // ... wiki settings
193
194         $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
195         parent::_addDependencies();
196     }
197
198 }
199
200 class cache_renderer extends cache_parser {
201     function _useCache() {
202         global $conf;
203
204         if (!parent::_useCache()) return false;
205
206         if (!isset($this->page)) {
207             return true;
208         }
209
210         if ($this->_time < @filemtime(metaFN($this->page,'.meta'))) return false;         // meta cache older than file it depends on?
211
212         // check current link existence is consistent with cache version
213         // first check the purgefile
214         // - if the cache is more recent than the purgefile we know no links can have been updated
215         if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
216             return true;
217         }
218
219         // for wiki pages, check metadata dependencies
220         $metadata = p_get_metadata($this->page);
221
222         if (!isset($metadata['relation']['references']) ||
223                 empty($metadata['relation']['references'])) {
224             return true;
225         }
226
227         foreach ($metadata['relation']['references'] as $id => $exists) {
228             if ($exists != page_exists($id,'',false)) return false;
229         }
230
231         return true;
232     }
233
234     function _addDependencies() {
235
236         // renderer cache file dependencies ...
237         $files = array(
238                 DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
239                 );
240
241         // page implies metadata and possibly some other dependencies
242         if (isset($this->page)) {
243
244             $valid = p_get_metadata($this->page, 'date valid');         // for xhtml this will render the metadata if needed
245             if (!empty($valid['age'])) {
246                 $this->depends['age'] = isset($this->depends['age']) ?
247                     min($this->depends['age'],$valid['age']) : $valid['age'];
248             }
249         }
250
251         $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
252         parent::_addDependencies();
253     }
254 }
255
256 class cache_instructions extends cache_parser {
257
258     function cache_instructions($id, $file) {
259         parent::cache_parser($id, $file, 'i');
260     }
261
262     function retrieveCache($clean=true) {
263         $contents = io_readFile($this->cache, false);
264         return !empty($contents) ? unserialize($contents) : array();
265     }
266
267     function storeCache($instructions) {
268         return io_savefile($this->cache,serialize($instructions));
269     }
270 }