xref: /dokuwiki/inc/Cache/Cache.php (revision a95427a565a82aca45ae276c0f1d330606f6f9b4)
10db5771eSMichael Große<?php
20db5771eSMichael Große
30db5771eSMichael Großenamespace dokuwiki\Cache;
40db5771eSMichael Große
5903d43feSMichael Großeuse dokuwiki\Debug\PropertyDeprecationHelper;
6cbb44eabSAndreas 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
18*a95427a5SAndreas Gohr    public $depends = []; // 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
58cbb44eabSAndreas Gohr    public function setEvent($event)
59d2f1d7a1SMichael Große    {
60cbb44eabSAndreas 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     */
76*a95427a5SAndreas Gohr    public function useCache($depends = [])
770db5771eSMichael Große    {
780db5771eSMichael Große        $this->depends = $depends;
7942c00b45SMichael Große        $this->addDependencies();
800db5771eSMichael Große
8130ee8867Skalenpw        if ($this->getEvent()) {
82033eb35bSkalenpw            return $this->stats(
83033eb35bSkalenpw                Event::createAndTrigger(
8430ee8867Skalenpw                    $this->getEvent(),
85033eb35bSkalenpw                    $this,
86*a95427a5SAndreas Gohr                    [$this, 'makeDefaultCacheDecision']
87033eb35bSkalenpw                )
8827f63a23SAndreas Gohr            );
890db5771eSMichael Große        }
9027f63a23SAndreas Gohr
9127f63a23SAndreas Gohr        return $this->stats($this->makeDefaultCacheDecision());
920db5771eSMichael Große    }
930db5771eSMichael Große
940db5771eSMichael Große    /**
9543ff1056SMichael Große     * internal method containing cache use decision logic
960db5771eSMichael Große     *
970db5771eSMichael Große     * this function processes the following keys in the depends array
980db5771eSMichael Große     *   purge - force a purge on any non empty value
990db5771eSMichael Große     *   age   - expire cache if older than age (seconds)
1000db5771eSMichael Große     *   files - expire cache if any file in this array was updated more recently than the cache
1010db5771eSMichael Große     *
1020db5771eSMichael Große     * Note that this function needs to be public as it is used as callback for the event handler
1030db5771eSMichael Große     *
1040db5771eSMichael Große     * can be overridden
1050db5771eSMichael Große     *
10642c00b45SMichael Große     * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead!
10742c00b45SMichael Große     *
1080db5771eSMichael Große     * @return bool               see useCache()
1090db5771eSMichael Große     */
11072c2bae8SMichael Große    public function makeDefaultCacheDecision()
1110db5771eSMichael Große    {
112b4b0b31bSMichael Große        if ($this->_nocache) {
1130db5771eSMichael Große            return false;
1140db5771eSMichael Große        }                              // caching turned off
1150db5771eSMichael Große        if (!empty($this->depends['purge'])) {
1160db5771eSMichael Große            return false;
1170db5771eSMichael Große        }              // purge requested?
118b4b0b31bSMichael Große        if (!($this->_time = @filemtime($this->cache))) {
1190db5771eSMichael Große            return false;
1200db5771eSMichael Große        }   // cache exists?
1210db5771eSMichael Große
1220db5771eSMichael Große        // cache too old?
123b4b0b31bSMichael Große        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) {
1240db5771eSMichael Große            return false;
1250db5771eSMichael Große        }
1260db5771eSMichael Große
1270db5771eSMichael Große        if (!empty($this->depends['files'])) {
1280db5771eSMichael Große            foreach ($this->depends['files'] as $file) {
129b4b0b31bSMichael Große                if ($this->_time <= @filemtime($file)) {
1300db5771eSMichael Große                    return false;
1310db5771eSMichael Große                }         // cache older than files it depends on?
1320db5771eSMichael Große            }
1330db5771eSMichael Große        }
1340db5771eSMichael Große
1350db5771eSMichael Große        return true;
1360db5771eSMichael Große    }
1370db5771eSMichael Große
1380db5771eSMichael Große    /**
1390db5771eSMichael Große     * add dependencies to the depends array
1400db5771eSMichael Große     *
1410db5771eSMichael Große     * this method should only add dependencies,
1420db5771eSMichael Große     * it should not remove any existing dependencies and
1430db5771eSMichael Große     * it should only overwrite a dependency when the new value is more stringent than the old
1440db5771eSMichael Große     */
14542c00b45SMichael Große    protected function addDependencies()
1460db5771eSMichael Große    {
1470db5771eSMichael Große        global $INPUT;
1480db5771eSMichael Große        if ($INPUT->has('purge')) {
1490db5771eSMichael Große            $this->depends['purge'] = true;
1500db5771eSMichael Große        }   // purge requested
1510db5771eSMichael Große    }
1520db5771eSMichael Große
1530db5771eSMichael Große    /**
1540db5771eSMichael Große     * retrieve the cached data
1550db5771eSMichael Große     *
1560db5771eSMichael Große     * @param   bool $clean true to clean line endings, false to leave line endings alone
1570db5771eSMichael Große     * @return  string          cache contents
1580db5771eSMichael Große     */
1590db5771eSMichael Große    public function retrieveCache($clean = true)
1600db5771eSMichael Große    {
1610db5771eSMichael Große        return io_readFile($this->cache, $clean);
1620db5771eSMichael Große    }
1630db5771eSMichael Große
1640db5771eSMichael Große    /**
1650db5771eSMichael Große     * cache $data
1660db5771eSMichael Große     *
1670db5771eSMichael Große     * @param   string $data the data to be cached
1680db5771eSMichael Große     * @return  bool           true on success, false otherwise
1690db5771eSMichael Große     */
1700db5771eSMichael Große    public function storeCache($data)
1710db5771eSMichael Große    {
172b4b0b31bSMichael Große        if ($this->_nocache) {
1730db5771eSMichael Große            return false;
1740db5771eSMichael Große        }
1750db5771eSMichael Große
176033eb35bSkalenpw        return io_saveFile($this->cache, $data);
1770db5771eSMichael Große    }
1780db5771eSMichael Große
1790db5771eSMichael Große    /**
1800db5771eSMichael Große     * remove any cached data associated with this cache instance
1810db5771eSMichael Große     */
1820db5771eSMichael Große    public function removeCache()
1830db5771eSMichael Große    {
1840db5771eSMichael Große        @unlink($this->cache);
1850db5771eSMichael Große    }
1860db5771eSMichael Große
1870db5771eSMichael Große    /**
1880db5771eSMichael Große     * Record cache hits statistics.
1890db5771eSMichael Große     * (Only when debugging allowed, to reduce overhead.)
1900db5771eSMichael Große     *
1910db5771eSMichael Große     * @param    bool $success result of this cache use attempt
1920db5771eSMichael Große     * @return   bool              pass-thru $success value
1930db5771eSMichael Große     */
19442c00b45SMichael Große    protected function stats($success)
1950db5771eSMichael Große    {
1960db5771eSMichael Große        global $conf;
1970db5771eSMichael Große        static $stats = null;
1980db5771eSMichael Große        static $file;
1990db5771eSMichael Große
2000db5771eSMichael Große        if (!$conf['allowdebug']) {
2010db5771eSMichael Große            return $success;
2020db5771eSMichael Große        }
2030db5771eSMichael Große
2040db5771eSMichael Große        if (is_null($stats)) {
2050db5771eSMichael Große            $file = $conf['cachedir'] . '/cache_stats.txt';
2060db5771eSMichael Große            $lines = explode("\n", io_readFile($file));
2070db5771eSMichael Große
2080db5771eSMichael Große            foreach ($lines as $line) {
2090db5771eSMichael Große                $i = strpos($line, ',');
2100db5771eSMichael Große                $stats[substr($line, 0, $i)] = $line;
2110db5771eSMichael Große            }
2120db5771eSMichael Große        }
2130db5771eSMichael Große
2140db5771eSMichael Große        if (isset($stats[$this->ext])) {
215*a95427a5SAndreas Gohr            [$ext, $count, $hits] = explode(',', $stats[$this->ext]);
2160db5771eSMichael Große        } else {
2170db5771eSMichael Große            $ext = $this->ext;
2180db5771eSMichael Große            $count = 0;
2190db5771eSMichael Große            $hits = 0;
2200db5771eSMichael Große        }
2210db5771eSMichael Große
2220db5771eSMichael Große        $count++;
2230db5771eSMichael Große        if ($success) {
2240db5771eSMichael Große            $hits++;
2250db5771eSMichael Große        }
2260db5771eSMichael Große        $stats[$this->ext] = "$ext,$count,$hits";
2270db5771eSMichael Große
228*a95427a5SAndreas Gohr        io_saveFile($file, implode("\n", $stats));
2290db5771eSMichael Große
2300db5771eSMichael Große        return $success;
2310db5771eSMichael Große    }
232d2f1d7a1SMichael Große
233d2f1d7a1SMichael Große    /**
234d2f1d7a1SMichael Große     * @return bool
235d2f1d7a1SMichael Große     */
236d2f1d7a1SMichael Große    public function isNoCache()
237d2f1d7a1SMichael Große    {
238b4b0b31bSMichael Große        return $this->_nocache;
239d2f1d7a1SMichael Große    }
2400db5771eSMichael Große}
241