1<?php 2/** 3 * DokuWiki Actions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9if(!defined('DOKU_INC')) die('meh.'); 10require_once(DOKU_INC.'inc/template.php'); 11 12 13/** 14 * Call the needed action handlers 15 * 16 * @author Andreas Gohr <andi@splitbrain.org> 17 * @triggers ACTION_ACT_PREPROCESS 18 * @triggers ACTION_HEADERS_SEND 19 */ 20function act_dispatch(){ 21 global $INFO; 22 global $ACT; 23 global $ID; 24 global $QUERY; 25 global $lang; 26 global $conf; 27 global $license; 28 29 $preact = $ACT; 30 31 // give plugins an opportunity to process the action 32 $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT); 33 if ($evt->advise_before()) { 34 35 //sanitize $ACT 36 $ACT = act_clean($ACT); 37 38 //check if searchword was given - else just show 39 $s = cleanID($QUERY); 40 if($ACT == 'search' && empty($s)){ 41 $ACT = 'show'; 42 } 43 44 //login stuff 45 if(in_array($ACT,array('login','logout'))){ 46 $ACT = act_auth($ACT); 47 } 48 49 //check if user is asking to (un)subscribe a page 50 if($ACT == 'subscribe') { 51 try { 52 $ACT = act_subscription($ACT); 53 } catch (Exception $e) { 54 msg($e->getMessage(), -1); 55 } 56 } 57 58 //check permissions 59 $ACT = act_permcheck($ACT); 60 61 //register 62 $nil = array(); 63 if($ACT == 'register' && $_POST['save'] && register()){ 64 $ACT = 'login'; 65 } 66 67 if ($ACT == 'resendpwd' && act_resendpwd()) { 68 $ACT = 'login'; 69 } 70 71 //update user profile 72 if ($ACT == 'profile') { 73 if(!$_SERVER['REMOTE_USER']) { 74 $ACT = 'login'; 75 } else { 76 if(updateprofile()) { 77 msg($lang['profchanged'],1); 78 $ACT = 'show'; 79 } 80 } 81 } 82 83 //revert 84 if($ACT == 'revert'){ 85 if(checkSecurityToken()){ 86 $ACT = act_revert($ACT); 87 }else{ 88 $ACT = 'show'; 89 } 90 } 91 92 //save 93 if($ACT == 'save'){ 94 if(checkSecurityToken()){ 95 $ACT = act_save($ACT); 96 }else{ 97 $ACT = 'show'; 98 } 99 } 100 101 //cancel conflicting edit 102 if($ACT == 'cancel') 103 $ACT = 'show'; 104 105 //draft deletion 106 if($ACT == 'draftdel') 107 $ACT = act_draftdel($ACT); 108 109 //draft saving on preview 110 if($ACT == 'preview') 111 $ACT = act_draftsave($ACT); 112 113 //edit 114 if(($ACT == 'edit' || $ACT == 'preview') && $INFO['editable']){ 115 $ACT = act_edit($ACT); 116 }else{ 117 unlock($ID); //try to unlock 118 } 119 120 //handle export 121 if(substr($ACT,0,7) == 'export_') 122 $ACT = act_export($ACT); 123 124 //display some infos 125 if($ACT == 'check'){ 126 check(); 127 $ACT = 'show'; 128 } 129 130 //handle admin tasks 131 if($ACT == 'admin'){ 132 // retrieve admin plugin name from $_REQUEST['page'] 133 if (!empty($_REQUEST['page'])) { 134 $pluginlist = plugin_list('admin'); 135 if (in_array($_REQUEST['page'], $pluginlist)) { 136 // attempt to load the plugin 137 if ($plugin =& plugin_load('admin',$_REQUEST['page']) !== null) 138 $plugin->handle(); 139 } 140 } 141 } 142 143 // check permissions again - the action may have changed 144 $ACT = act_permcheck($ACT); 145 } // end event ACTION_ACT_PREPROCESS default action 146 $evt->advise_after(); 147 unset($evt); 148 149 // when action 'show', the intial not 'show' and POST, do a redirect 150 if($ACT == 'show' && $preact != 'show' && strtolower($_SERVER['REQUEST_METHOD']) == 'post'){ 151 act_redirect($ID,$preact); 152 } 153 154 //call template FIXME: all needed vars available? 155 $headers[] = 'Content-Type: text/html; charset=utf-8'; 156 trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); 157 158 include(template('main.php')); 159 // output for the commands is now handled in inc/templates.php 160 // in function tpl_content() 161} 162 163function act_sendheaders($headers) { 164 foreach ($headers as $hdr) header($hdr); 165} 166 167/** 168 * Sanitize the action command 169 * 170 * Add all allowed commands here. 171 * 172 * @author Andreas Gohr <andi@splitbrain.org> 173 */ 174function act_clean($act){ 175 global $lang; 176 global $conf; 177 178 // check if the action was given as array key 179 if(is_array($act)){ 180 list($act) = array_keys($act); 181 } 182 183 //remove all bad chars 184 $act = strtolower($act); 185 $act = preg_replace('/[^1-9a-z_]+/','',$act); 186 187 if($act == 'export_html') $act = 'export_xhtml'; 188 if($act == 'export_htmlbody') $act = 'export_xhtmlbody'; 189 190 // check if action is disabled 191 if(!actionOK($act)){ 192 msg('Command disabled: '.htmlspecialchars($act),-1); 193 return 'show'; 194 } 195 196 //disable all acl related commands if ACL is disabled 197 if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin', 198 'subscribe','unsubscribe','profile','revert', 199 'resendpwd','subscribens','unsubscribens',))){ 200 msg('Command unavailable: '.htmlspecialchars($act),-1); 201 return 'show'; 202 } 203 204 if(!in_array($act,array('login','logout','register','save','cancel','edit','draft', 205 'preview','search','show','check','index','revisions', 206 'diff','recent','backlink','admin','subscribe','revert', 207 'unsubscribe','profile','resendpwd','recover','wordblock', 208 'draftdel','subscribens','unsubscribens',)) && substr($act,0,7) != 'export_' ) { 209 msg('Command unknown: '.htmlspecialchars($act),-1); 210 return 'show'; 211 } 212 return $act; 213} 214 215/** 216 * Run permissionchecks 217 * 218 * @author Andreas Gohr <andi@splitbrain.org> 219 */ 220function act_permcheck($act){ 221 global $INFO; 222 global $conf; 223 224 if(in_array($act,array('save','preview','edit','recover'))){ 225 if($INFO['exists']){ 226 if($act == 'edit'){ 227 //the edit function will check again and do a source show 228 //when no AUTH_EDIT available 229 $permneed = AUTH_READ; 230 }else{ 231 $permneed = AUTH_EDIT; 232 } 233 }else{ 234 $permneed = AUTH_CREATE; 235 } 236 }elseif(in_array($act,array('login','search','recent','profile'))){ 237 $permneed = AUTH_NONE; 238 }elseif($act == 'revert'){ 239 $permneed = AUTH_ADMIN; 240 if($INFO['ismanager']) $permneed = AUTH_EDIT; 241 }elseif($act == 'register'){ 242 $permneed = AUTH_NONE; 243 }elseif($act == 'resendpwd'){ 244 $permneed = AUTH_NONE; 245 }elseif($act == 'admin'){ 246 if($INFO['ismanager']){ 247 // if the manager has the needed permissions for a certain admin 248 // action is checked later 249 $permneed = AUTH_READ; 250 }else{ 251 $permneed = AUTH_ADMIN; 252 } 253 }else{ 254 $permneed = AUTH_READ; 255 } 256 if($INFO['perm'] >= $permneed) return $act; 257 258 return 'denied'; 259} 260 261/** 262 * Handle 'draftdel' 263 * 264 * Deletes the draft for the current page and user 265 */ 266function act_draftdel($act){ 267 global $INFO; 268 @unlink($INFO['draft']); 269 $INFO['draft'] = null; 270 return 'show'; 271} 272 273/** 274 * Saves a draft on preview 275 * 276 * @todo this currently duplicates code from ajax.php :-/ 277 */ 278function act_draftsave($act){ 279 global $INFO; 280 global $ID; 281 global $conf; 282 if($conf['usedraft'] && $_POST['wikitext']){ 283 $draft = array('id' => $ID, 284 'prefix' => $_POST['prefix'], 285 'text' => $_POST['wikitext'], 286 'suffix' => $_POST['suffix'], 287 'date' => $_POST['date'], 288 'client' => $INFO['client'], 289 ); 290 $cname = getCacheName($draft['client'].$ID,'.draft'); 291 if(io_saveFile($cname,serialize($draft))){ 292 $INFO['draft'] = $cname; 293 } 294 } 295 return $act; 296} 297 298/** 299 * Handle 'save' 300 * 301 * Checks for spam and conflicts and saves the page. 302 * Does a redirect to show the page afterwards or 303 * returns a new action. 304 * 305 * @author Andreas Gohr <andi@splitbrain.org> 306 */ 307function act_save($act){ 308 global $ID; 309 global $DATE; 310 global $PRE; 311 global $TEXT; 312 global $SUF; 313 global $SUM; 314 315 //spam check 316 if(checkwordblock()) 317 return 'wordblock'; 318 //conflict check //FIXME use INFO 319 if($DATE != 0 && @filemtime(wikiFN($ID)) > $DATE ) 320 return 'conflict'; 321 322 //save it 323 saveWikiText($ID,con($PRE,$TEXT,$SUF,1),$SUM,$_REQUEST['minor']); //use pretty mode for con 324 //unlock it 325 unlock($ID); 326 327 //delete draft 328 act_draftdel($act); 329 session_write_close(); 330 331 // when done, show page 332 return 'show'; 333} 334 335/** 336 * Revert to a certain revision 337 * 338 * @author Andreas Gohr <andi@splitbrain.org> 339 */ 340function act_revert($act){ 341 global $ID; 342 global $REV; 343 global $lang; 344 345 // when no revision is given, delete current one 346 // FIXME this feature is not exposed in the GUI currently 347 $text = ''; 348 $sum = $lang['deleted']; 349 if($REV){ 350 $text = rawWiki($ID,$REV); 351 if(!$text) return 'show'; //something went wrong 352 $sum = $lang['restored']; 353 } 354 355 // spam check 356 if(checkwordblock($Text)) 357 return 'wordblock'; 358 359 saveWikiText($ID,$text,$sum,false); 360 msg($sum,1); 361 362 //delete any draft 363 act_draftdel($act); 364 session_write_close(); 365 366 // when done, show current page 367 $_SERVER['REQUEST_METHOD'] = 'post'; //should force a redirect 368 $REV = ''; 369 return 'show'; 370} 371 372/** 373 * Do a redirect after receiving post data 374 * 375 * Tries to add the section id as hash mark after section editing 376 */ 377function act_redirect($id,$preact){ 378 global $PRE; 379 global $TEXT; 380 global $MSG; 381 382 //are there any undisplayed messages? keep them in session for display 383 //on the next page 384 if(isset($MSG) && count($MSG)){ 385 //reopen session, store data and close session again 386 @session_start(); 387 $_SESSION[DOKU_COOKIE]['msg'] = $MSG; 388 session_write_close(); 389 } 390 391 $opts = array( 392 'id' => $id, 393 'preact' => $preact 394 ); 395 //get section name when coming from section edit 396 if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){ 397 $check = false; //Byref 398 $opts['fragment'] = sectionID($match[0], $check); 399 } 400 401 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute'); 402} 403 404function act_redirect_execute($opts){ 405 $go = wl($opts['id'],'',true); 406 if(isset($opts['fragment'])) $go .= '#'.$opts['fragment']; 407 408 //show it 409 send_redirect($go); 410} 411 412/** 413 * Handle 'login', 'logout' 414 * 415 * @author Andreas Gohr <andi@splitbrain.org> 416 */ 417function act_auth($act){ 418 global $ID; 419 global $INFO; 420 421 //already logged in? 422 if(isset($_SERVER['REMOTE_USER']) && $act=='login'){ 423 return 'show'; 424 } 425 426 //handle logout 427 if($act=='logout'){ 428 $lockedby = checklock($ID); //page still locked? 429 if($lockedby == $_SERVER['REMOTE_USER']) 430 unlock($ID); //try to unlock 431 432 // do the logout stuff 433 auth_logoff(); 434 435 // rebuild info array 436 $INFO = pageinfo(); 437 438 act_redirect($ID,'login'); 439 } 440 441 return $act; 442} 443 444/** 445 * Handle 'edit', 'preview' 446 * 447 * @author Andreas Gohr <andi@splitbrain.org> 448 */ 449function act_edit($act){ 450 global $ID; 451 global $INFO; 452 453 //check if locked by anyone - if not lock for my self 454 $lockedby = checklock($ID); 455 if($lockedby) return 'locked'; 456 457 lock($ID); 458 return $act; 459} 460 461/** 462 * Export a wiki page for various formats 463 * 464 * Triggers ACTION_EXPORT_POSTPROCESS 465 * 466 * Event data: 467 * data['id'] -- page id 468 * data['mode'] -- requested export mode 469 * data['headers'] -- export headers 470 * data['output'] -- export output 471 * 472 * @author Andreas Gohr <andi@splitbrain.org> 473 * @author Michael Klier <chi@chimeric.de> 474 */ 475function act_export($act){ 476 global $ID; 477 global $REV; 478 global $conf; 479 global $lang; 480 481 $pre = ''; 482 $post = ''; 483 $output = ''; 484 $headers = array(); 485 486 // search engines: never cache exported docs! (Google only currently) 487 $headers['X-Robots-Tag'] = 'noindex'; 488 489 $mode = substr($act,7); 490 switch($mode) { 491 case 'raw': 492 $headers['Content-Type'] = 'text/plain; charset=utf-8'; 493 $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt'; 494 $output = rawWiki($ID,$REV); 495 break; 496 case 'xhtml': 497 $pre .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' . DOKU_LF; 498 $pre .= ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' . DOKU_LF; 499 $pre .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="'.$conf['lang'].'"' . DOKU_LF; 500 $pre .= ' lang="'.$conf['lang'].'" dir="'.$lang['direction'].'">' . DOKU_LF; 501 $pre .= '<head>' . DOKU_LF; 502 $pre .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . DOKU_LF; 503 $pre .= ' <title>'.$ID.'</title>' . DOKU_LF; 504 505 // get metaheaders 506 ob_start(); 507 tpl_metaheaders(); 508 $pre .= ob_get_clean(); 509 510 $pre .= '</head>' . DOKU_LF; 511 $pre .= '<body>' . DOKU_LF; 512 $pre .= '<div class="dokuwiki export">' . DOKU_LF; 513 514 // get toc 515 $pre .= tpl_toc(true); 516 517 $headers['Content-Type'] = 'text/html; charset=utf-8'; 518 $output = p_wiki_xhtml($ID,$REV,false); 519 520 $post .= '</div>' . DOKU_LF; 521 $post .= '</body>' . DOKU_LF; 522 $post .= '</html>' . DOKU_LF; 523 break; 524 case 'xhtmlbody': 525 $headers['Content-Type'] = 'text/html; charset=utf-8'; 526 $output = p_wiki_xhtml($ID,$REV,false); 527 break; 528 default: 529 $output = p_cached_output(wikiFN($ID,$REV), $mode); 530 $headers = p_get_metadata($ID,"format $mode"); 531 break; 532 } 533 534 // prepare event data 535 $data = array(); 536 $data['id'] = $ID; 537 $data['mode'] = $mode; 538 $data['headers'] = $headers; 539 $data['output'] =& $output; 540 541 trigger_event('ACTION_EXPORT_POSTPROCESS', $data); 542 543 if(!empty($data['output'])){ 544 if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){ 545 header("$key: $val"); 546 } 547 print $pre.$data['output'].$post; 548 exit; 549 } 550 return 'show'; 551} 552 553/** 554 * Handle page 'subscribe' 555 * 556 * Throws exception on error. 557 * 558 * @author Adrian Lang <lang@cosmocode.de> 559 */ 560function act_subscription($act){ 561 global $lang; 562 global $INFO; 563 564 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 565 // No post to handle, let tpl_subscribe manage the request. 566 return $act; 567 } 568 569 // Get and validate parameters. 570 if (!isset($_POST['subscribe_target'])) { 571 throw new Exception($lang['subscr_no_target']); 572 } 573 $target = $_POST['subscribe_target']; 574 $valid_styles = array('every', 'digest'); 575 if (substr($target, -1, 1) === ':') { 576 // Allow “list” subscribe style since the target is a namespace. 577 $valid_styles[] = 'list'; 578 } 579 $style = valid_input_set('subscribe_style', $valid_styles, $_POST, 580 $lang['subscr_invalid_style']); 581 $action = valid_input_set('subscribe_action', array('subscribe', 582 'unsubscribe'), 583 $_POST, $lang['subscr_invalid_action']); 584 585 // Check other conditions. 586 if ($action === 'subscribe') { 587 if ($INFO['userinfo']['mail'] === '') { 588 throw new Exception($lang['subscr_subscribe_noaddress']); 589 } 590 } elseif ($action === 'unsubscribe') { 591 $is = false; 592 foreach($INFO['subscribed'] as $subscr) { 593 if ($subscr['target'] === $target) { 594 $is = true; 595 } 596 } 597 if ($is === false) { 598 throw new Exception(sprintf($lang['subscr_not_subscribed_you'], 599 prettyprint_id($target))); 600 } 601 // subscription_set deletes a subscription if style = null. 602 $style = null; 603 } 604 605 // Perform action. 606 require_once DOKU_INC . 'inc/subscription.php'; 607 if (!subscription_set($target, $_SERVER['REMOTE_USER'], $style)) { 608 throw new Exception(sprintf($lang["subscr_{$action}_error"], 609 hsc($INFO['userinfo']['name']), 610 prettyprint_id($target))); 611 } 612 $INFO['subscribed'] = get_info_subscribed(); 613 msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']), 614 prettyprint_id($target)), 1); 615 return $act; 616} 617 618//Setup VIM: ex: et ts=2 enc=utf-8 : 619