5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
9 if(!defined('DOKU_INC')) die('meh.');
12 * Call the needed action handlers
14 * @author Andreas Gohr <andi@splitbrain.org>
15 * @triggers ACTION_ACT_PREPROCESS
16 * @triggers ACTION_HEADERS_SEND
18 function act_dispatch(){
23 /* @var Input $INPUT */
30 // give plugins an opportunity to process the action
31 $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT);
34 if ($evt->advise_before()) {
37 $ACT = act_validate($ACT);
39 //check if searchword was given - else just show
41 if($ACT == 'search' && empty($s)){
46 if(in_array($ACT,array('login','logout'))){
47 $ACT = act_auth($ACT);
50 //check if user is asking to (un)subscribe a page
51 if($ACT == 'subscribe') {
53 $ACT = act_subscription($ACT);
54 } catch (Exception
$e) {
55 msg($e->getMessage(), -1);
66 $ACT = act_permcheck($ACT);
69 if ($ACT == 'sitemap'){
74 if ($ACT == 'recent'){
75 $show_changes = $INPUT->str('show_changes');
76 if (!empty($show_changes)) {
77 set_doku_pref('show_changes', $show_changes);
83 $difftype = $INPUT->str('difftype');
84 if (!empty($difftype)) {
85 set_doku_pref('difftype', $difftype);
90 if($ACT == 'register' && $INPUT->post
->bool('save') && register()){
94 if ($ACT == 'resendpwd' && act_resendpwd()) {
98 // user profile changes
99 if (in_array($ACT, array('profile','profile_delete'))) {
100 if(!$INPUT->server
->str('REMOTE_USER')) {
105 if(updateprofile()) {
106 msg($lang['profchanged'],1);
110 case 'profile_delete' :
111 if(auth_deleteprofile()){
112 msg($lang['profdeleted'],1);
123 if($ACT == 'revert'){
124 if(checkSecurityToken()){
125 $ACT = act_revert($ACT);
133 if(checkSecurityToken()){
134 $ACT = act_save($ACT);
140 //cancel conflicting edit
145 if($ACT == 'draftdel')
146 $ACT = act_draftdel($ACT);
148 //draft saving on preview
149 if($ACT == 'preview') {
150 $headers[] = "X-XSS-Protection: 0";
151 $ACT = act_draftsave($ACT);
155 if(in_array($ACT, array('edit', 'preview', 'recover'))) {
156 $ACT = act_edit($ACT);
158 unlock($ID); //try to unlock
162 if(substr($ACT,0,7) == 'export_')
163 $ACT = act_export($ACT);
167 // retrieve admin plugin name from $_REQUEST['page']
168 if (($page = $INPUT->str('page', '', true)) != '') {
169 /** @var $plugin DokuWiki_Admin_Plugin */
170 if ($plugin = plugin_getRequestAdminPlugin()){
176 // check permissions again - the action may have changed
177 $ACT = act_permcheck($ACT);
178 } // end event ACTION_ACT_PREPROCESS default action
179 $evt->advise_after();
180 // Make sure plugs can handle 'denied'
181 if($conf['send404'] && $ACT == 'denied') {
186 // when action 'show', the intial not 'show' and POST, do a redirect
187 if($ACT == 'show' && $preact != 'show' && strtolower($INPUT->server
->str('REQUEST_METHOD')) == 'post'){
188 act_redirect($ID,$preact);
195 //call template FIXME: all needed vars available?
196 $headers[] = 'Content-Type: text/html; charset=utf-8';
197 trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders');
199 include(template('main.php'));
200 // output for the commands is now handled in inc/templates.php
201 // in function tpl_content()
205 * Send the given headers using header()
207 * @param array $headers The headers that shall be sent
209 function act_sendheaders($headers) {
210 foreach ($headers as $hdr) header($hdr);
214 * Sanitize the action command
216 * @author Andreas Gohr <andi@splitbrain.org>
218 * @param array|string $act
221 function act_clean($act){
222 // check if the action was given as array key
224 list($act) = array_keys($act);
227 //remove all bad chars
228 $act = strtolower($act);
229 $act = preg_replace('/[^1-9a-z_]+/','',$act);
231 if($act == 'export_html') $act = 'export_xhtml';
232 if($act == 'export_htmlbody') $act = 'export_xhtmlbody';
234 if($act === '') $act = 'show';
239 * Sanitize and validate action commands.
241 * Add all allowed commands here.
243 * @author Andreas Gohr <andi@splitbrain.org>
245 * @param array|string $act
248 function act_validate($act) {
252 $act = act_clean($act);
254 // check if action is disabled
256 msg('Command disabled: '.htmlspecialchars($act),-1);
260 //disable all acl related commands if ACL is disabled
261 if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin',
262 'subscribe','unsubscribe','profile','revert',
263 'resendpwd','profile_delete'))){
264 msg('Command unavailable: '.htmlspecialchars($act),-1);
268 //is there really a draft?
269 if($act == 'draft' && !file_exists($INFO['draft'])) return 'edit';
271 if(!in_array($act,array('login','logout','register','save','cancel','edit','draft',
272 'preview','search','show','check','index','revisions',
273 'diff','recent','backlink','admin','subscribe','revert',
274 'unsubscribe','profile','profile_delete','resendpwd','recover',
275 'draftdel','sitemap','media')) && substr($act,0,7) != 'export_' ) {
276 msg('Command unknown: '.htmlspecialchars($act),-1);
283 * Run permissionchecks
285 * @author Andreas Gohr <andi@splitbrain.org>
287 * @param string $act action command
288 * @return string action command
290 function act_permcheck($act){
293 if(in_array($act,array('save','preview','edit','recover'))){
296 //the edit function will check again and do a source show
297 //when no AUTH_EDIT available
298 $permneed = AUTH_READ
;
300 $permneed = AUTH_EDIT
;
303 $permneed = AUTH_CREATE
;
305 }elseif(in_array($act,array('login','search','recent','profile','profile_delete','index', 'sitemap'))){
306 $permneed = AUTH_NONE
;
307 }elseif($act == 'revert'){
308 $permneed = AUTH_ADMIN
;
309 if($INFO['ismanager']) $permneed = AUTH_EDIT
;
310 }elseif($act == 'register'){
311 $permneed = AUTH_NONE
;
312 }elseif($act == 'resendpwd'){
313 $permneed = AUTH_NONE
;
314 }elseif($act == 'admin'){
315 if($INFO['ismanager']){
316 // if the manager has the needed permissions for a certain admin
317 // action is checked later
318 $permneed = AUTH_READ
;
320 $permneed = AUTH_ADMIN
;
323 $permneed = AUTH_READ
;
325 if($INFO['perm'] >= $permneed) return $act;
333 * Deletes the draft for the current page and user
335 * @param string $act action command
336 * @return string action command
338 function act_draftdel($act){
340 @unlink
($INFO['draft']);
341 $INFO['draft'] = null;
346 * Saves a draft on preview
348 * @todo this currently duplicates code from ajax.php :-/
350 * @param string $act action command
351 * @return string action command
353 function act_draftsave($act){
358 if($conf['usedraft'] && $INPUT->post
->has('wikitext')) {
359 $draft = array('id' => $ID,
360 'prefix' => substr($INPUT->post
->str('prefix'), 0, -1),
361 'text' => $INPUT->post
->str('wikitext'),
362 'suffix' => $INPUT->post
->str('suffix'),
363 'date' => $INPUT->post
->int('date'),
364 'client' => $INFO['client'],
366 $cname = getCacheName($draft['client'].$ID,'.draft');
367 if(io_saveFile($cname,serialize($draft))){
368 $INFO['draft'] = $cname;
377 * Checks for spam and conflicts and saves the page.
378 * Does a redirect to show the page afterwards or
379 * returns a new action.
381 * @author Andreas Gohr <andi@splitbrain.org>
383 * @param string $act action command
384 * @return string action command
386 function act_save($act){
398 if(checkwordblock()) {
399 msg($lang['wordblock'], -1);
403 if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE )
407 saveWikiText($ID,con($PRE,$TEXT,$SUF,true),$SUM,$INPUT->bool('minor')); //use pretty mode for con
413 session_write_close();
415 // when done, show page
420 * Revert to a certain revision
422 * @author Andreas Gohr <andi@splitbrain.org>
424 * @param string $act action command
425 * @return string action command
427 function act_revert($act){
431 /* @var Input $INPUT */
433 // FIXME $INFO['writable'] currently refers to the attic version
435 // if (!$INFO['writable']) {
439 // when no revision is given, delete current one
440 // FIXME this feature is not exposed in the GUI currently
442 $sum = $lang['deleted'];
444 $text = rawWiki($ID,$REV);
445 if(!$text) return 'show'; //something went wrong
446 $sum = sprintf($lang['restored'], dformat($REV));
451 if (checkwordblock($text)) {
452 msg($lang['wordblock'], -1);
456 saveWikiText($ID,$text,$sum,false);
461 session_write_close();
463 // when done, show current page
464 $INPUT->server
->set('REQUEST_METHOD','post'); //should force a redirect
470 * Do a redirect after receiving post data
472 * Tries to add the section id as hash mark after section editing
474 * @param string $id page id
475 * @param string $preact action command before redirect
477 function act_redirect($id,$preact){
485 //get section name when coming from section edit
486 if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){
487 $check = false; //Byref
488 $opts['fragment'] = sectionID($match[0], $check);
491 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute');
495 * Execute the redirect
497 * @param array $opts id and fragment for the redirect and the preact
499 function act_redirect_execute($opts){
500 $go = wl($opts['id'],'',true);
501 if(isset($opts['fragment'])) $go .= '#'.$opts['fragment'];
508 * Handle 'login', 'logout'
510 * @author Andreas Gohr <andi@splitbrain.org>
512 * @param string $act action command
513 * @return string action command
515 function act_auth($act){
518 /* @var Input $INPUT */
522 if($INPUT->server
->has('REMOTE_USER') && $act=='login'){
528 $lockedby = checklock($ID); //page still locked?
529 if($lockedby == $INPUT->server
->str('REMOTE_USER')){
530 unlock($ID); //try to unlock
533 // do the logout stuff
536 // rebuild info array
539 act_redirect($ID,'login');
546 * Handle 'edit', 'preview', 'recover'
548 * @author Andreas Gohr <andi@splitbrain.org>
550 * @param string $act action command
551 * @return string action command
553 function act_edit($act){
567 if ($INFO['exists']) {
569 list($PRE,$TEXT,$SUF) = rawWikiSlices($RANGE,$ID,$REV);
571 $TEXT = rawWiki($ID,$REV);
574 $TEXT = pageTemplate($ID);
578 //set summary default
581 $SUM = sprintf($lang['restored'], dformat($REV));
582 }elseif(!$INFO['exists']){
583 $SUM = $lang['created'];
587 // Use the date of the newest revision, not of the revision we edit
588 // This is used for conflict detection
589 if(!$DATE) $DATE = @filemtime
(wikiFN($ID));
591 //check if locked by anyone - if not lock for my self
592 //do not lock when the user can't edit anyway
593 if ($INFO['writable']) {
594 $lockedby = checklock($ID);
595 if($lockedby) return 'locked';
604 * Export a wiki page for various formats
606 * Triggers ACTION_EXPORT_POSTPROCESS
609 * data['id'] -- page id
610 * data['mode'] -- requested export mode
611 * data['headers'] -- export headers
612 * data['output'] -- export output
614 * @author Andreas Gohr <andi@splitbrain.org>
615 * @author Michael Klier <chi@chimeric.de>
617 * @param string $act action command
618 * @return string action command
620 function act_export($act){
630 // search engines: never cache exported docs! (Google only currently)
631 $headers['X-Robots-Tag'] = 'noindex';
633 $mode = substr($act,7);
636 $headers['Content-Type'] = 'text/plain; charset=utf-8';
637 $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt';
638 $output = rawWiki($ID,$REV);
641 $pre .= '<!DOCTYPE html>' . DOKU_LF
;
642 $pre .= '<html lang="'.$conf['lang'].'" dir="'.$lang['direction'].'">' . DOKU_LF
;
643 $pre .= '<head>' . DOKU_LF
;
644 $pre .= ' <meta charset="utf-8" />' . DOKU_LF
;
645 $pre .= ' <title>'.$ID.'</title>' . DOKU_LF
;
650 $pre .= ob_get_clean();
652 $pre .= '</head>' . DOKU_LF
;
653 $pre .= '<body>' . DOKU_LF
;
654 $pre .= '<div class="dokuwiki export">' . DOKU_LF
;
657 $pre .= tpl_toc(true);
659 $headers['Content-Type'] = 'text/html; charset=utf-8';
660 $output = p_wiki_xhtml($ID,$REV,false);
662 $post .= '</div>' . DOKU_LF
;
663 $post .= '</body>' . DOKU_LF
;
664 $post .= '</html>' . DOKU_LF
;
667 $headers['Content-Type'] = 'text/html; charset=utf-8';
668 $output = p_wiki_xhtml($ID,$REV,false);
671 $output = p_cached_output(wikiFN($ID,$REV), $mode, $ID);
672 $headers = p_get_metadata($ID,"format $mode");
676 // prepare event data
679 $data['mode'] = $mode;
680 $data['headers'] = $headers;
681 $data['output'] =& $output;
683 trigger_event('ACTION_EXPORT_POSTPROCESS', $data);
685 if(!empty($data['output'])){
686 if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){
687 header("$key: $val");
689 print $pre.$data['output'].$post;
696 * Handle sitemap delivery
698 * @author Michael Hamann <michael@content-space.de>
700 * @param string $act action command
702 function act_sitemap($act) {
705 if ($conf['sitemap'] < 1 ||
!is_numeric($conf['sitemap'])) {
707 print "Sitemap generation is disabled.";
711 $sitemap = Sitemapper
::getFilePath();
712 if (Sitemapper
::sitemapIsCompressed()) {
713 $mime = 'application/x-gzip';
715 $mime = 'application/xml; charset=utf-8';
718 // Check if sitemap file exists, otherwise create it
719 if (!is_readable($sitemap)) {
720 Sitemapper
::generate();
723 if (is_readable($sitemap)) {
725 header('Content-Type: '.$mime);
726 header('Content-Disposition: attachment; filename='.utf8_basename($sitemap));
728 http_conditionalRequest(filemtime($sitemap));
731 //use x-sendfile header to pass the delivery to compatible webservers
732 http_sendfile($sitemap);
739 print "Could not read the sitemap file - bad permissions?";
744 * Handle page 'subscribe'
746 * Throws exception on error.
748 * @author Adrian Lang <lang@cosmocode.de>
750 * @param string $act action command
751 * @return string action command
752 * @throws Exception if (un)subscribing fails
754 function act_subscription($act){
758 /* @var Input $INPUT */
761 // subcriptions work for logged in users only
762 if(!$INPUT->server
->str('REMOTE_USER')) return 'show';
764 // get and preprocess data.
766 foreach(array('target', 'style', 'action') as $param) {
767 if ($INPUT->has("sub_$param")) {
768 $params[$param] = $INPUT->str("sub_$param");
772 // any action given? if not just return and show the subscription page
773 if(empty($params['action']) ||
!checkSecurityToken()) return $act;
775 // Handle POST data, may throw exception.
776 trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, 'subscription_handle_post');
778 $target = $params['target'];
779 $style = $params['style'];
780 $action = $params['action'];
783 $sub = new Subscription();
784 if($action == 'unsubscribe'){
785 $ok = $sub->remove($target, $INPUT->server
->str('REMOTE_USER'), $style);
787 $ok = $sub->add($target, $INPUT->server
->str('REMOTE_USER'), $style);
791 msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
792 prettyprint_id($target)), 1);
793 act_redirect($ID, $act);
795 throw new Exception(sprintf($lang["subscr_{$action}_error"],
796 hsc($INFO['userinfo']['name']),
797 prettyprint_id($target)));
800 // Assure that we have valid data if act_redirect somehow fails.
801 $INFO['subscribed'] = $sub->user_subscription();
808 * Validates POST data for a subscribe or unsubscribe request. This is the
809 * default action for the event ACTION_HANDLE_SUBSCRIBE.
811 * @author Adrian Lang <lang@cosmocode.de>
813 * @param array &$params the parameters: target, style and action
816 function subscription_handle_post(&$params) {
819 /* @var Input $INPUT */
822 // Get and validate parameters.
823 if (!isset($params['target'])) {
824 throw new Exception('no subscription target given');
826 $target = $params['target'];
827 $valid_styles = array('every', 'digest');
828 if (substr($target, -1, 1) === ':') {
829 // Allow “list” subscribe style since the target is a namespace.
830 $valid_styles[] = 'list';
832 $style = valid_input_set('style', $valid_styles, $params,
833 'invalid subscription style given');
834 $action = valid_input_set('action', array('subscribe', 'unsubscribe'),
835 $params, 'invalid subscription action given');
837 // Check other conditions.
838 if ($action === 'subscribe') {
839 if ($INFO['userinfo']['mail'] === '') {
840 throw new Exception($lang['subscr_subscribe_noaddress']);
842 } elseif ($action === 'unsubscribe') {
844 foreach($INFO['subscribed'] as $subscr) {
845 if ($subscr['target'] === $target) {
850 throw new Exception(sprintf($lang['subscr_not_subscribed'],
851 $INPUT->server
->str('REMOTE_USER'),
852 prettyprint_id($target)));
854 // subscription_set deletes a subscription if style = null.
858 $params = compact('target', 'style', 'action');
861 //Setup VIM: ex: et ts=2 :