1<?php 2/** 3 * Move Plugin Page Rewriter 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Michael Hamann <michael@content-space.de> 7 * @author Gary Owen <gary@isection.co.uk> 8 * @author Andreas Gohr <gohr@cosmocode.de> 9 */ 10// must be run within Dokuwiki 11if(!defined('DOKU_INC')) die(); 12 13// load required handler class 14require_once(dirname(__FILE__) . '/handler.php'); 15 16/** 17 * Class helper_plugin_move_rewrite 18 * 19 * This class handles the rewriting of wiki text to update the links 20 */ 21class helper_plugin_move_rewrite extends DokuWiki_Plugin { 22 23 /** 24 * Under what key is move data to be saved in metadata 25 */ 26 const METAKEY = 'plugin_move'; 27 28 /** 29 * What is they filename of the lockfile 30 */ 31 const LOCKFILENAME = '_plugin_move.lock'; 32 33 /** 34 * @var string symbol to make move operations easily recognizable in change log 35 */ 36 public $symbol = '↷'; 37 38 /** 39 * This function loads and returns the persistent metadata for the move plugin. If there is metadata for the 40 * pagemove plugin (not the old one but the version that immediately preceeded the move plugin) it will be migrated. 41 * 42 * @param string $id The id of the page the metadata shall be loaded for 43 * @return array|null The metadata of the page 44 */ 45 public function getMoveMeta($id) { 46 $all_meta = p_get_metadata($id, '', METADATA_DONT_RENDER); 47 48 /* todo migrate old move data 49 if(isset($all_meta['plugin_pagemove']) && !is_null($all_meta['plugin_pagemove'])) { 50 if(isset($all_meta[self::METAKEY])) { 51 $all_meta[self::METAKEY] = array_merge_recursive($all_meta['plugin_pagemove'], $all_meta[self::METAKEY]); 52 } else { 53 $all_meta[self::METAKEY] = $all_meta['plugin_pagemove']; 54 } 55 p_set_metadata($id, array(self::METAKEY => $all_meta[self::METAKEY], 'plugin_pagemove' => null), false, true); 56 } 57 */ 58 59 // discard missing or empty array or string 60 $meta = !empty($all_meta[self::METAKEY]) ? $all_meta[self::METAKEY] : array(); 61 if(!isset($meta['origin'])) { 62 $meta['origin'] = ''; 63 } 64 if(!isset($meta['pages'])) { 65 $meta['pages'] = array(); 66 } 67 if(!isset($meta['media'])) { 68 $meta['media'] = array(); 69 } 70 71 return $meta; 72 } 73 74 /** 75 * Remove any existing move meta data for the given page 76 * 77 * @param $id 78 */ 79 public function unsetMoveMeta($id) { 80 p_set_metadata($id, array(self::METAKEY => array()), false, true); 81 } 82 83 /** 84 * Add info about a moved document to the metadata of an affected page 85 * 86 * @param string $id affected page 87 * @param string $src moved document's original id 88 * @param string $dst moved document's new id 89 * @param string $type 'media' or 'page' 90 * @throws Exception on wrong argument 91 */ 92 public function setMoveMeta($id, $src, $dst, $type) { 93 $this->setMoveMetas($id, array($src => $dst), $type); 94 } 95 96 /** 97 * Add info about several moved documents to the metadata of an affected page 98 * 99 * @param string $id affected page 100 * @param array $moves list of moves (src is key, dst is value) 101 * @param string $type 'media' or 'page' 102 * @throws Exception 103 */ 104 public function setMoveMetas($id, $moves, $type) { 105 if($type != 'pages' && $type != 'media') { 106 throw new Exception('wrong type specified'); 107 } 108 if(!page_exists($id, '', false)) { 109 return; 110 } 111 112 $meta = $this->getMoveMeta($id); 113 foreach($moves as $src => $dst) { 114 $meta[$type][] = array($src, $dst); 115 } 116 117 p_set_metadata($id, array(self::METAKEY => $meta), false, true); 118 } 119 120 /** 121 * Store info about the move of a page in its own meta data 122 * 123 * This has to be called before the move is executed 124 * 125 * @param string $id moved page's original (and still current) id 126 */ 127 public function setSelfMoveMeta($id) { 128 $meta = $this->getMoveMeta($id); 129 // was this page moved multiple times? keep the orignal name til rewriting occured 130 if(isset($meta['origin']) && $meta['origin'] !== '') { 131 return; 132 } 133 $meta['origin'] = $id; 134 135 p_set_metadata($id, array(self::METAKEY => $meta), false, true); 136 } 137 138 /** 139 * Check if rewrites may be executed within this process right now 140 * 141 * @return bool 142 */ 143 public static function isLocked() { 144 global $PLUGIN_MOVE_WORKING; 145 global $conf; 146 $lockfile = $conf['lockdir'] . self::LOCKFILENAME; 147 return ((isset($PLUGIN_MOVE_WORKING) && $PLUGIN_MOVE_WORKING > 0) || file_exists($lockfile)); 148 } 149 150 /** 151 * Do not allow any rewrites in this process right now 152 */ 153 public static function addLock() { 154 global $PLUGIN_MOVE_WORKING; 155 global $conf; 156 $PLUGIN_MOVE_WORKING = $PLUGIN_MOVE_WORKING ? $PLUGIN_MOVE_WORKING + 1 : 1; 157 $lockfile = $conf['lockdir'] . self::LOCKFILENAME; 158 if (!file_exists($lockfile)) { 159 io_savefile($lockfile, "1\n"); 160 } else { 161 $stack = intval(file_get_contents($lockfile)); 162 ++$stack; 163 io_savefile($lockfile, strval($stack)); 164 } 165 } 166 167 /** 168 * Allow rerites in this process again, unless some other lock exists 169 */ 170 public static function removeLock() { 171 global $PLUGIN_MOVE_WORKING; 172 global $conf; 173 $PLUGIN_MOVE_WORKING = $PLUGIN_MOVE_WORKING ? $PLUGIN_MOVE_WORKING - 1 : 0; 174 $lockfile = $conf['lockdir'] . self::LOCKFILENAME; 175 if (!file_exists($lockfile)) { 176 throw new Exception("removeLock failed: lockfile missing"); 177 } else { 178 $stack = intval(file_get_contents($lockfile)); 179 if($stack === 1) { 180 unlink($lockfile); 181 } else { 182 --$stack; 183 io_savefile($lockfile, strval($stack)); 184 } 185 } 186 } 187 188 /** 189 * Allow rewrites in this process again. 190 * 191 * @author Michael Große <grosse@cosmocode.de> 192 */ 193 public static function removeAllLocks() { 194 global $conf; 195 $lockfile = $conf['lockdir'] . self::LOCKFILENAME; 196 if (file_exists($lockfile)) { 197 unlink($lockfile); 198 } 199 unset($GLOBALS['PLUGIN_MOVE_WORKING']); 200 } 201 202 203 /** 204 * Rewrite a text in order to fix the content after the given moves. 205 * 206 * @param string $id The id of the wiki page, if the page itself was moved the old id 207 * @param string $text The text to be rewritten 208 * @return string The rewritten wiki text 209 */ 210 public function rewrite($id, $text) { 211 $meta = $this->getMoveMeta($id); 212 213 $handlers = array(); 214 $pages = $meta['pages']; 215 $media = $meta['media']; 216 $origin = $meta['origin']; 217 if($origin == '') $origin = $id; 218 219 $data = array( 220 'id' => $id, 221 'origin' => &$origin, 222 'pages' => &$pages, 223 'media_moves' => &$media, 224 'handlers' => &$handlers 225 ); 226 227 /* 228 * PLUGIN_MOVE_HANDLERS REGISTER event: 229 * 230 * Plugin handlers can be registered in the $handlers array, the key is the plugin name as it is given to the handler 231 * The handler needs to be a valid callback, it will get the following parameters: 232 * $match, $state, $pos, $pluginname, $handler. The first three parameters are equivalent to the parameters 233 * of the handle()-function of syntax plugins, the $pluginname is just the plugin name again so handler functions 234 * that handle multiple plugins can distinguish for which the match is. The last parameter is the handler object 235 * which is an instance of helper_plugin_move_handle 236 */ 237 trigger_event('PLUGIN_MOVE_HANDLERS_REGISTER', $data); 238 239 $modes = p_get_parsermodes(); 240 241 // Create the parser 242 $Parser = new Doku_Parser(); 243 244 // Add the Handler 245 /** @var $Parser->Handler helper_plugin_move_handler */ 246 $Parser->Handler = $this->loadHelper('move_handler'); 247 $Parser->Handler->init($id, $origin, $pages, $media, $handlers); 248 249 //add modes to parser 250 foreach($modes as $mode) { 251 $Parser->addMode($mode['mode'], $mode['obj']); 252 } 253 254 return $Parser->parse($text); 255 } 256 257 /** 258 * Rewrite the text of a page according to the recorded moves, the rewritten text is saved 259 * 260 * @param string $id The id of the page that shall be rewritten 261 * @param string|null $text Old content of the page. When null is given the content is loaded from disk 262 * @return string|bool The rewritten content, false on error 263 */ 264 public function rewritePage($id, $text = null, $save = true) { 265 $meta = $this->getMoveMeta($id); 266 if(is_null($text)) { 267 $text = rawWiki($id); 268 } 269 270 if($meta['pages'] || $meta['media']) { 271 $old_text = $text; 272 $text = $this->rewrite($id, $text); 273 274 $changed = ($old_text != $text); 275 $file = wikiFN($id, '', false); 276 if ($save === true) { 277 if(is_writable($file) || !$changed) { 278 if($changed) { 279 // Wait a second when the page has just been rewritten 280 $oldRev = filemtime(wikiFN($id)); 281 if($oldRev == time()) sleep(1); 282 283 saveWikiText($id, $text, $this->symbol . ' ' . $this->getLang('linkchange'), $this->getConf('minor')); 284 } 285 $this->unsetMoveMeta($id); 286 } else { 287 // FIXME: print error here or fail silently? 288 msg('Error: Page ' . hsc($id) . ' needs to be rewritten because of page renames but is not writable.', -1); 289 return false; 290 } 291 } 292 } 293 294 return $text; 295 } 296 297} 298