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 /** 269 * The goal is to recreate the document 270 * in the {@link helper_plugin_move_handler::$calls} 271 * variable (this is a string, not an array of calls) 272 * 273 * We got the {@link syntax_plugin_combo_link::handle() render match of the handle function} 274 * 275 * Unfortunately, all environemnt propertes of {@link helper_plugin_move_handler} 276 * such as {@link helper_plugin_move_handler::$id} are private 277 * 278 * The code below calls then the rewrite function {@link helper_plugin_move_handler::internallink()} 279 * and change the content modified in the {@link helper_plugin_move_handler::$calls} variable 280 * 281 */ 282 if ($state !== DOKU_LEXER_ENTER) { 283 // Description and ending 284 $handler->calls .= $match; 285 return; 286 } 287 288 /** 289 * All environment on the {@link helper_plugin_move_handler handler} are private 290 * We can't get it, we just hack around the move of the handler then 291 */ 292 $parseAttributes = syntax_plugin_combo_link::parse($match); 293 $ref = $parseAttributes[syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE]; 294 try { 295 $link = LinkMarkup::createFromRef($ref); 296 $isWikiUri = $link->getMarkupRef()->getSchemeType() === MarkupRef::WIKI_URI; 297 } catch (ExceptionCompile $e) { 298 LogUtility::error("Unable to rewrite the markup reference for a link move. The markup ref ($ref) could not be parsed. Error: {$e->getMessage()}"); 299 $handler->calls .= $match; 300 return; 301 } 302 303 if (!$isWikiUri) { 304 // Other type of links 305 $handler->calls .= $match; 306 return; 307 } 308 309 /** 310 * This function will modify and add the link to the new output (ie calls) 311 * {@link helper_plugin_move_handler::$calls} 312 */ 313 $handler->internallink($match, $state, $pos); 314 315 /** 316 * Internal Link Calls Hack 317 * that delete the ]] 318 */ 319 $suffix = "]]"; 320 if (substr($handler->calls, -strlen($suffix)) === $suffix) { 321 $handler->calls = substr($handler->calls, 0, strlen($handler->calls) - strlen($suffix)); 322 } 323 324 325 } 326 327 private function reportError(Exception $exception) 328 { 329 // We catch the errors if any to not stop the move 330 // There is no transaction feature (it happens or not) 331 $message = "An error occurred during the move replication to the database. Error message was: " . $exception->getMessage(); 332 if (PluginUtility::isDevOrTest()) { 333 throw new RuntimeException($exception); 334 } else { 335 LogUtility::error($message, self::CANONICAL, $exception); 336 } 337 } 338 339 340} 341