xref: /template/strap/action/linkmove.php (revision 313de40a7a81adb8606d463d69a8d40c7499c8f8)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
337748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
4c3437056SNickeau
5c3437056SNickeauuse ComboStrap\DatabasePageRow;
604fd306cSNickeauuse ComboStrap\ExceptionCompile;
704fd306cSNickeauuse ComboStrap\ExceptionNotFound;
804fd306cSNickeauuse ComboStrap\ExecutionContext;
94cadd4f8SNickeauuse ComboStrap\FileSystems;
1004fd306cSNickeauuse ComboStrap\LinkMarkup;
11c3437056SNickeauuse ComboStrap\LogUtility;
1204fd306cSNickeauuse ComboStrap\MarkupPath;
1304fd306cSNickeauuse ComboStrap\MarkupRef;
1404fd306cSNickeauuse ComboStrap\Meta\Field\Aliases;
1504fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDbStore;
1604fd306cSNickeauuse 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";
3304fd306cSNickeau    const FILE_MOVE_OPERATION = "move";
3404fd306cSNickeau
3504fd306cSNickeau    public static function isMoveOperation(): bool
3604fd306cSNickeau    {
3704fd306cSNickeau        try {
3804fd306cSNickeau            $executionContext = ExecutionContext::getActualOrCreateFromEnv();
3904fd306cSNickeau            $executionContext->getRuntimeObject(action_plugin_combo_linkmove::FILE_MOVE_OPERATION);
4004fd306cSNickeau            return true;
4104fd306cSNickeau        } catch (ExceptionNotFound $e) {
4204fd306cSNickeau            return false;
4304fd306cSNickeau        }
4404fd306cSNickeau    }
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    {
11504fd306cSNickeau
11604fd306cSNickeau
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();
12504fd306cSNickeau            LogUtility::warning("The move lock file is present, the move was canceled.");
12604fd306cSNickeau            return;
12704fd306cSNickeau        }
12804fd306cSNickeau
12904fd306cSNickeau        /**
13004fd306cSNickeau         * The move plugin just delete the file
13104fd306cSNickeau         * and recreate it.
13204fd306cSNickeau         * There is no move operation
13304fd306cSNickeau         * Therefore, because a delete in the database is a soft delete (ie if not exists)
13404fd306cSNickeau         * We move it now
13504fd306cSNickeau         */
13604fd306cSNickeau        $sourceId = $event->data["src_id"];
13704fd306cSNickeau        $targetId = $event->data["dst_id"];
13804fd306cSNickeau        try {
13904fd306cSNickeau
14004fd306cSNickeau            /**
14104fd306cSNickeau             * Update the dokuwiki id and path
14204fd306cSNickeau             */
14304fd306cSNickeau            try {
14404fd306cSNickeau                $databasePage = DatabasePageRow::getFromDokuWikiId($sourceId);
14504fd306cSNickeau            } catch (ExceptionNotFound $e) {
14604fd306cSNickeau                LogUtility::warning("The source database row $sourceId was not found in the database");
14704fd306cSNickeau                return;
14804fd306cSNickeau            }
14904fd306cSNickeau
15004fd306cSNickeau            $databasePage->updatePathAndDokuwikiId($targetId);
15104fd306cSNickeau
15204fd306cSNickeau            /**
15304fd306cSNickeau             * Advertise the move
15404fd306cSNickeau             * Because otherwise the file is deleted
15504fd306cSNickeau             * in {@link action_plugin_combo_pagesystemmutation}
15604fd306cSNickeau             */
15704fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
15804fd306cSNickeau                ->setRuntimeObject(self::FILE_MOVE_OPERATION, $sourceId);
15904fd306cSNickeau
16004fd306cSNickeau        } catch (Exception $exception) {
16104fd306cSNickeau            $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             */
19004fd306cSNickeau            try {
19104fd306cSNickeau                $databasePage = DatabasePageRow::getFromDokuWikiId($targetId);
19204fd306cSNickeau            } catch (ExceptionNotFound $e) {
19304fd306cSNickeau                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             */
20004fd306cSNickeau            $targetPage = MarkupPath::createMarkupFromId($targetId);
201c3437056SNickeau            $targetPageId = PageId::createForPage($targetPage);
202c3437056SNickeau            $targetPageIdValue = $targetPageId->getValueFromStore();
203c3437056SNickeau            $databasePageIdValue = $databasePage->getPageId();
204c3437056SNickeau
20504fd306cSNickeau            if ($targetPageIdValue === null) {
20604fd306cSNickeau                $targetPageId
20704fd306cSNickeau                    ->setValue($databasePageIdValue)
20804fd306cSNickeau                    ->persist();
20904fd306cSNickeau            } else {
21004fd306cSNickeau                /**
21104fd306cSNickeau                 * {@link helper_plugin_move_op::movePage()}
21204fd306cSNickeau                 * delete and save the file with log
21304fd306cSNickeau                 */
21404fd306cSNickeau                $targetPageId
21504fd306cSNickeau                    ->setValueForce($databasePageIdValue)
21604fd306cSNickeau                    ->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
23104fd306cSNickeau            /**
23204fd306cSNickeau             * Stop advertising the move
23304fd306cSNickeau             */
23404fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
23504fd306cSNickeau                ->closeAndRemoveRuntimeVariableIfExists(self::FILE_MOVE_OPERATION);
236c3437056SNickeau
237c3437056SNickeau        } catch (Exception $exception) {
23804fd306cSNickeau            $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    {
268*313de40aSNicolas GERARD        // Wiki text was introduced
269*313de40aSNicolas GERARD        // https://github.com/michitux/dokuwiki-plugin-move/commit/74ee74225777949c09b76ccc9caf218f15070b44
270*313de40aSNicolas GERARD        $useWikiText = true;
271*313de40aSNicolas GERARD        if (!property_exists($handler, 'wikitext')) {
272*313de40aSNicolas GERARD            // old move plugin
273*313de40aSNicolas GERARD            $useWikiText = false;
274*313de40aSNicolas GERARD        }
275007225e5Sgerardnico        /**
27604fd306cSNickeau         * The goal is to recreate the document
27704fd306cSNickeau         * in the {@link helper_plugin_move_handler::$calls}
27804fd306cSNickeau         * variable (this is a string, not an array of calls)
27904fd306cSNickeau         *
28004fd306cSNickeau         * We got the {@link syntax_plugin_combo_link::handle() render match of the handle function}
28104fd306cSNickeau         *
28204fd306cSNickeau         * Unfortunately, all environemnt propertes of {@link helper_plugin_move_handler}
28304fd306cSNickeau         * such as {@link helper_plugin_move_handler::$id} are private
28404fd306cSNickeau         *
28504fd306cSNickeau         * The code below calls then the rewrite function {@link helper_plugin_move_handler::internallink()}
28604fd306cSNickeau         * and change the content modified in the {@link helper_plugin_move_handler::$calls} variable
2875f891b7eSNickeau         *
288007225e5Sgerardnico         */
28904fd306cSNickeau        if ($state !== DOKU_LEXER_ENTER) {
29004fd306cSNickeau            // Description and ending
291*313de40aSNicolas GERARD            if ($useWikiText) {
292*313de40aSNicolas GERARD                $handler->wikitext .= $match;
293*313de40aSNicolas GERARD            } else {
29404fd306cSNickeau                $handler->calls .= $match;
295*313de40aSNicolas GERARD            }
29604fd306cSNickeau            return;
29704fd306cSNickeau        }
298007225e5Sgerardnico
29904fd306cSNickeau        /**
30004fd306cSNickeau         * All environment on the {@link helper_plugin_move_handler handler} are private
30104fd306cSNickeau         * We can't get it, we just hack around the move of the handler then
30204fd306cSNickeau         */
30304fd306cSNickeau        $parseAttributes = syntax_plugin_combo_link::parse($match);
30404fd306cSNickeau        $ref = $parseAttributes[syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE];
30504fd306cSNickeau        try {
30604fd306cSNickeau            $link = LinkMarkup::createFromRef($ref);
30704fd306cSNickeau            $isWikiUri = $link->getMarkupRef()->getSchemeType() === MarkupRef::WIKI_URI;
30804fd306cSNickeau        } catch (ExceptionCompile $e) {
30904fd306cSNickeau            LogUtility::error("Unable to rewrite the markup reference for a link move. The markup ref ($ref) could not be parsed. Error: {$e->getMessage()}");
31004fd306cSNickeau            $handler->calls .= $match;
31104fd306cSNickeau            return;
31204fd306cSNickeau        }
31304fd306cSNickeau
31404fd306cSNickeau        if (!$isWikiUri) {
31504fd306cSNickeau            // Other type of links
316*313de40aSNicolas GERARD            if ($useWikiText) {
317*313de40aSNicolas GERARD                $handler->wikitext .= $match;
318*313de40aSNicolas GERARD            } else {
31904fd306cSNickeau                $handler->calls .= $match;
320*313de40aSNicolas GERARD            }
32104fd306cSNickeau            return;
32204fd306cSNickeau        }
32304fd306cSNickeau
32404fd306cSNickeau        /**
32504fd306cSNickeau         * This function will modify and add the link to the new output (ie calls)
32604fd306cSNickeau         * {@link helper_plugin_move_handler::$calls}
32704fd306cSNickeau         */
3285f891b7eSNickeau        $handler->internallink($match, $state, $pos);
32904fd306cSNickeau
33004fd306cSNickeau        /**
33104fd306cSNickeau         * Internal Link Calls Hack
33204fd306cSNickeau         * that delete the ]]
33304fd306cSNickeau         */
3345f891b7eSNickeau        $suffix = "]]";
335*313de40aSNicolas GERARD        if ($useWikiText) {
336*313de40aSNicolas GERARD            if (substr($handler->wikitext, -strlen($suffix)) === $suffix) {
337*313de40aSNicolas GERARD                $handler->wikitext = substr($handler->wikitext, 0, strlen($handler->wikitext) - strlen($suffix));
338*313de40aSNicolas GERARD            }
339*313de40aSNicolas GERARD        } else {
34004fd306cSNickeau            if (substr($handler->calls, -strlen($suffix)) === $suffix) {
34121913ab3SNickeau                $handler->calls = substr($handler->calls, 0, strlen($handler->calls) - strlen($suffix));
3425f891b7eSNickeau            }
343*313de40aSNicolas GERARD        }
3445f891b7eSNickeau
3455f891b7eSNickeau
3465f891b7eSNickeau    }
34704fd306cSNickeau
34804fd306cSNickeau    private function reportError(Exception $exception)
34904fd306cSNickeau    {
35004fd306cSNickeau        // We catch the errors if any to not stop the move
35104fd306cSNickeau        // There is no transaction feature (it happens or not)
35204fd306cSNickeau        $message = "An error occurred during the move replication to the database. Error message was: " . $exception->getMessage();
35304fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
35404fd306cSNickeau            throw new RuntimeException($exception);
3555f891b7eSNickeau        } else {
35604fd306cSNickeau            LogUtility::error($message, self::CANONICAL, $exception);
3575f891b7eSNickeau        }
358007225e5Sgerardnico    }
359007225e5Sgerardnico
360007225e5Sgerardnico
361007225e5Sgerardnico}
362