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