xref: /dokuwiki/inc/Cache/Cache.php (revision cbb44eabe033d70affb048ec0daf4e579e09dd20)
10db5771eSMichael Große<?php
20db5771eSMichael Große
30db5771eSMichael Großenamespace dokuwiki\Cache;
40db5771eSMichael Große
5b4b0b31bSMichael Großeuse \dokuwiki\Debug\PropertyDeprecationHelper;
6*cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
7b4b0b31bSMichael Große
80db5771eSMichael Große/**
90db5771eSMichael Große * Generic handling of caching
100db5771eSMichael Große */
110db5771eSMichael Großeclass Cache
120db5771eSMichael Große{
13b4b0b31bSMichael Große    use PropertyDeprecationHelper;
14b4b0b31bSMichael Große
150db5771eSMichael Große    public $key = '';          // primary identifier for this item
160db5771eSMichael Große    public $ext = '';          // file ext for cache data, secondary identifier for this item
170db5771eSMichael Große    public $cache = '';        // cache file name
180db5771eSMichael Große    public $depends = array(); // array containing cache dependency information,
1972c2bae8SMichael Große    //   used by makeDefaultCacheDecision to determine cache validity
200db5771eSMichael Große
21b4b0b31bSMichael Große    // phpcs:disable
22b4b0b31bSMichael Große    /**
23b4b0b31bSMichael Große     * @deprecated since 2019-02-02 use the respective getters instead!
24b4b0b31bSMichael Große     */
25b4b0b31bSMichael Große    protected $_event = '';       // event to be triggered during useCache
26b4b0b31bSMichael Große    protected $_time;
27b4b0b31bSMichael Große    protected $_nocache = false;  // if set to true, cache will not be used or stored
28b4b0b31bSMichael Große    // phpcs:enable
290db5771eSMichael Große
300db5771eSMichael Große    /**
310db5771eSMichael Große     * @param string $key primary identifier
320db5771eSMichael Große     * @param string $ext file extension
330db5771eSMichael Große     */
340db5771eSMichael Große    public function __construct($key, $ext)
350db5771eSMichael Große    {
360db5771eSMichael Große        $this->key = $key;
370db5771eSMichael Große        $this->ext = $ext;
380db5771eSMichael Große        $this->cache = getCacheName($key, $ext);
390db5771eSMichael Große
400db5771eSMichael Große        /**
41d2f1d7a1SMichael Große         * @deprecated since 2019-02-02 use the respective getters instead!
42d2f1d7a1SMichael Große         */
43b4b0b31bSMichael Große        $this->deprecatePublicProperty('_event');
44b4b0b31bSMichael Große        $this->deprecatePublicProperty('_time');
45b4b0b31bSMichael Große        $this->deprecatePublicProperty('_nocache');
46d2f1d7a1SMichael Große    }
47d2f1d7a1SMichael Große
48d2f1d7a1SMichael Große    public function getTime()
49d2f1d7a1SMichael Große    {
50b4b0b31bSMichael Große        return $this->_time;
51d2f1d7a1SMichael Große    }
52d2f1d7a1SMichael Große
53d2f1d7a1SMichael Große    public function getEvent()
54d2f1d7a1SMichael Große    {
55b4b0b31bSMichael Große        return $this->_event;
56d2f1d7a1SMichael Große    }
57d2f1d7a1SMichael Große
58*cbb44eabSAndreas Gohr    public function setEvent($event)
59d2f1d7a1SMichael Große    {
60*cbb44eabSAndreas Gohr        $this->_event = $event;
61d2f1d7a1SMichael Große    }
62d2f1d7a1SMichael Große
63d2f1d7a1SMichael Große    /**
640db5771eSMichael Große     * public method to determine whether the cache can be used
650db5771eSMichael Große     *
660db5771eSMichael Große     * to assist in centralisation of event triggering and calculation of cache statistics,
6772c2bae8SMichael Große     * don't override this function override makeDefaultCacheDecision()
680db5771eSMichael Große     *
690db5771eSMichael Große     * @param  array $depends array of cache dependencies, support dependecies:
700db5771eSMichael Große     *                            'age'   => max age of the cache in seconds
710db5771eSMichael Große     *                            'files' => cache must be younger than mtime of each file
720db5771eSMichael Große     *                                       (nb. dependency passes if file doesn't exist)
730db5771eSMichael Große     *
740db5771eSMichael Große     * @return bool    true if cache can be used, false otherwise
750db5771eSMichael Große     */
760db5771eSMichael Große    public function useCache($depends = array())
770db5771eSMichael Große    {
780db5771eSMichael Große        $this->depends = $depends;
7942c00b45SMichael Große        $this->addDependencies();
800db5771eSMichael Große
81b4b0b31bSMichael Große        if ($this->_event) {
82*cbb44eabSAndreas Gohr            return $this->stats(Event::createAndTrigger($this->_event, $this, array($this, 'makeDefaultCacheDecision')));
830db5771eSMichael Große        } else {
8472c2bae8SMichael Große            return $this->stats($this->makeDefaultCacheDecision());
850db5771eSMichael Große        }
860db5771eSMichael Große    }
870db5771eSMichael Große
880db5771eSMichael Große    /**
8943ff1056SMichael Große     * internal method containing cache use decision logic
900db5771eSMichael Große     *
910db5771eSMichael Große     * this function processes the following keys in the depends array
920db5771eSMichael Große     *   purge - force a purge on any non empty value
930db5771eSMichael Große     *   age   - expire cache if older than age (seconds)
940db5771eSMichael Große     *   files - expire cache if any file in this array was updated more recently than the cache
950db5771eSMichael Große     *
960db5771eSMichael Große     * Note that this function needs to be public as it is used as callback for the event handler
970db5771eSMichael Große     *
980db5771eSMichael Große     * can be overridden
990db5771eSMichael Große     *
10042c00b45SMichael Große     * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead!
10142c00b45SMichael Große     *
1020db5771eSMichael Große     * @return bool               see useCache()
1030db5771eSMichael Große     */
10472c2bae8SMichael Große    public function makeDefaultCacheDecision()
1050db5771eSMichael Große    {
1060db5771eSMichael Große
107b4b0b31bSMichael Große        if ($this->_nocache) {
1080db5771eSMichael Große            return false;
1090db5771eSMichael Große        }                              // caching turned off
1100db5771eSMichael Große        if (!empty($this->depends['purge'])) {
1110db5771eSMichael Große            return false;
1120db5771eSMichael Große        }              // purge requested?
113b4b0b31bSMichael Große        if (!($this->_time = @filemtime($this->cache))) {
1140db5771eSMichael Große            return false;
1150db5771eSMichael Große        }   // cache exists?
1160db5771eSMichael Große
1170db5771eSMichael Große        // cache too old?
118b4b0b31bSMichael Große        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) {
1190db5771eSMichael Große            return false;
1200db5771eSMichael Große        }
1210db5771eSMichael Große
1220db5771eSMichael Große        if (!empty($this->depends['files'])) {
1230db5771eSMichael Große            foreach ($this->depends['files'] as $file) {
124b4b0b31bSMichael Große                if ($this->_time <= @filemtime($file)) {
1250db5771eSMichael Große                    return false;
1260db5771eSMichael Große                }         // cache older than files it depends on?
1270db5771eSMichael Große            }
1280db5771eSMichael Große        }
1290db5771eSMichael Große
1300db5771eSMichael Große        return true;
1310db5771eSMichael Große    }
1320db5771eSMichael Große
1330db5771eSMichael Große    /**
1340db5771eSMichael Große     * add dependencies to the depends array
1350db5771eSMichael Große     *
1360db5771eSMichael Große     * this method should only add dependencies,
1370db5771eSMichael Große     * it should not remove any existing dependencies and
1380db5771eSMichael Große     * it should only overwrite a dependency when the new value is more stringent than the old
1390db5771eSMichael Große     */
14042c00b45SMichael Große    protected function addDependencies()
1410db5771eSMichael Große    {
1420db5771eSMichael Große        global $INPUT;
1430db5771eSMichael Große        if ($INPUT->has('purge')) {
1440db5771eSMichael Große            $this->depends['purge'] = true;
1450db5771eSMichael Große        }   // purge requested
1460db5771eSMichael Große    }
1470db5771eSMichael Große
1480db5771eSMichael Große    /**
1490db5771eSMichael Große     * retrieve the cached data
1500db5771eSMichael Große     *
1510db5771eSMichael Große     * @param   bool $clean true to clean line endings, false to leave line endings alone
1520db5771eSMichael Große     * @return  string          cache contents
1530db5771eSMichael Große     */
1540db5771eSMichael Große    public function retrieveCache($clean = true)
1550db5771eSMichael Große    {
1560db5771eSMichael Große        return io_readFile($this->cache, $clean);
1570db5771eSMichael Große    }
1580db5771eSMichael Große
1590db5771eSMichael Große    /**
1600db5771eSMichael Große     * cache $data
1610db5771eSMichael Große     *
1620db5771eSMichael Große     * @param   string $data the data to be cached
1630db5771eSMichael Große     * @return  bool           true on success, false otherwise
1640db5771eSMichael Große     */
1650db5771eSMichael Große    public function storeCache($data)
1660db5771eSMichael Große    {
167b4b0b31bSMichael Große        if ($this->_nocache) {
1680db5771eSMichael Große            return false;
1690db5771eSMichael Große        }
1700db5771eSMichael Große
1710db5771eSMichael Große        return io_savefile($this->cache, $data);
1720db5771eSMichael Große    }
1730db5771eSMichael Große
1740db5771eSMichael Große    /**
1750db5771eSMichael Große     * remove any cached data associated with this cache instance
1760db5771eSMichael Große     */
1770db5771eSMichael Große    public function removeCache()
1780db5771eSMichael Große    {
1790db5771eSMichael Große        @unlink($this->cache);
1800db5771eSMichael Große    }
1810db5771eSMichael Große
1820db5771eSMichael Große    /**
1830db5771eSMichael Große     * Record cache hits statistics.
1840db5771eSMichael Große     * (Only when debugging allowed, to reduce overhead.)
1850db5771eSMichael Große     *
1860db5771eSMichael Große     * @param    bool $success result of this cache use attempt
1870db5771eSMichael Große     * @return   bool              pass-thru $success value
1880db5771eSMichael Große     */
18942c00b45SMichael Große    protected function stats($success)
1900db5771eSMichael Große    {
1910db5771eSMichael Große        global $conf;
1920db5771eSMichael Große        static $stats = null;
1930db5771eSMichael Große        static $file;
1940db5771eSMichael Große
1950db5771eSMichael Große        if (!$conf['allowdebug']) {
1960db5771eSMichael Große            return $success;
1970db5771eSMichael Große        }
1980db5771eSMichael Große
1990db5771eSMichael Große        if (is_null($stats)) {
2000db5771eSMichael Große            $file = $conf['cachedir'] . '/cache_stats.txt';
2010db5771eSMichael Große            $lines = explode("\n", io_readFile($file));
2020db5771eSMichael Große
2030db5771eSMichael Große            foreach ($lines as $line) {
2040db5771eSMichael Große                $i = strpos($line, ',');
2050db5771eSMichael Große                $stats[substr($line, 0, $i)] = $line;
2060db5771eSMichael Große            }
2070db5771eSMichael Große        }
2080db5771eSMichael Große
2090db5771eSMichael Große        if (isset($stats[$this->ext])) {
2100db5771eSMichael Große            list($ext, $count, $hits) = explode(',', $stats[$this->ext]);
2110db5771eSMichael Große        } else {
2120db5771eSMichael Große            $ext = $this->ext;
2130db5771eSMichael Große            $count = 0;
2140db5771eSMichael Große            $hits = 0;
2150db5771eSMichael Große        }
2160db5771eSMichael Große
2170db5771eSMichael Große        $count++;
2180db5771eSMichael Große        if ($success) {
2190db5771eSMichael Große            $hits++;
2200db5771eSMichael Große        }
2210db5771eSMichael Große        $stats[$this->ext] = "$ext,$count,$hits";
2220db5771eSMichael Große
2230db5771eSMichael Große        io_saveFile($file, join("\n", $stats));
2240db5771eSMichael Große
2250db5771eSMichael Große        return $success;
2260db5771eSMichael Große    }
227d2f1d7a1SMichael Große
228d2f1d7a1SMichael Große    /**
229d2f1d7a1SMichael Große     * @return bool
230d2f1d7a1SMichael Große     */
231d2f1d7a1SMichael Große    public function isNoCache()
232d2f1d7a1SMichael Große    {
233b4b0b31bSMichael Große        return $this->_nocache;
234d2f1d7a1SMichael Große    }
2350db5771eSMichael Große}
236