1 | <?php |
||
16 | class 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 | /** @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() { |
||
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 |
||
72 | * @triggers ACTION_ACT_PREPROCESS |
||
73 | */ |
||
74 | protected function setupAction($actionname) { |
||
75 | $presetup = $actionname; |
||
76 | |||
77 | try { |
||
78 | $this->action = $this->loadAction($actionname); |
||
79 | $this->checkAction($this->action); |
||
80 | $this->action->preProcess(); |
||
81 | |||
82 | } catch(ActionException $e) { |
||
83 | // we should have gotten a new action |
||
84 | $actionname = $e->getNewAction(); |
||
85 | |||
86 | // this one should trigger a user message |
||
87 | if(is_a($e, ActionDisabledException::class)) { |
||
88 | msg('Action disabled: ' . hsc($presetup), -1); |
||
89 | } |
||
90 | |||
91 | // some actions may request the display of a message |
||
92 | if($e->displayToUser()) { |
||
93 | msg(hsc($e->getMessage()), -1); |
||
94 | } |
||
95 | |||
96 | // do setup for new action |
||
97 | $this->transitionAction($presetup, $actionname); |
||
98 | |||
99 | } catch(NoActionException $e) { |
||
100 | // give plugins an opportunity to process the actionname |
||
101 | $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname); |
||
102 | if($evt->advise_before()) { |
||
103 | if($actionname == $presetup) { |
||
104 | // no plugin changed the action, complain and switch to show |
||
105 | msg('Action unknown: ' . hsc($actionname), -1); |
||
106 | $actionname = 'show'; |
||
107 | } |
||
108 | $this->transitionAction($presetup, $actionname); |
||
109 | } else { |
||
110 | // event said the action should be kept, assume action plugin will handle it later |
||
111 | $this->action = new Plugin($actionname); |
||
112 | } |
||
113 | $evt->advise_after(); |
||
114 | |||
115 | } catch(\Exception $e) { |
||
116 | $this->handleFatalException($e); |
||
117 | } |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Transitions from one action to another |
||
122 | * |
||
123 | * Basically just calls setupAction() again but does some checks before. |
||
124 | * |
||
125 | * @param string $from current action name |
||
126 | * @param string $to new action name |
||
127 | * @param null|ActionException $e any previous exception that caused the transition |
||
128 | */ |
||
129 | protected function transitionAction($from, $to, $e = null) { |
||
145 | |||
146 | /** |
||
147 | * Aborts all processing with a message |
||
148 | * |
||
149 | * When a FataException instanc is passed, the code is treated as Status code |
||
150 | * |
||
151 | * @param \Exception|FatalException $e |
||
152 | */ |
||
153 | protected function handleFatalException(\Exception $e) { |
||
162 | |||
163 | /** |
||
164 | * Load the given action |
||
165 | * |
||
166 | * This translates the given name to a class name by uppercasing the first letter. |
||
167 | * Underscores translate to camelcase names. For actions with underscores, the different |
||
168 | * parts are removed beginning from the end until a matching class is found. The instatiated |
||
169 | * Action will always have the full original action set as Name |
||
170 | * |
||
171 | * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export' |
||
172 | * |
||
173 | * @param $actionname |
||
174 | * @return AbstractAction |
||
175 | * @throws NoActionException |
||
176 | */ |
||
177 | public function loadAction($actionname) { |
||
178 | $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else? |
||
179 | $parts = explode('_', $actionname); |
||
180 | while(!empty($parts)) { |
||
181 | $load = join('_', $parts); |
||
182 | $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_')); |
||
183 | if(class_exists($class)) { |
||
184 | return new $class($actionname); |
||
185 | } |
||
186 | array_pop($parts); |
||
187 | } |
||
188 | |||
189 | throw new NoActionException(); |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Execute all the checks to see if this action can be executed |
||
194 | * |
||
195 | * @param AbstractAction $action |
||
196 | * @throws ActionDisabledException |
||
197 | * @throws ActionException |
||
198 | */ |
||
199 | public function checkAction(AbstractAction $action) { |
||
200 | global $INFO; |
||
201 | global $ID; |
||
202 | |||
203 | if(in_array($action->getActionName(), $this->disabled)) { |
||
204 | throw new ActionDisabledException(); |
||
205 | } |
||
206 | |||
207 | $action->checkPermissions(); |
||
208 | |||
209 | if(isset($INFO)) { |
||
210 | $perm = $INFO['perm']; |
||
211 | } else { |
||
212 | $perm = auth_quickaclcheck($ID); |
||
213 | } |
||
214 | |||
215 | if($perm < $action->minimumPermission()) { |
||
216 | throw new ActionException('denied'); |
||
217 | } |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Returns the action handling the current request |
||
222 | * |
||
223 | * @return AbstractAction |
||
224 | */ |
||
225 | public function getAction() { |
||
228 | } |
||
229 |