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