1 <?php
2 
3 use dokuwiki\Extension\ActionPlugin;
4 use dokuwiki\Extension\Event;
5 use dokuwiki\Extension\EventHandler;
6 use dokuwiki\plugin\sqlite\SQLiteDB;
7 use dokuwiki\plugin\struct\meta\Assignments;
8 use dokuwiki\plugin\struct\meta\SearchConfig;
9 use dokuwiki\plugin\struct\meta\SearchConfigParameters;
10 
11 /**
12  * Handle caching of pages containing struct aggregations
13  */
14 class 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