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