1<?php
2
3use dokuwiki\Extension\ActionPlugin;
4use dokuwiki\Extension\Event;
5use dokuwiki\Extension\EventHandler;
6use dokuwiki\plugin\sqlite\SQLiteDB;
7use dokuwiki\plugin\struct\meta\Assignments;
8use dokuwiki\plugin\struct\meta\SearchConfig;
9use dokuwiki\plugin\struct\meta\SearchConfigParameters;
10
11/**
12 * Handle caching of pages containing struct aggregations
13 */
14class action_plugin_struct_cache extends ActionPlugin
15{
16    /**
17     * Registers a callback function for a given event
18     *
19     * @param EventHandler $controller DokuWiki's event controller object
20     * @return void
21     */
22    public function register(EventHandler $controller)
23    {
24        $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'handleCacheSchemachange');
25        $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'handleCacheAggregation');
26        $controller->register_hook('PARSER_CACHE_USE', 'AFTER', $this, 'handleCacheDynamic');
27    }
28
29    /**
30     * @return string the refresh file
31     */
32    public static function getSchemaRefreshFile()
33    {
34        global $conf;
35        return $conf['cachedir'] . '/struct.schemarefresh';
36    }
37
38    /**
39     * For pages potentially containing schema data, refresh the cache when schema data has been
40     * updated
41     *
42     * @param Event $event event object by reference
43     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
44     *                           handler was registered]
45     * @return bool
46     */
47    public function handleCacheSchemachange(Event $event, $param)
48    {
49        /** @var \cache_parser $cache */
50        $cache = $event->data;
51        if ($cache->mode != 'xhtml') return true;
52        if (!$cache->page) return true; // not a page cache
53
54        $assignments = Assignments::getInstance();
55        if (!$assignments->getPageAssignments($cache->page)) return true; // no struct here
56
57        $cache->depends['files'][] = self::getSchemaRefreshFile();
58        return true;
59    }
60
61    /**
62     * For pages containing an aggregation, add the last modified date of the database itself
63     * to the cache dependencies
64     *
65     * @param Event $event event object by reference
66     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
67     *                           handler was registered]
68     * @return bool
69     */
70    public function handleCacheAggregation(Event $event, $param)
71    {
72        global $INPUT;
73
74        /** @var \cache_parser $cache */
75        $cache = $event->data;
76        if ($cache->mode != 'xhtml') return true;
77        if (!$cache->page) return true; // not a page cache
78
79        $meta = p_get_metadata($cache->page, 'plugin struct');
80        if (isset($meta['hasaggregation'])) {
81            /** @var helper_plugin_struct_db $db */
82            $db = plugin_load('helper', 'struct_db');
83            // cache depends on last database save
84            $sqlite = $db->getDB(false);
85            if ($sqlite instanceof SQLiteDB) {
86                $cache->depends['files'][] = $sqlite->getDbFile();
87            }
88
89            // dynamic renders should never overwrite the default page cache
90            // we need this in additon to handle_cache_dynamic() below because we can only
91            // influence if a cache is used, not that it will be written
92            if (
93                $INPUT->has(SearchConfigParameters::$PARAM_FILTER) ||
94                $INPUT->has(SearchConfigParameters::$PARAM_OFFSET) ||
95                $INPUT->has(SearchConfigParameters::$PARAM_SORT)
96            ) {
97                $cache->key .= 'dynamic';
98            }
99
100            // cache depends on today's date
101            if ($meta['hasaggregation'] & SearchConfig::$CACHE_DATE) {
102                $oldage = $cache->depends['age'];
103                $newage = time() - mktime(0, 0, 1); // time since first second today
104                $cache->depends['age'] = min($oldage, $newage);
105            }
106
107            // cache depends on current user
108            if ($meta['hasaggregation'] & SearchConfig::$CACHE_USER) {
109                $cache->key .= ';' . $INPUT->server->str('REMOTE_USER');
110            }
111
112            // rebuild cachename
113            $cache->cache = getCacheName($cache->key, $cache->ext);
114        }
115
116        return true;
117    }
118
119    /**
120     * Disable cache when dymanic parameters are present
121     *
122     * @param Event $event event object by reference
123     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
124     *                           handler was registered]
125     * @return bool
126     */
127    public function handleCacheDynamic(Event $event, $param)
128    {
129        /** @var \cache_parser $cache */
130        $cache = $event->data;
131        if ($cache->mode != 'xhtml') return true;
132        if (!$cache->page) return true; // not a page cache
133        global $INPUT;
134
135        // disable cache use when one of these parameters is present
136        foreach (
137            [
138                SearchConfigParameters::$PARAM_FILTER,
139                SearchConfigParameters::$PARAM_OFFSET,
140                SearchConfigParameters::$PARAM_SORT
141            ] as $key
142        ) {
143            if ($INPUT->has($key)) {
144                $event->result = false;
145                return true;
146            }
147        }
148
149        return true;
150    }
151}
152