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 /** @var helper_plugin_move_plan $plan */ 123 $plan = plugin_load('helper', 'move_plan'); 124 if($plan->isCommited()) { 125 echo json_encode(['error' => $this->getLang('cantrename')]); 126 return; 127 } 128 $plan->setOption('autorewrite', true); 129 $plan->addPageMove($src, $dst); // add the page move to the plan 130 131 if($doMedia) { // move media with the page? 132 $srcNS = getNS($src); 133 $dstNS = getNS($dst); 134 $srcNSLen = strlen($srcNS); 135 // we don't do this for root namespace or if namespace hasn't changed 136 if ($srcNS != '' && $srcNS != $dstNS) { 137 $media = p_get_metadata($src, 'relation media'); 138 if (is_array($media)) { 139 foreach ($media as $file => $exists) { 140 if(!$exists) continue; 141 $mediaNS = getNS($file); 142 if ($mediaNS == $srcNS) { 143 $plan->addMediaMove($file, $dstNS . substr($file, $srcNSLen)); 144 } 145 } 146 } 147 } 148 } 149 150 try { 151 // commit and execute the plan 152 $plan->commit(); 153 do { 154 $next = $plan->nextStep(); 155 if ($next === false) throw new \Exception('Move plan failed'); 156 } while ($next > 0); 157 echo json_encode(array('redirect_url' => wl($dst, '', true, '&'))); 158 } catch (\Exception $e) { 159 // error should be in $MSG 160 if(isset($MSG[0])) { 161 $error = $MSG[0]; // first error 162 } else { 163 $error = $this->getLang('cantrename') . ' ' . $e->getMessage(); 164 } 165 echo json_encode(['error' => $error]); 166 } 167 } 168 169 /** 170 * Handle media renames in media manager 171 * 172 * @param Doku_Event $event 173 * @return void 174 */ 175 public function handleAjaxMediaManager(Doku_Event $event) 176 { 177 if ($event->data !== 'plugin_move_rename_mediamanager') return; 178 179 if (!checkSecurityToken()) { 180 throw new \Exception('Security token did not match'); 181 } 182 183 $event->preventDefault(); 184 $event->stopPropagation(); 185 186 global $INPUT; 187 global $MSG; 188 global $USERINFO; 189 190 $src = cleanID($INPUT->str('src')); 191 $dst = cleanID($INPUT->str('dst')); 192 193 /** @var helper_plugin_move_op $moveOperator */ 194 $moveOperator = plugin_load('helper', 'move_op'); 195 196 if ($src && $dst) { 197 header('Content-Type: application/json'); 198 199 $response = []; 200 201 // check user/group restrictions 202 if ( 203 !auth_isMember($this->getConf('allowrename'), $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']) 204 ) { 205 $response['error'] = $this->getLang('notallowed'); 206 echo json_encode($response); 207 return; 208 } 209 210 $response['success'] = $moveOperator->moveMedia($src, $dst); 211 212 if ($response['success']) { 213 $ns = getNS($dst); 214 $response['redirect_url'] = wl($dst, ['do' => 'media', 'ns' => $ns], true, '&'); 215 } else { 216 $response['error'] = sprintf($this->getLang('mediamoveerror'), $src); 217 if (isset($MSG)) { 218 foreach ($MSG as $msg) { 219 $response['error'] .= ' ' . $msg['msg']; 220 } 221 } 222 } 223 224 echo json_encode($response); 225 } 226 } 227 228 /** 229 * Determines if it would be okay to show a rename page button for the given page and current user 230 * 231 * @param $id 232 * @return bool 233 */ 234 public function renameOkay($id) { 235 global $conf; 236 global $ACT; 237 global $USERINFO; 238 if(!($ACT == 'show' || empty($ACT))) return false; 239 if(!page_exists($id)) return false; 240 if(auth_quickaclcheck($id) < AUTH_EDIT) return false; 241 if(checklock($id) !== false || @file_exists(wikiLockFN($id))) return false; 242 if(!$conf['useacl']) return true; 243 if(!isset($_SERVER['REMOTE_USER'])) return false; 244 if(!auth_isMember($this->getConf('allowrename'), $_SERVER['REMOTE_USER'], (array) $USERINFO['grps'])) return false; 245 246 return true; 247 } 248 249 /** 250 * Use this in your template to add a simple "move this page" link 251 * 252 * Alternatively give anything the class "plugin_move_page" - it will automatically be hidden and shown and 253 * trigger the page move dialog. 254 */ 255 public function tpl() { 256 echo '<a href="" class="plugin_move_page">'; 257 echo $this->getLang('renamepage'); 258 echo '</a>'; 259 } 260 261} 262