Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

cache_parser::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 2
eloc 5
nc 2
nop 3
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 <[email protected]>
7
 */
8
9
if(!defined('DOKU_INC')) die('meh.');
10
11
/**
12
 * Generic handling of caching
13
 */
14
class cache {
15
    public $key = '';          // primary identifier for this item
16
    public $ext = '';          // file ext for cache data, secondary identifier for this item
17
    public $cache = '';        // cache file name
18
    public $depends = array(); // array containing cache dependency information,
19
                               //   used by _useCache to determine cache validity
20
21
    var $_event = '';       // event to be triggered during useCache
22
    var $_time;
23
    var $_nocache = false;  // if set to true, cache will not be used or stored
24
25
    /**
26
     * @param string $key primary identifier
27
     * @param string $ext file extension
28
     */
29
    public function __construct($key,$ext) {
30
        $this->key = $key;
31
        $this->ext = $ext;
32
        $this->cache = getCacheName($key,$ext);
33
    }
34
35
    /**
36
     * public method to determine whether the cache can be used
37
     *
38
     * to assist in centralisation of event triggering and calculation of cache statistics,
39
     * don't override this function override _useCache()
40
     *
41
     * @param  array   $depends   array of cache dependencies, support dependecies:
42
     *                            'age'   => max age of the cache in seconds
43
     *                            'files' => cache must be younger than mtime of each file
44
     *                                       (nb. dependency passes if file doesn't exist)
45
     *
46
     * @return bool    true if cache can be used, false otherwise
47
     */
48
    public function useCache($depends=array()) {
49
        $this->depends = $depends;
50
        $this->_addDependencies();
51
52
        if ($this->_event) {
53
            return $this->_stats(trigger_event($this->_event, $this, array($this,'_useCache')));
54
        } else {
55
            return $this->_stats($this->_useCache());
56
        }
57
    }
58
59
    /**
60
     * private method containing cache use decision logic
61
     *
62
     * this function processes the following keys in the depends array
63
     *   purge - force a purge on any non empty value
64
     *   age   - expire cache if older than age (seconds)
65
     *   files - expire cache if any file in this array was updated more recently than the cache
66
     *
67
     * Note that this function needs to be public as it is used as callback for the event handler
68
     *
69
     * can be overridden
70
     *
71
     * @return bool               see useCache()
72
     */
73
    public function _useCache() {
74
75
        if ($this->_nocache) return false;                              // caching turned off
76
        if (!empty($this->depends['purge'])) return false;              // purge requested?
77
        if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
78
79
        // cache too old?
80
        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
81
82
        if (!empty($this->depends['files'])) {
83
            foreach ($this->depends['files'] as $file) {
84
                if ($this->_time <= @filemtime($file)) return false;         // cache older than files it depends on?
85
            }
86
        }
87
88
        return true;
89
    }
90
91
    /**
92
     * add dependencies to the depends array
93
     *
94
     * this method should only add dependencies,
95
     * it should not remove any existing dependencies and
96
     * it should only overwrite a dependency when the new value is more stringent than the old
97
     */
98
    protected function _addDependencies() {
99
        global $INPUT;
100
        if ($INPUT->has('purge')) $this->depends['purge'] = true;   // purge requested
101
    }
102
103
    /**
104
     * retrieve the cached data
105
     *
106
     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
107
     * @return  string          cache contents
108
     */
109
    public function retrieveCache($clean=true) {
110
        return io_readFile($this->cache, $clean);
111
    }
112
113
    /**
114
     * cache $data
115
     *
116
     * @param   string $data   the data to be cached
117
     * @return  bool           true on success, false otherwise
118
     */
119
    public function storeCache($data) {
120
        if ($this->_nocache) return false;
121
122
        return io_savefile($this->cache, $data);
123
    }
124
125
    /**
126
     * remove any cached data associated with this cache instance
127
     */
128
    public function removeCache() {
129
        @unlink($this->cache);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
130
    }
131
132
    /**
133
     * Record cache hits statistics.
134
     * (Only when debugging allowed, to reduce overhead.)
135
     *
136
     * @param    bool   $success   result of this cache use attempt
137
     * @return   bool              pass-thru $success value
138
     */
139
    protected function _stats($success) {
140
        global $conf;
141
        static $stats = null;
142
        static $file;
143
144
        if (!$conf['allowdebug']) { return $success; }
145
146
        if (is_null($stats)) {
147
            $file = $conf['cachedir'].'/cache_stats.txt';
148
            $lines = explode("\n",io_readFile($file));
149
150
            foreach ($lines as $line) {
151
                $i = strpos($line,',');
152
                $stats[substr($line,0,$i)] = $line;
153
            }
154
        }
155
156
        if (isset($stats[$this->ext])) {
157
            list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
158
        } else {
159
            $ext = $this->ext;
160
            $count = 0;
161
            $hits = 0;
162
        }
163
164
        $count++;
165
        if ($success) $hits++;
166
        $stats[$this->ext] = "$ext,$count,$hits";
167
168
        io_saveFile($file,join("\n",$stats));
169
170
        return $success;
171
    }
172
}
173
174
/**
175
 * Parser caching
176
 */
