1<?php 2/** 3 * Move Plugin Page Rename Functionality 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <gohr@cosmocode.de> 7 */ 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11/** 12 * Class action_plugin_move_rename 13 */ 14class action_plugin_move_rename extends DokuWiki_Action_Plugin { 15 16 /** 17 * Register event handlers. 18 * 19 * @param Doku_Event_Handler $controller The plugin controller 20 */ 21 public function register(Doku_Event_Handler $controller) { 22 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handle_init'); 23 24 // TODO: DEPRECATED JAN 2018 25 $controller->register_hook('TEMPLATE_PAGETOOLS_DISPLAY', 'BEFORE', $this, 'handle_pagetools'); 26 27 $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'addsvgbutton', array()); 28 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); 29 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxMediaManager'); 30 } 31 32 /** 33 * set JavaScript info if renaming of current page is possible 34 */ 35 public function handle_init() { 36 global $JSINFO; 37 global $INFO; 38 global $INPUT; 39 global $USERINFO; 40 41 if (isset($INFO['id'])) { 42 $JSINFO['move_renameokay'] = $this->renameOkay($INFO['id']); 43 } else { 44 $JSINFO['move_renameokay'] = false; 45 } 46 47 $JSINFO['move_allowrename'] = auth_isMember( 48 $this->getConf('allowrename'), 49 $INPUT->server->str('REMOTE_USER'), 50 $USERINFO['grps'] ?? [] 51 ); 52 } 53 54 /** 55 * Adds a button to the default template 56 * 57 * TODO: DEPRECATED JAN 2018 58 * 59 * @param Doku_Event $event 60 */ 61 public function handle_pagetools(Doku_Event $event) { 62 if($event->data['view'] != 'main') return; 63 if (!$this->getConf('pagetools_integration')) { 64 return; 65 } 66 67 $newitem = '<li class="plugin_move_page"><a href=""><span>' . $this->getLang('renamepage') . '</span></a></li>'; 68 $offset = count($event->data['items']) - 1; 69 $event->data['items'] = 70 array_slice($event->data['items'], 0, $offset, true) + 71 array('plugin_move' => $newitem) + 72 array_slice($event->data['items'], $offset, null, true); 73 } 74 75 /** 76 * Add 'rename' button to page tools, new SVG based mechanism 77 * 78 * @param Doku_Event $event 79 */ 80 public function addsvgbutton(Doku_Event $event) { 81 global $INFO, $JSINFO; 82 if( 83 $event->data['view'] !== 'page' || 84 !$this->getConf('pagetools_integration') || 85 empty($JSINFO['move_renameokay']) 86 ) { 87 return; 88 } 89 if(!$INFO['exists']) { 90 return; 91 } 92 array_splice($event->data['items'], -1, 0, array(new \dokuwiki\plugin\move\MenuItem())); 93 } 94 95 /** 96 * Rename a single page 97 * 98 * This creates a plan and executes it right away. If the user selected to move media with the page, 99 * all media files used in the original page that are located in the same namespace are moved with the page 100 * to the new namespace. 101 * 102 */ 103 public function handle_ajax(Doku_Event $event) { 104 if($event->data != 'plugin_move_rename') return; 105 $event->preventDefault(); 106 $event->stopPropagation(); 107 108 global $MSG; 109 global $INPUT; 110 111 $src = cleanID($INPUT->str('id')); 112 $dst = cleanID($INPUT->str('newid')); 113 $doMedia = $INPUT->bool('media'); 114 115 header('Content-Type: application/json'); 116 117 if(!$this->renameOkay($src)) { 118 echo json_encode(['error' => $this->getLang('cantrename')]); 119 return; 120 } 121 122 if(!$dst || $dst == $src) { 123 echo json_encode(['error' => $this->getLang('nodst')]); 124 return; 125 } 126 127 /** @var helper_plugin_move_plan $plan */ 128 $plan = plugin_load('helper', 'move_plan'); 129 if($plan->isCommited()) { 130 echo json_encode(['error' => $this->getLang('cantrename')]); 131 return; 132 } 133 $plan->setOption('autorewrite', true); 134 $plan->addPageMove($src, $dst); // add the page move to the plan 135 136 if($doMedia) { // move media with the page? 137 $srcNS = getNS($src); 138 $dstNS = getNS($dst); 139 $srcNSLen = strlen($srcNS); 140 // we don't do this for root namespace or if namespace hasn't changed 141 if ($srcNS != '' && $srcNS != $dstNS) { 142 $media = p_get_metadata($src, 'relation media'); 143 if (is_array($media)) { 144 foreach ($media as $file => $exists) { 145 if(!$exists) continue; 146 $mediaNS = getNS($file); 147 if ($mediaNS == $srcNS) { 148 $plan->addMediaMove($file, $dstNS . substr($file, $srcNSLen)); 149 } 150 } 151 } 152 } 153 } 154 155 try { 156 // commit and execute the plan 157 $plan->commit(); 158 do { 159 $next = $plan->nextStep(); 160 if ($next === false) throw new \Exception('Move plan failed'); 161 } while ($next > 0); 162 echo json_encode(array('redirect_url' => wl($dst, '', true, '&'))); 163 } catch (\Exception $e) { 164 // error should be in $MSG 165 if(isset($MSG[0])) { 166 $error = $MSG[0]; // first error 167 } else { 168 $error = $this->getLang('cantrename') . ' ' . $e->getMessage(); 169 } 170 echo json_encode(['error' => $error]); 171 } 172 } 173 174 /** 175 * Handle media renames in media manager 176 * 177 * @param Doku_Event $event 178 * @return void 179 */ 180 public function handleAjaxMediaManager(Doku_Event $event) 181 { 182 if ($event->data !== 'plugin_move_rename_mediamanager') return; 183 184 if (!checkSecurityToken()) { 185 throw new \Exception('Security token did not match'); 186 } 187 188 $event->preventDefault(); 189 $event->stopPropagation(); 190 191 global $INPUT; 192 global $MSG; 193 global $USERINFO; 194 195 $src = cleanID($INPUT->str('src')); 196 $dst = cleanID($INPUT->str('dst')); 197 198 /** @var helper_plugin_move_op $moveOperator */ 199 $moveOperator = plugin_load('helper', 'move_op'); 200 201 if ($src && $dst) { 202 header('Content-Type: application/json'); 203 204 $response = []; 205 206 // check user/group restrictions 207 if ( 208 !auth_isMember($this->getConf('allowrename'), $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']) 209 ) { 210 $response['error'] = $this->getLang('notallowed'); 211 echo json_encode($response); 212 return; 213 } 214 215 $response['success'] = $moveOperator->moveMedia($src, $dst); 216 217 if ($response['success']) { 218 $ns = getNS($dst); 219 $response['redirect_url'] = wl($dst, ['do' => 'media', 'ns' => $ns], true, '&'); 220 } else { 221 $response['error'] = sprintf($this->getLang('mediamoveerror'), $src); 222 if (isset($MSG)) { 223 foreach ($MSG as $msg) { 224 $response['error'] .= ' ' . $msg['msg']; 225 } 226 } 227 } 228 229 echo json_encode($response); 230 } 231 } 232 233 /** 234 * Determines if it would be okay to show a rename page button for the given page and current user 235 * 236 * @param $id 237 * @return bool 238 */ 239 public function renameOkay($id) { 240 global $conf; 241 global $ACT; 242 global $USERINFO; 243 if(!($ACT == 'show' || empty($ACT))) return false; 244 if(!page_exists($id)) return false; 245 if(auth_quickaclcheck($id) < AUTH_EDIT) return false; 246 if(checklock($id) !== false || @file_exists(wikiLockFN($id))) return false; 247 if(!$conf['useacl']) return true; 248 if(!isset($_SERVER['REMOTE_USER'])) return false; 249 if(!auth_isMember($this->getConf('allowrename'), $_SERVER['REMOTE_USER'], (array) $USERINFO['grps'])) return false; 250 251 return true; 252 } 253 254 /** 255 * Use this in your template to add a simple "move this page" link 256 * 257 * Alternatively give anything the class "plugin_move_page" - it will automatically be hidden and shown and 258 * trigger the page move dialog. 259 */ 260 public function tpl() { 261 echo '<a href="" class="plugin_move_page">'; 262 echo $this->getLang('renamepage'); 263 echo '</a>'; 264 } 265 266} 267