Merge pull request #4111 from dokuwiki-translate/lang_update_739_1700675130
[dokuwiki.git] / inc / ActionRouter.php
blobdaed634d0756f27f52957662e51ee7fc0166d205
1 <?php
3 namespace dokuwiki;
5 use dokuwiki\Extension\Event;
6 use dokuwiki\Action\AbstractAction;
7 use dokuwiki\Action\Exception\ActionDisabledException;
8 use dokuwiki\Action\Exception\ActionException;
9 use dokuwiki\Action\Exception\FatalException;
10 use dokuwiki\Action\Exception\NoActionException;
11 use dokuwiki\Action\Plugin;
13 /**
14 * Class ActionRouter
15 * @package dokuwiki
17 class ActionRouter
19 /** @var AbstractAction */
20 protected $action;
22 /** @var ActionRouter */
23 protected static $instance;
25 /** @var int transition counter */
26 protected $transitions = 0;
28 /** maximum loop */
29 protected const MAX_TRANSITIONS = 5;
31 /** @var string[] the actions disabled in the configuration */
32 protected $disabled;
34 /**
35 * ActionRouter constructor. Singleton, thus protected!
37 * Sets up the correct action based on the $ACT global. Writes back
38 * the selected action to $ACT
40 protected function __construct()
42 global $ACT;
43 global $conf;
45 $this->disabled = explode(',', $conf['disableactions']);
46 $this->disabled = array_map('trim', $this->disabled);
48 $ACT = act_clean($ACT);
49 $this->setupAction($ACT);
50 $ACT = $this->action->getActionName();
53 /**
54 * Get the singleton instance
56 * @param bool $reinit
57 * @return ActionRouter
59 public static function getInstance($reinit = false)
61 if ((!self::$instance instanceof \dokuwiki\ActionRouter) || $reinit) {
62 self::$instance = new ActionRouter();
64 return self::$instance;
67 /**
68 * Setup the given action
70 * Instantiates the right class, runs permission checks and pre-processing and
71 * sets $action
73 * @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility
74 * @triggers ACTION_ACT_PREPROCESS
76 protected function setupAction(&$actionname)
78 $presetup = $actionname;
80 try {
81 // give plugins an opportunity to process the actionname
82 $evt = new Event('ACTION_ACT_PREPROCESS', $actionname);
83 if ($evt->advise_before()) {
84 $this->action = $this->loadAction($actionname);
85 $this->checkAction($this->action);
86 $this->action->preProcess();
87 } else {
88 // event said the action should be kept, assume action plugin will handle it later
89 $this->action = new Plugin($actionname);
91 $evt->advise_after();
92 } catch (ActionException $e) {
93 // we should have gotten a new action
94 $actionname = $e->getNewAction();
96 // this one should trigger a user message
97 if ($e instanceof ActionDisabledException) {
98 msg('Action disabled: ' . hsc($presetup), -1);
101 // some actions may request the display of a message
102 if ($e->displayToUser()) {
103 msg(hsc($e->getMessage()), -1);
106 // do setup for new action
107 $this->transitionAction($presetup, $actionname);
108 } catch (NoActionException $e) {
109 msg('Action unknown: ' . hsc($actionname), -1);
110 $actionname = 'show';
111 $this->transitionAction($presetup, $actionname);
112 } catch (\Exception $e) {
113 $this->handleFatalException($e);
118 * Transitions from one action to another
120 * Basically just calls setupAction() again but does some checks before.
122 * @param string $from current action name
123 * @param string $to new action name
124 * @param null|ActionException $e any previous exception that caused the transition
126 protected function transitionAction($from, $to, $e = null)
128 $this->transitions++;
130 // no infinite recursion
131 if ($from == $to) {
132 $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
135 // larger loops will be caught here
136 if ($this->transitions >= self::MAX_TRANSITIONS) {
137 $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e));
140 // do the recursion
141 $this->setupAction($to);
145 * Aborts all processing with a message
147 * When a FataException instanc is passed, the code is treated as Status code
149 * @param \Exception|FatalException $e
150 * @throws FatalException during unit testing
152 protected function handleFatalException(\Throwable $e)
154 if ($e instanceof FatalException) {
155 http_status($e->getCode());
156 } else {
157 http_status(500);
159 if (defined('DOKU_UNITTEST')) {
160 throw $e;
162 ErrorHandler::logException($e);
163 $msg = 'Something unforeseen has happened: ' . $e->getMessage();
164 nice_die(hsc($msg));
168 * Load the given action
170 * This translates the given name to a class name by uppercasing the first letter.
171 * Underscores translate to camelcase names. For actions with underscores, the different
172 * parts are removed beginning from the end until a matching class is found. The instatiated
173 * Action will always have the full original action set as Name
175 * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export'
177 * @param $actionname
178 * @return AbstractAction
179 * @throws NoActionException
181 public function loadAction($actionname)
183 $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else?
184 $parts = explode('_', $actionname);
185 while ($parts !== []) {
186 $load = implode('_', $parts);
187 $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_'));
188 if (class_exists($class)) {
189 return new $class($actionname);
191 array_pop($parts);
194 throw new NoActionException();
198 * Execute all the checks to see if this action can be executed
200 * @param AbstractAction $action
201 * @throws ActionDisabledException
202 * @throws ActionException
204 public function checkAction(AbstractAction $action)
206 global $INFO;
207 global $ID;
209 if (in_array($action->getActionName(), $this->disabled)) {
210 throw new ActionDisabledException();
213 $action->checkPreconditions();
215 if (isset($INFO)) {
216 $perm = $INFO['perm'];
217 } else {
218 $perm = auth_quickaclcheck($ID);
221 if ($perm < $action->minimumPermission()) {
222 throw new ActionException('denied');
227 * Returns the action handling the current request
229 * @return AbstractAction
231 public function getAction()
233 return $this->action;