xref: /dokuwiki/inc/cache.php (revision 64159a61e94d0ce680071c8890e144982c3a8cbe)
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
9cefd14cbSGerrit Uitslag/**
10cefd14cbSGerrit Uitslag * Generic handling of caching
11cefd14cbSGerrit Uitslag */
124b5f4f4eSchrisclass cache {
13c59b3e00SGerrit Uitslag    public $key = '';          // primary identifier for this item
14c59b3e00SGerrit Uitslag    public $ext = '';          // file ext for cache data, secondary identifier for this item
15c59b3e00SGerrit Uitslag    public $cache = '';        // cache file name
16c59b3e00SGerrit Uitslag    public $depends = array(); // array containing cache dependency information,
170abe1d3eSchris                               //   used by _useCache to determine cache validity
184b5f4f4eSchris
199c9753d6SAndreas Gohr    public $_event = '';       // event to be triggered during useCache
209c9753d6SAndreas Gohr    public $_time;
219c9753d6SAndreas Gohr    public $_nocache = false;  // if set to true, cache will not be used or stored
224b5f4f4eSchris
23c59b3e00SGerrit Uitslag    /**
24c59b3e00SGerrit Uitslag     * @param string $key primary identifier
25c59b3e00SGerrit Uitslag     * @param string $ext file extension
26c59b3e00SGerrit Uitslag     */
275c3b310dSChristopher Smith    public function __construct($key,$ext) {
284b5f4f4eSchris        $this->key = $key;
294b5f4f4eSchris        $this->ext = $ext;
304b5f4f4eSchris        $this->cache = getCacheName($key,$ext);
314b5f4f4eSchris    }
324b5f4f4eSchris
334b5f4f4eSchris    /**
344b5f4f4eSchris     * public method to determine whether the cache can be used
354b5f4f4eSchris     *
36b2356002SChristopher Smith     * to assist in centralisation of event triggering and calculation of cache statistics,
374b5f4f4eSchris     * don't override this function override _useCache()
384b5f4f4eSchris     *
394b5f4f4eSchris     * @param  array   $depends   array of cache dependencies, support dependecies:
404b5f4f4eSchris     *                            'age'   => max age of the cache in seconds
414b5f4f4eSchris     *                            'files' => cache must be younger than mtime of each file
42ce6b63d9Schris     *                                       (nb. dependency passes if file doesn't exist)
434b5f4f4eSchris     *
444b5f4f4eSchris     * @return bool    true if cache can be used, false otherwise
454b5f4f4eSchris     */
46c59b3e00SGerrit Uitslag    public function useCache($depends=array()) {
474b5f4f4eSchris        $this->depends = $depends;
480abe1d3eSchris        $this->_addDependencies();
494b5f4f4eSchris
504b5f4f4eSchris        if ($this->_event) {
514b5f4f4eSchris            return $this->_stats(trigger_event($this->_event, $this, array($this,'_useCache')));
524b5f4f4eSchris        } else {
534b5f4f4eSchris            return $this->_stats($this->_useCache());
544b5f4f4eSchris        }
554b5f4f4eSchris    }
564b5f4f4eSchris
570abe1d3eSchris    /**
584b5f4f4eSchris     * private method containing cache use decision logic
594b5f4f4eSchris     *
600abe1d3eSchris     * this function processes the following keys in the depends array
610abe1d3eSchris     *   purge - force a purge on any non empty value
620abe1d3eSchris     *   age   - expire cache if older than age (seconds)
630abe1d3eSchris     *   files - expire cache if any file in this array was updated more recently than the cache
640abe1d3eSchris     *
65a8795974SMichael Hamann     * Note that this function needs to be public as it is used as callback for the event handler
66a8795974SMichael Hamann     *
670abe1d3eSchris     * can be overridden
684b5f4f4eSchris     *
694b5f4f4eSchris     * @return bool               see useCache()
704b5f4f4eSchris     */
71a8795974SMichael Hamann    public function _useCache() {
724b5f4f4eSchris
73b2356002SChristopher Smith        if ($this->_nocache) return false;                              // caching turned off
740abe1d3eSchris        if (!empty($this->depends['purge'])) return false;              // purge requested?
754b5f4f4eSchris        if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
764b5f4f4eSchris
774b5f4f4eSchris        // cache too old?
784b5f4f4eSchris        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
794b5f4f4eSchris
804b5f4f4eSchris        if (!empty($this->depends['files'])) {
814b5f4f4eSchris            foreach ($this->depends['files'] as $file) {
82a257b0bdSMartin Doucha                if ($this->_time <= @filemtime($file)) return false;         // cache older than files it depends on?
834b5f4f4eSchris            }
844b5f4f4eSchris        }
854b5f4f4eSchris
864b5f4f4eSchris        return true;
874b5f4f4eSchris    }
884b5f4f4eSchris
894b5f4f4eSchris    /**
900abe1d3eSchris     * add dependencies to the depends array
910abe1d3eSchris     *
920abe1d3eSchris     * this method should only add dependencies,
930abe1d3eSchris     * it should not remove any existing dependencies and
940abe1d3eSchris     * it should only overwrite a dependency when the new value is more stringent than the old
950abe1d3eSchris     */
96c59b3e00SGerrit Uitslag    protected function _addDependencies() {
977d01a0eaSTom N Harris        global $INPUT;
987d01a0eaSTom N Harris        if ($INPUT->has('purge')) $this->depends['purge'] = true;   // purge requested
990abe1d3eSchris    }
1000abe1d3eSchris
1010abe1d3eSchris    /**
1024b5f4f4eSchris     * retrieve the cached data
1034b5f4f4eSchris     *
1044b5f4f4eSchris     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
1054b5f4f4eSchris     * @return  string          cache contents
1064b5f4f4eSchris     */
107c59b3e00SGerrit Uitslag    public function retrieveCache($clean=true) {
1084b5f4f4eSchris        return io_readFile($this->cache, $clean);
1094b5f4f4eSchris    }
1104b5f4f4eSchris
1114b5f4f4eSchris    /**
1124b5f4f4eSchris     * cache $data
1134b5f4f4eSchris     *
1144b5f4f4eSchris     * @param   string $data   the data to be cached
115cbaf4259SChris Smith     * @return  bool           true on success, false otherwise
1164b5f4f4eSchris     */
117c59b3e00SGerrit Uitslag    public function storeCache($data) {
118b2356002SChristopher Smith        if ($this->_nocache) return false;
119b2356002SChristopher Smith
120cbaf4259SChris Smith        return io_savefile($this->cache, $data);
1214b5f4f4eSchris    }
1224b5f4f4eSchris
1234b5f4f4eSchris    /**
1244b5f4f4eSchris     * remove any cached data associated with this cache instance
1254b5f4f4eSchris     */
126c59b3e00SGerrit Uitslag    public function removeCache() {
1274b5f4f4eSchris        @unlink($this->cache);
1284b5f4f4eSchris    }
1294b5f4f4eSchris
1304b5f4f4eSchris    /**
13133c1bd61SBen Coburn     * Record cache hits statistics.
13233c1bd61SBen Coburn     * (Only when debugging allowed, to reduce overhead.)
1334b5f4f4eSchris     *
1344b5f4f4eSchris     * @param    bool   $success   result of this cache use attempt
1354b5f4f4eSchris     * @return   bool              pass-thru $success value
1364b5f4f4eSchris     */
137c59b3e00SGerrit Uitslag    protected function _stats($success) {
1384b5f4f4eSchris        global $conf;
13949eb6e38SAndreas Gohr        static $stats = null;
1404b5f4f4eSchris        static $file;
1414b5f4f4eSchris
14233c1bd61SBen Coburn        if (!$conf['allowdebug']) { return $success; }
14333c1bd61SBen Coburn
1444b5f4f4eSchris        if (is_null($stats)) {
1454b5f4f4eSchris            $file = $conf['cachedir'].'/cache_stats.txt';
1464b5f4f4eSchris            $lines = explode("\n",io_readFile($file));
1474b5f4f4eSchris
1484b5f4f4eSchris            foreach ($lines as $line) {
1494b5f4f4eSchris                $i = strpos($line,',');
1504b5f4f4eSchris                $stats[substr($line,0,$i)] = $line;
1514b5f4f4eSchris            }
1524b5f4f4eSchris        }
1534b5f4f4eSchris
1544b5f4f4eSchris        if (isset($stats[$this->ext])) {
1550abe1d3eSchris            list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
1564b5f4f4eSchris        } else {
1574b5f4f4eSchris            $ext = $this->ext;
1584b5f4f4eSchris            $count = 0;
1590abe1d3eSchris            $hits = 0;
1604b5f4f4eSchris        }
1614b5f4f4eSchris
1624b5f4f4eSchris        $count++;
1630abe1d3eSchris        if ($success) $hits++;
1640abe1d3eSchris        $stats[$this->ext] = "$ext,$count,$hits";
1654b5f4f4eSchris
1664b5f4f4eSchris        io_saveFile($file,join("\n",$stats));
1674b5f4f4eSchris
1684b5f4f4eSchris        return $success;
1694b5f4f4eSchris    }
1704b5f4f4eSchris}
1714b5f4f4eSchris
172cefd14cbSGerrit Uitslag/**
173cefd14cbSGerrit Uitslag * Parser caching
174cefd14cbSGerrit Uitslag */
1754b5f4f4eSchrisclass cache_parser extends cache {
1764b5f4f4eSchris
177c59b3e00SGerrit Uitslag    public $file = '';       // source file for cache
178c59b3e00SGerrit Uitslag    public $mode = '';       // input mode (represents the processing the input file will undergo)
1798f3419f5SAndreas Gohr    public $page = '';
1804b5f4f4eSchris
1819c9753d6SAndreas Gohr    public $_event = 'PARSER_CACHE_USE';
1824b5f4f4eSchris
183c59b3e00SGerrit Uitslag    /**
184c59b3e00SGerrit Uitslag     *
185c59b3e00SGerrit Uitslag     * @param string $id page id
186c59b3e00SGerrit Uitslag     * @param string $file source file for cache
187c59b3e00SGerrit Uitslag     * @param string $mode input mode
188c59b3e00SGerrit Uitslag     */
1892863d103SChristopher Smith    public function __construct($id, $file, $mode) {
1904b5f4f4eSchris        if ($id) $this->page = $id;
1914b5f4f4eSchris        $this->file = $file;
1924b5f4f4eSchris        $this->mode = $mode;
1934b5f4f4eSchris
1941cb97a10SChristopher Smith        parent::__construct($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
1954b5f4f4eSchris    }
1964b5f4f4eSchris
197c59b3e00SGerrit Uitslag    /**
198c59b3e00SGerrit Uitslag     * method contains cache use decision logic
199c59b3e00SGerrit Uitslag     *
200c59b3e00SGerrit Uitslag     * @return bool               see useCache()
201c59b3e00SGerrit Uitslag     */
202a8795974SMichael Hamann    public function _useCache() {
2034b5f4f4eSchris
20479e79377SAndreas Gohr        if (!file_exists($this->file)) return false;                   // source exists?
2050abe1d3eSchris        return parent::_useCache();
2060abe1d3eSchris    }
2074b5f4f4eSchris
208c59b3e00SGerrit Uitslag    protected function _addDependencies() {
2094b5f4f4eSchris
2104b5f4f4eSchris        // parser cache file dependencies ...
2114b5f4f4eSchris        $files = array($this->file,                              // ... source
2124b5f4f4eSchris                DOKU_INC.'inc/parser/parser.php',                // ... parser
2134b5f4f4eSchris                DOKU_INC.'inc/parser/handler.php',               // ... handler
2144b5f4f4eSchris                );
215f8121585SChris Smith        $files = array_merge($files, getConfigFiles('main'));    // ... wiki settings
2164b5f4f4eSchris
217*64159a61SAndreas Gohr        $this->depends['files'] = !empty($this->depends['files']) ?
218*64159a61SAndreas Gohr            array_merge($files, $this->depends['files']) :
219*64159a61SAndreas Gohr            $files;
2200abe1d3eSchris        parent::_addDependencies();
2214b5f4f4eSchris    }
2224b5f4f4eSchris
2234b5f4f4eSchris}
2244b5f4f4eSchris
225cefd14cbSGerrit Uitslag/**
226cefd14cbSGerrit Uitslag * Caching of data of renderer
227cefd14cbSGerrit Uitslag */
2284b5f4f4eSchrisclass cache_renderer extends cache_parser {
229c59b3e00SGerrit Uitslag
230c59b3e00SGerrit Uitslag    /**
231c59b3e00SGerrit Uitslag     * method contains cache use decision logic
232c59b3e00SGerrit Uitslag     *
233c59b3e00SGerrit Uitslag     * @return bool               see useCache()
234c59b3e00SGerrit Uitslag     */
235a8795974SMichael Hamann    public function _useCache() {
236b9991f57Schris        global $conf;
2374b5f4f4eSchris
2380abe1d3eSchris        if (!parent::_useCache()) return false;
2394b5f4f4eSchris
240c0322273SAdrian Lang        if (!isset($this->page)) {
241c0322273SAdrian Lang            return true;
242c0322273SAdrian Lang        }
243c0322273SAdrian Lang
244*64159a61SAndreas Gohr        // meta cache older than file it depends on?
245*64159a61SAndreas Gohr        if ($this->_time < @filemtime(metaFN($this->page,'.meta'))) return false;
2462302d6beSMartin Doucha
247c0322273SAdrian Lang        // check current link existence is consistent with cache version
248c0322273SAdrian Lang        // first check the purgefile
249c0322273SAdrian Lang        // - if the cache is more recent than the purgefile we know no links can have been updated
2502e3d6a01SKazutaka Miyasaka        if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
251c0322273SAdrian Lang            return true;
252c0322273SAdrian Lang        }
253c0322273SAdrian Lang
254ce6b63d9Schris        // for wiki pages, check metadata dependencies
255ce6b63d9Schris        $metadata = p_get_metadata($this->page);
2560abe1d3eSchris
257c0322273SAdrian Lang        if (!isset($metadata['relation']['references']) ||
2582e3d6a01SKazutaka Miyasaka                empty($metadata['relation']['references'])) {
259c0322273SAdrian Lang            return true;
260c0322273SAdrian Lang        }
2610abe1d3eSchris
262c0322273SAdrian Lang        foreach ($metadata['relation']['references'] as $id => $exists) {
263103c256aSChris Smith            if ($exists != page_exists($id,'',false)) return false;
2644b5f4f4eSchris        }
2654b5f4f4eSchris
2664b5f4f4eSchris        return true;
2674b5f4f4eSchris    }
2680abe1d3eSchris
269c59b3e00SGerrit Uitslag    protected function _addDependencies() {
270b2356002SChristopher Smith        global $conf;
271b2356002SChristopher Smith
272b2356002SChristopher Smith        // default renderer cache file 'age' is dependent on 'cachetime' setting, two special values:
273b2356002SChristopher Smith        //    -1 : do not cache (should not be overridden)
274b2356002SChristopher Smith        //    0  : cache never expires (can be overridden) - no need to set depends['age']
275b2356002SChristopher Smith        if ($conf['cachetime'] == -1) {
276b2356002SChristopher Smith            $this->_nocache = true;
277b2356002SChristopher Smith            return;
278b2356002SChristopher Smith        } elseif ($conf['cachetime'] > 0) {
279b2356002SChristopher Smith            $this->depends['age'] = isset($this->depends['age']) ?
280b2356002SChristopher Smith                min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
281b2356002SChristopher Smith        }
2820abe1d3eSchris
2830abe1d3eSchris        // renderer cache file dependencies ...
2840abe1d3eSchris        $files = array(
2850abe1d3eSchris                DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
2860abe1d3eSchris                );
287ce6b63d9Schris
288ce6b63d9Schris        // page implies metadata and possibly some other dependencies
289ce6b63d9Schris        if (isset($this->page)) {
2900a69dff7Schris
291*64159a61SAndreas Gohr            // for xhtml this will render the metadata if needed
292*64159a61SAndreas Gohr            $valid = p_get_metadata($this->page, 'date valid');
2930a69dff7Schris            if (!empty($valid['age'])) {
2940a69dff7Schris                $this->depends['age'] = isset($this->depends['age']) ?
2950a69dff7Schris                    min($this->depends['age'],$valid['age']) : $valid['age'];
2960a69dff7Schris            }
297ce6b63d9Schris        }
2980abe1d3eSchris
299*64159a61SAndreas Gohr        $this->depends['files'] = !empty($this->depends['files']) ?
300*64159a61SAndreas Gohr            array_merge($files, $this->depends['files']) :
301*64159a61SAndreas Gohr            $files;
302*64159a61SAndreas Gohr
3030abe1d3eSchris        parent::_addDependencies();
3040abe1d3eSchris    }
3054b5f4f4eSchris}
3064b5f4f4eSchris
307cefd14cbSGerrit Uitslag/**
308cefd14cbSGerrit Uitslag * Caching of parser instructions
309cefd14cbSGerrit Uitslag */
3104b5f4f4eSchrisclass cache_instructions extends cache_parser {
3114b5f4f4eSchris
312c59b3e00SGerrit Uitslag    /**
313c59b3e00SGerrit Uitslag     * @param string $id page id
314c59b3e00SGerrit Uitslag     * @param string $file source file for cache
315c59b3e00SGerrit Uitslag     */
31679eec18fSChristopher Smith    public function __construct($id, $file) {
3171cb97a10SChristopher Smith        parent::__construct($id, $file, 'i');
3184b5f4f4eSchris    }
3194b5f4f4eSchris
320c59b3e00SGerrit Uitslag    /**
321c59b3e00SGerrit Uitslag     * retrieve the cached data
322c59b3e00SGerrit Uitslag     *
323c59b3e00SGerrit Uitslag     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
324e0c26282SGerrit Uitslag     * @return  array          cache contents
325c59b3e00SGerrit Uitslag     */
326c59b3e00SGerrit Uitslag    public function retrieveCache($clean=true) {
3274b5f4f4eSchris        $contents = io_readFile($this->cache, false);
3284b5f4f4eSchris        return !empty($contents) ? unserialize($contents) : array();
3294b5f4f4eSchris    }
3304b5f4f4eSchris
331c59b3e00SGerrit Uitslag    /**
332c59b3e00SGerrit Uitslag     * cache $instructions
333c59b3e00SGerrit Uitslag     *
334e0c26282SGerrit Uitslag     * @param   array $instructions  the instruction to be cached
335c59b3e00SGerrit Uitslag     * @return  bool                  true on success, false otherwise
336c59b3e00SGerrit Uitslag     */
337c59b3e00SGerrit Uitslag    public function storeCache($instructions) {
338b2356002SChristopher Smith        if ($this->_nocache) return false;
339b2356002SChristopher Smith
340cbaf4259SChris Smith        return io_savefile($this->cache,serialize($instructions));
3414b5f4f4eSchris    }
3424b5f4f4eSchris}
343