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