xref: /dokuwiki/inc/Cache/Cache.php (revision d2f1d7a17e2e29d7a2471b10445570be500d337c)
10db5771eSMichael Große<?php
20db5771eSMichael Große
30db5771eSMichael Großenamespace dokuwiki\Cache;
40db5771eSMichael Große
50db5771eSMichael Große/**
60db5771eSMichael Große * Generic handling of caching
70db5771eSMichael Große */
80db5771eSMichael Großeclass Cache
90db5771eSMichael Große{
100db5771eSMichael Große    public $key = '';          // primary identifier for this item
110db5771eSMichael Große    public $ext = '';          // file ext for cache data, secondary identifier for this item
120db5771eSMichael Große    public $cache = '';        // cache file name
130db5771eSMichael Große    public $depends = array(); // array containing cache dependency information,
1472c2bae8SMichael Große    //   used by makeDefaultCacheDecision to determine cache validity
150db5771eSMichael Große
16*d2f1d7a1SMichael Große    protected $event = '';       // event to be triggered during useCache
17*d2f1d7a1SMichael Große    protected $time;
18*d2f1d7a1SMichael Große    protected $nocache = false;  // if set to true, cache will not be used or stored
190db5771eSMichael Große
200db5771eSMichael Große    /**
210db5771eSMichael Große     * @param string $key primary identifier
220db5771eSMichael Große     * @param string $ext file extension
230db5771eSMichael Große     */
240db5771eSMichael Große    public function __construct($key, $ext)
250db5771eSMichael Große    {
260db5771eSMichael Große        $this->key = $key;
270db5771eSMichael Große        $this->ext = $ext;
280db5771eSMichael Große        $this->cache = getCacheName($key, $ext);
290db5771eSMichael Große    }
300db5771eSMichael Große
310db5771eSMichael Große    /**
32*d2f1d7a1SMichael Große     * @deprecated since 2019-02-02 use the respective getters instead!
33*d2f1d7a1SMichael Große     */
34*d2f1d7a1SMichael Große    public function __get($key)
35*d2f1d7a1SMichael Große    {
36*d2f1d7a1SMichael Große        if ($key === '_event') {
37*d2f1d7a1SMichael Große            trigger_error(
38*d2f1d7a1SMichael Große                '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()',
39*d2f1d7a1SMichael Große                E_USER_DEPRECATED
40*d2f1d7a1SMichael Große            );
41*d2f1d7a1SMichael Große            return $this->getEvent();
42*d2f1d7a1SMichael Große        }
43*d2f1d7a1SMichael Große
44*d2f1d7a1SMichael Große        if ($key === '_time') {
45*d2f1d7a1SMichael Große            trigger_error(
46*d2f1d7a1SMichael Große                '\dokuwiki\Cache\Cache::_time is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getTime()',
47*d2f1d7a1SMichael Große                E_USER_DEPRECATED
48*d2f1d7a1SMichael Große            );
49*d2f1d7a1SMichael Große            return $this->getTime();
50*d2f1d7a1SMichael Große        }
51*d2f1d7a1SMichael Große        return $this->$$key;
52*d2f1d7a1SMichael Große    }
53*d2f1d7a1SMichael Große
54*d2f1d7a1SMichael Große    public function __set($name, $value)
55*d2f1d7a1SMichael Große    {
56*d2f1d7a1SMichael Große        if ($name === '_event') {
57*d2f1d7a1SMichael Große            trigger_error(
58*d2f1d7a1SMichael Große                '\dokuwiki\Cache\Cache::_event is deprecated since 2019-02-02. Use \dokuwiki\Cache\Cache::getEvent()',
59*d2f1d7a1SMichael Große                E_USER_DEPRECATED
60*d2f1d7a1SMichael Große            );
61*d2f1d7a1SMichael Große            $this->setEvent($value);
62*d2f1d7a1SMichael Große        }
63*d2f1d7a1SMichael Große        $this->$$name = $value;
64*d2f1d7a1SMichael Große    }
65*d2f1d7a1SMichael Große
66*d2f1d7a1SMichael Große    public function getTime()
67*d2f1d7a1SMichael Große    {
68*d2f1d7a1SMichael Große        return $this->time;
69*d2f1d7a1SMichael Große    }
70*d2f1d7a1SMichael Große
71*d2f1d7a1SMichael Große    public function getEvent()
72*d2f1d7a1SMichael Große    {
73*d2f1d7a1SMichael Große        return $this->event;
74*d2f1d7a1SMichael Große    }
75*d2f1d7a1SMichael Große
76*d2f1d7a1SMichael Große    public function setEvent($event)
77*d2f1d7a1SMichael Große    {
78*d2f1d7a1SMichael Große        $this->event = $event;
79*d2f1d7a1SMichael Große    }
80*d2f1d7a1SMichael Große
81*d2f1d7a1SMichael Große    /**
820db5771eSMichael Große     * public method to determine whether the cache can be used
830db5771eSMichael Große     *
840db5771eSMichael Große     * to assist in centralisation of event triggering and calculation of cache statistics,
8572c2bae8SMichael Große     * don't override this function override makeDefaultCacheDecision()
860db5771eSMichael Große     *
870db5771eSMichael Große     * @param  array $depends array of cache dependencies, support dependecies:
880db5771eSMichael Große     *                            'age'   => max age of the cache in seconds
890db5771eSMichael Große     *                            'files' => cache must be younger than mtime of each file
900db5771eSMichael Große     *                                       (nb. dependency passes if file doesn't exist)
910db5771eSMichael Große     *
920db5771eSMichael Große     * @return bool    true if cache can be used, false otherwise
930db5771eSMichael Große     */
940db5771eSMichael Große    public function useCache($depends = array())
950db5771eSMichael Große    {
960db5771eSMichael Große        $this->depends = $depends;
9742c00b45SMichael Große        $this->addDependencies();
980db5771eSMichael Große
99*d2f1d7a1SMichael Große        if ($this->event) {
100*d2f1d7a1SMichael Große            return $this->stats(trigger_event($this->event, $this, array($this, 'makeDefaultCacheDecision')));
1010db5771eSMichael Große        } else {
10272c2bae8SMichael Große            return $this->stats($this->makeDefaultCacheDecision());
1030db5771eSMichael Große        }
1040db5771eSMichael Große    }
1050db5771eSMichael Große
1060db5771eSMichael Große    /**
10743ff1056SMichael Große     * internal method containing cache use decision logic
1080db5771eSMichael Große     *
1090db5771eSMichael Große     * this function processes the following keys in the depends array
1100db5771eSMichael Große     *   purge - force a purge on any non empty value
1110db5771eSMichael Große     *   age   - expire cache if older than age (seconds)
1120db5771eSMichael Große     *   files - expire cache if any file in this array was updated more recently than the cache
1130db5771eSMichael Große     *
1140db5771eSMichael Große     * Note that this function needs to be public as it is used as callback for the event handler
1150db5771eSMichael Große     *
1160db5771eSMichael Große     * can be overridden
1170db5771eSMichael Große     *
11842c00b45SMichael Große     * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead!
11942c00b45SMichael Große     *
1200db5771eSMichael Große     * @return bool               see useCache()
1210db5771eSMichael Große     */
12272c2bae8SMichael Große    public function makeDefaultCacheDecision()
1230db5771eSMichael Große    {
1240db5771eSMichael Große
125*d2f1d7a1SMichael Große        if ($this->nocache) {
1260db5771eSMichael Große            return false;
1270db5771eSMichael Große        }                              // caching turned off
1280db5771eSMichael Große        if (!empty($this->depends['purge'])) {
1290db5771eSMichael Große            return false;
1300db5771eSMichael Große        }              // purge requested?
131*d2f1d7a1SMichael Große        if (!($this->time = @filemtime($this->cache))) {
1320db5771eSMichael Große            return false;
1330db5771eSMichael Große        }   // cache exists?
1340db5771eSMichael Große
1350db5771eSMichael Große        // cache too old?
136*d2f1d7a1SMichael Große        if (!empty($this->depends['age']) && ((time() - $this->time) > $this->depends['age'])) {
1370db5771eSMichael Große            return false;
1380db5771eSMichael Große        }
1390db5771eSMichael Große
1400db5771eSMichael Große        if (!empty($this->depends['files'])) {
1410db5771eSMichael Große            foreach ($this->depends['files'] as $file) {
142*d2f1d7a1SMichael Große                if ($this->time <= @filemtime($file)) {
1430db5771eSMichael Große                    return false;
1440db5771eSMichael Große                }         // cache older than files it depends on?
1450db5771eSMichael Große            }
1460db5771eSMichael Große        }
1470db5771eSMichael Große
1480db5771eSMichael Große        return true;
1490db5771eSMichael Große    }
1500db5771eSMichael Große
1510db5771eSMichael Große    /**
1520db5771eSMichael Große     * add dependencies to the depends array
1530db5771eSMichael Große     *
1540db5771eSMichael Große     * this method should only add dependencies,
1550db5771eSMichael Große     * it should not remove any existing dependencies and
1560db5771eSMichael Große     * it should only overwrite a dependency when the new value is more stringent than the old
1570db5771eSMichael Große     */
15842c00b45SMichael Große    protected function addDependencies()
1590db5771eSMichael Große    {
1600db5771eSMichael Große        global $INPUT;
1610db5771eSMichael Große        if ($INPUT->has('purge')) {
1620db5771eSMichael Große            $this->depends['purge'] = true;
1630db5771eSMichael Große        }   // purge requested
1640db5771eSMichael Große    }
1650db5771eSMichael Große
1660db5771eSMichael Große    /**
1670db5771eSMichael Große     * retrieve the cached data
1680db5771eSMichael Große     *
1690db5771eSMichael Große     * @param   bool $clean true to clean line endings, false to leave line endings alone
1700db5771eSMichael Große     * @return  string          cache contents
1710db5771eSMichael Große     */
1720db5771eSMichael Große    public function retrieveCache($clean = true)
1730db5771eSMichael Große    {
1740db5771eSMichael Große        return io_readFile($this->cache, $clean);
1750db5771eSMichael Große    }
1760db5771eSMichael Große
1770db5771eSMichael Große    /**
1780db5771eSMichael Große     * cache $data
1790db5771eSMichael Große     *
1800db5771eSMichael Große     * @param   string $data the data to be cached
1810db5771eSMichael Große     * @return  bool           true on success, false otherwise
1820db5771eSMichael Große     */
1830db5771eSMichael Große    public function storeCache($data)
1840db5771eSMichael Große    {
185*d2f1d7a1SMichael Große        if ($this->nocache) {
1860db5771eSMichael Große            return false;
1870db5771eSMichael Große        }
1880db5771eSMichael Große
1890db5771eSMichael Große        return io_savefile($this->cache, $data);
1900db5771eSMichael Große    }
1910db5771eSMichael Große
1920db5771eSMichael Große    /**
1930db5771eSMichael Große     * remove any cached data associated with this cache instance
1940db5771eSMichael Große     */
1950db5771eSMichael Große    public function removeCache()
1960db5771eSMichael Große    {
1970db5771eSMichael Große        @unlink($this->cache);
1980db5771eSMichael Große    }
1990db5771eSMichael Große
2000db5771eSMichael Große    /**
2010db5771eSMichael Große     * Record cache hits statistics.
2020db5771eSMichael Große     * (Only when debugging allowed, to reduce overhead.)
2030db5771eSMichael Große     *
2040db5771eSMichael Große     * @param    bool $success result of this cache use attempt
2050db5771eSMichael Große     * @return   bool              pass-thru $success value
2060db5771eSMichael Große     */
20742c00b45SMichael Große    protected function stats($success)
2080db5771eSMichael Große    {
2090db5771eSMichael Große        global $conf;
2100db5771eSMichael Große        static $stats = null;
2110db5771eSMichael Große        static $file;
2120db5771eSMichael Große
2130db5771eSMichael Große        if (!$conf['allowdebug']) {
2140db5771eSMichael Große            return $success;
2150db5771eSMichael Große        }
2160db5771eSMichael Große
2170db5771eSMichael Große        if (is_null($stats)) {
2180db5771eSMichael Große            $file = $conf['cachedir'] . '/cache_stats.txt';
2190db5771eSMichael Große            $lines = explode("\n", io_readFile($file));
2200db5771eSMichael Große
2210db5771eSMichael Große            foreach ($lines as $line) {
2220db5771eSMichael Große                $i = strpos($line, ',');
2230db5771eSMichael Große                $stats[substr($line, 0, $i)] = $line;
2240db5771eSMichael Große            }
2250db5771eSMichael Große        }
2260db5771eSMichael Große
2270db5771eSMichael Große        if (isset($stats[$this->ext])) {
2280db5771eSMichael Große            list($ext, $count, $hits) = explode(',', $stats[$this->ext]);
2290db5771eSMichael Große        } else {
2300db5771eSMichael Große            $ext = $this->ext;
2310db5771eSMichael Große            $count = 0;
2320db5771eSMichael Große            $hits = 0;
2330db5771eSMichael Große        }
2340db5771eSMichael Große
2350db5771eSMichael Große        $count++;
2360db5771eSMichael Große        if ($success) {
2370db5771eSMichael Große            $hits++;
2380db5771eSMichael Große        }
2390db5771eSMichael Große        $stats[$this->ext] = "$ext,$count,$hits";
2400db5771eSMichael Große
2410db5771eSMichael Große        io_saveFile($file, join("\n", $stats));
2420db5771eSMichael Große
2430db5771eSMichael Große        return $success;
2440db5771eSMichael Große    }
245*d2f1d7a1SMichael Große
246*d2f1d7a1SMichael Große    /**
247*d2f1d7a1SMichael Große     * @return bool
248*d2f1d7a1SMichael Große     */
249*d2f1d7a1SMichael Große    public function isNoCache()
250*d2f1d7a1SMichael Große    {
251*d2f1d7a1SMichael Große        return $this->nocache;
252*d2f1d7a1SMichael Große    }
2530db5771eSMichael Große}
254