xref: /dokuwiki/inc/cache.php (revision 4b5f4f4ed319790fe7f0729560616b55c4e64715)
1*4b5f4f4eSchris<?php
2*4b5f4f4eSchris/**
3*4b5f4f4eSchris * Generic class to handle caching
4*4b5f4f4eSchris *
5*4b5f4f4eSchris * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6*4b5f4f4eSchris * @author     Chris Smith <chris@jalakai.co.uk>
7*4b5f4f4eSchris */
8*4b5f4f4eSchris
9*4b5f4f4eSchrisif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
10*4b5f4f4eSchris
11*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/io.php');
12*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/pageutils.php');
13*4b5f4f4eSchrisrequire_once(DOKU_INC.'inc/parserutils.php');
14*4b5f4f4eSchris
15*4b5f4f4eSchrisclass cache {
16*4b5f4f4eSchris  var $key = '';        // primary identifier for this item
17*4b5f4f4eSchris  var $ext = '';        // file ext for cache data, secondary identifier for this item
18*4b5f4f4eSchris  var $cache = '';      // cache file name
19*4b5f4f4eSchris
20*4b5f4f4eSchris  var $_event = '';      // event to be triggered during useCache
21*4b5f4f4eSchris
22*4b5f4f4eSchris  function cache($key,$ext) {
23*4b5f4f4eSchris    $this->key = $key;
24*4b5f4f4eSchris    $this->ext = $ext;
25*4b5f4f4eSchris    $this->cache = getCacheName($key,$ext);
26*4b5f4f4eSchris  }
27*4b5f4f4eSchris
28*4b5f4f4eSchris  /**
29*4b5f4f4eSchris   * public method to determine whether the cache can be used
30*4b5f4f4eSchris   *
31*4b5f4f4eSchris   * to assist in cetralisation of event triggering and calculation of cache statistics,
32*4b5f4f4eSchris   * don't override this function override _useCache()
33*4b5f4f4eSchris   *
34*4b5f4f4eSchris   * @param  array   $depends   array of cache dependencies, support dependecies:
35*4b5f4f4eSchris   *                            'age'   => max age of the cache in seconds
36*4b5f4f4eSchris   *                            'files' => cache must be younger than mtime of each file
37*4b5f4f4eSchris   *
38*4b5f4f4eSchris   * @return bool    true if cache can be used, false otherwise
39*4b5f4f4eSchris   */
40*4b5f4f4eSchris  function useCache($depends=array()) {
41*4b5f4f4eSchris    $this->depends = $depends;
42*4b5f4f4eSchris
43*4b5f4f4eSchris    if ($this->_event) {
44*4b5f4f4eSchris      return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
45*4b5f4f4eSchris    } else {
46*4b5f4f4eSchris      return $this->_stats($this->_useCache());
47*4b5f4f4eSchris    }
48*4b5f4f4eSchris  }
49*4b5f4f4eSchris
50*4b5f4f4eSchris  /*
51*4b5f4f4eSchris   * private method containing cache use decision logic
52*4b5f4f4eSchris   *
53*4b5f4f4eSchris   * this function can be overridden
54*4b5f4f4eSchris   *
55*4b5f4f4eSchris   * @return bool               see useCache()
56*4b5f4f4eSchris   */
57*4b5f4f4eSchris  function _useCache() {
58*4b5f4f4eSchris
59*4b5f4f4eSchris    if (isset($_REQUEST['purge'])) return false;                    // purge requested?
60*4b5f4f4eSchris    if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
61*4b5f4f4eSchris
62*4b5f4f4eSchris    // cache too old?
63*4b5f4f4eSchris    if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
64*4b5f4f4eSchris
65*4b5f4f4eSchris    if (!empty($this->depends['files'])) {
66*4b5f4f4eSchris      foreach ($this->depends['files'] as $file) {
67*4b5f4f4eSchris        if ($this->_time < @filemtime($file)) return false;         // cache older than files it depends on?
68*4b5f4f4eSchris      }
69*4b5f4f4eSchris    }
70*4b5f4f4eSchris
71*4b5f4f4eSchris    return true;
72*4b5f4f4eSchris  }
73*4b5f4f4eSchris
74*4b5f4f4eSchris  /**
75*4b5f4f4eSchris   * retrieve the cached data
76*4b5f4f4eSchris   *
77*4b5f4f4eSchris   * @param   bool   $clean   true to clean line endings, false to leave line endings alone
78*4b5f4f4eSchris   * @return  string          cache contents
79*4b5f4f4eSchris   */
80*4b5f4f4eSchris  function retrieveCache($clean=true) {
81*4b5f4f4eSchris    return io_readFile($this->cache, $clean);
82*4b5f4f4eSchris  }
83*4b5f4f4eSchris
84*4b5f4f4eSchris  /**
85*4b5f4f4eSchris   * cache $data
86*4b5f4f4eSchris   *
87*4b5f4f4eSchris   * @param   string $data   the data to be cached
88*4b5f4f4eSchris   * @return  none
89*4b5f4f4eSchris   */
90*4b5f4f4eSchris  function storeCache($data) {
91*4b5f4f4eSchris    io_savefile($this->cache, $data);
92*4b5f4f4eSchris  }
93*4b5f4f4eSchris
94*4b5f4f4eSchris  /**
95*4b5f4f4eSchris   * remove any cached data associated with this cache instance
96*4b5f4f4eSchris   */
97*4b5f4f4eSchris  function removeCache() {
98*4b5f4f4eSchris    @unlink($this->cache);
99*4b5f4f4eSchris  }
100*4b5f4f4eSchris
101*4b5f4f4eSchris  /**
102*4b5f4f4eSchris   * record cache hits statistics
103*4b5f4f4eSchris   *
104*4b5f4f4eSchris   * @param    bool   $success   result of this cache use attempt
105*4b5f4f4eSchris   * @return   bool              pass-thru $success value
106*4b5f4f4eSchris   */
107*4b5f4f4eSchris  function _stats($success) {
108*4b5f4f4eSchris    global $conf;
109*4b5f4f4eSchris    static $stats = NULL;
110*4b5f4f4eSchris    static $file;
111*4b5f4f4eSchris
112*4b5f4f4eSchris    if (is_null($stats)) {
113*4b5f4f4eSchris      $file = $conf['cachedir'].'/cache_stats.txt';
114*4b5f4f4eSchris      $lines = explode("\n",io_readFile($file));
115*4b5f4f4eSchris
116*4b5f4f4eSchris      foreach ($lines as $line) {
117*4b5f4f4eSchris        $i = strpos($line,',');
118*4b5f4f4eSchris	$stats[substr($line,0,$i)] = $line;
119*4b5f4f4eSchris      }
120*4b5f4f4eSchris    }
121*4b5f4f4eSchris
122*4b5f4f4eSchris    if (isset($stats[$this->ext])) {
123*4b5f4f4eSchris      list($ext,$count,$successes) = explode(',',$stats[$this->ext]);
124*4b5f4f4eSchris    } else {
125*4b5f4f4eSchris      $ext = $this->ext;
126*4b5f4f4eSchris      $count = 0;
127*4b5f4f4eSchris      $successes = 0;
128*4b5f4f4eSchris    }
129*4b5f4f4eSchris
130*4b5f4f4eSchris    $count++;
131*4b5f4f4eSchris    $successes += $success ? 1 : 0;
132*4b5f4f4eSchris    $stats[$this->ext] = "$ext,$count,$successes";
133*4b5f4f4eSchris
134*4b5f4f4eSchris    io_saveFile($file,join("\n",$stats));
135*4b5f4f4eSchris
136*4b5f4f4eSchris    return $success;
137*4b5f4f4eSchris  }
138*4b5f4f4eSchris}
139*4b5f4f4eSchris
140*4b5f4f4eSchrisclass cache_parser extends cache {
141*4b5f4f4eSchris
142*4b5f4f4eSchris  var $file = '';       // source file for cache
143*4b5f4f4eSchris  var $mode = '';       // input mode (represents the processing the input file will undergo)
144*4b5f4f4eSchris
145*4b5f4f4eSchris  var $_event = 'PARSER_CACHE_USE';
146*4b5f4f4eSchris
147*4b5f4f4eSchris  function cache_parser($id, $file, $mode) {
148*4b5f4f4eSchris    if ($id) $this->page = $id;
149*4b5f4f4eSchris    $this->file = $file;
150*4b5f4f4eSchris    $this->mode = $mode;
151*4b5f4f4eSchris
152*4b5f4f4eSchris    parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
153*4b5f4f4eSchris  }
154*4b5f4f4eSchris
155*4b5f4f4eSchris  function _useCache() {
156*4b5f4f4eSchris    global $conf;
157*4b5f4f4eSchris
158*4b5f4f4eSchris    if (!@file_exists($this->file)) return false;                   // source exists?
159*4b5f4f4eSchris
160*4b5f4f4eSchris    if (!isset($this->depends['age'])) $this->depends['age'] = $conf['cachetime'];
161*4b5f4f4eSchris
162*4b5f4f4eSchris    // parser cache file dependencies ...
163*4b5f4f4eSchris    $files = array($this->file,                                     // ... source
164*4b5f4f4eSchris                   DOKU_CONF.'dokuwiki.php',                        // ... config
165*4b5f4f4eSchris                   DOKU_CONF.'local.php',                           // ... local config
166*4b5f4f4eSchris                   DOKU_INC.'inc/parser/parser.php',                // ... parser
167*4b5f4f4eSchris                   DOKU_INC.'inc/parser/handler.php',               // ... handler
168*4b5f4f4eSchris             );
169*4b5f4f4eSchris
170*4b5f4f4eSchris    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
171*4b5f4f4eSchris    return parent::_useCache($depends);
172*4b5f4f4eSchris  }
173*4b5f4f4eSchris
174*4b5f4f4eSchris}
175*4b5f4f4eSchris
176*4b5f4f4eSchrisclass cache_renderer extends cache_parser {
177*4b5f4f4eSchris
178*4b5f4f4eSchris  function _useCache() {
179*4b5f4f4eSchris    global $conf;
180*4b5f4f4eSchris
181*4b5f4f4eSchris    // renderer cache file dependencies ...
182*4b5f4f4eSchris    $files = array(
183*4b5f4f4eSchris#                   $conf['cachedir'].'/purgefile',                 // ... purgefile - time of last add
184*4b5f4f4eSchris                   DOKU_INC.'inc/parser/'.$this->mode.'.php',      // ... the renderer
185*4b5f4f4eSchris             );
186*4b5f4f4eSchris
187*4b5f4f4eSchris    if (isset($this->page)) { $files[] = metaFN($this->page,'.meta'); }
188*4b5f4f4eSchris
189*4b5f4f4eSchris    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
190*4b5f4f4eSchris    if (!parent::_useCache($depends)) return false;
191*4b5f4f4eSchris
192*4b5f4f4eSchris    // for wiki pages, check for internal link status changes
193*4b5f4f4eSchris    if (isset($this->page)) {
194*4b5f4f4eSchris      $links = p_get_metadata($this->page,"relation references");
195*4b5f4f4eSchris
196*4b5f4f4eSchris      if (!empty($links)) {
197*4b5f4f4eSchris        foreach ($links as $id => $exists) {
198*4b5f4f4eSchris          if ($exists != @file_exists(wikiFN($id,'',false))) return false;
199*4b5f4f4eSchris	}
200*4b5f4f4eSchris      }
201*4b5f4f4eSchris    }
202*4b5f4f4eSchris
203*4b5f4f4eSchris    return true;
204*4b5f4f4eSchris  }
205*4b5f4f4eSchris}
206*4b5f4f4eSchris
207*4b5f4f4eSchrisclass cache_instructions extends cache_parser {
208*4b5f4f4eSchris
209*4b5f4f4eSchris  function cache_instructions($id, $file) {
210*4b5f4f4eSchris    parent::cache_parser($id, $file, 'i');
211*4b5f4f4eSchris  }
212*4b5f4f4eSchris
213*4b5f4f4eSchris  function retrieveCache() {
214*4b5f4f4eSchris    $contents = io_readFile($this->cache, false);
215*4b5f4f4eSchris    return !empty($contents) ? unserialize($contents) : array();
216*4b5f4f4eSchris  }
217*4b5f4f4eSchris
218*4b5f4f4eSchris  function storeCache($instructions) {
219*4b5f4f4eSchris    io_savefile($this->cache,serialize($instructions));
220*4b5f4f4eSchris  }
221*4b5f4f4eSchris}
222