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; 10a3f6fae6SAndreas Gohruse dokuwiki\Action\Plugin; 1164ab5140SAndreas Gohr 12a3f6fae6SAndreas Gohr/** 13a3f6fae6SAndreas Gohr * Class ActionRouter 14a3f6fae6SAndreas Gohr * @package dokuwiki 15a3f6fae6SAndreas Gohr */ 1664ab5140SAndreas Gohrclass ActionRouter { 1764ab5140SAndreas Gohr 1864ab5140SAndreas Gohr /** @var AbstractAction */ 1964ab5140SAndreas Gohr protected $action; 2064ab5140SAndreas Gohr 2164ab5140SAndreas Gohr /** @var ActionRouter */ 22*ae7bcdc7SAndreas Gohr protected static $instance; 2364ab5140SAndreas Gohr 2450701b66SAndreas Gohr /** @var int transition counter */ 2550701b66SAndreas Gohr protected $transitions = 0; 2650701b66SAndreas Gohr 2750701b66SAndreas Gohr /** maximum loop */ 2850701b66SAndreas Gohr const MAX_TRANSITIONS = 5; 2950701b66SAndreas Gohr 3064ab5140SAndreas Gohr /** 3164ab5140SAndreas Gohr * ActionRouter constructor. Singleton, thus protected! 32a3f6fae6SAndreas Gohr * 33a3f6fae6SAndreas Gohr * Sets up the correct action based on the $ACT global. Writes back 34a3f6fae6SAndreas Gohr * the selected action to $ACT 3564ab5140SAndreas Gohr */ 3664ab5140SAndreas Gohr protected function __construct() { 37a3f6fae6SAndreas Gohr global $ACT; 38a3f6fae6SAndreas Gohr $ACT = act_clean($ACT); 39a3f6fae6SAndreas Gohr $this->setupAction($ACT); 40a3f6fae6SAndreas Gohr $ACT = $this->action->getActionName(); 4164ab5140SAndreas Gohr } 4264ab5140SAndreas Gohr 4364ab5140SAndreas Gohr /** 4464ab5140SAndreas Gohr * Get the singleton instance 4564ab5140SAndreas Gohr * 4664ab5140SAndreas Gohr * @param bool $reinit 4764ab5140SAndreas Gohr * @return ActionRouter 4864ab5140SAndreas Gohr */ 49*ae7bcdc7SAndreas Gohr public static function getInstance($reinit = false) { 50*ae7bcdc7SAndreas Gohr if((self::$instance === null) || $reinit) { 51*ae7bcdc7SAndreas Gohr self::$instance = new ActionRouter(); 5264ab5140SAndreas Gohr } 53*ae7bcdc7SAndreas Gohr return self::$instance; 5464ab5140SAndreas Gohr } 5564ab5140SAndreas Gohr 5664ab5140SAndreas Gohr /** 5764ab5140SAndreas Gohr * Setup the given action 5864ab5140SAndreas Gohr * 5964ab5140SAndreas Gohr * Instantiates the right class, runs permission checks and pre-processing and 60a3f6fae6SAndreas Gohr * sets $action 6164ab5140SAndreas Gohr * 6264ab5140SAndreas Gohr * @param string $actionname 63a3f6fae6SAndreas Gohr * @triggers ACTION_ACT_PREPROCESS 6464ab5140SAndreas Gohr */ 6564ab5140SAndreas Gohr protected function setupAction($actionname) { 66a3f6fae6SAndreas Gohr $presetup = $actionname; 67a3f6fae6SAndreas Gohr 6864ab5140SAndreas Gohr try { 6964ab5140SAndreas Gohr $this->action = $this->loadAction($actionname); 7064ab5140SAndreas Gohr $this->action->checkPermissions(); 7164ab5140SAndreas Gohr $this->ensureMinimumPermission($this->action->minimumPermission()); 7264ab5140SAndreas Gohr $this->action->preProcess(); 7364ab5140SAndreas Gohr 7464ab5140SAndreas Gohr } catch(ActionException $e) { 7564ab5140SAndreas Gohr // we should have gotten a new action 76a3f6fae6SAndreas Gohr $actionname = $e->getNewAction(); 7764ab5140SAndreas Gohr 7864ab5140SAndreas Gohr // this one should trigger a user message 7964ab5140SAndreas Gohr if(is_a($e, ActionDisabledException::class)) { 80a3f6fae6SAndreas Gohr msg('Action disabled: ' . hsc($presetup), -1); 8164ab5140SAndreas Gohr } 8264ab5140SAndreas Gohr 8364ab5140SAndreas Gohr // do setup for new action 8450701b66SAndreas Gohr $this->transitionAction($presetup, $actionname); 8564ab5140SAndreas Gohr 8664ab5140SAndreas Gohr } catch(NoActionException $e) { 87a3f6fae6SAndreas Gohr // give plugins an opportunity to process the actionname 88a3f6fae6SAndreas Gohr $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname); 89a3f6fae6SAndreas Gohr if($evt->advise_before()) { 90a3f6fae6SAndreas Gohr if($actionname == $presetup) { 91a3f6fae6SAndreas Gohr // no plugin changed the action, complain and switch to show 92a3f6fae6SAndreas Gohr msg('Action unknown: ' . hsc($actionname), -1); 93a3f6fae6SAndreas Gohr $actionname = 'show'; 94a3f6fae6SAndreas Gohr } 9550701b66SAndreas Gohr $this->transitionAction($presetup, $actionname); 96a3f6fae6SAndreas Gohr } else { 97a3f6fae6SAndreas Gohr // event said the action should be kept, assume action plugin will handle it later 98a3f6fae6SAndreas Gohr $this->action = new Plugin(); 99a3f6fae6SAndreas Gohr $this->action->setActionName($actionname); 100a3f6fae6SAndreas Gohr } 101a3f6fae6SAndreas Gohr $evt->advise_after(); 10264ab5140SAndreas Gohr 10364ab5140SAndreas Gohr } catch(\Exception $e) { 10464ab5140SAndreas Gohr $this->handleFatalException($e); 10564ab5140SAndreas Gohr } 10664ab5140SAndreas Gohr } 10764ab5140SAndreas Gohr 10864ab5140SAndreas Gohr /** 10950701b66SAndreas Gohr * Transitions from one action to another 11050701b66SAndreas Gohr * 11150701b66SAndreas Gohr * Basically just calls setupAction() again but does some checks before. Also triggers 11250701b66SAndreas Gohr * redirects for POST to show transitions 11350701b66SAndreas Gohr * 11450701b66SAndreas Gohr * @param string $from current action name 11550701b66SAndreas Gohr * @param string $to new action name 11650701b66SAndreas Gohr * @param null|ActionException $e any previous exception that caused the transition 11750701b66SAndreas Gohr */ 11850701b66SAndreas Gohr protected function transitionAction($from, $to, $e = null) { 11950701b66SAndreas Gohr global $INPUT; 12050701b66SAndreas Gohr global $ID; 12150701b66SAndreas Gohr 12250701b66SAndreas Gohr $this->transitions++; 12350701b66SAndreas Gohr 12450701b66SAndreas Gohr // no infinite recursion 12550701b66SAndreas Gohr if($from == $to) { 12650701b66SAndreas Gohr $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); 12750701b66SAndreas Gohr } 12850701b66SAndreas Gohr 12950701b66SAndreas Gohr // larger loops will be caught here 13050701b66SAndreas Gohr if($this->transitions >= self::MAX_TRANSITIONS) { 13150701b66SAndreas Gohr $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e)); 13250701b66SAndreas Gohr } 13350701b66SAndreas Gohr 13450701b66SAndreas Gohr // POST transitions to show should be a redirect 13550701b66SAndreas Gohr if($to == 'show' && $from != $to && strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') { 13650701b66SAndreas Gohr act_redirect($ID, $from); // FIXME we may want to move this function to the class 13750701b66SAndreas Gohr } 13850701b66SAndreas Gohr 13950701b66SAndreas Gohr // do the recursion 14050701b66SAndreas Gohr $this->setupAction($to); 14150701b66SAndreas Gohr } 14250701b66SAndreas Gohr 14350701b66SAndreas Gohr /** 14464ab5140SAndreas Gohr * Check that the given minimum permissions are reached 14564ab5140SAndreas Gohr * 14664ab5140SAndreas Gohr * @param int $permneed 14764ab5140SAndreas Gohr * @throws ActionException 14864ab5140SAndreas Gohr */ 14964ab5140SAndreas Gohr protected function ensureMinimumPermission($permneed) { 15064ab5140SAndreas Gohr global $INFO; 15164ab5140SAndreas Gohr if($INFO['perm'] < $permneed) { 15264ab5140SAndreas Gohr throw new ActionException('denied'); 15364ab5140SAndreas Gohr } 15464ab5140SAndreas Gohr } 15564ab5140SAndreas Gohr 15664ab5140SAndreas Gohr /** 15764ab5140SAndreas Gohr * Aborts all processing with a message 15864ab5140SAndreas Gohr * 15964ab5140SAndreas Gohr * When a FataException instanc is passed, the code is treated as Status code 16064ab5140SAndreas Gohr * 16164ab5140SAndreas Gohr * @param \Exception|FatalException $e 16264ab5140SAndreas Gohr */ 16364ab5140SAndreas Gohr protected function handleFatalException(\Exception $e) { 16464ab5140SAndreas Gohr if(is_a($e, FatalException::class)) { 16564ab5140SAndreas Gohr http_status($e->getCode()); 16664ab5140SAndreas Gohr } else { 16764ab5140SAndreas Gohr http_status(500); 16864ab5140SAndreas Gohr } 16964ab5140SAndreas Gohr $msg = 'Something unforseen has happened: ' . $e->getMessage(); 17064ab5140SAndreas Gohr nice_die(hsc($msg)); 17164ab5140SAndreas Gohr } 17264ab5140SAndreas Gohr 17364ab5140SAndreas Gohr /** 17464ab5140SAndreas Gohr * Load the given action 17564ab5140SAndreas Gohr * 17664ab5140SAndreas Gohr * @param $actionname 17764ab5140SAndreas Gohr * @return AbstractAction 17864ab5140SAndreas Gohr * @throws NoActionException 17964ab5140SAndreas Gohr */ 18064ab5140SAndreas Gohr protected function loadAction($actionname) { 18164ab5140SAndreas Gohr $class = 'dokuwiki\\Action\\' . ucfirst(strtolower($actionname)); 18264ab5140SAndreas Gohr if(class_exists($class)) { 18364ab5140SAndreas Gohr return new $class; 18464ab5140SAndreas Gohr } 18564ab5140SAndreas Gohr throw new NoActionException(); 18664ab5140SAndreas Gohr } 18764ab5140SAndreas Gohr 18864ab5140SAndreas Gohr /** 18964ab5140SAndreas Gohr * Returns the action handling the current request 19064ab5140SAndreas Gohr * 19164ab5140SAndreas Gohr * @return AbstractAction 19264ab5140SAndreas Gohr */ 19364ab5140SAndreas Gohr public function getAction() { 19464ab5140SAndreas Gohr return $this->action; 19564ab5140SAndreas Gohr } 19664ab5140SAndreas Gohr} 197