1<?php 2 3require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 4 5use ComboStrap\DatabasePageRow; 6use ComboStrap\ExceptionCompile; 7use ComboStrap\ExceptionNotFound; 8use ComboStrap\ExecutionContext; 9use ComboStrap\FileSystems; 10use ComboStrap\LinkMarkup; 11use ComboStrap\LogUtility; 12use ComboStrap\MarkupPath; 13use ComboStrap\MarkupRef; 14use ComboStrap\Meta\Field\Aliases; 15use ComboStrap\Meta\Store\MetadataDbStore; 16use ComboStrap\Meta\Store\MetadataDokuWikiStore; 17use ComboStrap\PageId; 18use ComboStrap\PluginUtility; 19use ComboStrap\Site; 20 21 22/** 23 * Class action_plugin_combo_move 24 * Handle the move of a page in order to update: 25 * * the link 26 * * the data in the database 27 */ 28class action_plugin_combo_linkmove extends DokuWiki_Action_Plugin 29{ 30 31 32 const CANONICAL = "move"; 33 const FILE_MOVE_OPERATION = "move"; 34 35 public static function isMoveOperation(): bool 36 { 37 try { 38 $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 39 $executionContext->getRuntimeObject(action_plugin_combo_linkmove::FILE_MOVE_OPERATION); 40 return true; 41 } catch (ExceptionNotFound $e) { 42 return false; 43 } 44 } 45 46 private static function checkAndSendAMessageIfLockFilePresent(): bool 47 { 48 $lockFile = Site::getDataDirectory()->resolve("locks_plugin_move.lock"); 49 if (!FileSystems::exists($lockFile)) { 50 return false; 51 } 52 $lockFileDateTimeModified = FileSystems::getModifiedTime($lockFile); 53 $lockFileModifiedTimestamp = $lockFileDateTimeModified->getTimestamp(); 54 $now = time(); 55 56 $distance = $now - $lockFileModifiedTimestamp; 57 $lockFileAgeInMinute = ($distance) / 60; 58 if ($lockFileAgeInMinute > 5) { 59 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."); 60 return true; 61 } 62 return false; 63 } 64 65 /** 66 * As explained https://www.dokuwiki.org/plugin:move#support_for_other_plugins 67 * @param Doku_Event_Handler $controller 68 */ 69 function register(Doku_Event_Handler $controller) 70 { 71 72 /** 73 * To rewrite the page meta in the database 74 */ 75 $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'BEFORE', $this, 'handle_rename_before', array()); 76 $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'AFTER', $this, 'handle_rename_after', array()); 77 78 /** 79 * To rewrite the link 80 */ 81 $controller->register_hook('PLUGIN_MOVE_HANDLERS_REGISTER', 'BEFORE', $this, 'handle_link', array()); 82 83 84 /** 85 * Check for the presence of a lock file 86 */ 87 $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 'check_lock_file_age', array()); 88 89 90 } 91 92 /** 93 * @param Doku_Event $event 94 * @param $params 95 * 96 * When a lock file is present, 97 * the move plugin will purge the data in {@link action_plugin_move_rewrite::handle_cache()} 98 * making the rendering fucking slow 99 * We check that the lock file is not 100 */ 101 function check_lock_file_age(Doku_Event $event, $params) 102 { 103 self::checkAndSendAMessageIfLockFilePresent(); 104 105 } 106 107 /** 108 * Handle the path modification of a page 109 * @param Doku_Event $event - https://www.dokuwiki.org/plugin:move#for_plugin_authors 110 * @param $params 111 * 112 */ 113 function handle_rename_before(Doku_Event $event, $params) 114 { 115 116 117 /** 118 * Check that the lock file is not older 119 * Lock file bigger than 5 minutes 120 * Is not really possible 121 */ 122 $result = self::checkAndSendAMessageIfLockFilePresent(); 123 if ($result === true) { 124 $event->preventDefault(); 125 LogUtility::warning("The move lock file is present, the move was canceled."); 126 return; 127 } 128 129 /** 130 * The move plugin just delete the file 131 * and recreate it. 132 * There is no move operation 133 * Therefore, because a delete in the database is a soft delete (ie if not exists) 134 * We move it now 135 */ 136 $sourceId = $event->data["src_id"]; 137 $targetId = $event->data["dst_id"]; 138 try { 139 140 /** 141 * Update the dokuwiki id and path 142 */ 143 try { 144 $databasePage = DatabasePageRow::getFromDokuWikiId($sourceId); 145 } catch (ExceptionNotFound $e) { 146 LogUtility::warning("The source database row $sourceId was not found in the database"); 147 return; 148 } 149 150 $databasePage->updatePathAndDokuwikiId($targetId); 151 152 /** 153 * Advertise the move 154 * Because otherwise the file is deleted 155 * in {@link action_plugin_combo_pagesystemmutation} 156 */ 157 ExecutionContext::getActualOrCreateFromEnv() 158 ->setRuntimeObject(self::FILE_MOVE_OPERATION, $sourceId); 159 160 } catch (Exception $exception) { 161 $this->reportError($exception); 162 } 163 164 } 165 166 /** 167 * Handle the path modification of a page after 168 * 169 * The metadata file should also have been moved 170 * 171 * @param Doku_Event $event - https://www.dokuwiki.org/plugin:move#for_plugin_authors 172 * @param $params 173 * 174 */ 175 function handle_rename_after(Doku_Event $event, $params) 176 { 177 /** 178 * 179 * $event->data 180 * src_id ⇒ string – the original ID of the page 181 * dst_id ⇒ string – the new ID of the page 182 */ 183 $sourceId = $event->data["src_id"]; 184 $targetId = $event->data["dst_id"]; 185 try { 186 187 /** 188 * Update the dokuwiki id and path 189 */ 190 try { 191 $databasePage = DatabasePageRow::getFromDokuWikiId($targetId); 192 } catch (ExceptionNotFound $e) { 193 LogUtility::warning("The target database row with the id ($targetId) was not found in the database"); 194 return; 195 } 196 197 /** 198 * Check page id 199 */ 200 $targetPage = MarkupPath::createMarkupFromId($targetId); 201 $targetPageId = PageId::createForPage($targetPage); 202 $targetPageIdValue = $targetPageId->getValueFromStore(); 203 $databasePageIdValue = $databasePage->getPageId(); 204 205 if ($targetPageIdValue === null) { 206 $targetPageId 207 ->setValue($databasePageIdValue) 208 ->persist(); 209 } else { 210 /** 211 * {@link helper_plugin_move_op::movePage()} 212 * delete and save the file with log 213 */ 214 $targetPageId 215 ->setValueForce($databasePageIdValue) 216 ->persist(); 217 } 218 219 /** 220 * Add the alias 221 */ 222 Aliases::createForPage($targetPage) 223 ->addAlias($sourceId) 224 ->setWriteStore(MetadataDokuWikiStore::class) 225 ->sendToWriteStore() 226 ->persist() 227 ->setReadStore(MetadataDbStore::class) 228 ->sendToWriteStore() 229 ->persist(); 230 231 /** 232 * Stop advertising the move 233 */ 234 ExecutionContext::getActualOrCreateFromEnv() 235 ->closeAndRemoveRuntimeVariableIfExists(self::FILE_MOVE_OPERATION); 236 237 } catch (Exception $exception) { 238 $this->reportError($exception); 239 } 240 241 } 242 243 244 /** 245 * Handle the move of a page 246 * @param Doku_Event $event 247 * @param $params 248 */ 249 function handle_link(Doku_Event $event, $params) 250 { 251 /** 252 * The handlers is the name of the component (ie refers to the {@link syntax_plugin_combo_link} handler) 253 * and 'rewrite_combo' to the below method 254 */ 255 $event->data['handlers'][syntax_plugin_combo_link::COMPONENT] = array($this, 'rewrite_combo'); 256 } 257 258 /** 259 * 260 * @param $match 261 * @param $state 262 * @param $pos 263 * @param $plugin 264 * @param helper_plugin_move_handler $handler 265 */ 266 public function rewrite_combo($match, $state, $pos, $plugin, helper_plugin_move_handler $handler) 267 { 268 // Wiki text was introduced 269 // https://github.com/michitux/dokuwiki-plugin-move/commit/74ee74225777949c09b76ccc9caf218f15070b44 270 $useWikiText = true; 271 if (!property_exists($handler, 'wikitext')) { 272 // old move plugin 273 $useWikiText = false; 274 } 275 /** 276 * The goal is to recreate the document 277 * in the {@link helper_plugin_move_handler::$calls} 278 * variable (this is a string, not an array of calls) 279 * 280 * We got the {@link syntax_plugin_combo_link::handle() render match of the handle function} 281 * 282 * Unfortunately, all environemnt propertes of {@link helper_plugin_move_handler} 283 * such as {@link helper_plugin_move_handler::$id} are private 284 * 285 * The code below calls then the rewrite function {@link helper_plugin_move_handler::internallink()} 286 * and change the content modified in the {@link helper_plugin_move_handler::$calls} variable 287 * 288 */ 289 if ($state !== DOKU_LEXER_ENTER) { 290 // Description and ending 291 if ($useWikiText) { 292 $handler->wikitext .= $match; 293 } else { 294 $handler->calls .= $match; 295 } 296 return; 297 } 298 299 /** 300 * All environment on the {@link helper_plugin_move_handler handler} are private 301 * We can't get it, we just hack around the move of the handler then 302 */ 303 $parseAttributes = syntax_plugin_combo_link::parse($match); 304 $ref = $parseAttributes[syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE]; 305 try { 306 $link = LinkMarkup::createFromRef($ref); 307 $isWikiUri = $link->getMarkupRef()->getSchemeType() === MarkupRef::WIKI_URI; 308 } catch (ExceptionCompile $e) { 309 LogUtility::error("Unable to rewrite the markup reference for a link move. The markup ref ($ref) could not be parsed. Error: {$e->getMessage()}"); 310 $handler->calls .= $match; 311 return; 312 } 313 314 if (!$isWikiUri) { 315 // Other type of links 316 if ($useWikiText) { 317 $handler->wikitext .= $match; 318 } else { 319 $handler->calls .= $match; 320 } 321 return; 322 } 323 324 /** 325 * This function will modify and add the link to the new output (ie calls) 326 * {@link helper_plugin_move_handler::$calls} 327 */ 328 $handler->internallink($match, $state, $pos); 329 330 /** 331 * Internal Link Calls Hack 332 * that delete the ]] 333 */ 334 $suffix = "]]"; 335 if ($useWikiText) { 336 if (substr($handler->wikitext, -strlen($suffix)) === $suffix) { 337 $handler->wikitext = substr($handler->wikitext, 0, strlen($handler->wikitext) - strlen($suffix)); 338 } 339 } else { 340 if (substr($handler->calls, -strlen($suffix)) === $suffix) { 341 $handler->calls = substr($handler->calls, 0, strlen($handler->calls) - strlen($suffix)); 342 } 343 } 344 345 346 } 347 348 private function reportError(Exception $exception) 349 { 350 // We catch the errors if any to not stop the move 351 // There is no transaction feature (it happens or not) 352 $message = "An error occurred during the move replication to the database. Error message was: " . $exception->getMessage(); 353 if (PluginUtility::isDevOrTest()) { 354 throw new RuntimeException($exception); 355 } else { 356 LogUtility::error($message, self::CANONICAL, $exception); 357 } 358 } 359 360 361} 362