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