[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 namespace dokuwiki; 4 5 use dokuwiki\Action\AbstractAction; 6 use dokuwiki\Action\Exception\ActionDisabledException; 7 use dokuwiki\Action\Exception\ActionException; 8 use dokuwiki\Action\Exception\FatalException; 9 use dokuwiki\Action\Exception\NoActionException; 10 use dokuwiki\Action\Plugin; 11 12 /** 13 * Class ActionRouter 14 * @package dokuwiki 15 */ 16 class ActionRouter { 17 18 /** @var AbstractAction */ 19 protected $action; 20 21 /** @var ActionRouter */ 22 protected static $instance = null; 23 24 /** @var int transition counter */ 25 protected $transitions = 0; 26 27 /** maximum loop */ 28 const MAX_TRANSITIONS = 5; 29 30 /** @var string[] the actions disabled in the configuration */ 31 protected $disabled; 32 33 /** 34 * ActionRouter constructor. Singleton, thus protected! 35 * 36 * Sets up the correct action based on the $ACT global. Writes back 37 * the selected action to $ACT 38 */ 39 protected function __construct() { 40 global $ACT; 41 global $conf; 42 43 $this->disabled = explode(',', $conf['disableactions']); 44 $this->disabled = array_map('trim', $this->disabled); 45 $this->transitions = 0; 46 47 $ACT = act_clean($ACT); 48 $this->setupAction($ACT); 49 $ACT = $this->action->getActionName(); 50 } 51 52 /** 53 * Get the singleton instance 54 * 55 * @param bool $reinit 56 * @return ActionRouter 57 */ 58 public static function getInstance($reinit = false) { 59 if((self::$instance === null) || $reinit) { 60 self::$instance = new ActionRouter(); 61 } 62 return self::$instance; 63 } 64 65 /** 66 * Setup the given action 67 * 68 * Instantiates the right class, runs permission checks and pre-processing and 69 * sets $action 70 * 71 * @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility 72 * @triggers ACTION_ACT_PREPROCESS 73 */ 74 protected function setupAction(&$actionname) { 75 $presetup = $actionname; 76 77 try { 78 // give plugins an opportunity to process the actionname 79 $evt = new Extension\Event('ACTION_ACT_PREPROCESS', $actionname); 80 if ($evt->advise_before()) { 81 $this->action = $this->loadAction($actionname); 82 $this->checkAction($this->action); 83 $this->action->preProcess(); 84 } else { 85 // event said the action should be kept, assume action plugin will handle it later 86 $this->action = new Plugin($actionname); 87 } 88 $evt->advise_after(); 89 90 } catch(ActionException $e) { 91 // we should have gotten a new action 92 $actionname = $e->getNewAction(); 93 94 // this one should trigger a user message 95 if(is_a($e, ActionDisabledException::class)) { 96 msg('Action disabled: ' . hsc($presetup), -1); 97 } 98 99 // some actions may request the display of a message 100 if($e->displayToUser()) { 101 msg(hsc($e->getMessage()), -1); 102 } 103 104 // do setup for new action 105 $this->transitionAction($presetup, $actionname); 106 107 } catch(NoActionException $e) { 108 msg('Action unknown: ' . hsc($actionname), -1); 109 $actionname = 'show'; 110 $this->transitionAction($presetup, $actionname); 111 } catch(\Exception $e) { 112 $this->handleFatalException($e); 113 } 114 } 115 116 /** 117 * Transitions from one action to another 118 * 119 * Basically just calls setupAction() again but does some checks before. 120 * 121 * @param string $from current action name 122 * @param string $to new action name 123 * @param null|ActionException $e any previous exception that caused the transition 124 */ 125 protected function transitionAction($from, $to, $e = null) { 126 $this->transitions++; 127 128 // no infinite recursion 129 if($from == $to) { 130 $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); 131 } 132 133 // larger loops will be caught here 134 if($this->transitions >= self::MAX_TRANSITIONS) { 135 $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e)); 136 } 137 138 // do the recursion 139 $this->setupAction($to); 140 } 141 142 /** 143 * Aborts all processing with a message 144 * 145 * When a FataException instanc is passed, the code is treated as Status code 146 * 147 * @param \Exception|FatalException $e 148 * @throws FatalException during unit testing 149 */ 150 protected function handleFatalException(\Exception $e) { 151 if(is_a($e, FatalException::class)) { 152 http_status($e->getCode()); 153 } else { 154 http_status(500); 155 } 156 if(defined('DOKU_UNITTEST')) { 157 throw $e; 158 } 159 ErrorHandler::logException($e); 160 $msg = 'Something unforeseen has happened: ' . $e->getMessage(); 161 nice_die(hsc($msg)); 162 } 163 164 /** 165 * Load the given action 166 * 167 * This translates the given name to a class name by uppercasing the first letter. 168 * Underscores translate to camelcase names. For actions with underscores, the different 169 * parts are removed beginning from the end until a matching class is found. The instatiated 170 * Action will always have the full original action set as Name 171 * 172 * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export' 173 * 174 * @param $actionname 175 * @return AbstractAction 176 * @throws NoActionException 177 */ 178 public function loadAction($actionname) { 179 $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else? 180 $parts = explode('_', $actionname); 181 while(!empty($parts)) { 182 $load = join('_', $parts); 183 $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_')); 184 if(class_exists($class)) { 185 return new $class($actionname); 186 } 187 array_pop($parts); 188 } 189 190 throw new NoActionException(); 191 } 192 193 /** 194 * Execute all the checks to see if this action can be executed 195 * 196 * @param AbstractAction $action 197 * @throws ActionDisabledException 198 * @throws ActionException 199 */ 200 public function checkAction(AbstractAction $action) { 201 global $INFO; 202 global $ID; 203 204 if(in_array($action->getActionName(), $this->disabled)) { 205 throw new ActionDisabledException(); 206 } 207 208 $action->checkPreconditions(); 209 210 if(isset($INFO)) { 211 $perm = $INFO['perm']; 212 } else { 213 $perm = auth_quickaclcheck($ID); 214 } 215 216 if($perm < $action->minimumPermission()) { 217 throw new ActionException('denied'); 218 } 219 } 220 221 /** 222 * Returns the action handling the current request 223 * 224 * @return AbstractAction 225 */ 226 public function getAction() { 227 return $this->action; 228 } 229 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body