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