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