about summary refs log tree commit diff stats
path: root/file.lua
diff options
context:
space:
mode:
Diffstat (limited to 'file.lua')
0 files changed, 0 insertions, 0 deletions
6 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228



































































































































































































































                                                                                                                    
<?php

namespace dokuwiki;

use dokuwiki\Action\AbstractAction;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Action\Exception\FatalException;
use dokuwiki\Action\Exception\NoActionException;
use dokuwiki\Action\Plugin;

/**
 * Class ActionRouter
 * @package dokuwiki
 */
class ActionRouter {

    /** @var  AbstractAction */
    protected $action;

    /** @var  ActionRouter */
    protected static $instance = null;

    /** @var int transition counter */
    protected $transitions = 0;

    /** maximum loop */
    const MAX_TRANSITIONS = 5;

    /** @var string[] the actions disabled in the configuration */
    protected $disabled;

    /**
     * ActionRouter constructor. Singleton, thus protected!
     *
     * Sets up the correct action based on the $ACT global. Writes back
     * the selected action to $ACT
     */
    protected function __construct() {
        global $ACT;
        global $conf;

        $this->disabled = explode(',', $conf['disableactions']);
        $this->disabled = array_map('trim', $this->disabled);
        $this->transitions = 0;

        $ACT = act_clean($ACT);
        $this->setupAction($ACT);
        $ACT = $this->action->getActionName();
    }

    /**
     * Get the singleton instance
     *
     * @param bool $reinit
     * @return ActionRouter
     */
    public static function getInstance($reinit = false) {
        if((self::$instance === null) || $reinit) {
            self::$instance = new ActionRouter();
        }
        return self::$instance;
    }

    /**
     * Setup the given action
     *
     * Instantiates the right class, runs permission checks and pre-processing and
     * sets $action
     *
     * @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility
     * @triggers ACTION_ACT_PREPROCESS
     */
    protected function setupAction(&$actionname) {
        $presetup = $actionname;

        try {
            // give plugins an opportunity to process the actionname
            $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname);
            if ($evt->advise_before()) {
                $this->action = $this->loadAction($actionname);
                $this->checkAction($this->action);
                $this->action->preProcess();
            } else {
                // event said the action should be kept, assume action plugin will handle it later
                $this->action = new Plugin($actionname);
            }
            $evt->advise_after();

        } catch(ActionException $e) {
            // we should have gotten a new action
            $actionname = $e->getNewAction();

            // this one should trigger a user message
            if(is_a($e, ActionDisabledException::class)) {
                msg('Action disabled: ' . hsc($presetup), -1);
            }

            // some actions may request the display of a message
            if($e->displayToUser()) {
                msg(hsc($e->getMessage()), -1);
            }

            // do setup for new action
            $this->transitionAction($presetup, $actionname);

        } catch(NoActionException $e) {
            msg('Action unknown: ' . hsc($actionname), -1);
            $actionname = 'show';
            $this->transitionAction($presetup, $actionname);
        } catch(\Exception $e) {
            $this->handleFatalException($e);
        }
    }

    /**
     * Transitions from one action to another
     *
     * Basically just calls setupAction() again but does some checks before.
     *
     * @param string $from current action name
     * @param string $to new action name
     * @param null|ActionException $e any previous exception that caused the transition
     */
    protected function transitionAction($from, $to, $e = null) {
        $this->transitions++;

        // no infinite recursion
        if($from == $to) {
            $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
        }

        // larger loops will be caught here
        if($this->transitions >= self::MAX_TRANSITIONS) {
            $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e));
        }

        // do the recursion
        $this->setupAction($to);
    }

    /**
     * Aborts all processing with a message
     *
     * When a FataException instanc is passed, the code is treated as Status code
     *
     * @param \Exception|FatalException $e
     * @throws FatalException during unit testing
     */
    protected function handleFatalException(\Exception $e) {
        if(is_a($e, FatalException::class)) {
            http_status($e->getCode());
        } else {
            http_status(500);
        }
        if(defined('DOKU_UNITTEST')) {
            throw $e;
        }
        $msg = 'Something unforseen has happened: ' . $e->getMessage();
        nice_die(hsc($msg));
    }

    /**
     * Load the given action
     *
     * This translates the given name to a class name by uppercasing the first letter.
     * Underscores translate to camelcase names. For actions with underscores, the different
     * parts are removed beginning from the end until a matching class is found. The instatiated
     * Action will always have the full original action set as Name
     *
     * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export'
     *
     * @param $actionname
     * @return AbstractAction
     * @throws NoActionException
     */
    public function loadAction($actionname) {
        $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else?
        $parts = explode('_', $actionname);
        while(!empty($parts)) {
            $load = join('_', $parts);
            $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_'));
            if(class_exists($class)) {
                return new $class($actionname);
            }
            array_pop($parts);
        }

        throw new NoActionException();
    }

    /**
     * Execute all the checks to see if this action can be executed
     *
     * @param AbstractAction $action
     * @throws ActionDisabledException
     * @throws ActionException
     */
    public function checkAction(AbstractAction $action) {
        global $INFO;
        global $ID;

        if(in_array($action->getActionName(), $this->disabled)) {
            throw new ActionDisabledException();
        }

        $action->checkPreconditions();

        if(isset($INFO)) {
            $perm = $INFO['perm'];
        } else {
            $perm = auth_quickaclcheck($ID);
        }

        if($perm < $action->minimumPermission()) {
            throw new ActionException('denied');
        }
    }

    /**
     * Returns the action handling the current request
     *
     * @return AbstractAction
     */
    public function getAction() {
        return $this->action;
    }
}