123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- <?php
- namespace dokuwiki;
- use dokuwiki\Action\AbstractAction;
- use dokuwiki\Action\Exception\ActionDisabledException;
- use dokuwiki\Action\Exception\ActionException;
- use dokuwiki\Action\Exception\FatalException;
- use dokuwiki\Action\Exception\NoActionException;
- use dokuwiki\Action\Plugin;
- /**
- * Class ActionRouter
- * @package dokuwiki
- */
- class ActionRouter {
- /** @var AbstractAction */
- protected $action;
- /** @var ActionRouter */
- protected static $instance = null;
- /** @var int transition counter */
- protected $transitions = 0;
- /** maximum loop */
- const MAX_TRANSITIONS = 5;
- /** @var string[] the actions disabled in the configuration */
- protected $disabled;
- /**
- * ActionRouter constructor. Singleton, thus protected!
- *
- * Sets up the correct action based on the $ACT global. Writes back
- * the selected action to $ACT
- */
- protected function __construct() {
- global $ACT;
- global $conf;
- $this->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;
- }
- }
|