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