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