1<?php
2
3use ComboStrap\CacheExpirationDate;
4use ComboStrap\CacheExpirationFrequency;
5use ComboStrap\CacheLog;
6use ComboStrap\Cron;
7use ComboStrap\Event;
8use ComboStrap\ExceptionCompile;
9use ComboStrap\ExceptionNotFound;
10use ComboStrap\ExecutionContext;
11use ComboStrap\Iso8601Date;
12use ComboStrap\LogUtility;
13use ComboStrap\MarkupCacheDependencies;
14use ComboStrap\MarkupPath;
15use ComboStrap\PagePath;
16use dokuwiki\Cache\CacheRenderer;
17
18require_once(__DIR__ . '/../vendor/autoload.php');
19
20/**
21 * Can we use the parser cache
22 *
23 *
24 *
25 */
26class action_plugin_combo_cacheexpiration extends DokuWiki_Action_Plugin
27{
28
29
30    const CANONICAL = CacheExpirationFrequency::CANONICAL;
31    const SLOT_CACHE_EXPIRATION_EVENT = "slot-cache-expiration";
32    const REQUESTED_ID = "requested-id";
33
34
35    /**
36     * @param Doku_Event_Handler $controller
37     */
38    function register(Doku_Event_Handler $controller)
39    {
40
41
42        /**
43         * Page expiration feature
44         */
45        $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'slotCreateCacheExpiration', array());
46
47
48        /**
49         * Process the Async event
50         */
51        $controller->register_hook(self::SLOT_CACHE_EXPIRATION_EVENT, 'AFTER', $this, 'handleSlotCacheExpiration');
52
53    }
54
55
56    /**
57     *
58     * Purge the cache if needed
59     * @param Doku_Event $event
60     * @param $params
61     */
62    function slotCreateCacheExpiration(Doku_Event $event, $params)
63    {
64
65        /**
66         * No cache for all mode
67         * (ie xhtml, instruction)
68         */
69        $data = &$event->data;
70        $pageId = $data->page;
71
72        /**
73         * For whatever reason, the cache file of XHTML
74         * may be empty - No error found on the web server or the log.
75         *
76         * We just delete it then.
77         *
78         * It has been seen after the creation of a new page or a `move` of the page.
79         */
80        if ($data instanceof CacheRenderer) {
81            if ($data->mode === "xhtml") {
82                if (file_exists($data->cache)) {
83                    if (filesize($data->cache) === 0) {
84                        $data->depends["purge"] = true;
85                        return;
86                    }
87                }
88            }
89        }
90
91        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
92        $cacheManager = $executionContext->getCacheManager();
93        $shouldSlotExpire = $cacheManager->shouldSlotExpire($pageId);
94        if ($shouldSlotExpire) {
95            try {
96                $requestedWikiId = $executionContext->getRequestedPath()->getWikiId();
97            } catch (ExceptionNotFound $e) {
98                LogUtility::internalError("Cache expiration: The requested path could not be determined, default context path was set instead.");
99                $requestedWikiId = $executionContext->getContextPath()->getWikiId();
100            }
101            Event::createEvent(
102                self::SLOT_CACHE_EXPIRATION_EVENT,
103                [
104                    PagePath::getPersistentName() => $pageId,
105                    self::REQUESTED_ID => $requestedWikiId
106                ]
107            );
108        }
109
110
111    }
112
113    public function handleSlotCacheExpiration($event)
114    {
115        $data = $event->data;
116        $slotPath = $data[PagePath::getPersistentName()];
117        $requestedId = $data[self::REQUESTED_ID];
118
119        /**
120         * The cache file may be dependent on the requested id
121         * ie (@link MarkupCacheDependencies::OUTPUT_DEPENDENCIES}
122         */
123        global $ID;
124        $keep = $ID;
125        try {
126            $ID = $requestedId;
127            $slot = MarkupPath::createPageFromAbsoluteId($slotPath);
128
129            /**
130             * Calculate a new expiration date
131             * And set it here because setting a new metadata
132             * will make the cache unusable
133             */
134            $cacheExpirationDateMeta = CacheExpirationDate::createForPage($slot);
135            $actualDate = $cacheExpirationDateMeta->getValue();
136            $cacheExpirationFrequency = CacheExpirationFrequency::createForPage($slot)
137                ->getValue();
138            try {
139                $newDate = Cron::getDate($cacheExpirationFrequency);
140            } catch (ExceptionCompile $e) {
141                LogUtility::msg("Error while calculating the new expiration date. Error: {$e->getMessage()}");
142                return;
143            }
144            if ($newDate < $actualDate) {
145                LogUtility::msg("The new calculated date cache expiration frequency ({$newDate->format(Iso8601Date::getFormat())}) is lower than the current date ({$actualDate->format(Iso8601Date::getFormat())})");
146            }
147            try {
148                $cacheExpirationDateMeta
149                    ->setValue($newDate)
150                    ->persist();
151            } catch (ExceptionCompile $e) {
152                LogUtility::msg("Error while persisting the new expiration date. Error:{$e->getMessage()}");
153                return;
154            }
155
156            /**
157             * Cache deletion
158             */
159            $message = "Expiration Date has expired";
160            $outputDocument = $slot->getInstructionsDocument();
161            CacheLog::deleteCacheIfExistsAndLog(
162                $outputDocument,
163                self::SLOT_CACHE_EXPIRATION_EVENT,
164                $message);
165            $fetcher = $slot->createHtmlFetcherWithItselfAsContextPath();
166            CacheLog::deleteCacheIfExistsAndLog(
167                $fetcher,
168                self::SLOT_CACHE_EXPIRATION_EVENT,
169                $message);
170
171            /**
172             * Re-render
173             */
174            $fetcher2 = $slot->createHtmlFetcherWithItselfAsContextPath();
175            CacheLog::renderCacheAndLog(
176                $fetcher2,
177                self::SLOT_CACHE_EXPIRATION_EVENT,
178                $message);
179
180
181        } finally {
182            $ID = $keep;
183        }
184
185    }
186
187
188}
189