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