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