1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Danny Lin <danny.0838@gmail.com> 5 */ 6 7// must be run within Dokuwiki 8if(!defined('DOKU_INC')) die(); 9if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 10require_once (DOKU_PLUGIN . 'action.php'); 11require_once(DOKU_INC.'inc/fulltext.php'); 12 13class action_plugin_editx extends DokuWiki_Action_Plugin { 14 15 /** 16 * register the eventhandlers 17 */ 18 function register(Doku_Event_Handler $contr) { 19 $contr->register_hook('TPL_ACT_RENDER', 'AFTER', $this, '_append_to_edit', array()); 20 $contr->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, '_handle_act', array()); 21 $contr->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, '_handle_tpl_act', array()); 22 } 23 24 /** 25 * main hooks 26 */ 27 function _append_to_edit(&$event, $param) { 28 global $ID; 29 if ($event->data != 'edit') return; 30 if (!$this->_auth_check_all($ID)) return; 31 $link = sprintf('<a href="%s" class="action editx" rel="nofollow">%s</a>', wl($ID,'do=editx'), $this->getLang('pagemanagement')); 32 $intro = $this->locale_xhtml('intro'); 33 $intro = str_replace( '@LINK@', $link, $intro ); 34 print $intro; 35 } 36 37 function _handle_act(&$event, $param) { 38 if($event->data != 'editx') return; 39 $event->preventDefault(); 40 } 41 42 function _handle_tpl_act(&$event, $param) { 43 if($event->data != 'editx') return; 44 $event->preventDefault(); 45 46 switch ($_REQUEST['work']) { 47 case 'rename': 48 $opts['oldpage'] = cleanID($_REQUEST['oldpage']); 49 $opts['newpage'] = cleanID($_REQUEST['newpage']); 50 $opts['summary'] = $_REQUEST['summary']; 51 $opts['rp_nr'] = $_REQUEST['rp_nr']; 52 $opts['confirm'] = $_REQUEST['rp_confirm']; 53 $this->_rename_page($opts); 54 break; 55 case 'delete': 56 $opts['oldpage'] = cleanID($_REQUEST['oldpage']); 57 $opts['summary'] = $_REQUEST['summary']; 58 $opts['purge'] = $_REQUEST['dp_purge']; 59 $opts['confirm'] = $_REQUEST['dp_confirm']; 60 $this->_delete_page($opts); 61 break; 62 default: 63 $this->_print_form(); 64 break; 65 } 66 } 67 68 /** 69 * helper functions 70 */ 71 function _auth_check_list($list) { 72 global $conf; 73 global $USERINFO; 74 75 if(!$conf['useacl']) return true; //no ACL - then no checks 76 77 $allowed = explode(',',$list); 78 $allowed = array_map('trim', $allowed); 79 $allowed = array_unique($allowed); 80 $allowed = array_filter($allowed); 81 82 if(!count($allowed)) return true; //no restrictions 83 84 $user = $_SERVER['REMOTE_USER']; 85 $groups = (array) $USERINFO['grps']; 86 87 if(in_array($user,$allowed)) return true; //user explicitly mentioned 88 89 //check group memberships 90 foreach($groups as $group){ 91 if(in_array('@'.$group,$allowed)) return true; 92 } 93 94 //still here? no access! 95 return false; 96 } 97 98 function _auth_check_all($id) { 99 return $this->_auth_can_rename($id) || 100 $this->_auth_can_delete($id); 101 } 102 103 function _auth_can_rename($id) { 104 static $cache = null; 105 if (!$cache[$id]) { 106 $cache[$id] = auth_quickaclcheck($id)>=AUTH_EDIT && 107 $this->_auth_check_list($this->getConf('user_rename')); 108 } 109 return $cache[$id]; 110 } 111 112 function _auth_can_rename_nr($id) { 113 static $cache = null; 114 if (!$cache[$id]) { 115 $cache[$id] = auth_quickaclcheck($id)>=AUTH_DELETE && 116 $this->_auth_check_list($this->getConf('user_rename_nr')); 117 } 118 return $cache[$id]; 119 } 120 121 function _auth_can_delete($id) { 122 static $cache = null; 123 if (!$cache[$id]) { 124 $cache[$id] = auth_quickaclcheck($id)>=AUTH_DELETE && 125 $this->_auth_check_list($this->getConf('user_delete')); 126 } 127 return $cache[$id]; 128 } 129 130 function _locate_filepairs(&$opts, $dir, $regex ){ 131 global $conf; 132 $oldpath = $conf[$dir].'/'.str_replace(':','/',$opts['oldns']); 133 $newpath = $conf[$dir].'/'.str_replace(':','/',$opts['newns']); 134 if (!$opts['oldfiles']) $opts['oldfiles'] = array(); 135 if (!$opts['newfiles']) $opts['newfiles'] = array(); 136 $dh = @opendir($oldpath); 137 if($dh) { 138 while(($file = readdir($dh)) !== false){ 139 if ($file{0}=='.') continue; 140 $oldfile = $oldpath.$file; 141 if (is_file($oldfile) && preg_match($regex,$file)){ 142 $opts['oldfiles'][] = $oldfile; 143 if ($opts['move']) { 144 $newfilebase = str_replace($opts['oldname'], $opts['newname'], $file); 145 $newfile = $newpath.$newfilebase; 146 if (@file_exists($newfile)) { 147 $this->errors[] = sprintf( $this->getLang('rp_msg_file_conflict'), '<a href="'. wl($opts['newpage']) . '">'.$opts['newpage'].'</a>', $newfilebase ); 148 return false; 149 } 150 $opts['newfiles'][] = $newfile; 151 } 152 } 153 } 154 closedir($dh); 155 return true; 156 } 157 return false; 158 } 159 160 function _apply_moves(&$opts) { 161 foreach ($opts['oldfiles'] as $i => $oldfile) { 162 $newfile = $opts['newfiles'][$i]; 163 $newdir = dirname($newfile); 164 if (!io_mkdir_p($newdir)) continue; 165 io_rename($oldfile, $newfile); 166 } 167 } 168 169 function _apply_deletes(&$opts) { 170 foreach ($opts['oldfiles'] as $oldfile) { 171 unlink($oldfile); 172 } 173 } 174 175 function _FN($id) { 176 $id = str_replace(':','/',$id); 177 $id = utf8_encodeFN($id); 178 return $id; 179 } 180 181 function _custom_delete_page($id, $summary) { 182 global $ID, $INFO, $conf; 183 // mark as nonexist to prevent indexerWebBug 184 if ($id==$ID) $INFO['exists'] = 0; 185 // delete page, meta and attic 186 $file = wikiFN($id); 187 $old = @filemtime($file); // from page 188 if (file_exists($file)) unlink($file); 189 $opts['oldname'] = $this->_FN(noNS($id)); 190 $opts['oldns'] = $this->_FN(getNS($id)); 191 if ($opts['oldns']) $opts['oldns'] .= '/'; 192 $this->_locate_filepairs( $opts, 'metadir', '/^'.$opts['oldname'].'\.(?!mlist)\w*?$/' ); 193 $this->_locate_filepairs( $opts, 'olddir', '/^'.$opts['oldname'].'\.\d{10}\.txt(\.gz|\.bz2)?$/' ); 194 $this->_apply_deletes($opts); 195 io_sweepNS($id, 'datadir'); 196 io_sweepNS($id, 'metadir'); 197 io_sweepNS($id, 'olddir'); 198 // send notify mails 199 notify($id,'admin',$old,$summary); 200 notify($id,'subscribers',$old,$summary); 201 // update the purgefile (timestamp of the last time anything within the wiki was changed) 202 io_saveFile($conf['cachedir'].'/purgefile',time()); 203 // if useheading is enabled, purge the cache of all linking pages 204 if(useHeading('content')){ 205 $pages = ft_backlinks($id); 206 foreach ($pages as $page) { 207 $cache = new cache_renderer($page, wikiFN($page), 'xhtml'); 208 $cache->removeCache(); 209 } 210 } 211 } 212 213 /** 214 * main functions 215 */ 216 function _rename_page(&$opts) { 217 // check confirmation 218 if (!$opts['confirm']) { 219 $this->errors[] = $this->getLang('rp_msg_unconfirmed'); 220 } 221 // check old page 222 if (!$opts['oldpage']) { 223 $this->errors[] = $this->getLang('rp_msg_old_empty'); 224 } else if (!page_exists($opts['oldpage'])) { 225 $this->errors[] = sprintf( $this->getLang('rp_msg_old_noexist'), $opts['oldpage'] ); 226 } else if (!$this->_auth_can_rename($opts['oldpage'])) { 227 $this->errors[] = sprintf( $this->getLang('rp_msg_auth'), $opts['oldpage'] ); 228 } else if (checklock($opts['oldpage'])) { 229 $this->errors[] = sprintf( $this->getLang('rp_msg_locked'), $opts['oldpage'] ); 230 } 231 // check noredirect 232 if ($opts['rp_nr'] && !$this->_auth_can_rename_nr($opts['oldpage'])) 233 $this->errors[] = $this->getLang('rp_msg_auth_nr'); 234 // check new page 235 if (!$opts['newpage']) { 236 $this->errors[] = $this->getLang('rp_msg_new_empty'); 237 } else if (page_exists($opts['newpage'])) { 238 $this->errors[] = sprintf( $this->getLang('rp_msg_new_exist'), '<a href="'. wl($opts['newpage']) . '">'.$opts['newpage'].'</a>' ); 239 } else if (!$this->_auth_can_rename($opts['newpage'])) { 240 $this->errors[] = sprintf( $this->getLang('rp_msg_auth'), $opts['newpage'] ); 241 } else if (checklock($opts['newpage'])) { 242 $this->errors[] = sprintf( $this->getLang('rp_msg_locked'), $opts['newpage'] ); 243 } 244 // try to locate moves 245 if (!$this->errors) { 246 $opts['move'] = true; 247 $opts['oldname'] = $this->_FN(noNS($opts['oldpage'])); 248 $opts['newname'] = $this->_FN(noNS($opts['newpage'])); 249 $opts['oldns'] = $this->_FN(getNS($opts['oldpage'])); 250 $opts['newns'] = $this->_FN(getNS($opts['newpage'])); 251 if ($opts['oldns']) $opts['oldns'] .= '/'; 252 if ($opts['newns']) $opts['newns'] .= '/'; 253 $this->_locate_filepairs( $opts, 'metadir', '/^'.$opts['oldname'].'\.(?!mlist|meta|indexed)\w*?$/' ); 254 $this->_locate_filepairs( $opts, 'olddir', '/^'.$opts['oldname'].'\.\d{10}\.txt(\.gz|\.bz2)?$/' ); 255 } 256 // if no error do rename 257 if (!$this->errors) { 258 // move meta and attic 259 $this->_apply_moves($opts); 260 // save to newpage 261 $text = rawWiki($opts['oldpage']); 262 if ($opts['summary']) 263 $summary = sprintf( $this->getLang('rp_newsummaryx'), $opts['oldpage'], $opts['newpage'], $opts['summary'] ); 264 else 265 $summary = sprintf( $this->getLang('rp_newsummary'), $opts['oldpage'], $opts['newpage'] ); 266 saveWikiText($opts['newpage'],$text,$summary); 267 // purge or recreate old page 268 $summary = $opts['summary'] ? 269 sprintf( $this->getLang('rp_oldsummaryx'), $opts['oldpage'], $opts['newpage'], $opts['summary'] ) : 270 sprintf( $this->getLang('rp_oldsummary'), $opts['oldpage'], $opts['newpage'] ); 271 if ($opts['rp_nr']) { 272 $this->_custom_delete_page( $opts['oldpage'], $summary ); 273 // write change log afterwards, or it would be deleted 274 addLogEntry( null, $opts['oldpage'], DOKU_CHANGE_TYPE_DELETE, $summary ); // also writes to global changes 275 unlink(metaFN($opts['oldpage'],'.changes')); // purge page changes 276 } 277 else { 278 $text = $this->getConf('redirecttext'); 279 if (!$text) $text = $this->getLang('redirecttext'); 280 $text = str_replace( '@ID@', $opts['newpage'], $text ); 281 @unlink(wikiFN($opts['oldpage'])); // remove old page file so no additional history 282 saveWikiText($opts['oldpage'],$text,$summary); 283 } 284 } 285 // show messages 286 if ($this->errors) { 287 foreach ($this->errors as $error) msg( $error, -1 ); 288 } 289 else { 290 $msg = sprintf( $this->getLang('rp_msg_success'), $opts['oldpage'], '<a href="'. wl($opts['newpage']) . '">'.$opts['newpage'].'</a>' ); 291 msg( $msg, 1 ); 292 } 293 // display form and table 294 $data = array( rp_newpage => $opts['newpage'], rp_summary => $opts['summary'], rp_nr => $opts['rp_nr'] ); 295 $this->_print_form($data); 296 } 297 298 function _delete_page(&$opts) { 299 // check confirm 300 if (!$opts['confirm']) { 301 $this->errors[] = $this->getLang('dp_msg_unconfirmed'); 302 } 303 // check old page 304 if (!$opts['oldpage']) { 305 $this->errors[] = $this->getLang('dp_msg_old_empty'); 306 } else if (!$this->_auth_can_delete($opts['oldpage'])) { 307 $this->errors[] = sprintf( $this->getLang('dp_msg_auth'), $opts['oldpage'] ); 308 } 309 // if no error do delete 310 if (!$this->errors) { 311 $summary = $opts['summary'] ? 312 sprintf( $this->getLang('dp_oldsummaryx'), $opts['summary'] ) : 313 $this->getLang('dp_oldsummary'); 314 $this->_custom_delete_page( $opts['oldpage'], $summary ); 315 // write change log afterwards, or it would be deleted 316 addLogEntry( null, $opts['oldpage'], DOKU_CHANGE_TYPE_DELETE, $summary ); // also writes to global changes 317 if ($opts['purge']) unlink(metaFN($opts['oldpage'],'.changes')); // purge page changes 318 } 319 // show messages 320 if ($this->errors) { 321 foreach ($this->errors as $error) msg( $error, -1 ); 322 } 323 else { 324 $msg = sprintf( $this->getLang('dp_msg_success'), $opts['oldpage'] ); 325 msg( $msg, 1 ); 326 } 327 // display form and table 328 $data = array( dp_purge => $opts['purge'], dp_summary => $opts['summary'] ); 329 $this->_print_form($data); 330 } 331 332 function _print_form($data=null) { 333 global $ID, $lang; 334 $chk = ' checked="checked"'; 335?> 336<h1><?php echo sprintf( $this->getLang('title'), $ID); ?></h1> 337<div id="config__manager"> 338<?php 339 if ($this->_auth_can_rename($ID)) { 340?> 341 <form action="<?php echo wl($ID); ?>" method="post"> 342 <fieldset> 343 <legend><?php echo $this->getLang('rp_title'); ?></legend> 344 <input type="hidden" name="do" value="editx" /> 345 <input type="hidden" name="work" value="rename" /> 346 <input type="hidden" name="oldpage" value="<?php echo $ID; ?>" /> 347 <table class="inline"> 348 <tr> 349 <td class="label"><?php echo $this->getLang('rp_newpage'); ?></td> 350 <td class="value"><input class="edit" type="input" name="newpage" value="<?php echo $data['rp_newpage']; ?>" /></td> 351 </tr> 352 <tr> 353 <td class="label"><?php echo $this->getLang('rp_summary'); ?></td> 354 <td class="value"><input class="edit" type="input" name="summary" value="<?php echo $data['rp_summary']; ?>" /></td> 355 </tr> 356<?php 357 if ($this->_auth_can_rename_nr($ID)) { 358?> 359 <tr> 360 <td class="label"><?php echo $this->getLang('rp_nr'); ?></td> 361 <td class="value"><input type="checkbox" name="rp_nr" value="1"<?php if ($data['rp_nr']) echo $chk; ?> /></td> 362 </tr> 363<?php 364 } 365?> 366 <tr> 367 <td class="label"><?php echo $this->getLang('rp_confirm'); ?></td> 368 <td class="value"><input type="checkbox" name="rp_confirm" value="1" /></td> 369 </tr> 370 </table> 371 <p> 372 <input type="submit" class="button" value="<?php echo $lang['btn_save']; ?>" /> 373 <input type="reset" class="button" value="<?php echo $lang['btn_reset']; ?>" /> 374 </p> 375 </fieldset> 376 </form> 377<?php 378 } 379 if ($this->_auth_can_delete($ID)) { 380?> 381 <form action="<?php echo wl($ID); ?>" method="post"> 382 <fieldset> 383 <legend><?php echo $this->getLang('dp_title'); ?></legend> 384 <input type="hidden" name="do" value="editx" /> 385 <input type="hidden" name="work" value="delete" /> 386 <input type="hidden" name="oldpage" value="<?php echo $ID; ?>" /> 387 <table class="inline"> 388 <tr> 389 <td class="label"><?php echo $this->getLang('dp_summary'); ?></td> 390 <td class="value"><input class="edit" type="input" name="summary" value="<?php echo $data['dp_summary']; ?>" /></td> 391 </tr> 392 <tr> 393 <td class="label"><?php echo $this->getLang('dp_purge'); ?></td> 394 <td class="value"><input type="checkbox" name="dp_purge" value="1"<?php if ($data['dp_purge']) echo $chk; ?> /></td> 395 </tr> 396 <tr> 397 <td class="label"><?php echo $this->getLang('dp_confirm'); ?></td> 398 <td class="value"><input type="checkbox" name="dp_confirm" value="1" /></td> 399 </tr> 400 </table> 401 <p> 402 <input type="submit" class="button" value="<?php echo $lang['btn_save']; ?>" /> 403 <input type="reset" class="button" value="<?php echo $lang['btn_reset']; ?>" /> 404 </p> 405 </fieldset> 406 </form> 407<?php 408 } 409?> 410</div> 411<?php 412 } 413} 414// vim:ts=4:sw=4:et:enc=utf-8: 415