xref: /dokuwiki/inc/cache.php (revision 0a69dff7134e858ffe6b95410196a8712522167b)
14b5f4f4eSchris<?php
24b5f4f4eSchris/**
34b5f4f4eSchris * Generic class to handle caching
44b5f4f4eSchris *
54b5f4f4eSchris * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
64b5f4f4eSchris * @author     Chris Smith <chris@jalakai.co.uk>
74b5f4f4eSchris */
84b5f4f4eSchris
94b5f4f4eSchrisif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
104b5f4f4eSchris
114b5f4f4eSchrisrequire_once(DOKU_INC.'inc/io.php');
124b5f4f4eSchrisrequire_once(DOKU_INC.'inc/pageutils.php');
134b5f4f4eSchrisrequire_once(DOKU_INC.'inc/parserutils.php');
144b5f4f4eSchris
154b5f4f4eSchrisclass cache {
164b5f4f4eSchris  var $key = '';          // primary identifier for this item
174b5f4f4eSchris  var $ext = '';          // file ext for cache data, secondary identifier for this item
184b5f4f4eSchris  var $cache = '';        // cache file name
190abe1d3eSchris  var $depends = array(); // array containing cache dependency information,
200abe1d3eSchris                          //   used by _useCache to determine cache validity
214b5f4f4eSchris
224b5f4f4eSchris  var $_event = '';       // event to be triggered during useCache
234b5f4f4eSchris
244b5f4f4eSchris  function cache($key,$ext) {
254b5f4f4eSchris    $this->key = $key;
264b5f4f4eSchris    $this->ext = $ext;
274b5f4f4eSchris    $this->cache = getCacheName($key,$ext);
284b5f4f4eSchris  }
294b5f4f4eSchris
304b5f4f4eSchris  /**
314b5f4f4eSchris   * public method to determine whether the cache can be used
324b5f4f4eSchris   *
334b5f4f4eSchris   * to assist in cetralisation of event triggering and calculation of cache statistics,
344b5f4f4eSchris   * don't override this function override _useCache()
354b5f4f4eSchris   *
364b5f4f4eSchris   * @param  array   $depends   array of cache dependencies, support dependecies:
374b5f4f4eSchris   *                            'age'   => max age of the cache in seconds
384b5f4f4eSchris   *                            'files' => cache must be younger than mtime of each file
39ce6b63d9Schris   *                                       (nb. dependency passes if file doesn't exist)
404b5f4f4eSchris   *
414b5f4f4eSchris   * @return bool    true if cache can be used, false otherwise
424b5f4f4eSchris   */
434b5f4f4eSchris  function useCache($depends=array()) {
444b5f4f4eSchris    $this->depends = $depends;
450abe1d3eSchris    $this->_addDependencies();
464b5f4f4eSchris
474b5f4f4eSchris    if ($this->_event) {
484b5f4f4eSchris      return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
494b5f4f4eSchris    } else {
504b5f4f4eSchris      return $this->_stats($this->_useCache());
514b5f4f4eSchris    }
524b5f4f4eSchris  }
534b5f4f4eSchris
540abe1d3eSchris  /**
554b5f4f4eSchris   * private method containing cache use decision logic
564b5f4f4eSchris   *
570abe1d3eSchris   * this function processes the following keys in the depends array
580abe1d3eSchris   *   purge - force a purge on any non empty value
590abe1d3eSchris   *   age   - expire cache if older than age (seconds)
600abe1d3eSchris   *   files - expire cache if any file in this array was updated more recently than the cache
610abe1d3eSchris   *
620abe1d3eSchris   * can be overridden
634b5f4f4eSchris   *
644b5f4f4eSchris   * @return bool               see useCache()
654b5f4f4eSchris   */
664b5f4f4eSchris  function _useCache() {
674b5f4f4eSchris
680abe1d3eSchris    if (!empty($this->depends['purge'])) return false;              // purge requested?
694b5f4f4eSchris    if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
704b5f4f4eSchris
714b5f4f4eSchris    // cache too old?
724b5f4f4eSchris    if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
734b5f4f4eSchris
744b5f4f4eSchris    if (!empty($this->depends['files'])) {
754b5f4f4eSchris      foreach ($this->depends['files'] as $file) {
764b5f4f4eSchris        if ($this->_time < @filemtime($file)) return false;         // cache older than files it depends on?
774b5f4f4eSchris      }
784b5f4f4eSchris    }
794b5f4f4eSchris
804b5f4f4eSchris    return true;
814b5f4f4eSchris  }
824b5f4f4eSchris
834b5f4f4eSchris  /**
840abe1d3eSchris   * add dependencies to the depends array
850abe1d3eSchris   *
860abe1d3eSchris   * this method should only add dependencies,
870abe1d3eSchris   * it should not remove any existing dependencies and
880abe1d3eSchris   * it should only overwrite a dependency when the new value is more stringent than the old
890abe1d3eSchris   */
900abe1d3eSchris  function _addDependencies() {
910abe1d3eSchris    if (isset($_REQUEST['purge'])) $this->depends['purge'] = true;   // purge requested
920abe1d3eSchris  }
930abe1d3eSchris
940abe1d3eSchris  /**
954b5f4f4eSchris   * retrieve the cached data
964b5f4f4eSchris   *
974b5f4f4eSchris   * @param   bool   $clean   true to clean line endings, false to leave line endings alone
984b5f4f4eSchris   * @return  string          cache contents
994b5f4f4eSchris   */
1004b5f4f4eSchris  function retrieveCache($clean=true) {
1014b5f4f4eSchris    return io_readFile($this->cache, $clean);
1024b5f4f4eSchris  }
1034b5f4f4eSchris
1044b5f4f4eSchris  /**
1054b5f4f4eSchris   * cache $data
1064b5f4f4eSchris   *
1074b5f4f4eSchris   * @param   string $data   the data to be cached
1084b5f4f4eSchris   * @return  none
1094b5f4f4eSchris   */
1104b5f4f4eSchris  function storeCache($data) {
1114b5f4f4eSchris    io_savefile($this->cache, $data);
1124b5f4f4eSchris  }
1134b5f4f4eSchris
1144b5f4f4eSchris  /**
1154b5f4f4eSchris   * remove any cached data associated with this cache instance
1164b5f4f4eSchris   */
1174b5f4f4eSchris  function removeCache() {
1184b5f4f4eSchris    @unlink($this->cache);
1194b5f4f4eSchris  }
1204b5f4f4eSchris
1214b5f4f4eSchris  /**
12233c1bd61SBen Coburn   * Record cache hits statistics.
12333c1bd61SBen Coburn   * (Only when debugging allowed, to reduce overhead.)
1244b5f4f4eSchris   *
1254b5f4f4eSchris   * @param    bool   $success   result of this cache use attempt
1264b5f4f4eSchris   * @return   bool              pass-thru $success value
1274b5f4f4eSchris   */
1284b5f4f4eSchris  function _stats($success) {
1294b5f4f4eSchris    global $conf;
1304b5f4f4eSchris    static $stats = NULL;
1314b5f4f4eSchris    static $file;
1324b5f4f4eSchris
13333c1bd61SBen Coburn    if (!$conf['allowdebug']) { return $success; }
13433c1bd61SBen Coburn
1354b5f4f4eSchris    if (is_null($stats)) {
1364b5f4f4eSchris      $file = $conf['cachedir'].'/cache_stats.txt';
1374b5f4f4eSchris      $lines = explode("\n",io_readFile($file));
1384b5f4f4eSchris
1394b5f4f4eSchris      foreach ($lines as $line) {
1404b5f4f4eSchris        $i = strpos($line,',');
1414b5f4f4eSchris        $stats[substr($line,0,$i)] = $line;
1424b5f4f4eSchris      }
1434b5f4f4eSchris    }
1444b5f4f4eSchris
1454b5f4f4eSchris    if (isset($stats[$this->ext])) {
1460abe1d3eSchris      list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
1474b5f4f4eSchris    } else {
1484b5f4f4eSchris      $ext = $this->ext;
1494b5f4f4eSchris      $count = 0;
1500abe1d3eSchris      $hits = 0;
1514b5f4f4eSchris    }
1524b5f4f4eSchris
1534b5f4f4eSchris    $count++;
1540abe1d3eSchris    if ($success) $hits++;
1550abe1d3eSchris    $stats[$this->ext] = "$ext,$count,$hits";
1564b5f4f4eSchris
1574b5f4f4eSchris    io_saveFile($file,join("\n",$stats));
1584b5f4f4eSchris
1594b5f4f4eSchris    return $success;
1604b5f4f4eSchris  }
1614b5f4f4eSchris}
1624b5f4f4eSchris
1634b5f4f4eSchrisclass cache_parser extends cache {
1644b5f4f4eSchris
1654b5f4f4eSchris  var $file = '';       // source file for cache
1664b5f4f4eSchris  var $mode = '';       // input mode (represents the processing the input file will undergo)
1674b5f4f4eSchris
1684b5f4f4eSchris  var $_event = 'PARSER_CACHE_USE';
1694b5f4f4eSchris
1704b5f4f4eSchris  function cache_parser($id, $file, $mode) {
1714b5f4f4eSchris    if ($id) $this->page = $id;
1724b5f4f4eSchris    $this->file = $file;
1734b5f4f4eSchris    $this->mode = $mode;
1744b5f4f4eSchris
1754b5f4f4eSchris    parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
1764b5f4f4eSchris  }
1774b5f4f4eSchris
1784b5f4f4eSchris  function _useCache() {
1794b5f4f4eSchris
1804b5f4f4eSchris    if (!@file_exists($this->file)) return false;                   // source exists?
1810abe1d3eSchris    return parent::_useCache();
1820abe1d3eSchris  }
1834b5f4f4eSchris
1840abe1d3eSchris  function _addDependencies() {
185bb4866bdSchris    global $conf;
1860abe1d3eSchris
1870abe1d3eSchris    $this->depends['age'] = isset($this->depends['age']) ?
1880abe1d3eSchris                   min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
1894b5f4f4eSchris
1904b5f4f4eSchris    // parser cache file dependencies ...
1914b5f4f4eSchris    $files = array($this->file,                                     // ... source
1924b5f4f4eSchris                   DOKU_CONF.'dokuwiki.php',                        // ... config
1934b5f4f4eSchris                   DOKU_CONF.'local.php',                           // ... local config
1944b5f4f4eSchris                   DOKU_INC.'inc/parser/parser.php',                // ... parser
1954b5f4f4eSchris                   DOKU_INC.'inc/parser/handler.php',               // ... handler
1964b5f4f4eSchris             );
1974b5f4f4eSchris
1984b5f4f4eSchris    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
1990abe1d3eSchris    parent::_addDependencies();
2004b5f4f4eSchris  }
2014b5f4f4eSchris
2024b5f4f4eSchris}
2034b5f4f4eSchris
2044b5f4f4eSchrisclass cache_renderer extends cache_parser {
2054b5f4f4eSchris
206b9991f57Schris  function useCache($depends=array()) {
207b9991f57Schris    $use = parent::useCache($depends);
208b9991f57Schris
209b9991f57Schris    // meta data needs to be kept in step with the cache
210b9991f57Schris    if (!$use && isset($this->page)) {
211b9991f57Schris      p_set_metadata($this->page,array(),true);
212b9991f57Schris    }
213b9991f57Schris
214b9991f57Schris    return $use;
215b9991f57Schris  }
216b9991f57Schris
2174b5f4f4eSchris  function _useCache() {
218b9991f57Schris    global $conf;
2194b5f4f4eSchris
2200abe1d3eSchris    if (!parent::_useCache()) return false;
2214b5f4f4eSchris
222ce6b63d9Schris    // for wiki pages, check metadata dependencies
2234b5f4f4eSchris    if (isset($this->page)) {
224ce6b63d9Schris      $metadata = p_get_metadata($this->page);
2250abe1d3eSchris
226ce6b63d9Schris      // check currnent link existence is consistent with cache version
227ce6b63d9Schris      // first check the purgefile
2280abe1d3eSchris      // - if the cache is more recent that the purgefile we know no links can have been updated
2290abe1d3eSchris      if ($this->_time < @filemtime($conf['cachedir'].'/purgefile')) {
2300abe1d3eSchris
231ce6b63d9Schris#       $links = p_get_metadata($this->page,"relation references");
232ce6b63d9Schris        $links = $metadata['relation']['references'];
2334b5f4f4eSchris
2344b5f4f4eSchris        if (!empty($links)) {
2354b5f4f4eSchris          foreach ($links as $id => $exists) {
2364b5f4f4eSchris            if ($exists != @file_exists(wikiFN($id,'',false))) return false;
2374b5f4f4eSchris          }
2384b5f4f4eSchris        }
2394b5f4f4eSchris      }
2400abe1d3eSchris    }
2414b5f4f4eSchris
2424b5f4f4eSchris    return true;
2434b5f4f4eSchris  }
2440abe1d3eSchris
2450abe1d3eSchris  function _addDependencies() {
2460abe1d3eSchris
2470abe1d3eSchris    // renderer cache file dependencies ...
2480abe1d3eSchris    $files = array(
2490abe1d3eSchris                   DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
2500abe1d3eSchris             );
251ce6b63d9Schris
252ce6b63d9Schris    // page implies metadata and possibly some other dependencies
253ce6b63d9Schris    if (isset($this->page)) {
254*0a69dff7Schris
255ce6b63d9Schris      $metafile = metaFN($this->page,'.meta');
256ce6b63d9Schris      if (@file_exists($metafile)) {
257ce6b63d9Schris        $files[] = $metafile;                                       // ... the page's own metadata
258ce6b63d9Schris        $files[] = DOKU_INC.'inc/parser/metadata.php';              // ... the metadata renderer
259*0a69dff7Schris
260*0a69dff7Schris        $valid = p_get_metadata($this->page, 'date valid');
261*0a69dff7Schris        if (!empty($valid['age'])) {
262*0a69dff7Schris          $this->depends['age'] = isset($this->depends['age']) ?
263*0a69dff7Schris                   min($this->depends['age'],$valid['age']) : $valid['age'];
264*0a69dff7Schris        }
265*0a69dff7Schris
266ce6b63d9Schris      } else {
267ce6b63d9Schris        $this->depends['purge'] = true;                             // ... purging cache will generate metadata
268ce6b63d9Schris        return;
269ce6b63d9Schris      }
270ce6b63d9Schris    }
2710abe1d3eSchris
2720abe1d3eSchris    $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
2730abe1d3eSchris    parent::_addDependencies();
2740abe1d3eSchris  }
2754b5f4f4eSchris}
2764b5f4f4eSchris
2774b5f4f4eSchrisclass cache_instructions extends cache_parser {
2784b5f4f4eSchris
2794b5f4f4eSchris  function cache_instructions($id, $file) {
2804b5f4f4eSchris    parent::cache_parser($id, $file, 'i');
2814b5f4f4eSchris  }
2824b5f4f4eSchris
2834b5f4f4eSchris  function retrieveCache() {
2844b5f4f4eSchris    $contents = io_readFile($this->cache, false);
2854b5f4f4eSchris    return !empty($contents) ? unserialize($contents) : array();
2864b5f4f4eSchris  }
2874b5f4f4eSchris
2884b5f4f4eSchris  function storeCache($instructions) {
2894b5f4f4eSchris    io_savefile($this->cache,serialize($instructions));
2904b5f4f4eSchris  }
2914b5f4f4eSchris}
292