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