xref: /plugin/combo/action/linkmove.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
337748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
4c3437056SNickeau
5c3437056SNickeauuse ComboStrap\DatabasePageRow;
6*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
7*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
8*04fd306cSNickeauuse ComboStrap\ExecutionContext;
94cadd4f8SNickeauuse ComboStrap\FileSystems;
10*04fd306cSNickeauuse ComboStrap\LinkMarkup;
11c3437056SNickeauuse ComboStrap\LogUtility;
12*04fd306cSNickeauuse ComboStrap\MarkupPath;
13*04fd306cSNickeauuse ComboStrap\MarkupRef;
14*04fd306cSNickeauuse ComboStrap\Meta\Field\Aliases;
15*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDbStore;
16*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
17c3437056SNickeauuse ComboStrap\PageId;
18c3437056SNickeauuse ComboStrap\PluginUtility;
19c3437056SNickeauuse ComboStrap\Site;
20c3437056SNickeau
21007225e5Sgerardnico
22007225e5Sgerardnico/**
23007225e5Sgerardnico * Class action_plugin_combo_move
24c3437056SNickeau * Handle the move of a page in order to update:
25c3437056SNickeau *   * the link
26c3437056SNickeau *   * the data in the database
27007225e5Sgerardnico */
28007225e5Sgerardnicoclass action_plugin_combo_linkmove extends DokuWiki_Action_Plugin
29007225e5Sgerardnico{
30007225e5Sgerardnico
31c3437056SNickeau
32c3437056SNickeau    const CANONICAL = "move";
33*04fd306cSNickeau    const FILE_MOVE_OPERATION = "move";
34*04fd306cSNickeau
35*04fd306cSNickeau    public static function isMoveOperation(): bool
36*04fd306cSNickeau    {
37*04fd306cSNickeau        try {
38*04fd306cSNickeau            $executionContext = ExecutionContext::getActualOrCreateFromEnv();
39*04fd306cSNickeau            $executionContext->getRuntimeObject(action_plugin_combo_linkmove::FILE_MOVE_OPERATION);
40*04fd306cSNickeau            return true;
41*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
42*04fd306cSNickeau            return false;
43*04fd306cSNickeau        }
44*04fd306cSNickeau    }
45c3437056SNickeau
46c3437056SNickeau    private static function checkAndSendAMessageIfLockFilePresent(): bool
47c3437056SNickeau    {
484cadd4f8SNickeau        $lockFile = Site::getDataDirectory()->resolve("locks_plugin_move.lock");
494cadd4f8SNickeau        if (!FileSystems::exists($lockFile)) {
50c3437056SNickeau            return false;
51c3437056SNickeau        }
524cadd4f8SNickeau        $lockFileDateTimeModified = FileSystems::getModifiedTime($lockFile);
53c3437056SNickeau        $lockFileModifiedTimestamp = $lockFileDateTimeModified->getTimestamp();
54c3437056SNickeau        $now = time();
55c3437056SNickeau
56c3437056SNickeau        $distance = $now - $lockFileModifiedTimestamp;
57c3437056SNickeau        $lockFileAgeInMinute = ($distance) / 60;
58c3437056SNickeau        if ($lockFileAgeInMinute > 5) {
59c3437056SNickeau            LogUtility::msg("The move lockfile ($lockFile) exists and is older than 5 minutes (exactly $lockFileAgeInMinute minutes). If you are no more in a move, you should delete this file otherwise it will disable the move of page and the cache.");
60c3437056SNickeau            return true;
61c3437056SNickeau        }
62c3437056SNickeau        return false;
63c3437056SNickeau    }
64c3437056SNickeau
65007225e5Sgerardnico    /**
66531e725cSNickeau     * As explained https://www.dokuwiki.org/plugin:move#support_for_other_plugins
67007225e5Sgerardnico     * @param Doku_Event_Handler $controller
68007225e5Sgerardnico     */
69007225e5Sgerardnico    function register(Doku_Event_Handler $controller)
70007225e5Sgerardnico    {
71c3437056SNickeau
72c3437056SNickeau        /**
73c3437056SNickeau         * To rewrite the page meta in the database
74c3437056SNickeau         */
75c3437056SNickeau        $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'BEFORE', $this, 'handle_rename_before', array());
76c3437056SNickeau        $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'AFTER', $this, 'handle_rename_after', array());
77c3437056SNickeau
78c3437056SNickeau        /**
79c3437056SNickeau         * To rewrite the link
80c3437056SNickeau         */
81c3437056SNickeau        $controller->register_hook('PLUGIN_MOVE_HANDLERS_REGISTER', 'BEFORE', $this, 'handle_link', array());
82c3437056SNickeau
83c3437056SNickeau
84c3437056SNickeau        /**
85c3437056SNickeau         * Check for the presence of a lock file
86c3437056SNickeau         */
87c3437056SNickeau        $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 'check_lock_file_age', array());
88c3437056SNickeau
89c3437056SNickeau
90007225e5Sgerardnico    }
91007225e5Sgerardnico
92007225e5Sgerardnico    /**
93c3437056SNickeau     * @param Doku_Event $event
94c3437056SNickeau     * @param $params
95c3437056SNickeau     *
96c3437056SNickeau     * When a lock file is present,
97c3437056SNickeau     * the move plugin will purge the data in {@link action_plugin_move_rewrite::handle_cache()}
98c3437056SNickeau     * making the rendering fucking slow
99c3437056SNickeau     * We check that the lock file is not
100c3437056SNickeau     */
101c3437056SNickeau    function check_lock_file_age(Doku_Event $event, $params)
102c3437056SNickeau    {
103c3437056SNickeau        self::checkAndSendAMessageIfLockFilePresent();
104c3437056SNickeau
105c3437056SNickeau    }
106c3437056SNickeau
107c3437056SNickeau    /**
108c3437056SNickeau     * Handle the path modification of a page
109c3437056SNickeau     * @param Doku_Event $event - https://www.dokuwiki.org/plugin:move#for_plugin_authors
110c3437056SNickeau     * @param $params
111c3437056SNickeau     *
112c3437056SNickeau     */
113c3437056SNickeau    function handle_rename_before(Doku_Event $event, $params)
114c3437056SNickeau    {
115*04fd306cSNickeau
116*04fd306cSNickeau
117c3437056SNickeau        /**
118c3437056SNickeau         * Check that the lock file is not older
119c3437056SNickeau         * Lock file bigger than 5 minutes
120c3437056SNickeau         * Is not really possible
121c3437056SNickeau         */
122c3437056SNickeau        $result = self::checkAndSendAMessageIfLockFilePresent();
123c3437056SNickeau        if ($result === true) {
124c3437056SNickeau            $event->preventDefault();
125*04fd306cSNickeau            LogUtility::warning("The move lock file is present, the move was canceled.");
126*04fd306cSNickeau            return;
127*04fd306cSNickeau        }
128*04fd306cSNickeau
129*04fd306cSNickeau        /**
130*04fd306cSNickeau         * The move plugin just delete the file
131*04fd306cSNickeau         * and recreate it.
132*04fd306cSNickeau         * There is no move operation
133*04fd306cSNickeau         * Therefore, because a delete in the database is a soft delete (ie if not exists)
134*04fd306cSNickeau         * We move it now
135*04fd306cSNickeau         */
136*04fd306cSNickeau        $sourceId = $event->data["src_id"];
137*04fd306cSNickeau        $targetId = $event->data["dst_id"];
138*04fd306cSNickeau        try {
139*04fd306cSNickeau
140*04fd306cSNickeau            /**
141*04fd306cSNickeau             * Update the dokuwiki id and path
142*04fd306cSNickeau             */
143*04fd306cSNickeau            try {
144*04fd306cSNickeau                $databasePage = DatabasePageRow::getFromDokuWikiId($sourceId);
145*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
146*04fd306cSNickeau                LogUtility::warning("The source database row $sourceId was not found in the database");
147*04fd306cSNickeau                return;
148*04fd306cSNickeau            }
149*04fd306cSNickeau
150*04fd306cSNickeau            $databasePage->updatePathAndDokuwikiId($targetId);
151*04fd306cSNickeau
152*04fd306cSNickeau            /**
153*04fd306cSNickeau             * Advertise the move
154*04fd306cSNickeau             * Because otherwise the file is deleted
155*04fd306cSNickeau             * in {@link action_plugin_combo_pagesystemmutation}
156*04fd306cSNickeau             */
157*04fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
158*04fd306cSNickeau                ->setRuntimeObject(self::FILE_MOVE_OPERATION, $sourceId);
159*04fd306cSNickeau
160*04fd306cSNickeau        } catch (Exception $exception) {
161*04fd306cSNickeau            $this->reportError($exception);
162c3437056SNickeau        }
163c3437056SNickeau
164c3437056SNickeau    }
165c3437056SNickeau
166c3437056SNickeau    /**
167c3437056SNickeau     * Handle the path modification of a page after
168c3437056SNickeau     *
169c3437056SNickeau     * The metadata file should also have been moved
170c3437056SNickeau     *
171c3437056SNickeau     * @param Doku_Event $event - https://www.dokuwiki.org/plugin:move#for_plugin_authors
172c3437056SNickeau     * @param $params
173c3437056SNickeau     *
174c3437056SNickeau     */
175c3437056SNickeau    function handle_rename_after(Doku_Event $event, $params)
176c3437056SNickeau    {
177c3437056SNickeau        /**
178c3437056SNickeau         *
179c3437056SNickeau         * $event->data
180c3437056SNickeau         * src_id ⇒ string – the original ID of the page
181c3437056SNickeau         * dst_id ⇒ string – the new ID of the page
182c3437056SNickeau         */
183c3437056SNickeau        $sourceId = $event->data["src_id"];
184c3437056SNickeau        $targetId = $event->data["dst_id"];
185c3437056SNickeau        try {
186c3437056SNickeau
187c3437056SNickeau            /**
188c3437056SNickeau             * Update the dokuwiki id and path
189c3437056SNickeau             */
190*04fd306cSNickeau            try {
191*04fd306cSNickeau                $databasePage = DatabasePageRow::getFromDokuWikiId($targetId);
192*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
193*04fd306cSNickeau                LogUtility::warning("The target database row with the id ($targetId) was not found in the database");
194c3437056SNickeau                return;
195c3437056SNickeau            }
196c3437056SNickeau
197c3437056SNickeau            /**
198c3437056SNickeau             * Check page id
199c3437056SNickeau             */
200*04fd306cSNickeau            $targetPage = MarkupPath::createMarkupFromId($targetId);
201c3437056SNickeau            $targetPageId = PageId::createForPage($targetPage);
202c3437056SNickeau            $targetPageIdValue = $targetPageId->getValueFromStore();
203c3437056SNickeau            $databasePageIdValue = $databasePage->getPageId();
204c3437056SNickeau
205*04fd306cSNickeau            if ($targetPageIdValue === null) {
206*04fd306cSNickeau                $targetPageId
207*04fd306cSNickeau                    ->setValue($databasePageIdValue)
208*04fd306cSNickeau                    ->persist();
209*04fd306cSNickeau            } else {
210*04fd306cSNickeau                /**
211*04fd306cSNickeau                 * {@link helper_plugin_move_op::movePage()}
212*04fd306cSNickeau                 * delete and save the file with log
213*04fd306cSNickeau                 */
214*04fd306cSNickeau                $targetPageId
215*04fd306cSNickeau                    ->setValueForce($databasePageIdValue)
216*04fd306cSNickeau                    ->persist();
217c3437056SNickeau            }
218c3437056SNickeau
219c3437056SNickeau            /**
220c3437056SNickeau             * Add the alias
221c3437056SNickeau             */
222c3437056SNickeau            Aliases::createForPage($targetPage)
223c3437056SNickeau                ->addAlias($sourceId)
224c3437056SNickeau                ->setWriteStore(MetadataDokuWikiStore::class)
225c3437056SNickeau                ->sendToWriteStore()
226c3437056SNickeau                ->persist()
227c3437056SNickeau                ->setReadStore(MetadataDbStore::class)
228c3437056SNickeau                ->sendToWriteStore()
229c3437056SNickeau                ->persist();
230c3437056SNickeau
231*04fd306cSNickeau            /**
232*04fd306cSNickeau             * Stop advertising the move
233*04fd306cSNickeau             */
234*04fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
235*04fd306cSNickeau                ->closeAndRemoveRuntimeVariableIfExists(self::FILE_MOVE_OPERATION);
236c3437056SNickeau
237c3437056SNickeau        } catch (Exception $exception) {
238*04fd306cSNickeau            $this->reportError($exception);
239c3437056SNickeau        }
240c3437056SNickeau
241c3437056SNickeau    }
242c3437056SNickeau
243c3437056SNickeau
244c3437056SNickeau    /**
245007225e5Sgerardnico     * Handle the move of a page
246007225e5Sgerardnico     * @param Doku_Event $event
247007225e5Sgerardnico     * @param $params
248007225e5Sgerardnico     */
249c3437056SNickeau    function handle_link(Doku_Event $event, $params)
250007225e5Sgerardnico    {
251007225e5Sgerardnico        /**
252ef295d81Sgerardnico         * The handlers is the name of the component (ie refers to the {@link syntax_plugin_combo_link} handler)
253ef295d81Sgerardnico         * and 'rewrite_combo' to the below method
254007225e5Sgerardnico         */
255ef295d81Sgerardnico        $event->data['handlers'][syntax_plugin_combo_link::COMPONENT] = array($this, 'rewrite_combo');
256007225e5Sgerardnico    }
257007225e5Sgerardnico
258007225e5Sgerardnico    /**
259007225e5Sgerardnico     *
260007225e5Sgerardnico     * @param $match
261007225e5Sgerardnico     * @param $state
262007225e5Sgerardnico     * @param $pos
263007225e5Sgerardnico     * @param $plugin
264007225e5Sgerardnico     * @param helper_plugin_move_handler $handler
265007225e5Sgerardnico     */
266007225e5Sgerardnico    public function rewrite_combo($match, $state, $pos, $plugin, helper_plugin_move_handler $handler)
267007225e5Sgerardnico    {
268007225e5Sgerardnico        /**
269*04fd306cSNickeau         * The goal is to recreate the document
270*04fd306cSNickeau         * in the {@link helper_plugin_move_handler::$calls}
271*04fd306cSNickeau         * variable (this is a string, not an array of calls)
272*04fd306cSNickeau         *
273*04fd306cSNickeau         * We got the {@link syntax_plugin_combo_link::handle() render match of the handle function}
274*04fd306cSNickeau         *
275*04fd306cSNickeau         * Unfortunately, all environemnt propertes of {@link helper_plugin_move_handler}
276*04fd306cSNickeau         * such as {@link helper_plugin_move_handler::$id} are private
277*04fd306cSNickeau         *
278*04fd306cSNickeau         * The code below calls then the rewrite function {@link helper_plugin_move_handler::internallink()}
279*04fd306cSNickeau         * and change the content modified in the {@link helper_plugin_move_handler::$calls} variable
2805f891b7eSNickeau         *
281007225e5Sgerardnico         */
282*04fd306cSNickeau        if ($state !== DOKU_LEXER_ENTER) {
283*04fd306cSNickeau            // Description and ending
284*04fd306cSNickeau            $handler->calls .= $match;
285*04fd306cSNickeau            return;
286*04fd306cSNickeau        }
287007225e5Sgerardnico
288*04fd306cSNickeau        /**
289*04fd306cSNickeau         * All environment on the {@link helper_plugin_move_handler handler} are private
290*04fd306cSNickeau         * We can't get it, we just hack around the move of the handler then
291*04fd306cSNickeau         */
292*04fd306cSNickeau        $parseAttributes = syntax_plugin_combo_link::parse($match);
293*04fd306cSNickeau        $ref = $parseAttributes[syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE];
294*04fd306cSNickeau        try {
295*04fd306cSNickeau            $link = LinkMarkup::createFromRef($ref);
296*04fd306cSNickeau            $isWikiUri = $link->getMarkupRef()->getSchemeType() === MarkupRef::WIKI_URI;
297*04fd306cSNickeau        } catch (ExceptionCompile $e) {
298*04fd306cSNickeau            LogUtility::error("Unable to rewrite the markup reference for a link move. The markup ref ($ref) could not be parsed. Error: {$e->getMessage()}");
299*04fd306cSNickeau            $handler->calls .= $match;
300*04fd306cSNickeau            return;
301*04fd306cSNickeau        }
302*04fd306cSNickeau
303*04fd306cSNickeau        if (!$isWikiUri) {
304*04fd306cSNickeau            // Other type of links
305*04fd306cSNickeau            $handler->calls .= $match;
306*04fd306cSNickeau            return;
307*04fd306cSNickeau        }
308*04fd306cSNickeau
309*04fd306cSNickeau        /**
310*04fd306cSNickeau         * This function will modify and add the link to the new output (ie calls)
311*04fd306cSNickeau         * {@link helper_plugin_move_handler::$calls}
312*04fd306cSNickeau         */
3135f891b7eSNickeau        $handler->internallink($match, $state, $pos);
314*04fd306cSNickeau
315*04fd306cSNickeau        /**
316*04fd306cSNickeau         * Internal Link Calls Hack
317*04fd306cSNickeau         * that delete the ]]
318*04fd306cSNickeau         */
3195f891b7eSNickeau        $suffix = "]]";
320*04fd306cSNickeau        if (substr($handler->calls, -strlen($suffix)) === $suffix) {
32121913ab3SNickeau            $handler->calls = substr($handler->calls, 0, strlen($handler->calls) - strlen($suffix));
3225f891b7eSNickeau        }
3235f891b7eSNickeau
3245f891b7eSNickeau
3255f891b7eSNickeau    }
326*04fd306cSNickeau
327*04fd306cSNickeau    private function reportError(Exception $exception)
328*04fd306cSNickeau    {
329*04fd306cSNickeau        // We catch the errors if any to not stop the move
330*04fd306cSNickeau        // There is no transaction feature (it happens or not)
331*04fd306cSNickeau        $message = "An error occurred during the move replication to the database. Error message was: " . $exception->getMessage();
332*04fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
333*04fd306cSNickeau            throw new RuntimeException($exception);
3345f891b7eSNickeau        } else {
335*04fd306cSNickeau            LogUtility::error($message, self::CANONICAL, $exception);
3365f891b7eSNickeau        }
337007225e5Sgerardnico    }
338007225e5Sgerardnico
339007225e5Sgerardnico
340007225e5Sgerardnico}
341