You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

298 lines
9.9 KiB

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