xref: /dokuwiki/inc/cache.php (revision 08fa66807e2772b1f07756f37cfa6dab93db20a0)
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  var $depends = array(); // array containing cache dependency information,
20                          //   used by _useCache to determine cache validity
21
22  var $_event = '';       // event to be triggered during useCache
23
24  function cache($key,$ext) {
25    $this->key = $key;
26    $this->ext = $ext;
27    $this->cache = getCacheName($key,$ext);
28  }
29
30  /**
31   * public method to determine whether the cache can be used
32   *
33   * to assist in cetralisation of event triggering and calculation of cache statistics,
34   * don't override this function override _useCache()
35   *
36   * @param  array   $depends   array of cache dependencies, support dependecies:
37   *                            'age'   => max age of the cache in seconds
38   *                            'files' => cache must be younger than mtime of each file
39   *
40   * @return bool    true if cache can be used, false otherwise
41   */
42  function useCache($depends=array()) {
43    $this->depends = $depends;
44    $this->_addDependencies();
45
46    if ($this->_event) {
47      return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
48    } else {
49      return $this->_stats($this->_useCache());
50    }
51  }
52
53  /**
54   * private method containing cache use decision logic
55   *
56   * this function processes the following keys in the depends array
57   *   purge - force a purge on any non empty value
58   *   age   - expire cache if older than age (seconds)
59   *   files - expire cache if any file in this array was updated more recently than the cache
60   *
61   * can be overridden
62   *
63   * @return bool               see useCache()
64   */
65  function _useCache() {
66
67    if (!empty($this->depends['purge'])) return false;              // purge requested?
68    if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
69
70    // cache too old?
71    if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
72
73    if (!empty($this->depends['files'])) {
74      foreach ($this->depends['files'] as $file) {
75        if ($this->_time < @filemtime($file)) return false;         // cache older than files it depends on?
76      }
77    }
78
79    return true;
80  }
81
82  /**
83   * add dependencies to the depends array
84   *
85   * this method should only add dependencies,
86   * it should not remove any existing dependencies and
87   * it should only overwrite a dependency when the new value is more stringent than the old
88   */
89  function _addDependencies() {
90    if (isset($_REQUEST['purge'])) $this->depends['purge'] = true;   // purge requested
91  }
92
93  /**
94   * retrieve the cached data
95   *
96   * @param   bool   $clean   true to clean line endings, false to leave line endings alone
97   * @return  string          cache contents
98   */
99  function retrieveCache($clean=true) {
100    return io_readFile($this->cache, $clean);
101  }
102
103  /**
104   * cache $data
105   *
106   * @param   string $data   the data to be cached
107   * @return  none
108   */
109  function storeCache($data) {
110    io_savefile($this->cache, $data);
111  }
112
113  /**
114   * remove any cached data associated with this cache instance
115   */
116  function removeCache() {
117    @unlink($this->cache);
118  }
119
120  /**
121   * record cache hits statistics
122   *
123   * @param    bool   $success   result of this cache use attempt
124   * @return   bool              pass-thru $success value
125   */
126  function _stats($success) {
127    global $conf;
128    static $stats = NULL;
129    static $file;
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
159class 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
182    $this->depends['age'] = isset($this->depends['age']) ?
183                   min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
184
185    // parser cache file dependencies ...
186    $files = array($this->file,                                     // ... source
187                   DOKU_CONF.'dokuwiki.php',                        // ... config
188                   DOKU_CONF.'local.php',                           // ... local config
189                   DOKU_INC.'inc/parser/parser.php',                // ... parser
190                   DOKU_INC.'inc/parser/handler.php',               // ... handler
191             );
192
193    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
194    parent::_addDependencies();
195  }
196
197}
198
199class cache_renderer extends cache_parser {
200
201  function _useCache() {
202
203    if (!parent::_useCache()) return false;
204
205    // for wiki pages, check for internal link status changes
206    if (isset($this->page)) {
207
208      // check the purgefile
209      // - if the cache is more recent that the purgefile we know no links can have been updated
210      if ($this->_time < @filemtime($conf['cachedir'].'/purgefile')) {
211
212        $links = p_get_metadata($this->page,"relation references");
213
214        if (!empty($links)) {
215          foreach ($links as $id => $exists) {
216            if ($exists != @file_exists(wikiFN($id,'',false))) return false;
217          }
218        }
219      }
220    }
221
222    return true;
223  }
224
225  function _addDependencies() {
226
227    // renderer cache file dependencies ...
228    $files = array(
229                   DOKU_INC.'inc/parser/'.$this->mode.'.php',      // ... the renderer
230             );
231
232    if (isset($this->page)) { $files[] = metaFN($this->page,'.meta'); }  // ... the page's own metadata
233
234    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
235    parent::_addDependencies();
236  }
237}
238
239class cache_instructions extends cache_parser {
240
241  function cache_instructions($id, $file) {
242    parent::cache_parser($id, $file, 'i');
243  }
244
245  function retrieveCache() {
246    $contents = io_readFile($this->cache, false);
247    return !empty($contents) ? unserialize($contents) : array();
248  }
249
250  function storeCache($instructions) {
251    io_savefile($this->cache,serialize($instructions));
252  }
253}
254