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 // check if the file extension is unchanged 110 if (pathinfo(mediaFN($src), PATHINFO_EXTENSION) !== pathinfo(mediaFN($dst), PATHINFO_EXTENSION)) { 111 msg($this->getLang('extensionchange'), -1); 112 return false; 113 } 114 115 return true; 116 } 117 118 /** 119 * Execute a page move/rename 120 * 121 * @param string $src original ID 122 * @param string $dst new ID 123 * @return bool 124 */ 125 public function movePage($src, $dst) { 126 if(!$this->checkPage($src, $dst)) return false; 127 128 // lock rewrites 129 helper_plugin_move_rewrite::addLock(); 130 131 /** @var helper_plugin_move_rewrite $Rewriter */ 132 $Rewriter = plugin_load('helper', 'move_rewrite'); 133 134 // remember what this page was called before the move in meta data 135 $Rewriter->setSelfMoveMeta($src); 136 137 // ft_backlinks() is not used here, as it does a hidden page and acl check but we really need all pages 138 $affected_pages = idx_get_indexer()->lookupKey('relation_references', $src); 139 $affected_pages[] = $dst; // the current page is always affected, because all relative links may have changed 140 $affected_pages = array_unique($affected_pages); 141 142 $src_ns = getNS($src); 143 $src_name = noNS($src); 144 $dst_ns = getNS($dst); 145 $dst_name = noNS($dst); 146 147 // pass this info on to other plugins 148 $eventdata = array( 149 // this is for compatibility to old plugin 150 'opts' => array( 151 'ns' => $src_ns, 152 'name' => $src_name, 153 'newns' => $dst_ns, 154 'newname' => $dst_name, 155 ), 156 'affected_pages' => &$affected_pages, 157 'src_id' => $src, 158 'dst_id' => $dst, 159 ); 160 161 // give plugins the option to add their own meta files to the list of files that need to be moved 162 // to the oldfiles/newfiles array or to adjust their own metadata, database, ... 163 // and to add other pages to the affected pages 164 $event = new Doku_Event('PLUGIN_MOVE_PAGE_RENAME', $eventdata); 165 if($event->advise_before()) { 166 lock($src); 167 168 /** @var helper_plugin_move_file $FileMover */ 169 $FileMover = plugin_load('helper', 'move_file'); 170 171 // Move the Subscriptions & Indexes (new feature since Spring 2013 release) 172 $Indexer = idx_get_indexer(); 173 if(($idx_msg = $Indexer->renamePage($src, $dst)) !== true 174 || ($idx_msg = $Indexer->renameMetaValue('relation_references', $src, $dst)) !== true 175 ) { 176 msg(sprintf($this->getLang('indexerror'), $idx_msg), -1); 177 return false; 178 } 179 if(!$FileMover->movePageMeta($src_ns, $src_name, $dst_ns, $dst_name)) { 180 msg(sprintf($this->getLang('metamoveerror'), $src), -1); 181 return false; 182 } 183 184 // prepare the summary for the changelog entry 185 if($src_ns == $dst_ns) { 186 $lang_key = 'renamed'; 187 } elseif($src_name == $dst_name) { 188 $lang_key = 'moved'; 189 } else { 190 $lang_key = 'move_rename'; 191 } 192 $summary = $this->symbol . ' ' . sprintf($this->getLang($lang_key), $src, $dst); 193 194 // Wait a second when the page has just been rewritten 195 $oldRev = filemtime(wikiFN($src)); 196 if($oldRev == time()) sleep(1); 197 198 // Save the updated document in its new location 199 $text = rawWiki($src); 200 saveWikiText($dst, $text, $summary); 201 202 // Delete the orginal file 203 if(@file_exists(wikiFN($dst))) { 204 saveWikiText($src, '', $summary); 205 } 206 207 // Move the old revisions 208 if(!$FileMover->movePageAttic($src_ns, $src_name, $dst_ns, $dst_name)) { 209 // it's too late to stop the move, so just display a message. 210 msg(sprintf($this->getLang('atticmoveerror'), $src ), -1); 211 } 212 213 // Add meta data to all affected pages, so links get updated later 214 foreach($affected_pages as $id) { 215 $Rewriter->setMoveMeta($id, $src, $dst, 'pages'); 216 } 217 218 unlock($src); 219 } 220 $event->advise_after(); 221 222 // store this for later use 223 $this->affectedPages = $affected_pages; 224 225 // unlock rewrites 226 helper_plugin_move_rewrite::removeLock(); 227 228 return true; 229 } 230 231 /** 232 * Execute a media file move/rename 233 * 234 * @param string $src original ID 235 * @param string $dst new ID 236 * @return bool true if the move was successfully executed 237 */ 238 public function moveMedia($src, $dst) { 239 if(!$this->checkMedia($src, $dst)) return false; 240 241 // get all pages using this media 242 $affected_pages = idx_get_indexer()->lookupKey('relation_media', $src); 243 244 $src_ns = getNS($src); 245 $src_name = noNS($src); 246 $dst_ns = getNS($dst); 247 $dst_name = noNS($dst); 248 249 // pass this info on to other plugins 250 $eventdata = array( 251 // this is for compatibility to old plugin 252 'opts' => array( 253 'ns' => $src_ns, 254 'name' => $src_name, 255 'newns' => $dst_ns, 256 'newname' => $dst_name, 257 ), 258 'affected_pages' => &$affected_pages, 259 'src_id' => $src, 260 'dst_id' => $dst, 261 ); 262 263 // give plugins the option to add their own meta files to the list of files that need to be moved 264 // to the oldfiles/newfiles array or to adjust their own metadata, database, ... 265 // and to add other pages to the affected pages 266 $event = new Doku_Event('PLUGIN_MOVE_MEDIA_RENAME', $eventdata); 267 if($event->advise_before()) { 268 /** @var helper_plugin_move_file $FileMover */ 269 $FileMover = plugin_load('helper', 'move_file'); 270 /** @var helper_plugin_move_rewrite $Rewriter */ 271 $Rewriter = plugin_load('helper', 'move_rewrite'); 272 273 // Move the Subscriptions & Indexes (new feature since Spring 2013 release) 274 $Indexer = idx_get_indexer(); 275 if(($idx_msg = $Indexer->renameMetaValue('relation_media', $src, $dst)) !== true) { 276 msg(sprintf($this->getLang('indexerror'), $idx_msg), -1); 277 return false; 278 } 279 if(!$FileMover->moveMediaMeta($src_ns, $src_name, $dst_ns, $dst_name)) { 280 msg(sprintf($this->getLang('mediametamoveerror'), $src), -1); 281 return false; 282 } 283 284 // prepare directory 285 io_createNamespace($dst, 'media'); 286 287 // move it FIXME this does not create a changelog entry! 288 if(!io_rename(mediaFN($src), mediaFN($dst))) { 289 msg(sprintf($this->getLang('mediamoveerror'), $src), -1); 290 return false; 291 } 292 293 // clean up old ns 294 io_sweepNS($src, 'mediadir'); 295 296 // Move the old revisions 297 if(!$FileMover->moveMediaAttic($src_ns, $src_name, $dst_ns, $dst_name)) { 298 // it's too late to stop the move, so just display a message. 299 msg(sprintf($this->getLang('mediaatticmoveerror'), $src), -1); 300 } 301 302 // Add meta data to all affected pages, so links get updated later 303 foreach($affected_pages as $id) { 304 $Rewriter->setMoveMeta($id, $src, $dst, 'media'); 305 } 306 } 307 $event->advise_after(); 308 309 // store this for later use 310 $this->affectedPages = $affected_pages; 311 312 return true; 313 } 314 315 /** 316 * Get a list of pages that where affected by the last successful move operation 317 * 318 * @return array 319 */ 320 public function getAffectedPages() { 321 return $this->affectedPages; 322 } 323} 324