177
class cache_parser extends cache {
178
179
    public $file = '';       // source file for cache
180
    public $mode = '';       // input mode (represents the processing the input file will undergo)
181
    public $page = '';
182
183
    var $_event = 'PARSER_CACHE_USE';
184
185
    /**
186
     *
187
     * @param string $id page id
188
     * @param string $file source file for cache
189
     * @param string $mode input mode
190
     */
191
    public function __construct($id, $file, $mode) {
192
        if ($id) $this->page = $id;
193
        $this->file = $file;
194
        $this->mode = $mode;
195
196
        parent::__construct($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
197
    }
198
199
    /**
200
     * method contains cache use decision logic
201
     *
202
     * @return bool               see useCache()
203
     */
204
    public function _useCache() {
205
206
        if (!file_exists($this->file)) return false;                   // source exists?
207
        return parent::_useCache();
208
    }
209
210
    protected function _addDependencies() {
211
212
        // parser cache file dependencies ...
213
        $files = array($this->file,                              // ... source
214
                DOKU_INC.'inc/parser/parser.php',                // ... parser
215
                DOKU_INC.'inc/parser/handler.php',               // ... handler
216
                );
217
        $files = array_merge($files, getConfigFiles('main'));    // ... wiki settings
218
219
        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
220
        parent::_addDependencies();
221
    }
222
223
}
224
225
/**
226
 * Caching of data of renderer
227
 */
228
class cache_renderer extends cache_parser {
229
230
    /**
231
     * method contains cache use decision logic
232
     *
233
     * @return bool               see useCache()
234
     */
235
    public function _useCache() {
236
        global $conf;
237
238
        if (!parent::_useCache()) return false;
239
240
        if (!isset($this->page)) {
241
            return true;
242
        }
243
244
        if ($this->_time < @filemtime(metaFN($this->page,'.meta'))) return false;         // meta cache older than file it depends on?
0 ignored issues
show
Bug introduced by
The property _time cannot be accessed from this context as it is declared private in class cache.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
245
246
        // check current link existence is consistent with cache version
247
        // first check the purgefile
248
        // - if the cache is more recent than the purgefile we know no links can have been updated
249
        if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
0 ignored issues
show
Bug introduced by
The property _time cannot be accessed from this context as it is declared private in class cache.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
250
            return true;
251
        }
252
253
        // for wiki pages, check metadata dependencies
254
        $metadata = p_get_metadata($this->page);
255
256
        if (!isset($metadata['relation']['references']) ||
257
                empty($metadata['relation']['references'])) {
258
            return true;
259
        }
260
261
        foreach ($metadata['relation']['references'] as $id => $exists) {
262
            if ($exists != page_exists($id,'',false)) return false;
263
        }
264
265
        return true;
266
    }
267
268
    protected function _addDependencies() {
269
        global $conf;
270
271
        // default renderer cache file 'age' is dependent on 'cachetime' setting, two special values:
272
        //    -1 : do not cache (should not be overridden)
273
        //    0  : cache never expires (can be overridden) - no need to set depends['age']
274
        if ($conf['cachetime'] == -1) {
275
            $this->_nocache = true;
0 ignored issues
show
Bug introduced by
The property _nocache cannot be accessed from this context as it is declared private in class cache.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
276
            return;
277
        } elseif ($conf['cachetime'] > 0) {
278
            $this->depends['age'] = isset($this->depends['age']) ?
279
                min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
280
        }
281
282
        // renderer cache file dependencies ...
283
        $files = array(
284
                DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
285
                );
286
287
        // page implies metadata and possibly some other dependencies
288
        if (isset($this->page)) {
289
290
            $valid = p_get_metadata($this->page, 'date valid');         // for xhtml this will render the metadata if needed
291
            if (!empty($valid['age'])) {
292
                $this->depends['age'] = isset($this->depends['age']) ?
293
                    min($this->depends['age'],$valid['age']) : $valid['age'];
294
            }
295
        }
296
297
        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
298
        parent::_addDependencies();
299
    }
300
}
301
302
/**
303
 * Caching of parser instructions
304
 */
305
class cache_instructions extends cache_parser {
306
307
    /**
308
     * @param string $id page id
309
     * @param string $file source file for cache
310
     */
311
    public function __construct($id, $file) {
312
        parent::__construct($id, $file, 'i');
313
    }
314
315
    /**
316
     * retrieve the cached data
317
     *
318
     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
319
     * @return  array          cache contents
320
     */
321
    public function retrieveCache($clean=true) {
322
        $contents = io_readFile($this->cache, false);
323
        return !empty($contents) ? unserialize($contents) : array();
324
    }
325
326
    /**
327
     * cache $instructions
328
     *
329
     * @param   array $instructions  the instruction to be cached
330
     * @return  bool                  true on success, false otherwise
331
     */
332
    public function storeCache($instructions) {
333
        if ($this->_nocache) return false;
0 ignored issues
show
Bug introduced by
The property _nocache cannot be accessed from this context as it is declared private in class cache.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
334
335
        return io_savefile($this->cache,serialize($instructions));
336
    }
337
}
338