xref: /dokuwiki/inc/cache.php (revision f1b03b6ac2e0ee48533ebb49ba57bc6daea5ca9a)
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