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