Creating repository for dokuwiki modifications for sudaraka.org
[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         if (isset($_REQUEST['purge'])) $this->depends['purge'] = true;   // purge requested
88     }
89
90     /**
91      * retrieve the cached data
92      *
93      * @param   bool   $clean   true to clean line endings, false to leave line endings alone
94      * @return  string          cache contents
95      */
96     function retrieveCache($clean=true) {
97         return io_readFile($this->cache, $clean);
98     }
99
100     /**
101      * cache $data
102      *
103      * @param   string $data   the data to be cached
104      * @return  bool           true on success, false otherwise
105      */
106     function storeCache($data) {
107         return io_savefile($this->cache, $data);
108     }
109
110     /**
111      * remove any cached data associated with this cache instance
112      */
113     function removeCache() {
114         @unlink($this->cache);
115     }
116
117     /**
118      * Record cache hits statistics.
119      * (Only when debugging allowed, to reduce overhead.)
120      *
121      * @param    bool   $success   result of this cache use attempt
122      * @return   bool              pass-thru $success value
123      */
124     function _stats($success) {
125         global $conf;
126         static $stats = null;
127         static $file;
128
129         if (!$conf['allowdebug']) { return $success; }
130
131         if (is_null($stats)) {
132             $file = $conf['cachedir'].'/cache_stats.txt';
133             $lines = explode("\n",io_readFile($file));
134
135             foreach ($lines as $line) {
136                 $i = strpos($line,',');
137                 $stats[substr($line,0,$i)] = $line;
138             }
139         }
140
141         if (isset($stats[$this->ext])) {
142             list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
143         } else {
144             $ext = $this->ext;
145             $count = 0;
146             $hits = 0;
147         }
148
149         $count++;
150         if ($success) $hits++;
151         $stats[$this->ext] = "$ext,$count,$hits";
152
153         io_saveFile($file,join("\n",$stats));
154
155         return $success;
156     }
157 }
158
159 class cache_parser extends cache {
160
161     var $file = '';       // source file for cache
162     var $mode = '';       // input mode (represents the processing the input file will undergo)
163
164     var $_event = 'PARSER_CACHE_USE';
165
166     function cache_parser($id, $file, $mode) {
167         if ($id) $this->page = $id;
168         $this->file = $file;
169         $this->mode = $mode;
170
171         parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
172     }
173
174     function _useCache() {
175
176         if (!@file_exists($this->file)) return false;                   // source exists?
177         return parent::_useCache();
178     }
179
180     function _addDependencies() {
181         global $conf, $config_cascade;
182
183         $this->depends['age'] = isset($this->depends['age']) ?
184             min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
185
186         // parser cache file dependencies ...
187         $files = array($this->file,                                     // ... source
188                 DOKU_INC.'inc/parser/parser.php',                // ... parser
189                 DOKU_INC.'inc/parser/handler.php',               // ... handler
190                 );
191         $files = array_merge($files, getConfigFiles('main'));           // ... wiki settings
192
193         $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
194         parent::_addDependencies();
195     }
196
197 }
198
199 class cache_renderer extends cache_parser {
200     function _useCache() {
201         global $conf;
202
203         if (!parent::_useCache()) return false;
204
205         if (!isset($this->page)) {
206             return true;
207         }
208
209         // check current link existence is consistent with cache version
210         // first check the purgefile
211         // - if the cache is more recent than the purgefile we know no links can have been updated
212         if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
213             return true;
214         }
215
216         // for wiki pages, check metadata dependencies
217         $metadata = p_get_metadata($this->page);
218
219         if (!isset($metadata['relation']['references']) ||
220                 empty($metadata['relation']['references'])) {
221             return true;
222         }
223
224         foreach ($metadata['relation']['references'] as $id => $exists) {
225             if ($exists != page_exists($id,'',false)) return false;
226         }
227
228         return true;
229     }
230
231     function _addDependencies() {
232
233         // renderer cache file dependencies ...
234         $files = array(
235                 DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
236                 );
237
238         // page implies metadata and possibly some other dependencies
239         if (isset($this->page)) {
240
241             $metafile = metaFN($this->page,'.meta');
242             $files[] = $metafile;                                       // ... the page's own metadata
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 }