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.
 
 
 
 
 

324 lines
11 KiB

  1. <?php
  2. /**
  3. * Move Plugin Operation Execution
  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. class helper_plugin_move_op extends DokuWiki_Plugin {
  13. /**
  14. * @var string symbol to make move operations easily recognizable in change log
  15. */
  16. public $symbol = '↷';
  17. /**
  18. * @var array stores the affected pages of the last operation
  19. */
  20. protected $affectedPages = array();
  21. /**
  22. * Check if the given page can be moved to the given destination
  23. *
  24. * @param $src
  25. * @param $dst
  26. * @return bool
  27. */
  28. public function checkPage($src, $dst) {
  29. // Check we have rights to move this document
  30. if(!page_exists($src)) {
  31. msg(sprintf($this->getLang('notexist'), $src), -1);
  32. return false;
  33. }
  34. if(auth_quickaclcheck($src) < AUTH_EDIT) {
  35. msg(sprintf($this->getLang('norights'), $src), -1);
  36. return false;
  37. }
  38. // Check file is not locked
  39. // checklock checks if the page lock hasn't expired and the page hasn't been locked by another user
  40. // the file exists check checks if the page is reported unlocked if a lock exists which means that
  41. // the page is locked by the current user
  42. if(checklock($src) !== false || @file_exists(wikiLockFN($src))) {
  43. msg(sprintf($this->getLang('filelocked'), $src), -1);
  44. return false;
  45. }
  46. // Has the document name and/or namespace changed?
  47. if($src == $dst) {
  48. msg(sprintf($this->getLang('notchanged'), $src), -1);
  49. return false;
  50. }
  51. // Check the page does not already exist
  52. if(page_exists($dst)) {
  53. msg(sprintf($this->getLang('exists'), $src, $dst), -1);
  54. return false;
  55. }
  56. // Check if the current user can create the new page
  57. if(auth_quickaclcheck($dst) < AUTH_CREATE) {
  58. msg(sprintf($this->getLang('notargetperms'), $dst), -1);
  59. return false;
  60. }
  61. return true;
  62. }
  63. /**
  64. * Check if the given media file can be moved to the given destination
  65. *
  66. * @param $src
  67. * @param $dst
  68. * @return bool
  69. */
  70. public function checkMedia($src, $dst) {
  71. // Check we have rights to move this document
  72. if(!file_exists(mediaFN($src))) {
  73. msg(sprintf($this->getLang('medianotexist'), $src), -1);
  74. return false;
  75. }
  76. if(auth_quickaclcheck($src) < AUTH_DELETE) {
  77. msg(sprintf($this->getLang('nomediarights'), $src), -1);
  78. return false;
  79. }
  80. // Has the document name and/or namespace changed?
  81. if($src == $dst) {
  82. msg(sprintf($this->getLang('medianotchanged'), $src), -1);
  83. return false;
  84. }
  85. // Check the page does not already exist
  86. if(@file_exists(mediaFN($dst))) {
  87. msg(sprintf($this->getLang('mediaexists'), $src, $dst), -1);
  88. return false;
  89. }
  90. // Check if the current user can create the new page
  91. if(auth_quickaclcheck($dst) < AUTH_UPLOAD) {
  92. msg(sprintf($this->getLang('nomediatargetperms'), $dst), -1);
  93. return false;
  94. }
  95. // check if the file extension is unchanged
  96. if (pathinfo(mediaFN($src), PATHINFO_EXTENSION) !== pathinfo(mediaFN($dst), PATHINFO_EXTENSION)) {
  97. msg($this->getLang('extensionchange'), -1);
  98. return false;
  99. }
  100. return true;
  101. }
  102. /**
  103. * Execute a page move/rename
  104. *
  105. * @param string $src original ID
  106. * @param string $dst new ID
  107. * @return bool
  108. */
  109. public function movePage($src, $dst) {
  110. if(!$this->checkPage($src, $dst)) return false;
  111. // lock rewrites
  112. helper_plugin_move_rewrite::addLock();
  113. /** @var helper_plugin_move_rewrite $Rewriter */
  114. $Rewriter = plugin_load('helper', 'move_rewrite');
  115. // remember what this page was called before the move in meta data
  116. $Rewriter->setSelfMoveMeta($src);
  117. // ft_backlinks() is not used here, as it does a hidden page and acl check but we really need all pages
  118. $affected_pages = idx_get_indexer()->lookupKey('relation_references', $src);
  119. $affected_pages[] = $dst; // the current page is always affected, because all relative links may have changed
  120. $affected_pages = array_unique($affected_pages);
  121. $src_ns = getNS($src);
  122. $src_name = noNS($src);
  123. $dst_ns = getNS($dst);
  124. $dst_name = noNS($dst);
  125. // pass this info on to other plugins
  126. $eventdata = array(
  127. // this is for compatibility to old plugin
  128. 'opts' => array(
  129. 'ns' => $src_ns,
  130. 'name' => $src_name,
  131. 'newns' => $dst_ns,
  132. 'newname' => $dst_name,
  133. ),
  134. 'affected_pages' => &$affected_pages,
  135. 'src_id' => $src,
  136. 'dst_id' => $dst,
  137. );
  138. // give plugins the option to add their own meta files to the list of files that need to be moved
  139. // to the oldfiles/newfiles array or to adjust their own metadata, database, ...
  140. // and to add other pages to the affected pages
  141. $event = new Doku_Event('PLUGIN_MOVE_PAGE_RENAME', $eventdata);
  142. if($event->advise_before()) {
  143. lock($src);
  144. /** @var helper_plugin_move_file $FileMover */
  145. $FileMover = plugin_load('helper', 'move_file');
  146. // Move the Subscriptions & Indexes (new feature since Spring 2013 release)
  147. $Indexer = idx_get_indexer();
  148. if(($idx_msg = $Indexer->renamePage($src, $dst)) !== true
  149. || ($idx_msg = $Indexer->renameMetaValue('relation_references', $src, $dst)) !== true
  150. ) {
  151. msg(sprintf($this->getLang('indexerror'), $idx_msg), -1);
  152. return false;
  153. }
  154. if(!$FileMover->movePageMeta($src_ns, $src_name, $dst_ns, $dst_name)) {
  155. msg(sprintf($this->getLang('metamoveerror'), $src), -1);
  156. return false;
  157. }
  158. // prepare the summary for the changelog entry
  159. if($src_ns == $dst_ns) {
  160. $lang_key = 'renamed';
  161. } elseif($src_name == $dst_name) {
  162. $lang_key = 'moved';
  163. } else {
  164. $lang_key = 'move_rename';
  165. }
  166. $summary = $this->symbol . ' ' . sprintf($this->getLang($lang_key), $src, $dst);
  167. // Wait a second when the page has just been rewritten
  168. $oldRev = filemtime(wikiFN($src));
  169. if($oldRev == time()) sleep(1);
  170. // Save the updated document in its new location
  171. $text = rawWiki($src);
  172. saveWikiText($dst, $text, $summary);
  173. // Delete the orginal file
  174. if(@file_exists(wikiFN($dst))) {
  175. saveWikiText($src, '', $summary);
  176. }
  177. // Move the old revisions
  178. if(!$FileMover->movePageAttic($src_ns, $src_name, $dst_ns, $dst_name)) {
  179. // it's too late to stop the move, so just display a message.
  180. msg(sprintf($this->getLang('atticmoveerror'), $src ), -1);
  181. }
  182. // Add meta data to all affected pages, so links get updated later
  183. foreach($affected_pages as $id) {
  184. $Rewriter->setMoveMeta($id, $src, $dst, 'pages');
  185. }
  186. unlock($src);
  187. }
  188. $event->advise_after();
  189. // store this for later use
  190. $this->affectedPages = $affected_pages;
  191. // unlock rewrites
  192. helper_plugin_move_rewrite::removeLock();
  193. return true;
  194. }
  195. /**
  196. * Execute a media file move/rename
  197. *
  198. * @param string $src original ID
  199. * @param string $dst new ID
  200. * @return bool true if the move was successfully executed
  201. */
  202. public function moveMedia($src, $dst) {
  203. if(!$this->checkMedia($src, $dst)) return false;
  204. // get all pages using this media
  205. $affected_pages = idx_get_indexer()->lookupKey('relation_media', $src);
  206. $src_ns = getNS($src);
  207. $src_name = noNS($src);
  208. $dst_ns = getNS($dst);
  209. $dst_name = noNS($dst);
  210. // pass this info on to other plugins
  211. $eventdata = array(
  212. // this is for compatibility to old plugin
  213. 'opts' => array(
  214. 'ns' => $src_ns,
  215. 'name' => $src_name,
  216. 'newns' => $dst_ns,
  217. 'newname' => $dst_name,
  218. ),
  219. 'affected_pages' => &$affected_pages,
  220. 'src_id' => $src,
  221. 'dst_id' => $dst,
  222. );
  223. // give plugins the option to add their own meta files to the list of files that need to be moved
  224. // to the oldfiles/newfiles array or to adjust their own metadata, database, ...
  225. // and to add other pages to the affected pages
  226. $event = new Doku_Event('PLUGIN_MOVE_MEDIA_RENAME', $eventdata);
  227. if($event->advise_before()) {
  228. /** @var helper_plugin_move_file $FileMover */
  229. $FileMover = plugin_load('helper', 'move_file');
  230. /** @var helper_plugin_move_rewrite $Rewriter */
  231. $Rewriter = plugin_load('helper', 'move_rewrite');
  232. // Move the Subscriptions & Indexes (new feature since Spring 2013 release)
  233. $Indexer = idx_get_indexer();
  234. if(($idx_msg = $Indexer->renameMetaValue('relation_media', $src, $dst)) !== true) {
  235. msg(sprintf($this->getLang('indexerror'), $idx_msg), -1);
  236. return false;
  237. }
  238. if(!$FileMover->moveMediaMeta($src_ns, $src_name, $dst_ns, $dst_name)) {
  239. msg(sprintf($this->getLang('mediametamoveerror'), $src), -1);
  240. return false;
  241. }
  242. // prepare directory
  243. io_createNamespace($dst, 'media');
  244. // move it FIXME this does not create a changelog entry!
  245. if(!io_rename(mediaFN($src), mediaFN($dst))) {
  246. msg(sprintf($this->getLang('mediamoveerror'), $src), -1);
  247. return false;
  248. }
  249. // clean up old ns
  250. io_sweepNS($src, 'mediadir');
  251. // Move the old revisions
  252. if(!$FileMover->moveMediaAttic($src_ns, $src_name, $dst_ns, $dst_name)) {
  253. // it's too late to stop the move, so just display a message.
  254. msg(sprintf($this->getLang('mediaatticmoveerror'), $src), -1);
  255. }
  256. // Add meta data to all affected pages, so links get updated later
  257. foreach($affected_pages as $id) {
  258. $Rewriter->setMoveMeta($id, $src, $dst, 'media');
  259. }
  260. }
  261. $event->advise_after();
  262. // store this for later use
  263. $this->affectedPages = $affected_pages;
  264. return true;
  265. }
  266. /**
  267. * Get a list of pages that where affected by the last successful move operation
  268. *
  269. * @return array
  270. */
  271. public function getAffectedPages() {
  272. return $this->affectedPages;
  273. }
  274. }