about summary refs log tree commit diff stats
path: root/wiki/inc/Action
diff options
context:
space:
mode:
authorahriman <ahriman@falte.red>2018-12-03 19:22:25 -0500
committerahriman <ahriman@falte.red>2018-12-03 19:22:25 -0500
commit0ae8cbf5c0b1a198b963490985b7738392ebcb97 (patch)
treeb2c77ae72c6b717e2b97492065196ac5ffb2d9e2 /wiki/inc/Action
parentf57f6cc5a2d159f90168d292437dc4bd8cd7f934 (diff)
downloadsite-0ae8cbf5c0b1a198b963490985b7738392ebcb97.tar.gz
installed dokuwiki, added to navbar, updated news
Diffstat (limited to 'wiki/inc/Action')
-rw-r--r--wiki/inc/Action/AbstractAclAction.php25
-rw-r--r--wiki/inc/Action/AbstractAction.php88
-rw-r--r--wiki/inc/Action/AbstractAliasAction.php28
-rw-r--r--wiki/inc/Action/AbstractUserAction.php25
-rw-r--r--wiki/inc/Action/Admin.php56
-rw-r--r--wiki/inc/Action/Backlink.php24
-rw-r--r--wiki/inc/Action/Cancel.php25
-rw-r--r--wiki/inc/Action/Check.php26
-rw-r--r--wiki/inc/Action/Conflict.php34
-rw-r--r--wiki/inc/Action/Denied.php23
-rw-r--r--wiki/inc/Action/Diff.php35
-rw-r--r--wiki/inc/Action/Draft.php39
-rw-r--r--wiki/inc/Action/Draftdel.php36
-rw-r--r--wiki/inc/Action/Edit.php91
-rw-r--r--wiki/inc/Action/Exception/ActionAbort.php20
-rw-r--r--wiki/inc/Action/Exception/ActionAclRequiredException.php17
-rw-r--r--wiki/inc/Action/Exception/ActionDisabledException.php17
-rw-r--r--wiki/inc/Action/Exception/ActionException.php66
-rw-r--r--wiki/inc/Action/Exception/ActionUserRequiredException.php17
-rw-r--r--wiki/inc/Action/Exception/FatalException.php29
-rw-r--r--wiki/inc/Action/Exception/NoActionException.php15
-rw-r--r--wiki/inc/Action/Export.php112
-rw-r--r--wiki/inc/Action/Index.php25
-rw-r--r--wiki/inc/Action/Locked.php24
-rw-r--r--wiki/inc/Action/Login.php36
-rw-r--r--wiki/inc/Action/Logout.php50
-rw-r--r--wiki/inc/Action/Media.php24
-rw-r--r--wiki/inc/Action/Plugin.php32
-rw-r--r--wiki/inc/Action/Preview.php58
-rw-r--r--wiki/inc/Action/Profile.php45
-rw-r--r--wiki/inc/Action/ProfileDelete.php42
-rw-r--r--wiki/inc/Action/Recent.php40
-rw-r--r--wiki/inc/Action/Recover.php21
-rw-r--r--wiki/inc/Action/Redirect.php64
-rw-r--r--wiki/inc/Action/Register.php45
-rw-r--r--wiki/inc/Action/Resendpwd.php177
-rw-r--r--wiki/inc/Action/Revert.php65
-rw-r--r--wiki/inc/Action/Revisions.php24
-rw-r--r--wiki/inc/Action/Save.php60
-rw-r--r--wiki/inc/Action/Search.php135
-rw-r--r--wiki/inc/Action/Show.php36
-rw-r--r--wiki/inc/Action/Sitemap.php65
-rw-r--r--wiki/inc/Action/Source.php36
-rw-r--r--wiki/inc/Action/Subscribe.php166
44 files changed, 2118 insertions, 0 deletions
diff --git a/wiki/inc/Action/AbstractAclAction.php b/wiki/inc/Action/AbstractAclAction.php
new file mode 100644
index 0000000..871edb0
--- /dev/null
+++ b/wiki/inc/Action/AbstractAclAction.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAclRequiredException;
+
+/**
+ * Class AbstractAclAction
+ *
+ * An action that requires the ACL subsystem to be enabled (eg. useacl=1)
+ *
+ * @package dokuwiki\Action
+ */
+abstract class AbstractAclAction extends AbstractAction {
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+        global $conf;
+        global $auth;
+        if(!$conf['useacl']) throw new ActionAclRequiredException();
+        if(!$auth) throw new ActionAclRequiredException();
+    }
+
+}
diff --git a/wiki/inc/Action/AbstractAction.php b/wiki/inc/Action/AbstractAction.php
new file mode 100644
index 0000000..ea86238
--- /dev/null
+++ b/wiki/inc/Action/AbstractAction.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionDisabledException;
+use dokuwiki\Action\Exception\ActionException;
+use dokuwiki\Action\Exception\FatalException;
+
+/**
+ * Class AbstractAction
+ *
+ * Base class for all actions
+ *
+ * @package dokuwiki\Action
+ */
+abstract class AbstractAction {
+
+    /** @var string holds the name of the action (lowercase class name, no namespace) */
+    protected $actionname;
+
+    /**
+     * AbstractAction constructor.
+     *
+     * @param string $actionname the name of this action (see getActionName() for caveats)
+     */
+    public function __construct($actionname = '') {
+        if($actionname !== '') {
+            $this->actionname = $actionname;
+        } else {
+            // http://stackoverflow.com/a/27457689/172068
+            $this->actionname = strtolower(substr(strrchr(get_class($this), '\\'), 1));
+        }
+    }
+
+    /**
+     * Return the minimum permission needed
+     *
+     * This needs to return one of the AUTH_* constants. It will be checked against
+     * the current user and page after checkPermissions() ran through. If it fails,
+     * the user will be shown the Denied action.
+     *
+     * @return int
+     */
+    abstract public function minimumPermission();
+
+    /**
+     * Check conditions are met to run this action
+     *
+     * @throws ActionException
+     * @return void
+     */
+    public function checkPreconditions() {
+    }
+
+    /**
+     * Process data
+     *
+     * This runs before any output is sent to the browser.
+     *
+     * Throw an Exception if a different action should be run after this step.
+     *
+     * @throws ActionException
+     * @return void
+     */
+    public function preProcess() {
+    }
+
+    /**
+     * Output whatever content is wanted within tpl_content();
+     *
+     * @fixme we may want to return a Ui class here
+     */
+    public function tplContent() {
+        throw new FatalException('No content for Action ' . $this->actionname);
+    }
+
+    /**
+     * Returns the name of this action
+     *
+     * This is usually the lowercased class name, but may differ for some actions.
+     * eg. the export_ modes or for the Plugin action.
+     *
+     * @return string
+     */
+    public function getActionName() {
+        return $this->actionname;
+    }
+}
diff --git a/wiki/inc/Action/AbstractAliasAction.php b/wiki/inc/Action/AbstractAliasAction.php
new file mode 100644
index 0000000..7240f5e
--- /dev/null
+++ b/wiki/inc/Action/AbstractAliasAction.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\FatalException;
+
+/**
+ * Class AbstractAliasAction
+ *
+ * An action that is an alias for another action. Skips the minimumPermission check
+ *
+ * Be sure to implement preProcess() and throw an ActionAbort exception
+ * with the proper action.
+ *
+ * @package dokuwiki\Action
+ */
+abstract class AbstractAliasAction extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    public function preProcess() {
+        throw new FatalException('Alias Actions need to implement preProcess to load the aliased action');
+    }
+
+}
diff --git a/wiki/inc/Action/AbstractUserAction.php b/wiki/inc/Action/AbstractUserAction.php
new file mode 100644
index 0000000..b4e3f1a
--- /dev/null
+++ b/wiki/inc/Action/AbstractUserAction.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionUserRequiredException;
+
+/**
+ * Class AbstractUserAction
+ *
+ * An action that requires a logged in user
+ *
+ * @package dokuwiki\Action
+ */
+abstract class AbstractUserAction extends AbstractAclAction {
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+        global $INPUT;
+        if(!$INPUT->server->str('REMOTE_USER')) {
+            throw new ActionUserRequiredException();
+        }
+    }
+
+}
diff --git a/wiki/inc/Action/Admin.php b/wiki/inc/Action/Admin.php
new file mode 100644
index 0000000..8d43057
--- /dev/null
+++ b/wiki/inc/Action/Admin.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Admin
+ *
+ * Action to show the admin interface or admin plugins
+ *
+ * @package dokuwiki\Action
+ */
+class Admin extends AbstractUserAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+
+        if($INFO['ismanager']) {
+            return AUTH_READ; // let in check later
+        } else {
+            return AUTH_ADMIN;
+        }
+    }
+
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        global $INFO;
+        if(!$INFO['ismanager']) {
+            throw new ActionException('denied');
+        }
+    }
+
+    public function preProcess() {
+        global $INPUT;
+        global $INFO;
+
+        // retrieve admin plugin name from $_REQUEST['page']
+        if(($page = $INPUT->str('page', '', true)) != '') {
+            /** @var $plugin \DokuWiki_Admin_Plugin */
+            if($plugin = plugin_getRequestAdminPlugin()) { // FIXME this method does also permission checking
+                if($plugin->forAdminOnly() && !$INFO['isadmin']) {
+                    throw new ActionException('denied');
+                }
+                $plugin->handle();
+            }
+        }
+    }
+
+    public function tplContent() {
+        tpl_admin();
+    }
+
+}
diff --git a/wiki/inc/Action/Backlink.php b/wiki/inc/Action/Backlink.php
new file mode 100644
index 0000000..0337917
--- /dev/null
+++ b/wiki/inc/Action/Backlink.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Backlink
+ *
+ * Shows which pages link to the current page
+ *
+ * @package dokuwiki\Action
+ */
+class Backlink extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_backlinks();
+    }
+
+}
diff --git a/wiki/inc/Action/Cancel.php b/wiki/inc/Action/Cancel.php
new file mode 100644
index 0000000..d4d8277
--- /dev/null
+++ b/wiki/inc/Action/Cancel.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Cancel
+ *
+ * Alias for show. Aborts editing
+ *
+ * @package dokuwiki\Action
+ */
+class Cancel extends AbstractAliasAction {
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $ID;
+        unlock($ID);
+
+        // continue with draftdel -> redirect -> show
+        throw new ActionAbort('draftdel');
+    }
+
+}
diff --git a/wiki/inc/Action/Check.php b/wiki/inc/Action/Check.php
new file mode 100644
index 0000000..36ae8e8
--- /dev/null
+++ b/wiki/inc/Action/Check.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Check
+ *
+ * Adds some debugging info before aborting to show
+ *
+ * @package dokuwiki\Action
+ */
+class Check extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    public function preProcess() {
+        check();
+        throw new ActionAbort();
+    }
+
+}
diff --git a/wiki/inc/Action/Conflict.php b/wiki/inc/Action/Conflict.php
new file mode 100644
index 0000000..d880b5b
--- /dev/null
+++ b/wiki/inc/Action/Conflict.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Conflict
+ *
+ * Show the conflict resolution screen
+ *
+ * @package dokuwiki\Action
+ */
+class Conflict extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+        if($INFO['exists']) {
+            return AUTH_EDIT;
+        } else {
+            return AUTH_CREATE;
+        }
+    }
+
+    public function tplContent() {
+        global $PRE;
+        global $TEXT;
+        global $SUF;
+        global $SUM;
+
+        html_conflict(con($PRE, $TEXT, $SUF), $SUM);
+        html_diff(con($PRE, $TEXT, $SUF), false);
+    }
+
+}
diff --git a/wiki/inc/Action/Denied.php b/wiki/inc/Action/Denied.php
new file mode 100644
index 0000000..c8e0192
--- /dev/null
+++ b/wiki/inc/Action/Denied.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Denied
+ *
+ * Show the access denied screen
+ *
+ * @package dokuwiki\Action
+ */
+class Denied extends AbstractAclAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    public function tplContent() {
+        html_denied();
+    }
+
+}
diff --git a/wiki/inc/Action/Diff.php b/wiki/inc/Action/Diff.php
new file mode 100644
index 0000000..b14b1d0
--- /dev/null
+++ b/wiki/inc/Action/Diff.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Diff
+ *
+ * Show the differences between two revisions
+ *
+ * @package dokuwiki\Action
+ */
+class Diff extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $INPUT;
+
+        // store the selected diff type in cookie
+        $difftype = $INPUT->str('difftype');
+        if(!empty($difftype)) {
+            set_doku_pref('difftype', $difftype);
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_diff();
+    }
+
+}
diff --git a/wiki/inc/Action/Draft.php b/wiki/inc/Action/Draft.php
new file mode 100644
index 0000000..caf0870
--- /dev/null
+++ b/wiki/inc/Action/Draft.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Draft
+ *
+ * Screen to see and recover a draft
+ *
+ * @package dokuwiki\Action
+ * @fixme combine with Recover?
+ */
+class Draft extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+        if($INFO['exists']) {
+            return AUTH_EDIT;
+        } else {
+            return AUTH_CREATE;
+        }
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+        global $INFO;
+        if(!file_exists($INFO['draft'])) throw new ActionException('edit');
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_draft();
+    }
+
+}
diff --git a/wiki/inc/Action/Draftdel.php b/wiki/inc/Action/Draftdel.php
new file mode 100644
index 0000000..77378f7
--- /dev/null
+++ b/wiki/inc/Action/Draftdel.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Draftdel
+ *
+ * Delete a draft
+ *
+ * @package dokuwiki\Action
+ */
+class Draftdel extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_EDIT;
+    }
+
+    /**
+     * Delete an existing draft if any
+     *
+     * Reads draft information from $INFO. Redirects to show, afterwards.
+     *
+     * @throws ActionAbort
+     */
+    public function preProcess() {
+        global $INFO;
+        @unlink($INFO['draft']);
+        $INFO['draft'] = null;
+
+        throw new ActionAbort('redirect');
+    }
+
+}
diff --git a/wiki/inc/Action/Edit.php b/wiki/inc/Action/Edit.php
new file mode 100644
index 0000000..061c9e2
--- /dev/null
+++ b/wiki/inc/Action/Edit.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Edit
+ *
+ * Handle editing
+ *
+ * @package dokuwiki\Action
+ */
+class Edit extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+        if($INFO['exists']) {
+            return AUTH_READ; // we check again below
+        } else {
+            return AUTH_CREATE;
+        }
+    }
+
+    /**
+     * @inheritdoc falls back to 'source' if page not writable
+     */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+        global $INFO;
+
+        // no edit permission? view source
+        if($INFO['exists'] && !$INFO['writable']) {
+            throw new ActionAbort('source');
+        }
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $ID;
+        global $INFO;
+
+        global $TEXT;
+        global $RANGE;
+        global $PRE;
+        global $SUF;
+        global $REV;
+        global $SUM;
+        global $lang;
+        global $DATE;
+
+        if(!isset($TEXT)) {
+            if($INFO['exists']) {
+                if($RANGE) {
+                    list($PRE, $TEXT, $SUF) = rawWikiSlices($RANGE, $ID, $REV);
+                } else {
+                    $TEXT = rawWiki($ID, $REV);
+                }
+            } else {
+                $TEXT = pageTemplate($ID);
+            }
+        }
+
+        //set summary default
+        if(!$SUM) {
+            if($REV) {
+                $SUM = sprintf($lang['restored'], dformat($REV));
+            } elseif(!$INFO['exists']) {
+                $SUM = $lang['created'];
+            }
+        }
+
+        // Use the date of the newest revision, not of the revision we edit
+        // This is used for conflict detection
+        if(!$DATE) $DATE = @filemtime(wikiFN($ID));
+
+        //check if locked by anyone - if not lock for my self
+        $lockedby = checklock($ID);
+        if($lockedby) {
+            throw new ActionAbort('locked');
+        };
+        lock($ID);
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_edit();
+    }
+
+}
diff --git a/wiki/inc/Action/Exception/ActionAbort.php b/wiki/inc/Action/Exception/ActionAbort.php
new file mode 100644
index 0000000..9c188bb
--- /dev/null
+++ b/wiki/inc/Action/Exception/ActionAbort.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class ActionAbort
+ *
+ * Strictly speaking not an Exception but an expected execution path. Used to
+ * signal when one action is done and another should take over.
+ *
+ * If you want to signal the same but under some error condition use ActionException
+ * or one of it's decendants.
+ *
+ * The message will NOT be shown to the enduser
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class ActionAbort extends ActionException {
+
+}
diff --git a/wiki/inc/Action/Exception/ActionAclRequiredException.php b/wiki/inc/Action/Exception/ActionAclRequiredException.php
new file mode 100644
index 0000000..64a2c61
--- /dev/null
+++ b/wiki/inc/Action/Exception/ActionAclRequiredException.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class ActionAclRequiredException
+ *
+ * Thrown by AbstractACLAction when an action requires that the ACL subsystem is
+ * enabled but it isn't. You should not use it
+ *
+ * The message will NOT be shown to the enduser
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class ActionAclRequiredException extends ActionException {
+
+}
diff --git a/wiki/inc/Action/Exception/ActionDisabledException.php b/wiki/inc/Action/Exception/ActionDisabledException.php
new file mode 100644
index 0000000..40a0c7d
--- /dev/null
+++ b/wiki/inc/Action/Exception/ActionDisabledException.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class ActionDisabledException
+ *
+ * Thrown when the requested action has been disabled. Eg. through the 'disableactions'
+ * config setting. You should probably not use it.
+ *
+ * The message will NOT be shown to the enduser, but a generic information will be shown.
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class ActionDisabledException extends ActionException {
+
+}
diff --git a/wiki/inc/Action/Exception/ActionException.php b/wiki/inc/Action/Exception/ActionException.php
new file mode 100644
index 0000000..381584c
--- /dev/null
+++ b/wiki/inc/Action/Exception/ActionException.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class ActionException
+ *
+ * This exception and its subclasses signal that the current action should be
+ * aborted and a different action should be used instead. The new action can
+ * be given as parameter in the constructor. Defaults to 'show'
+ *
+ * The message will NOT be shown to the enduser
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class ActionException extends \Exception {
+
+    /** @var string the new action */
+    protected $newaction;
+
+    /** @var bool should the exception's message be shown to the user? */
+    protected $displayToUser = false;
+
+    /**
+     * ActionException constructor.
+     *
+     * When no new action is given 'show' is assumed. For requests that originated in a POST,
+     * a 'redirect' is used which will cause a redirect to the 'show' action.
+     *
+     * @param string|null $newaction the action that should be used next
+     * @param string $message optional message, will not be shown except for some dub classes
+     */
+    public function __construct($newaction = null, $message = '') {
+        global $INPUT;
+        parent::__construct($message);
+        if(is_null($newaction)) {
+            if(strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') {
+                $newaction = 'redirect';
+            } else {
+                $newaction = 'show';
+            }
+        }
+
+        $this->newaction = $newaction;
+    }
+
+    /**
+     * Returns the action to use next
+     *
+     * @return string
+     */
+    public function getNewAction() {
+        return $this->newaction;
+    }
+
+    /**
+     * Should this Exception's message be shown to the user?
+     *
+     * @param null|bool $set when null is given, the current setting is not changed
+     * @return bool
+     */
+    public function displayToUser($set = null) {
+        if(!is_null($set)) $this->displayToUser = $set;
+        return $set;
+    }
+}
diff --git a/wiki/inc/Action/Exception/ActionUserRequiredException.php b/wiki/inc/Action/Exception/ActionUserRequiredException.php
new file mode 100644
index 0000000..aab06cc
--- /dev/null
+++ b/wiki/inc/Action/Exception/ActionUserRequiredException.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class ActionUserRequiredException
+ *
+ * Thrown by AbstractUserAction when an action requires that a user is logged
+ * in but it isn't. You should not use it.
+ *
+ * The message will NOT be shown to the enduser
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class ActionUserRequiredException extends ActionException {
+
+}
diff --git a/wiki/inc/Action/Exception/FatalException.php b/wiki/inc/Action/Exception/FatalException.php
new file mode 100644
index 0000000..5f2516f
--- /dev/null
+++ b/wiki/inc/Action/Exception/FatalException.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class FatalException
+ *
+ * A fatal exception during handling the action
+ *
+ * Will abort all handling and display some info to the user. The HTTP status code
+ * can be defined.
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class FatalException extends \Exception {
+
+    protected $status;
+
+    /**
+     * FatalException constructor.
+     *
+     * @param string $message the message to send
+     * @param int $status the HTTP status to send
+     * @param null|\Exception $previous previous exception
+     */
+    public function __construct($message = 'A fatal error occured', $status = 500, $previous = null) {
+        parent::__construct($message, $status, $previous);
+    }
+}
diff --git a/wiki/inc/Action/Exception/NoActionException.php b/wiki/inc/Action/Exception/NoActionException.php
new file mode 100644
index 0000000..1c4e4d0
--- /dev/null
+++ b/wiki/inc/Action/Exception/NoActionException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace dokuwiki\Action\Exception;
+
+/**
+ * Class NoActionException
+ *
+ * Thrown in the ActionRouter when a wanted action can not be found. Triggers
+ * the unknown action event
+ *
+ * @package dokuwiki\Action\Exception
+ */
+class NoActionException extends \Exception {
+
+}
diff --git a/wiki/inc/Action/Export.php b/wiki/inc/Action/Export.php
new file mode 100644
index 0000000..1eec27e
--- /dev/null
+++ b/wiki/inc/Action/Export.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Export
+ *
+ * Handle exporting by calling the appropriate renderer
+ *
+ * @package dokuwiki\Action
+ */
+class Export extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /**
+     * Export a wiki page for various formats
+     *
+     * Triggers ACTION_EXPORT_POSTPROCESS
+     *
+     *  Event data:
+     *    data['id']      -- page id
+     *    data['mode']    -- requested export mode
+     *    data['headers'] -- export headers
+     *    data['output']  -- export output
+     *
+     * @author Andreas Gohr <andi@splitbrain.org>
+     * @author Michael Klier <chi@chimeric.de>
+     * @inheritdoc
+     */
+    public function preProcess() {
+        global $ID;
+        global $REV;
+        global $conf;
+        global $lang;
+
+        $pre = '';
+        $post = '';
+        $headers = array();
+
+        // search engines: never cache exported docs! (Google only currently)
+        $headers['X-Robots-Tag'] = 'noindex';
+
+        $mode = substr($this->actionname, 7);
+        switch($mode) {
+            case 'raw':
+                $headers['Content-Type'] = 'text/plain; charset=utf-8';
+                $headers['Content-Disposition'] = 'attachment; filename=' . noNS($ID) . '.txt';
+                $output = rawWiki($ID, $REV);
+                break;
+            case 'xhtml':
+                $pre .= '<!DOCTYPE html>' . DOKU_LF;
+                $pre .= '<html lang="' . $conf['lang'] . '" dir="' . $lang['direction'] . '">' . DOKU_LF;
+                $pre .= '<head>' . DOKU_LF;
+                $pre .= '  <meta charset="utf-8" />' . DOKU_LF; // FIXME improve wrapper
+                $pre .= '  <title>' . $ID . '</title>' . DOKU_LF;
+
+                // get metaheaders
+                ob_start();
+                tpl_metaheaders();
+                $pre .= ob_get_clean();
+
+                $pre .= '</head>' . DOKU_LF;
+                $pre .= '<body>' . DOKU_LF;
+                $pre .= '<div class="dokuwiki export">' . DOKU_LF;
+
+                // get toc
+                $pre .= tpl_toc(true);
+
+                $headers['Content-Type'] = 'text/html; charset=utf-8';
+                $output = p_wiki_xhtml($ID, $REV, false);
+
+                $post .= '</div>' . DOKU_LF;
+                $post .= '</body>' . DOKU_LF;
+                $post .= '</html>' . DOKU_LF;
+                break;
+            case 'xhtmlbody':
+                $headers['Content-Type'] = 'text/html; charset=utf-8';
+                $output = p_wiki_xhtml($ID, $REV, false);
+                break;
+            default:
+                $output = p_cached_output(wikiFN($ID, $REV), $mode, $ID);
+                $headers = p_get_metadata($ID, "format $mode");
+                break;
+        }
+
+        // prepare event data
+        $data = array();
+        $data['id'] = $ID;
+        $data['mode'] = $mode;
+        $data['headers'] = $headers;
+        $data['output'] =& $output;
+
+        trigger_event('ACTION_EXPORT_POSTPROCESS', $data);
+
+        if(!empty($data['output'])) {
+            if(is_array($data['headers'])) foreach($data['headers'] as $key => $val) {
+                header("$key: $val");
+            }
+            print $pre . $data['output'] . $post;
+            exit;
+        }
+
+        throw new ActionAbort();
+    }
+
+}
diff --git a/wiki/inc/Action/Index.php b/wiki/inc/Action/Index.php
new file mode 100644
index 0000000..c87a3f8
--- /dev/null
+++ b/wiki/inc/Action/Index.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Index
+ *
+ * Show the human readable sitemap. Do not confuse with Sitemap
+ *
+ * @package dokuwiki\Action
+ */
+class Index extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        global $IDX;
+        html_index($IDX);
+    }
+
+}
diff --git a/wiki/inc/Action/Locked.php b/wiki/inc/Action/Locked.php
new file mode 100644
index 0000000..3ff2c5b
--- /dev/null
+++ b/wiki/inc/Action/Locked.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Locked
+ *
+ * Show a locked screen when a page is locked
+ *
+ * @package dokuwiki\Action
+ */
+class Locked extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_locked();
+    }
+
+}
diff --git a/wiki/inc/Action/Login.php b/wiki/inc/Action/Login.php
new file mode 100644
index 0000000..7f903ff
--- /dev/null
+++ b/wiki/inc/Action/Login.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Login
+ *
+ * The login form. Actual logins are handled in inc/auth.php
+ *
+ * @package dokuwiki\Action
+ */
+class Login extends AbstractAclAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        global $INPUT;
+        parent::checkPreconditions();
+        if($INPUT->server->has('REMOTE_USER')) {
+            // nothing to do
+            throw new ActionException();
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_login();
+    }
+
+}
diff --git a/wiki/inc/Action/Logout.php b/wiki/inc/Action/Logout.php
new file mode 100644
index 0000000..1590904
--- /dev/null
+++ b/wiki/inc/Action/Logout.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionDisabledException;
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Logout
+ *
+ * Log out a user
+ *
+ * @package dokuwiki\Action
+ */
+class Logout extends AbstractUserAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        /** @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        if(!$auth->canDo('logout')) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $ID;
+        global $INPUT;
+
+        // when logging out during an edit session, unlock the page
+        $lockedby = checklock($ID);
+        if($lockedby == $INPUT->server->str('REMOTE_USER')) {
+            unlock($ID);
+        }
+
+        // do the logout stuff and redirect to login
+        auth_logoff();
+        send_redirect(wl($ID, array('do' => 'login')));
+
+        // should never be reached
+        throw new ActionException('login');
+    }
+
+}
diff --git a/wiki/inc/Action/Media.php b/wiki/inc/Action/Media.php
new file mode 100644
index 0000000..77a2a6f
--- /dev/null
+++ b/wiki/inc/Action/Media.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Media
+ *
+ * The full screen media manager
+ *
+ * @package dokuwiki\Action
+ */
+class Media extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        tpl_media();
+    }
+
+}
diff --git a/wiki/inc/Action/Plugin.php b/wiki/inc/Action/Plugin.php
new file mode 100644
index 0000000..c3e16bf
--- /dev/null
+++ b/wiki/inc/Action/Plugin.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Plugin
+ *
+ * Used to run action plugins
+ *
+ * @package dokuwiki\Action
+ */
+class Plugin extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /**
+     * Outputs nothing but a warning unless an action plugin overwrites it
+     *
+     * @inheritdoc
+     * @triggers TPL_ACT_UNKNOWN
+     */
+    public function tplContent() {
+        $evt = new \Doku_Event('TPL_ACT_UNKNOWN', $this->actionname);
+        if($evt->advise_before()) {
+            msg('Failed to handle action: ' . hsc($this->actionname), -1);
+        }
+        $evt->advise_after();
+    }
+}
diff --git a/wiki/inc/Action/Preview.php b/wiki/inc/Action/Preview.php
new file mode 100644
index 0000000..850b204
--- /dev/null
+++ b/wiki/inc/Action/Preview.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Preview
+ *
+ * preview during editing
+ *
+ * @package dokuwiki\Action
+ */
+class Preview extends Edit {
+
+    /** @inheritdoc */
+    public function preProcess() {
+        header('X-XSS-Protection: 0');
+        $this->savedraft();
+        parent::preProcess();
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        global $TEXT;
+        html_edit();
+        html_show($TEXT);
+    }
+
+    /**
+     * Saves a draft on preview
+     */
+    protected function savedraft() {
+        global $INFO;
+        global $ID;
+        global $INPUT;
+        global $conf;
+
+        if(!$conf['usedraft']) return;
+        if(!$INPUT->post->has('wikitext')) return;
+
+        // ensure environment (safeguard when used via AJAX)
+        assert(isset($INFO['client']), 'INFO.client should have been set');
+        assert(isset($ID), 'ID should have been set');
+
+        $draft = array(
+            'id' => $ID,
+            'prefix' => substr($INPUT->post->str('prefix'), 0, -1),
+            'text' => $INPUT->post->str('wikitext'),
+            'suffix' => $INPUT->post->str('suffix'),
+            'date' => $INPUT->post->int('date'),
+            'client' => $INFO['client'],
+        );
+        $cname = getCacheName($draft['client'] . $ID, '.draft');
+        if(io_saveFile($cname, serialize($draft))) {
+            $INFO['draft'] = $cname;
+        }
+    }
+
+}
diff --git a/wiki/inc/Action/Profile.php b/wiki/inc/Action/Profile.php
new file mode 100644
index 0000000..53d8d2f
--- /dev/null
+++ b/wiki/inc/Action/Profile.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionDisabledException;
+
+/**
+ * Class Profile
+ *
+ * Handle the profile form
+ *
+ * @package dokuwiki\Action
+ */
+class Profile extends AbstractUserAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        /** @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        if(!$auth->canDo('Profile')) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $lang;
+        if(updateprofile()) {
+            msg($lang['profchanged'], 1);
+            throw new ActionAbort('show');
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_updateprofile();
+    }
+
+}
diff --git a/wiki/inc/Action/ProfileDelete.php b/wiki/inc/Action/ProfileDelete.php
new file mode 100644
index 0000000..995f813
--- /dev/null
+++ b/wiki/inc/Action/ProfileDelete.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionDisabledException;
+
+/**
+ * Class ProfileDelete
+ *
+ * Delete a user account
+ *
+ * @package dokuwiki\Action
+ */
+class ProfileDelete extends AbstractUserAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        /** @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        if(!$auth->canDo('delUser')) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $lang;
+        if(auth_deleteprofile()) {
+            msg($lang['profdeleted'], 1);
+            throw new ActionAbort('show');
+        } else {
+            throw new ActionAbort('profile');
+        }
+    }
+
+}
diff --git a/wiki/inc/Action/Recent.php b/wiki/inc/Action/Recent.php
new file mode 100644
index 0000000..9273d52
--- /dev/null
+++ b/wiki/inc/Action/Recent.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Recent
+ *
+ * The recent changes view
+ *
+ * @package dokuwiki\Action
+ */
+class Recent extends AbstractAction {
+
+    /** @var string what type of changes to show */
+    protected $showType = 'both';
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $INPUT;
+        $show_changes = $INPUT->str('show_changes');
+        if(!empty($show_changes)) {
+            set_doku_pref('show_changes', $show_changes);
+            $this->showType = $show_changes;
+        } else {
+            $this->showType = get_doku_pref('show_changes', 'both');
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        global $INPUT;
+        html_recent((int) $INPUT->extract('first')->int('first'), $this->showType);
+    }
+
+}
diff --git a/wiki/inc/Action/Recover.php b/wiki/inc/Action/Recover.php
new file mode 100644
index 0000000..7966396
--- /dev/null
+++ b/wiki/inc/Action/Recover.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Recover
+ *
+ * Recover a draft
+ *
+ * @package dokuwiki\Action
+ */
+class Recover extends AbstractAliasAction {
+
+    /** @inheritdoc */
+    public function preProcess() {
+        throw new ActionAbort('edit');
+    }
+
+}
diff --git a/wiki/inc/Action/Redirect.php b/wiki/inc/Action/Redirect.php
new file mode 100644
index 0000000..2e28f45
--- /dev/null
+++ b/wiki/inc/Action/Redirect.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Redirect
+ *
+ * Used to redirect to the current page with the last edited section as a target if found
+ *
+ * @package dokuwiki\Action
+ */
+class Redirect extends AbstractAliasAction {
+
+    /**
+     * Redirect to the show action, trying to jump to the previously edited section
+     *
+     * @triggers ACTION_SHOW_REDIRECT
+     * @throws ActionAbort
+     */
+    public function preProcess() {
+        global $PRE;
+        global $TEXT;
+        global $INPUT;
+        global $ID;
+        global $ACT;
+
+        $opts = array(
+            'id' => $ID,
+            'preact' => $ACT
+        );
+        //get section name when coming from section edit
+        if($INPUT->has('hid')) {
+            // Use explicitly transmitted header id
+            $opts['fragment'] = $INPUT->str('hid');
+        } else if($PRE && preg_match('/^\s*==+([^=\n]+)/', $TEXT, $match)) {
+            // Fallback to old mechanism
+            $check = false; //Byref
+            $opts['fragment'] = sectionID($match[0], $check);
+        }
+
+        // execute the redirect
+        trigger_event('ACTION_SHOW_REDIRECT', $opts, array($this, 'redirect'));
+
+        // should never be reached
+        throw new ActionAbort('show');
+    }
+
+    /**
+     * Execute the redirect
+     *
+     * Default action for ACTION_SHOW_REDIRECT
+     *
+     * @param array $opts id and fragment for the redirect and the preact
+     */
+    public function redirect($opts) {
+        $go = wl($opts['id'], '', true);
+        if(isset($opts['fragment'])) $go .= '#' . $opts['fragment'];
+
+        //show it
+        send_redirect($go);
+    }
+}
diff --git a/wiki/inc/Action/Register.php b/wiki/inc/Action/Register.php
new file mode 100644
index 0000000..0d54158
--- /dev/null
+++ b/wiki/inc/Action/Register.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionDisabledException;
+
+/**
+ * Class Register
+ *
+ * Self registering a new user
+ *
+ * @package dokuwiki\Action
+ */
+class Register extends AbstractAclAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        /** @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        global $conf;
+        if(isset($conf['openregister']) && !$conf['openregister']) throw new ActionDisabledException();
+        if(!$auth->canDo('addUser')) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        if(register()) { // FIXME could be moved from auth to here
+            throw new ActionAbort('login');
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_register();
+    }
+
+}
diff --git a/wiki/inc/Action/Resendpwd.php b/wiki/inc/Action/Resendpwd.php
new file mode 100644
index 0000000..5b5e381
--- /dev/null
+++ b/wiki/inc/Action/Resendpwd.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionDisabledException;
+
+/**
+ * Class Resendpwd
+ *
+ * Handle password recovery
+ *
+ * @package dokuwiki\Action
+ */
+class Resendpwd extends AbstractAclAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        /** @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        global $conf;
+        if(isset($conf['resendpasswd']) && !$conf['resendpasswd']) throw new ActionDisabledException(); //legacy option
+        if(!$auth->canDo('modPass')) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        if($this->resendpwd()) {
+            throw new ActionAbort('login');
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_resendpwd();
+    }
+
+    /**
+     * Send a  new password
+     *
+     * This function handles both phases of the password reset:
+     *
+     *   - handling the first request of password reset
+     *   - validating the password reset auth token
+     *
+     * @author Benoit Chesneau <benoit@bchesneau.info>
+     * @author Chris Smith <chris@jalakai.co.uk>
+     * @author Andreas Gohr <andi@splitbrain.org>
+     * @fixme this should be split up into multiple methods
+     * @return bool true on success, false on any error
+     */
+    protected function resendpwd() {
+        global $lang;
+        global $conf;
+        /* @var \DokuWiki_Auth_Plugin $auth */
+        global $auth;
+        global $INPUT;
+
+        if(!actionOK('resendpwd')) {
+            msg($lang['resendna'], -1);
+            return false;
+        }
+
+        $token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth'));
+
+        if($token) {
+            // we're in token phase - get user info from token
+
+            $tfile = $conf['cachedir'] . '/' . $token{0} . '/' . $token . '.pwauth';
+            if(!file_exists($tfile)) {
+                msg($lang['resendpwdbadauth'], -1);
+                $INPUT->remove('pwauth');
+                return false;
+            }
+            // token is only valid for 3 days
+            if((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) {
+                msg($lang['resendpwdbadauth'], -1);
+                $INPUT->remove('pwauth');
+                @unlink($tfile);
+                return false;
+            }
+
+            $user = io_readfile($tfile);
+            $userinfo = $auth->getUserData($user, $requireGroups = false);
+            if(!$userinfo['mail']) {
+                msg($lang['resendpwdnouser'], -1);
+                return false;
+            }
+
+            if(!$conf['autopasswd']) { // we let the user choose a password
+                $pass = $INPUT->str('pass');
+
+                // password given correctly?
+                if(!$pass) return false;
+                if($pass != $INPUT->str('passchk')) {
+                    msg($lang['regbadpass'], -1);
+                    return false;
+                }
+
+                // change it
+                if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
+                    msg($lang['proffail'], -1);
+                    return false;
+                }
+
+            } else { // autogenerate the password and send by mail
+
+                $pass = auth_pwgen($user);
+                if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
+                    msg($lang['proffail'], -1);
+                    return false;
+                }
+
+                if(auth_sendPassword($user, $pass)) {
+                    msg($lang['resendpwdsuccess'], 1);
+                } else {
+                    msg($lang['regmailfail'], -1);
+                }
+            }
+
+            @unlink($tfile);
+            return true;
+
+        } else {
+            // we're in request phase
+
+            if(!$INPUT->post->bool('save')) return false;
+
+            if(!$INPUT->post->str('login')) {
+                msg($lang['resendpwdmissing'], -1);
+                return false;
+            } else {
+                $user = trim($auth->cleanUser($INPUT->post->str('login')));
+            }
+
+            $userinfo = $auth->getUserData($user, $requireGroups = false);
+            if(!$userinfo['mail']) {
+                msg($lang['resendpwdnouser'], -1);
+                return false;
+            }
+
+            // generate auth token
+            $token = md5(auth_randombytes(16)); // random secret
+            $tfile = $conf['cachedir'] . '/' . $token{0} . '/' . $token . '.pwauth';
+            $url = wl('', array('do' => 'resendpwd', 'pwauth' => $token), true, '&');
+
+            io_saveFile($tfile, $user);
+
+            $text = rawLocale('pwconfirm');
+            $trep = array(
+                'FULLNAME' => $userinfo['name'],
+                'LOGIN' => $user,
+                'CONFIRM' => $url
+            );
+
+            $mail = new \Mailer();
+            $mail->to($userinfo['name'] . ' <' . $userinfo['mail'] . '>');
+            $mail->subject($lang['regpwmail']);
+            $mail->setBody($text, $trep);
+            if($mail->send()) {
+                msg($lang['resendpwdconfirm'], 1);
+            } else {
+                msg($lang['regmailfail'], -1);
+            }
+            return true;
+        }
+        // never reached
+    }
+
+}
diff --git a/wiki/inc/Action/Revert.php b/wiki/inc/Action/Revert.php
new file mode 100644
index 0000000..ca35374
--- /dev/null
+++ b/wiki/inc/Action/Revert.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Revert
+ *
+ * Quick revert to an old revision
+ *
+ * @package dokuwiki\Action
+ */
+class Revert extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+        if($INFO['ismanager']) {
+            return AUTH_EDIT;
+        } else {
+            return AUTH_ADMIN;
+        }
+    }
+
+    /**
+     *
+     * @inheritdoc
+     * @throws ActionAbort
+     * @throws ActionException
+     * @todo check for writability of the current page ($INFO might do it wrong and check the attic version)
+     */
+    public function preProcess() {
+        if(!checkSecurityToken()) throw new ActionException();
+
+        global $ID;
+        global $REV;
+        global $lang;
+
+        // when no revision is given, delete current one
+        // FIXME this feature is not exposed in the GUI currently
+        $text = '';
+        $sum = $lang['deleted'];
+        if($REV) {
+            $text = rawWiki($ID, $REV);
+            if(!$text) throw new ActionException(); //something went wrong
+            $sum = sprintf($lang['restored'], dformat($REV));
+        }
+
+        // spam check
+        if(checkwordblock($text)) {
+            msg($lang['wordblock'], -1);
+            throw new ActionException('edit');
+        }
+
+        saveWikiText($ID, $text, $sum, false);
+        msg($sum, 1);
+        $REV = '';
+
+        // continue with draftdel -> redirect -> show
+        throw new ActionAbort('draftdel');
+    }
+
+}
diff --git a/wiki/inc/Action/Revisions.php b/wiki/inc/Action/Revisions.php
new file mode 100644
index 0000000..b8db531
--- /dev/null
+++ b/wiki/inc/Action/Revisions.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Revisions
+ *
+ * Show the list of old revisions of the current page
+ *
+ * @package dokuwiki\Action
+ */
+class Revisions extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        global $INPUT;
+        html_revisions($INPUT->int('first'));
+    }
+}
diff --git a/wiki/inc/Action/Save.php b/wiki/inc/Action/Save.php
new file mode 100644
index 0000000..0b24729
--- /dev/null
+++ b/wiki/inc/Action/Save.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionException;
+
+/**
+ * Class Save
+ *
+ * Save at the end of an edit session
+ *
+ * @package dokuwiki\Action
+ */
+class Save extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        global $INFO;
+        if($INFO['exists']) {
+            return AUTH_EDIT;
+        } else {
+            return AUTH_CREATE;
+        }
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        if(!checkSecurityToken()) throw new ActionException('preview');
+
+        global $ID;
+        global $DATE;
+        global $PRE;
+        global $TEXT;
+        global $SUF;
+        global $SUM;
+        global $lang;
+        global $INFO;
+        global $INPUT;
+
+        //spam check
+        if(checkwordblock()) {
+            msg($lang['wordblock'], -1);
+            throw new ActionException('edit');
+        }
+        //conflict check
+        if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE) {
+            throw new ActionException('conflict');
+        }
+
+        //save it
+        saveWikiText($ID, con($PRE, $TEXT, $SUF, true), $SUM, $INPUT->bool('minor')); //use pretty mode for con
+        //unlock it
+        unlock($ID);
+
+        // continue with draftdel -> redirect -> show
+        throw new ActionAbort('draftdel');
+    }
+
+}
diff --git a/wiki/inc/Action/Search.php b/wiki/inc/Action/Search.php
new file mode 100644
index 0000000..382fc47
--- /dev/null
+++ b/wiki/inc/Action/Search.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+
+/**
+ * Class Search
+ *
+ * Search for pages and content
+ *
+ * @package dokuwiki\Action
+ */
+class Search extends AbstractAction {
+
+    protected $pageLookupResults = array();
+    protected $fullTextResults = array();
+    protected $highlight = array();
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /**
+     * we only search if a search word was given
+     *
+     * @inheritdoc
+     */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+    }
+
+    public function preProcess()
+    {
+        global $QUERY, $ID, $conf, $INPUT;
+        $s = cleanID($QUERY);
+
+        if ($ID !== $conf['start'] && !$INPUT->has('q')) {
+            parse_str($INPUT->server->str('QUERY_STRING'), $urlParts);
+            $urlParts['q'] = $urlParts['id'];
+            $urlParts['id'] = $conf['start'];
+            $url = DOKU_URL . DOKU_SCRIPT . '?' . http_build_query($urlParts, null, '&');
+            send_redirect($url);
+        }
+
+        if ($s === '') throw new ActionAbort();
+        $this->adjustGlobalQuery();
+    }
+
+    /** @inheritdoc */
+    public function tplContent()
+    {
+        $this->execute();
+
+        $search = new \dokuwiki\Ui\Search($this->pageLookupResults, $this->fullTextResults, $this->highlight);
+        $search->show();
+    }
+
+
+    /**
+     * run the search
+     */
+    protected function execute()
+    {
+        global $INPUT, $QUERY;
+        $after = $INPUT->str('min');
+        $before = $INPUT->str('max');
+        $this->pageLookupResults = ft_pageLookup($QUERY, true, useHeading('navigation'), $after, $before);
+        $this->fullTextResults = ft_pageSearch($QUERY, $highlight, $INPUT->str('srt'), $after, $before);
+        $this->highlight = $highlight;
+    }
+
+    /**
+     * Adjust the global query accordingly to the config search_nslimit and search_fragment
+     *
+     * This will only do something if the search didn't originate from the form on the searchpage itself
+     */
+    protected function adjustGlobalQuery()
+    {
+        global $conf, $INPUT, $QUERY, $ID;
+
+        if ($INPUT->bool('sf')) {
+            return;
+        }
+
+        $Indexer = idx_get_indexer();
+        $parsedQuery = ft_queryParser($Indexer, $QUERY);
+
+        if (empty($parsedQuery['ns']) && empty($parsedQuery['notns'])) {
+            if ($conf['search_nslimit'] > 0) {
+                if (getNS($ID) !== false) {
+                    $nsParts = explode(':', getNS($ID));
+                    $ns = implode(':', array_slice($nsParts, 0, $conf['search_nslimit']));
+                    $QUERY .= " @$ns";
+                }
+            }
+        }
+
+        if ($conf['search_fragment'] !== 'exact') {
+            if (empty(array_diff($parsedQuery['words'], $parsedQuery['and']))) {
+                if (strpos($QUERY, '*') === false) {
+                    $queryParts = explode(' ', $QUERY);
+                    $queryParts = array_map(function ($part) {
+                        if (strpos($part, '@') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, 'ns:') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, '^') === 0) {
+                            return $part;
+                        }
+                        if (strpos($part, '-ns:') === 0) {
+                            return $part;
+                        }
+
+                        global $conf;
+
+                        if ($conf['search_fragment'] === 'starts_with') {
+                            return $part . '*';
+                        }
+                        if ($conf['search_fragment'] === 'ends_with') {
+                            return '*' . $part;
+                        }
+
+                        return '*' . $part . '*';
+
+                    }, $queryParts);
+                    $QUERY = implode(' ', $queryParts);
+                }
+            }
+        }
+    }
+}
diff --git a/wiki/inc/Action/Show.php b/wiki/inc/Action/Show.php
new file mode 100644
index 0000000..a5cb534
--- /dev/null
+++ b/wiki/inc/Action/Show.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Created by IntelliJ IDEA.
+ * User: andi
+ * Date: 2/10/17
+ * Time: 4:32 PM
+ */
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Show
+ *
+ * The default action of showing a page
+ *
+ * @package dokuwiki\Action
+ */
+class Show extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $ID;
+        unlock($ID);
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_show();
+    }
+
+}
diff --git a/wiki/inc/Action/Sitemap.php b/wiki/inc/Action/Sitemap.php
new file mode 100644
index 0000000..025c515
--- /dev/null
+++ b/wiki/inc/Action/Sitemap.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\FatalException;
+
+/**
+ * Class Sitemap
+ *
+ * Generate an XML sitemap for search engines. Do not confuse with Index
+ *
+ * @package dokuwiki\Action
+ */
+class Sitemap extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_NONE;
+    }
+
+    /**
+     * Handle sitemap delivery
+     *
+     * @author Michael Hamann <michael@content-space.de>
+     * @throws FatalException
+     * @inheritdoc
+     */
+    public function preProcess() {
+        global $conf;
+
+        if($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) {
+            throw new FatalException(404, 'Sitemap generation is disabled');
+        }
+
+        $sitemap = \Sitemapper::getFilePath();
+        if(\Sitemapper::sitemapIsCompressed()) {
+            $mime = 'application/x-gzip';
+        } else {
+            $mime = 'application/xml; charset=utf-8';
+        }
+
+        // Check if sitemap file exists, otherwise create it
+        if(!is_readable($sitemap)) {
+            \Sitemapper::generate();
+        }
+
+        if(is_readable($sitemap)) {
+            // Send headers
+            header('Content-Type: ' . $mime);
+            header('Content-Disposition: attachment; filename=' . utf8_basename($sitemap));
+
+            http_conditionalRequest(filemtime($sitemap));
+
+            // Send file
+            //use x-sendfile header to pass the delivery to compatible webservers
+            http_sendfile($sitemap);
+
+            readfile($sitemap);
+            exit;
+        }
+
+        throw new FatalException(500, 'Could not read the sitemap file - bad permissions?');
+    }
+
+}
diff --git a/wiki/inc/Action/Source.php b/wiki/inc/Action/Source.php
new file mode 100644
index 0000000..9b03fe9
--- /dev/null
+++ b/wiki/inc/Action/Source.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace dokuwiki\Action;
+
+/**
+ * Class Source
+ *
+ * Show the source of a page
+ *
+ * @package dokuwiki\Action
+ */
+class Source extends AbstractAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        global $TEXT;
+        global $INFO;
+        global $ID;
+        global $REV;
+
+        if($INFO['exists']) {
+            $TEXT = rawWiki($ID, $REV);
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        html_edit();
+    }
+
+}
diff --git a/wiki/inc/Action/Subscribe.php b/wiki/inc/Action/Subscribe.php
new file mode 100644
index 0000000..c165710
--- /dev/null
+++ b/wiki/inc/Action/Subscribe.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace dokuwiki\Action;
+
+use dokuwiki\Action\Exception\ActionAbort;
+use dokuwiki\Action\Exception\ActionDisabledException;
+
+/**
+ * Class Subscribe
+ *
+ * E-Mail subscription handling
+ *
+ * @package dokuwiki\Action
+ */
+class Subscribe extends AbstractUserAction {
+
+    /** @inheritdoc */
+    public function minimumPermission() {
+        return AUTH_READ;
+    }
+
+    /** @inheritdoc */
+    public function checkPreconditions() {
+        parent::checkPreconditions();
+
+        global $conf;
+        if(isset($conf['subscribers']) && !$conf['subscribers']) throw new ActionDisabledException();
+    }
+
+    /** @inheritdoc */
+    public function preProcess() {
+        try {
+            $this->handleSubscribeData();
+        } catch(ActionAbort $e) {
+            throw $e;
+        } catch(\Exception $e) {
+            msg($e->getMessage(), -1);
+        }
+    }
+
+    /** @inheritdoc */
+    public function tplContent() {
+        tpl_subscribe();
+    }
+
+    /**
+     * Handle page 'subscribe'
+     *
+     * @author Adrian Lang <lang@cosmocode.de>
+     * @throws \Exception if (un)subscribing fails
+     * @throws ActionAbort when (un)subscribing worked
+     */
+    protected function handleSubscribeData() {
+        global $lang;
+        global $INFO;
+        global $INPUT;
+
+        // get and preprocess data.
+        $params = array();
+        foreach(array('target', 'style', 'action') as $param) {
+            if($INPUT->has("sub_$param")) {
+                $params[$param] = $INPUT->str("sub_$param");
+            }
+        }
+
+        // any action given? if not just return and show the subscription page
+        if(empty($params['action']) || !checkSecurityToken()) return;
+
+        // Handle POST data, may throw exception.
+        trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, array($this, 'handlePostData'));
+
+        $target = $params['target'];
+        $style = $params['style'];
+        $action = $params['action'];
+
+        // Perform action.
+        $sub = new \Subscription();
+        if($action == 'unsubscribe') {
+            $ok = $sub->remove($target, $INPUT->server->str('REMOTE_USER'), $style);
+        } else {
+            $ok = $sub->add($target, $INPUT->server->str('REMOTE_USER'), $style);
+        }
+
+        if($ok) {
+            msg(
+                sprintf(
+                    $lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
+                    prettyprint_id($target)
+                ), 1
+            );
+            throw new ActionAbort('redirect');
+        } else {
+            throw new \Exception(
+                sprintf(
+                    $lang["subscr_{$action}_error"],
+                    hsc($INFO['userinfo']['name']),
+                    prettyprint_id($target)
+                )
+            );
+        }
+    }
+
+    /**
+     * Validate POST data
+     *
+     * Validates POST data for a subscribe or unsubscribe request. This is the
+     * default action for the event ACTION_HANDLE_SUBSCRIBE.
+     *
+     * @author Adrian Lang <lang@cosmocode.de>
+     *
+     * @param array &$params the parameters: target, style and action
+     * @throws \Exception
+     */
+    public function handlePostData(&$params) {
+        global $INFO;
+        global $lang;
+        global $INPUT;
+
+        // Get and validate parameters.
+        if(!isset($params['target'])) {
+            throw new \Exception('no subscription target given');
+        }
+        $target = $params['target'];
+        $valid_styles = array('every', 'digest');
+        if(substr($target, -1, 1) === ':') {
+            // Allow “list” subscribe style since the target is a namespace.
+            $valid_styles[] = 'list';
+        }
+        $style = valid_input_set(
+            'style', $valid_styles, $params,
+            'invalid subscription style given'
+        );
+        $action = valid_input_set(
+            'action', array('subscribe', 'unsubscribe'),
+            $params, 'invalid subscription action given'
+        );
+
+        // Check other conditions.
+        if($action === 'subscribe') {
+            if($INFO['userinfo']['mail'] === '') {
+                throw new \Exception($lang['subscr_subscribe_noaddress']);
+            }
+        } elseif($action === 'unsubscribe') {
+            $is = false;
+            foreach($INFO['subscribed'] as $subscr) {
+                if($subscr['target'] === $target) {
+                    $is = true;
+                }
+            }
+            if($is === false) {
+                throw new \Exception(
+                    sprintf(
+                        $lang['subscr_not_subscribed'],
+                        $INPUT->server->str('REMOTE_USER'),
+                        prettyprint_id($target)
+                    )
+                );
+            }
+            // subscription_set deletes a subscription if style = null.
+            $style = null;
+        }
+
+        $params = compact('target', 'style', 'action');
+    }
+
+}