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 static $instance; 23 24 /** @var int transition counter */ 25 protected $transitions = 0; 26 27 /** maximum loop */ 28 const MAX_TRANSITIONS = 5; 29 30 /** 31 * ActionRouter constructor. Singleton, thus protected! 32 * 33 * Sets up the correct action based on the $ACT global. Writes back 34 * the selected action to $ACT 35 */ 36 protected function __construct() { 37 global $ACT; 38 $ACT = act_clean($ACT); 39 $this->setupAction($ACT); 40 $ACT = $this->action->getActionName(); 41 } 42 43 /** 44 * Get the singleton instance 45 * 46 * @param bool $reinit 47 * @return ActionRouter 48 */ 49 public static function getInstance($reinit = false) { 50 if((self::$instance === null) || $reinit) { 51 self::$instance = new ActionRouter(); 52 } 53 return self::$instance; 54 } 55 56 /** 57 * Setup the given action 58 * 59 * Instantiates the right class, runs permission checks and pre-processing and 60 * sets $action 61 * 62 * @param string $actionname 63 * @triggers ACTION_ACT_PREPROCESS 64 */ 65 protected function setupAction($actionname) { 66 $presetup = $actionname; 67 68 try { 69 $this->action = $this->loadAction($actionname); 70 $this->action->checkPermissions(); 71 $this->ensureMinimumPermission($this->action->minimumPermission()); 72 $this->action->preProcess(); 73 74 } catch(ActionException $e) { 75 // we should have gotten a new action 76 $actionname = $e->getNewAction(); 77 78 // this one should trigger a user message 79 if(is_a($e, ActionDisabledException::class)) { 80 msg('Action disabled: ' . hsc($presetup), -1); 81 } 82 83 // do setup for new action 84 $this->transitionAction($presetup, $actionname); 85 86 } catch(NoActionException $e) { 87 // give plugins an opportunity to process the actionname 88 $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname); 89 if($evt->advise_before()) { 90 if($actionname == $presetup) { 91 // no plugin changed the action, complain and switch to show 92 msg('Action unknown: ' . hsc($actionname), -1); 93 $actionname = 'show'; 94 } 95 $this->transitionAction($presetup, $actionname); 96 } else { 97 // event said the action should be kept, assume action plugin will handle it later 98 $this->action = new Plugin(); 99 $this->action->setActionName($actionname); 100 } 101 $evt->advise_after(); 102 103 } catch(\Exception $e) { 104 $this->handleFatalException($e); 105 } 106 } 107 108 /** 109 * Transitions from one action to another 110 * 111 * Basically just calls setupAction() again but does some checks before. Also triggers 112 * redirects for POST to show transitions 113 * 114 * @param string $from current action name 115 * @param string $to new action name 116 * @param null|ActionException $e any previous exception that caused the transition 117 */ 118 protected function transitionAction($from, $to, $e = null) { 119 global $INPUT; 120 global $ID; 121 122 $this->transitions++; 123 124 // no infinite recursion 125 if($from == $to) { 126 $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); 127 } 128 129 // larger loops will be caught here 130 if($this->transitions >= self::MAX_TRANSITIONS) { 131 $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e)); 132 } 133 134 // POST transitions to show should be a redirect 135 if($to == 'show' && $from != $to && strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') { 136 act_redirect($ID, $from); // FIXME we may want to move this function to the class 137 } 138 139 // do the recursion 140 $this->setupAction($to); 141 } 142 143 /** 144 * Check that the given minimum permissions are reached 145 * 146 * @param int $permneed 147 * @throws ActionException 148 */ 149 protected function ensureMinimumPermission($permneed) { 150 global $INFO; 151 if($INFO['perm'] < $permneed) { 152 throw new ActionException('denied'); 153 } 154 } 155 156 /** 157 * Aborts all processing with a message 158 * 159 * When a FataException instanc is passed, the code is treated as Status code 160 * 161 * @param \Exception|FatalException $e 162 */ 163 protected function handleFatalException(\Exception $e) { 164 if(is_a($e, FatalException::class)) { 165 http_status($e->getCode()); 166 } else { 167 http_status(500); 168 } 169 $msg = 'Something unforseen has happened: ' . $e->getMessage(); 170 nice_die(hsc($msg)); 171 } 172 173 /** 174 * Load the given action 175 * 176 * @param $actionname 177 * @return AbstractAction 178 * @throws NoActionException 179 */ 180 protected function loadAction($actionname) { 181 $class = 'dokuwiki\\Action\\' . ucfirst(strtolower($actionname)); 182 if(class_exists($class)) { 183 return new $class; 184 } 185 throw new NoActionException(); 186 } 187 188 /** 189 * Returns the action handling the current request 190 * 191 * @return AbstractAction 192 */ 193 public function getAction() { 194 return $this->action; 195 } 196} 197