xref: /dokuwiki/inc/ActionRouter.php (revision a3f6fae668f2438bb3018620d664a9e7f1e6cd21)
1<?php
2
3namespace dokuwiki;
4
5use dokuwiki\Action\AbstractAction;
6use dokuwiki\Action\Exception\ActionDisabledException;
7use dokuwiki\Action\Exception\ActionException;
8use dokuwiki\Action\Exception\FatalException;
9use dokuwiki\Action\Exception\NoActionException;
10use dokuwiki\Action\Plugin;
11
12/**
13 * Class ActionRouter
14 * @package dokuwiki
15 */
16class ActionRouter {
17
18    /** @var  AbstractAction */
19    protected $action;
20
21    /** @var  ActionRouter */
22    protected $instance;
23
24    /**
25     * ActionRouter constructor. Singleton, thus protected!
26     *
27     * Sets up the correct action based on the $ACT global. Writes back
28     * the selected action to $ACT
29     */
30    protected function __construct() {
31        global $ACT;
32        $ACT = act_clean($ACT);
33        $this->setupAction($ACT);
34        $ACT = $this->action->getActionName();
35    }
36
37    /**
38     * Get the singleton instance
39     *
40     * @param bool $reinit
41     * @return ActionRouter
42     */
43    public function getInstance($reinit = false) {
44        if(($this->instance === null) || $reinit) {
45            $this->instance = new ActionRouter();
46        }
47        return $this->instance;
48    }
49
50    /**
51     * Setup the given action
52     *
53     * Instantiates the right class, runs permission checks and pre-processing and
54     * sets $action
55     *
56     * @param string $actionname
57     * @triggers ACTION_ACT_PREPROCESS
58     * @fixme implement redirect on action change with post
59     */
60    protected function setupAction($actionname) {
61        $presetup = $actionname;
62
63        try {
64            $this->action = $this->loadAction($actionname);
65            $this->action->checkPermissions();
66            $this->ensureMinimumPermission($this->action->minimumPermission());
67            $this->action->preProcess();
68
69        } catch(ActionException $e) {
70            // we should have gotten a new action
71            $actionname = $e->getNewAction();
72
73            // no infinite recursion
74            if($actionname == $presetup) {
75                // FIXME this doesn't catch larger circles
76                $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
77            }
78
79            // this one should trigger a user message
80            if(is_a($e, ActionDisabledException::class)) {
81                msg('Action disabled: ' . hsc($presetup), -1);
82            }
83
84            // do setup for new action
85            $this->setupAction($actionname);
86
87        } catch(NoActionException $e) {
88            // give plugins an opportunity to process the actionname
89            $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname);
90            if($evt->advise_before()) {
91                if($actionname == $presetup) {
92                    // no plugin changed the action, complain and switch to show
93                    msg('Action unknown: ' . hsc($actionname), -1);
94                    $actionname = 'show';
95                }
96                $this->setupAction($actionname);
97            } else {
98                // event said the action should be kept, assume action plugin will handle it later
99                $this->action = new Plugin();
100                $this->action->setActionName($actionname);
101            }
102            $evt->advise_after();
103
104        } catch(\Exception $e) {
105            $this->handleFatalException($e);
106        }
107    }
108
109    /**
110     * Check that the given minimum permissions are reached
111     *
112     * @param int $permneed
113     * @throws ActionException
114     */
115    protected function ensureMinimumPermission($permneed) {
116        global $INFO;
117        if($INFO['perm'] < $permneed) {
118            throw new ActionException('denied');
119        }
120    }
121
122    /**
123     * Aborts all processing with a message
124     *
125     * When a FataException instanc is passed, the code is treated as Status code
126     *
127     * @param \Exception|FatalException $e
128     */
129    protected function handleFatalException(\Exception $e) {
130        if(is_a($e, FatalException::class)) {
131            http_status($e->getCode());
132        } else {
133            http_status(500);
134        }
135        $msg = 'Something unforseen has happened: ' . $e->getMessage();
136        nice_die(hsc($msg));
137    }
138
139    /**
140     * Load the given action
141     *
142     * @param $actionname
143     * @return AbstractAction
144     * @throws NoActionException
145     */
146    protected function loadAction($actionname) {
147        $class = 'dokuwiki\\Action\\' . ucfirst(strtolower($actionname));
148        if(class_exists($class)) {
149            return new $class;
150        }
151        throw new NoActionException();
152    }
153
154    /**
155     * Returns the action handling the current request
156     *
157     * @return AbstractAction
158     */
159    public function getAction() {
160        return $this->action;
161    }
162}
163