disabled = explode(',', $conf['disableactions']); $this->disabled = array_map('trim', $this->disabled); $this->transitions = 0; $ACT = act_clean($ACT); $this->setupAction($ACT); $ACT = $this->action->getActionName(); } /** * Get the singleton instance * * @param bool $reinit * @return ActionRouter */ public static function getInstance($reinit = false) { if((self::$instance === null) || $reinit) { self::$instance = new ActionRouter(); } return self::$instance; } /** * Setup the given action * * Instantiates the right class, runs permission checks and pre-processing and * sets $action * * @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility * @triggers ACTION_ACT_PREPROCESS */ protected function setupAction(&$actionname) { $presetup = $actionname; try { // give plugins an opportunity to process the actionname $evt = new Extension\Event('ACTION_ACT_PREPROCESS', $actionname); if ($evt->advise_before()) { $this->action = $this->loadAction($actionname); $this->checkAction($this->action); $this->action->preProcess(); } else { // event said the action should be kept, assume action plugin will handle it later $this->action = new Plugin($actionname); } $evt->advise_after(); } catch(ActionException $e) { // we should have gotten a new action $actionname = $e->getNewAction(); // this one should trigger a user message if(is_a($e, ActionDisabledException::class)) { msg('Action disabled: ' . hsc($presetup), -1); } // some actions may request the display of a message if($e->displayToUser()) { msg(hsc($e->getMessage()), -1); } // do setup for new action $this->transitionAction($presetup, $actionname); } catch(NoActionException $e) { msg('Action unknown: ' . hsc($actionname), -1); $actionname = 'show'; $this->transitionAction($presetup, $actionname); } catch(\Exception $e) { $this->handleFatalException($e); } } /** * Transitions from one action to another * * Basically just calls setupAction() again but does some checks before. * * @param string $from current action name * @param string $to new action name * @param null|ActionException $e any previous exception that caused the transition */ protected function transitionAction($from, $to, $e = null) { $this->transitions++; // no infinite recursion if($from == $to) { $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); } // larger loops will be caught here if($this->transitions >= self::MAX_TRANSITIONS) { $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e)); } // do the recursion $this->setupAction($to); } /** * Aborts all processing with a message * * When a FataException instanc is passed, the code is treated as Status code * * @param \Exception|FatalException $e * @throws FatalException during unit testing */ protected function handleFatalException(\Exception $e) { if(is_a($e, FatalException::class)) { http_status($e->getCode()); } else { http_status(500); } if(defined('DOKU_UNITTEST')) { throw $e; } ErrorHandler::logException($e); $msg = 'Something unforeseen has happened: ' . $e->getMessage(); nice_die(hsc($msg)); } /** * Load the given action * * This translates the given name to a class name by uppercasing the first letter. * Underscores translate to camelcase names. For actions with underscores, the different * parts are removed beginning from the end until a matching class is found. The instatiated * Action will always have the full original action set as Name * * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export' * * @param $actionname * @return AbstractAction * @throws NoActionException */ public function loadAction($actionname) { $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else? $parts = explode('_', $actionname); while(!empty($parts)) { $load = join('_', $parts); $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_')); if(class_exists($class)) { return new $class($actionname); } array_pop($parts); } throw new NoActionException(); } /** * Execute all the checks to see if this action can be executed * * @param AbstractAction $action * @throws ActionDisabledException * @throws ActionException */ public function checkAction(AbstractAction $action) { global $INFO; global $ID; if(in_array($action->getActionName(), $this->disabled)) { throw new ActionDisabledException(); } $action->checkPreconditions(); if(isset($INFO)) { $perm = $INFO['perm']; } else { $perm = auth_quickaclcheck($ID); } if($perm < $action->minimumPermission()) { throw new ActionException('denied'); } } /** * Returns the action handling the current request * * @return AbstractAction */ public function getAction() { return $this->action; } }