xref: /dokuwiki/inc/cache.php (revision 98214867894eba512bf47cba3439ccba3968f49b)
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
9fa8adffeSAndreas Gohrif(!defined('DOKU_INC')) die('meh.');
104b5f4f4eSchris
114b5f4f4eSchrisclass cache {
124b5f4f4eSchris    var $key = '';          // primary identifier for this item
134b5f4f4eSchris    var $ext = '';          // file ext for cache data, secondary identifier for this item
144b5f4f4eSchris    var $cache = '';        // cache file name
150abe1d3eSchris    var $depends = array(); // array containing cache dependency information,
160abe1d3eSchris    //   used by _useCache to determine cache validity
174b5f4f4eSchris
184b5f4f4eSchris    var $_event = '';       // event to be triggered during useCache
194b5f4f4eSchris
204b5f4f4eSchris    function cache($key,$ext) {
214b5f4f4eSchris        $this->key = $key;
224b5f4f4eSchris        $this->ext = $ext;
234b5f4f4eSchris        $this->cache = getCacheName($key,$ext);
244b5f4f4eSchris    }
254b5f4f4eSchris
264b5f4f4eSchris    /**
274b5f4f4eSchris     * public method to determine whether the cache can be used
284b5f4f4eSchris     *
294b5f4f4eSchris     * to assist in cetralisation of event triggering and calculation of cache statistics,
304b5f4f4eSchris     * don't override this function override _useCache()
314b5f4f4eSchris     *
324b5f4f4eSchris     * @param  array   $depends   array of cache dependencies, support dependecies:
334b5f4f4eSchris     *                            'age'   => max age of the cache in seconds
344b5f4f4eSchris     *                            'files' => cache must be younger than mtime of each file
35ce6b63d9Schris     *                                       (nb. dependency passes if file doesn't exist)
364b5f4f4eSchris     *
374b5f4f4eSchris     * @return bool    true if cache can be used, false otherwise
384b5f4f4eSchris     */
394b5f4f4eSchris    function useCache($depends=array()) {
404b5f4f4eSchris        $this->depends = $depends;
410abe1d3eSchris        $this->_addDependencies();
424b5f4f4eSchris
434b5f4f4eSchris        if ($this->_event) {
444b5f4f4eSchris            return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
454b5f4f4eSchris        } else {
464b5f4f4eSchris            return $this->_stats($this->_useCache());
474b5f4f4eSchris        }
484b5f4f4eSchris    }
494b5f4f4eSchris
500abe1d3eSchris    /**
514b5f4f4eSchris     * private method containing cache use decision logic
524b5f4f4eSchris     *
530abe1d3eSchris     * this function processes the following keys in the depends array
540abe1d3eSchris     *   purge - force a purge on any non empty value
550abe1d3eSchris     *   age   - expire cache if older than age (seconds)
560abe1d3eSchris     *   files - expire cache if any file in this array was updated more recently than the cache
570abe1d3eSchris     *
580abe1d3eSchris     * can be overridden
594b5f4f4eSchris     *
604b5f4f4eSchris     * @return bool               see useCache()
614b5f4f4eSchris     */
624b5f4f4eSchris    function _useCache() {
634b5f4f4eSchris
640abe1d3eSchris        if (!empty($this->depends['purge'])) return false;              // purge requested?
654b5f4f4eSchris        if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
664b5f4f4eSchris
674b5f4f4eSchris        // cache too old?
684b5f4f4eSchris        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
694b5f4f4eSchris
704b5f4f4eSchris        if (!empty($this->depends['files'])) {
714b5f4f4eSchris            foreach ($this->depends['files'] as $file) {
724b5f4f4eSchris                if ($this->_time < @filemtime($file)) return false;         // cache older than files it depends on?
734b5f4f4eSchris            }
744b5f4f4eSchris        }
754b5f4f4eSchris
764b5f4f4eSchris        return true;
774b5f4f4eSchris    }
784b5f4f4eSchris
794b5f4f4eSchris    /**
800abe1d3eSchris     * add dependencies to the depends array
810abe1d3eSchris     *
820abe1d3eSchris     * this method should only add dependencies,
830abe1d3eSchris     * it should not remove any existing dependencies and
840abe1d3eSchris     * it should only overwrite a dependency when the new value is more stringent than the old
850abe1d3eSchris     */
860abe1d3eSchris    function _addDependencies() {
870abe1d3eSchris        if (isset($_REQUEST['purge'])) $this->depends['purge'] = true;   // purge requested
880abe1d3eSchris    }
890abe1d3eSchris
900abe1d3eSchris    /**
914b5f4f4eSchris     * retrieve the cached data
924b5f4f4eSchris     *
934b5f4f4eSchris     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
944b5f4f4eSchris     * @return  string          cache contents
954b5f4f4eSchris     */
964b5f4f4eSchris    function retrieveCache($clean=true) {
974b5f4f4eSchris        return io_readFile($this->cache, $clean);
984b5f4f4eSchris    }
994b5f4f4eSchris
1004b5f4f4eSchris    /**
1014b5f4f4eSchris     * cache $data
1024b5f4f4eSchris     *
1034b5f4f4eSchris     * @param   string $data   the data to be cached
104cbaf4259SChris Smith     * @return  bool           true on success, false otherwise
1054b5f4f4eSchris     */
1064b5f4f4eSchris    function storeCache($data) {
107cbaf4259SChris Smith        return io_savefile($this->cache, $data);
1084b5f4f4eSchris    }
1094b5f4f4eSchris
1104b5f4f4eSchris    /**
1114b5f4f4eSchris     * remove any cached data associated with this cache instance
1124b5f4f4eSchris     */
1134b5f4f4eSchris    function removeCache() {
1144b5f4f4eSchris        @unlink($this->cache);
1154b5f4f4eSchris    }
1164b5f4f4eSchris
1174b5f4f4eSchris    /**
11833c1bd61SBen Coburn     * Record cache hits statistics.
11933c1bd61SBen Coburn     * (Only when debugging allowed, to reduce overhead.)
1204b5f4f4eSchris     *
1214b5f4f4eSchris     * @param    bool   $success   result of this cache use attempt
1224b5f4f4eSchris     * @return   bool              pass-thru $success value
1234b5f4f4eSchris     */
1244b5f4f4eSchris    function _stats($success) {
1254b5f4f4eSchris        global $conf;
12649eb6e38SAndreas Gohr        static $stats = null;
1274b5f4f4eSchris        static $file;
1284b5f4f4eSchris
12933c1bd61SBen Coburn        if (!$conf['allowdebug']) { return $success; }
13033c1bd61SBen Coburn
1314b5f4f4eSchris        if (is_null($stats)) {
1324b5f4f4eSchris            $file = $conf['cachedir'].'/cache_stats.txt';
1334b5f4f4eSchris            $lines = explode("\n",io_readFile($file));
1344b5f4f4eSchris
1354b5f4f4eSchris            foreach ($lines as $line) {
1364b5f4f4eSchris                $i = strpos($line,',');
1374b5f4f4eSchris                $stats[substr($line,0,$i)] = $line;
1384b5f4f4eSchris            }
1394b5f4f4eSchris        }
1404b5f4f4eSchris
1414b5f4f4eSchris        if (isset($stats[$this->ext])) {
1420abe1d3eSchris            list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
1434b5f4f4eSchris        } else {
1444b5f4f4eSchris            $ext = $this->ext;
1454b5f4f4eSchris            $count = 0;
1460abe1d3eSchris            $hits = 0;
1474b5f4f4eSchris        }
1484b5f4f4eSchris
1494b5f4f4eSchris        $count++;
1500abe1d3eSchris        if ($success) $hits++;
1510abe1d3eSchris        $stats[$this->ext] = "$ext,$count,$hits";
1524b5f4f4eSchris
1534b5f4f4eSchris        io_saveFile($file,join("\n",$stats));
1544b5f4f4eSchris
1554b5f4f4eSchris        return $success;
1564b5f4f4eSchris    }
1574b5f4f4eSchris}
1584b5f4f4eSchris
1594b5f4f4eSchrisclass cache_parser extends cache {
1604b5f4f4eSchris
1614b5f4f4eSchris    var $file = '';       // source file for cache
1624b5f4f4eSchris    var $mode = '';       // input mode (represents the processing the input file will undergo)
1634b5f4f4eSchris
1644b5f4f4eSchris    var $_event = 'PARSER_CACHE_USE';
1654b5f4f4eSchris
1664b5f4f4eSchris    function cache_parser($id, $file, $mode) {
1674b5f4f4eSchris        if ($id) $this->page = $id;
1684b5f4f4eSchris        $this->file = $file;
1694b5f4f4eSchris        $this->mode = $mode;
1704b5f4f4eSchris
1714b5f4f4eSchris        parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
1724b5f4f4eSchris    }
1734b5f4f4eSchris
1744b5f4f4eSchris    function _useCache() {
1754b5f4f4eSchris
1764b5f4f4eSchris        if (!@file_exists($this->file)) return false;                   // source exists?
1770abe1d3eSchris        return parent::_useCache();
1780abe1d3eSchris    }
1794b5f4f4eSchris
1800abe1d3eSchris    function _addDependencies() {
181f8121585SChris Smith        global $conf, $config_cascade;
1820abe1d3eSchris
1830abe1d3eSchris        $this->depends['age'] = isset($this->depends['age']) ?
1840abe1d3eSchris            min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
1854b5f4f4eSchris
1864b5f4f4eSchris        // parser cache file dependencies ...
1874b5f4f4eSchris        $files = array($this->file,                                     // ... source
1884b5f4f4eSchris                DOKU_INC.'inc/parser/parser.php',                // ... parser
1894b5f4f4eSchris                DOKU_INC.'inc/parser/handler.php',               // ... handler
1904b5f4f4eSchris                );
191f8121585SChris Smith        $files = array_merge($files, getConfigFiles('main'));           // ... wiki settings
1924b5f4f4eSchris
1934b5f4f4eSchris        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
1940abe1d3eSchris        parent::_addDependencies();
1954b5f4f4eSchris    }
1964b5f4f4eSchris
1974b5f4f4eSchris}
1984b5f4f4eSchris
1994b5f4f4eSchrisclass cache_renderer extends cache_parser {
2004b5f4f4eSchris    function _useCache() {
201b9991f57Schris        global $conf;
2024b5f4f4eSchris
2030abe1d3eSchris        if (!parent::_useCache()) return false;
2044b5f4f4eSchris
205c0322273SAdrian Lang        if (!isset($this->page)) {
206c0322273SAdrian Lang            return true;
207c0322273SAdrian Lang        }
208c0322273SAdrian Lang
209c0322273SAdrian Lang        // check current link existence is consistent with cache version
210c0322273SAdrian Lang        // first check the purgefile
211c0322273SAdrian Lang        // - if the cache is more recent than the purgefile we know no links can have been updated
2122e3d6a01SKazutaka Miyasaka        if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
213c0322273SAdrian Lang            return true;
214c0322273SAdrian Lang        }
215c0322273SAdrian Lang
216ce6b63d9Schris        // for wiki pages, check metadata dependencies
217ce6b63d9Schris        $metadata = p_get_metadata($this->page);
2180abe1d3eSchris
219c0322273SAdrian Lang        if (!isset($metadata['relation']['references']) ||
2202e3d6a01SKazutaka Miyasaka                empty($metadata['relation']['references'])) {
221c0322273SAdrian Lang            return true;
222c0322273SAdrian Lang        }
2230abe1d3eSchris
224c0322273SAdrian Lang        foreach ($metadata['relation']['references'] as $id => $exists) {
225103c256aSChris Smith            if ($exists != page_exists($id,'',false)) return false;
2264b5f4f4eSchris        }
2274b5f4f4eSchris
2284b5f4f4eSchris        return true;
2294b5f4f4eSchris    }
2300abe1d3eSchris
2310abe1d3eSchris    function _addDependencies() {
2320abe1d3eSchris
2330abe1d3eSchris        // renderer cache file dependencies ...
2340abe1d3eSchris        $files = array(
2350abe1d3eSchris                DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
2360abe1d3eSchris                );
237ce6b63d9Schris
238ce6b63d9Schris        // page implies metadata and possibly some other dependencies
239ce6b63d9Schris        if (isset($this->page)) {
2400a69dff7Schris
241ce6b63d9Schris            $metafile = metaFN($this->page,'.meta');
242ce6b63d9Schris            $files[] = $metafile;                                       // ... the page's own metadata
2430a69dff7Schris
244*98214867SMichael Hamann            $valid = p_get_metadata($this->page, 'date valid');         // for xhtml this will render the metadata if needed
2450a69dff7Schris            if (!empty($valid['age'])) {
2460a69dff7Schris                $this->depends['age'] = isset($this->depends['age']) ?
2470a69dff7Schris                    min($this->depends['age'],$valid['age']) : $valid['age'];
2480a69dff7Schris            }
249ce6b63d9Schris        }
2500abe1d3eSchris
2510abe1d3eSchris        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
2520abe1d3eSchris        parent::_addDependencies();
2530abe1d3eSchris    }
2544b5f4f4eSchris}
2554b5f4f4eSchris
2564b5f4f4eSchrisclass cache_instructions extends cache_parser {
2574b5f4f4eSchris
2584b5f4f4eSchris    function cache_instructions($id, $file) {
2594b5f4f4eSchris        parent::cache_parser($id, $file, 'i');
2604b5f4f4eSchris    }
2614b5f4f4eSchris
262c66972f2SAdrian Lang    function retrieveCache($clean=true) {
2634b5f4f4eSchris        $contents = io_readFile($this->cache, false);
2644b5f4f4eSchris        return !empty($contents) ? unserialize($contents) : array();
2654b5f4f4eSchris    }
2664b5f4f4eSchris
2674b5f4f4eSchris    function storeCache($instructions) {
268cbaf4259SChris Smith        return io_savefile($this->cache,serialize($instructions));
2694b5f4f4eSchris    }
2704b5f4f4eSchris}
271