* @author Gary Owen * @author Andreas Gohr */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); // load required handler class require_once(dirname(__FILE__) . '/handler.php'); /** * Class helper_plugin_move_rewrite * * This class handles the rewriting of wiki text to update the links */ class helper_plugin_move_rewrite extends DokuWiki_Plugin { /** * Under what key is move data to be saved in metadata */ const METAKEY = 'plugin_move'; /** * What is they filename of the lockfile */ const LOCKFILENAME = '_plugin_move.lock'; /** * @var string symbol to make move operations easily recognizable in change log */ public $symbol = '↷'; /** * This function loads and returns the persistent metadata for the move plugin. If there is metadata for the * pagemove plugin (not the old one but the version that immediately preceeded the move plugin) it will be migrated. * * @param string $id The id of the page the metadata shall be loaded for * @return array|null The metadata of the page */ public function getMoveMeta($id) { $all_meta = p_get_metadata($id, '', METADATA_DONT_RENDER); /* todo migrate old move data if(isset($all_meta['plugin_pagemove']) && !is_null($all_meta['plugin_pagemove'])) { if(isset($all_meta[self::METAKEY])) { $all_meta[self::METAKEY] = array_merge_recursive($all_meta['plugin_pagemove'], $all_meta[self::METAKEY]); } else { $all_meta[self::METAKEY] = $all_meta['plugin_pagemove']; } p_set_metadata($id, array(self::METAKEY => $all_meta[self::METAKEY], 'plugin_pagemove' => null), false, true); } */ // discard missing or empty array or string $meta = !empty($all_meta[self::METAKEY]) ? $all_meta[self::METAKEY] : array(); if(!isset($meta['origin'])) { $meta['origin'] = ''; } if(!isset($meta['pages'])) { $meta['pages'] = array(); } if(!isset($meta['media'])) { $meta['media'] = array(); } return $meta; } /** * Remove any existing move meta data for the given page * * @param $id */ public function unsetMoveMeta($id) { p_set_metadata($id, array(self::METAKEY => array()), false, true); } /** * Add info about a moved document to the metadata of an affected page * * @param string $id affected page * @param string $src moved document's original id * @param string $dst moved document's new id * @param string $type 'media' or 'page' * @throws Exception on wrong argument */ public function setMoveMeta($id, $src, $dst, $type) { $this->setMoveMetas($id, array($src => $dst), $type); } /** * Add info about several moved documents to the metadata of an affected page * * @param string $id affected page * @param array $moves list of moves (src is key, dst is value) * @param string $type 'media' or 'page' * @throws Exception */ public function setMoveMetas($id, $moves, $type) { if($type != 'pages' && $type != 'media') { throw new Exception('wrong type specified'); } if(!page_exists($id, '', false)) { return; } $meta = $this->getMoveMeta($id); foreach($moves as $src => $dst) { $meta[$type][] = array($src, $dst); } p_set_metadata($id, array(self::METAKEY => $meta), false, true); } /** * Store info about the move of a page in its own meta data * * This has to be called before the move is executed * * @param string $id moved page's original (and still current) id */ public function setSelfMoveMeta($id) { $meta = $this->getMoveMeta($id); // was this page moved multiple times? keep the orignal name til rewriting occured if(isset($meta['origin']) && $meta['origin'] !== '') { return; } $meta['origin'] = $id; p_set_metadata($id, array(self::METAKEY => $meta), false, true); } /** * Check if rewrites may be executed within this process right now * * @return bool */ public static function isLocked() { global $PLUGIN_MOVE_WORKING; global $conf; $lockfile = $conf['lockdir'] . self::LOCKFILENAME; return ((isset($PLUGIN_MOVE_WORKING) && $PLUGIN_MOVE_WORKING > 0) || file_exists($lockfile)); } /** * Do not allow any rewrites in this process right now */ public static function addLock() { global $PLUGIN_MOVE_WORKING; global $conf; $PLUGIN_MOVE_WORKING = $PLUGIN_MOVE_WORKING ? $PLUGIN_MOVE_WORKING + 1 : 1; $lockfile = $conf['lockdir'] . self::LOCKFILENAME; if (!file_exists($lockfile)) { io_savefile($lockfile, "1\n"); } else { $stack = intval(file_get_contents($lockfile)); ++$stack; io_savefile($lockfile, strval($stack)); } } /** * Allow rerites in this process again, unless some other lock exists */ public static function removeLock() { global $PLUGIN_MOVE_WORKING; global $conf; $PLUGIN_MOVE_WORKING = $PLUGIN_MOVE_WORKING ? $PLUGIN_MOVE_WORKING - 1 : 0; $lockfile = $conf['lockdir'] . self::LOCKFILENAME; if (!file_exists($lockfile)) { throw new Exception("removeLock failed: lockfile missing"); } else { $stack = intval(file_get_contents($lockfile)); if($stack === 1) { unlink($lockfile); } else { --$stack; io_savefile($lockfile, strval($stack)); } } } /** * Allow rewrites in this process again. * * @author Michael Große */ public static function removeAllLocks() { global $conf; $lockfile = $conf['lockdir'] . self::LOCKFILENAME; if (file_exists($lockfile)) { unlink($lockfile); } unset($GLOBALS['PLUGIN_MOVE_WORKING']); } /** * Rewrite a text in order to fix the content after the given moves. * * @param string $id The id of the wiki page, if the page itself was moved the old id * @param string $text The text to be rewritten * @return string The rewritten wiki text */ public function rewrite($id, $text) { $meta = $this->getMoveMeta($id); $handlers = array(); $pages = $meta['pages']; $media = $meta['media']; $origin = $meta['origin']; if($origin == '') $origin = $id; $data = array( 'id' => $id, 'origin' => &$origin, 'pages' => &$pages, 'media_moves' => &$media, 'handlers' => &$handlers ); /* * PLUGIN_MOVE_HANDLERS REGISTER event: * * Plugin handlers can be registered in the $handlers array, the key is the plugin name as it is given to the handler * The handler needs to be a valid callback, it will get the following parameters: * $match, $state, $pos, $pluginname, $handler. The first three parameters are equivalent to the parameters * of the handle()-function of syntax plugins, the $pluginname is just the plugin name again so handler functions * that handle multiple plugins can distinguish for which the match is. The last parameter is the handler object * which is an instance of helper_plugin_move_handle */ trigger_event('PLUGIN_MOVE_HANDLERS_REGISTER', $data); $modes = p_get_parsermodes(); // Create the parser $Parser = new Doku_Parser(); // Add the Handler /** @var $Parser->Handler helper_plugin_move_handler */ $Parser->Handler = $this->loadHelper('move_handler'); $Parser->Handler->init($id, $origin, $pages, $media, $handlers); //add modes to parser foreach($modes as $mode) { $Parser->addMode($mode['mode'], $mode['obj']); } return $Parser->parse($text); } /** * Rewrite the text of a page according to the recorded moves, the rewritten text is saved * * @param string $id The id of the page that shall be rewritten * @param string|null $text Old content of the page. When null is given the content is loaded from disk * @return string|bool The rewritten content, false on error */ public function rewritePage($id, $text = null, $save = true) { $meta = $this->getMoveMeta($id); if(is_null($text)) { $text = rawWiki($id); } if($meta['pages'] || $meta['media']) { $old_text = $text; $text = $this->rewrite($id, $text); $changed = ($old_text != $text); $file = wikiFN($id, '', false); if ($save === true) { if(is_writable($file) || !$changed) { if($changed) { // Wait a second when the page has just been rewritten $oldRev = filemtime(wikiFN($id)); if($oldRev == time()) sleep(1); saveWikiText($id, $text, $this->symbol . ' ' . $this->getLang('linkchange'), $this->getConf('minor')); } $this->unsetMoveMeta($id); } else { // FIXME: print error here or fail silently? msg('Error: Page ' . hsc($id) . ' needs to be rewritten because of page renames but is not writable.', -1); return false; } } } return $text; } }