1<?php 2 3namespace dokuwiki; 4 5use dokuwiki\Action\AbstractAction; 6use dokuwiki\Action\Exception\ActionDisabledException; 7use dokuwiki\Action\Exception\ActionException; 8use dokuwiki\Action\Exception\FatalException; 9use dokuwiki\Action\Exception\NoActionException; 10use dokuwiki\Action\Plugin; 11 12/** 13 * Class ActionRouter 14 * @package dokuwiki 15 */ 16class ActionRouter { 17 18 /** @var AbstractAction */ 19 protected $action; 20 21 /** @var ActionRouter */ 22 protected $instance; 23 24 /** 25 * ActionRouter constructor. Singleton, thus protected! 26 * 27 * Sets up the correct action based on the $ACT global. Writes back 28 * the selected action to $ACT 29 */ 30 protected function __construct() { 31 global $ACT; 32 $ACT = act_clean($ACT); 33 $this->setupAction($ACT); 34 $ACT = $this->action->getActionName(); 35 } 36 37 /** 38 * Get the singleton instance 39 * 40 * @param bool $reinit 41 * @return ActionRouter 42 */ 43 public function getInstance($reinit = false) { 44 if(($this->instance === null) || $reinit) { 45 $this->instance = new ActionRouter(); 46 } 47 return $this->instance; 48 } 49 50 /** 51 * Setup the given action 52 * 53 * Instantiates the right class, runs permission checks and pre-processing and 54 * sets $action 55 * 56 * @param string $actionname 57 * @triggers ACTION_ACT_PREPROCESS 58 * @fixme implement redirect on action change with post 59 */ 60 protected function setupAction($actionname) { 61 $presetup = $actionname; 62 63 try { 64 $this->action = $this->loadAction($actionname); 65 $this->action->checkPermissions(); 66 $this->ensureMinimumPermission($this->action->minimumPermission()); 67 $this->action->preProcess(); 68 69 } catch(ActionException $e) { 70 // we should have gotten a new action 71 $actionname = $e->getNewAction(); 72 73 // no infinite recursion 74 if($actionname == $presetup) { 75 // FIXME this doesn't catch larger circles 76 $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); 77 } 78 79 // this one should trigger a user message 80 if(is_a($e, ActionDisabledException::class)) { 81 msg('Action disabled: ' . hsc($presetup), -1); 82 } 83 84 // do setup for new action 85 $this->setupAction($actionname); 86 87 } catch(NoActionException $e) { 88 // give plugins an opportunity to process the actionname 89 $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname); 90 if($evt->advise_before()) { 91 if($actionname == $presetup) { 92 // no plugin changed the action, complain and switch to show 93 msg('Action unknown: ' . hsc($actionname), -1); 94 $actionname = 'show'; 95 } 96 $this->setupAction($actionname); 97 } else { 98 // event said the action should be kept, assume action plugin will handle it later 99 $this->action = new Plugin(); 100 $this->action->setActionName($actionname); 101 } 102 $evt->advise_after(); 103 104 } catch(\Exception $e) { 105 $this->handleFatalException($e); 106 } 107 } 108 109 /** 110 * Check that the given minimum permissions are reached 111 * 112 * @param int $permneed 113 * @throws ActionException 114 */ 115 protected function ensureMinimumPermission($permneed) { 116 global $INFO; 117 if($INFO['perm'] < $permneed) { 118 throw new ActionException('denied'); 119 } 120 } 121 122 /** 123 * Aborts all processing with a message 124 * 125 * When a FataException instanc is passed, the code is treated as Status code 126 * 127 * @param \Exception|FatalException $e 128 */ 129 protected function handleFatalException(\Exception $e) { 130 if(is_a($e, FatalException::class)) { 131 http_status($e->getCode()); 132 } else { 133 http_status(500); 134 } 135 $msg = 'Something unforseen has happened: ' . $e->getMessage(); 136 nice_die(hsc($msg)); 137 } 138 139 /** 140 * Load the given action 141 * 142 * @param $actionname 143 * @return AbstractAction 144 * @throws NoActionException 145 */ 146 protected function loadAction($actionname) { 147 $class = 'dokuwiki\\Action\\' . ucfirst(strtolower($actionname)); 148 if(class_exists($class)) { 149 return new $class; 150 } 151 throw new NoActionException(); 152 } 153 154 /** 155 * Returns the action handling the current request 156 * 157 * @return AbstractAction 158 */ 159 public function getAction() { 160 return $this->action; 161 } 162} 163