summaryrefslogtreecommitdiff
path: root/platform/www/inc/ActionRouter.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/inc/ActionRouter.php')
-rw-r--r--platform/www/inc/ActionRouter.php228
1 files changed, 228 insertions, 0 deletions
diff --git a/platform/www/inc/ActionRouter.php b/platform/www/inc/ActionRouter.php
new file mode 100644
index 0000000..7d8a72a
--- /dev/null
+++ b/platform/www/inc/ActionRouter.php
@@ -0,0 +1,228 @@
+<?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;
+ }
+ $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;
+ }
+}