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