xref: /dokuwiki/inc/ActionRouter.php (revision a3f6fae668f2438bb3018620d664a9e7f1e6cd21)
164ab5140SAndreas Gohr<?php
264ab5140SAndreas Gohr
364ab5140SAndreas Gohrnamespace dokuwiki;
464ab5140SAndreas Gohr
564ab5140SAndreas Gohruse dokuwiki\Action\AbstractAction;
664ab5140SAndreas Gohruse dokuwiki\Action\Exception\ActionDisabledException;
764ab5140SAndreas Gohruse dokuwiki\Action\Exception\ActionException;
864ab5140SAndreas Gohruse dokuwiki\Action\Exception\FatalException;
964ab5140SAndreas Gohruse dokuwiki\Action\Exception\NoActionException;
10*a3f6fae6SAndreas Gohruse dokuwiki\Action\Plugin;
1164ab5140SAndreas Gohr
12*a3f6fae6SAndreas Gohr/**
13*a3f6fae6SAndreas Gohr * Class ActionRouter
14*a3f6fae6SAndreas Gohr * @package dokuwiki
15*a3f6fae6SAndreas Gohr */
1664ab5140SAndreas Gohrclass ActionRouter {
1764ab5140SAndreas Gohr
1864ab5140SAndreas Gohr    /** @var  AbstractAction */
1964ab5140SAndreas Gohr    protected $action;
2064ab5140SAndreas Gohr
2164ab5140SAndreas Gohr    /** @var  ActionRouter */
2264ab5140SAndreas Gohr    protected $instance;
2364ab5140SAndreas Gohr
2464ab5140SAndreas Gohr    /**
2564ab5140SAndreas Gohr     * ActionRouter constructor. Singleton, thus protected!
26*a3f6fae6SAndreas Gohr     *
27*a3f6fae6SAndreas Gohr     * Sets up the correct action based on the $ACT global. Writes back
28*a3f6fae6SAndreas Gohr     * the selected action to $ACT
2964ab5140SAndreas Gohr     */
3064ab5140SAndreas Gohr    protected function __construct() {
31*a3f6fae6SAndreas Gohr        global $ACT;
32*a3f6fae6SAndreas Gohr        $ACT = act_clean($ACT);
33*a3f6fae6SAndreas Gohr        $this->setupAction($ACT);
34*a3f6fae6SAndreas Gohr        $ACT = $this->action->getActionName();
3564ab5140SAndreas Gohr    }
3664ab5140SAndreas Gohr
3764ab5140SAndreas Gohr    /**
3864ab5140SAndreas Gohr     * Get the singleton instance
3964ab5140SAndreas Gohr     *
4064ab5140SAndreas Gohr     * @param bool $reinit
4164ab5140SAndreas Gohr     * @return ActionRouter
4264ab5140SAndreas Gohr     */
4364ab5140SAndreas Gohr    public function getInstance($reinit = false) {
4464ab5140SAndreas Gohr        if(($this->instance === null) || $reinit) {
4564ab5140SAndreas Gohr            $this->instance = new ActionRouter();
4664ab5140SAndreas Gohr        }
4764ab5140SAndreas Gohr        return $this->instance;
4864ab5140SAndreas Gohr    }
4964ab5140SAndreas Gohr
5064ab5140SAndreas Gohr    /**
5164ab5140SAndreas Gohr     * Setup the given action
5264ab5140SAndreas Gohr     *
5364ab5140SAndreas Gohr     * Instantiates the right class, runs permission checks and pre-processing and
54*a3f6fae6SAndreas Gohr     * sets $action
5564ab5140SAndreas Gohr     *
5664ab5140SAndreas Gohr     * @param string $actionname
57*a3f6fae6SAndreas Gohr     * @triggers ACTION_ACT_PREPROCESS
5864ab5140SAndreas Gohr     * @fixme implement redirect on action change with post
5964ab5140SAndreas Gohr     */
6064ab5140SAndreas Gohr    protected function setupAction($actionname) {
61*a3f6fae6SAndreas Gohr        $presetup = $actionname;
62*a3f6fae6SAndreas Gohr
6364ab5140SAndreas Gohr        try {
6464ab5140SAndreas Gohr            $this->action = $this->loadAction($actionname);
6564ab5140SAndreas Gohr            $this->action->checkPermissions();
6664ab5140SAndreas Gohr            $this->ensureMinimumPermission($this->action->minimumPermission());
6764ab5140SAndreas Gohr            $this->action->preProcess();
6864ab5140SAndreas Gohr
6964ab5140SAndreas Gohr        } catch(ActionException $e) {
7064ab5140SAndreas Gohr            // we should have gotten a new action
71*a3f6fae6SAndreas Gohr            $actionname = $e->getNewAction();
7264ab5140SAndreas Gohr
7364ab5140SAndreas Gohr            // no infinite recursion
74*a3f6fae6SAndreas Gohr            if($actionname == $presetup) {
7564ab5140SAndreas Gohr                // FIXME this doesn't catch larger circles
7664ab5140SAndreas Gohr                $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
7764ab5140SAndreas Gohr            }
7864ab5140SAndreas Gohr
7964ab5140SAndreas Gohr            // this one should trigger a user message
8064ab5140SAndreas Gohr            if(is_a($e, ActionDisabledException::class)) {
81*a3f6fae6SAndreas Gohr                msg('Action disabled: ' . hsc($presetup), -1);
8264ab5140SAndreas Gohr            }
8364ab5140SAndreas Gohr
8464ab5140SAndreas Gohr            // do setup for new action
85*a3f6fae6SAndreas Gohr            $this->setupAction($actionname);
8664ab5140SAndreas Gohr
8764ab5140SAndreas Gohr        } catch(NoActionException $e) {
88*a3f6fae6SAndreas Gohr            // give plugins an opportunity to process the actionname
89*a3f6fae6SAndreas Gohr            $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname);
90*a3f6fae6SAndreas Gohr            if($evt->advise_before()) {
91*a3f6fae6SAndreas Gohr                if($actionname == $presetup) {
92*a3f6fae6SAndreas Gohr                    // no plugin changed the action, complain and switch to show
93*a3f6fae6SAndreas Gohr                    msg('Action unknown: ' . hsc($actionname), -1);
94*a3f6fae6SAndreas Gohr                    $actionname = 'show';
95*a3f6fae6SAndreas Gohr                }
96*a3f6fae6SAndreas Gohr                $this->setupAction($actionname);
97*a3f6fae6SAndreas Gohr            } else {
98*a3f6fae6SAndreas Gohr                // event said the action should be kept, assume action plugin will handle it later
99*a3f6fae6SAndreas Gohr                $this->action = new Plugin();
100*a3f6fae6SAndreas Gohr                $this->action->setActionName($actionname);
101*a3f6fae6SAndreas Gohr            }
102*a3f6fae6SAndreas Gohr            $evt->advise_after();
10364ab5140SAndreas Gohr
10464ab5140SAndreas Gohr        } catch(\Exception $e) {
10564ab5140SAndreas Gohr            $this->handleFatalException($e);
10664ab5140SAndreas Gohr        }
10764ab5140SAndreas Gohr    }
10864ab5140SAndreas Gohr
10964ab5140SAndreas Gohr    /**
11064ab5140SAndreas Gohr     * Check that the given minimum permissions are reached
11164ab5140SAndreas Gohr     *
11264ab5140SAndreas Gohr     * @param int $permneed
11364ab5140SAndreas Gohr     * @throws ActionException
11464ab5140SAndreas Gohr     */
11564ab5140SAndreas Gohr    protected function ensureMinimumPermission($permneed) {
11664ab5140SAndreas Gohr        global $INFO;
11764ab5140SAndreas Gohr        if($INFO['perm'] < $permneed) {
11864ab5140SAndreas Gohr            throw new ActionException('denied');
11964ab5140SAndreas Gohr        }
12064ab5140SAndreas Gohr    }
12164ab5140SAndreas Gohr
12264ab5140SAndreas Gohr    /**
12364ab5140SAndreas Gohr     * Aborts all processing with a message
12464ab5140SAndreas Gohr     *
12564ab5140SAndreas Gohr     * When a FataException instanc is passed, the code is treated as Status code
12664ab5140SAndreas Gohr     *
12764ab5140SAndreas Gohr     * @param \Exception|FatalException $e
12864ab5140SAndreas Gohr     */
12964ab5140SAndreas Gohr    protected function handleFatalException(\Exception $e) {
13064ab5140SAndreas Gohr        if(is_a($e, FatalException::class)) {
13164ab5140SAndreas Gohr            http_status($e->getCode());
13264ab5140SAndreas Gohr        } else {
13364ab5140SAndreas Gohr            http_status(500);
13464ab5140SAndreas Gohr        }
13564ab5140SAndreas Gohr        $msg = 'Something unforseen has happened: ' . $e->getMessage();
13664ab5140SAndreas Gohr        nice_die(hsc($msg));
13764ab5140SAndreas Gohr    }
13864ab5140SAndreas Gohr
13964ab5140SAndreas Gohr    /**
14064ab5140SAndreas Gohr     * Load the given action
14164ab5140SAndreas Gohr     *
14264ab5140SAndreas Gohr     * @param $actionname
14364ab5140SAndreas Gohr     * @return AbstractAction
14464ab5140SAndreas Gohr     * @throws NoActionException
14564ab5140SAndreas Gohr     */
14664ab5140SAndreas Gohr    protected function loadAction($actionname) {
14764ab5140SAndreas Gohr        $class = 'dokuwiki\\Action\\' . ucfirst(strtolower($actionname));
14864ab5140SAndreas Gohr        if(class_exists($class)) {
14964ab5140SAndreas Gohr            return new $class;
15064ab5140SAndreas Gohr        }
15164ab5140SAndreas Gohr        throw new NoActionException();
15264ab5140SAndreas Gohr    }
15364ab5140SAndreas Gohr
15464ab5140SAndreas Gohr    /**
15564ab5140SAndreas Gohr     * Returns the action handling the current request
15664ab5140SAndreas Gohr     *
15764ab5140SAndreas Gohr     * @return AbstractAction
15864ab5140SAndreas Gohr     */
15964ab5140SAndreas Gohr    public function getAction() {
16064ab5140SAndreas Gohr        return $this->action;
16164ab5140SAndreas Gohr    }
16264ab5140SAndreas Gohr}
163