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