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