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