summaryrefslogtreecommitdiff
path: root/platform/www/inc/Extension
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/inc/Extension')
-rw-r--r--platform/www/inc/Extension/ActionPlugin.php22
-rw-r--r--platform/www/inc/Extension/AdminPlugin.php123
-rw-r--r--platform/www/inc/Extension/AuthPlugin.php461
-rw-r--r--platform/www/inc/Extension/CLIPlugin.php13
-rw-r--r--platform/www/inc/Extension/Event.php197
-rw-r--r--platform/www/inc/Extension/EventHandler.php108
-rw-r--r--platform/www/inc/Extension/Plugin.php13
-rw-r--r--platform/www/inc/Extension/PluginController.php393
-rw-r--r--platform/www/inc/Extension/PluginInterface.php162
-rw-r--r--platform/www/inc/Extension/PluginTrait.php256
-rw-r--r--platform/www/inc/Extension/RemotePlugin.php122
-rw-r--r--platform/www/inc/Extension/SyntaxPlugin.php132
12 files changed, 2002 insertions, 0 deletions
diff --git a/platform/www/inc/Extension/ActionPlugin.php b/platform/www/inc/Extension/ActionPlugin.php
new file mode 100644
index 0000000..ed6d820
--- /dev/null
+++ b/platform/www/inc/Extension/ActionPlugin.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * Action Plugin Prototype
+ *
+ * Handles action hooks within a plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+abstract class ActionPlugin extends Plugin
+{
+
+ /**
+ * Registers a callback function for a given event
+ *
+ * @param \Doku_Event_Handler $controller
+ */
+ abstract public function register(\Doku_Event_Handler $controller);
+}
diff --git a/platform/www/inc/Extension/AdminPlugin.php b/platform/www/inc/Extension/AdminPlugin.php
new file mode 100644
index 0000000..7900a1e
--- /dev/null
+++ b/platform/www/inc/Extension/AdminPlugin.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * Admin Plugin Prototype
+ *
+ * Implements an admin interface in a plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+abstract class AdminPlugin extends Plugin
+{
+
+ /**
+ * Return the text that is displayed at the main admin menu
+ * (Default localized language string 'menu' is returned, override this function for setting another name)
+ *
+ * @param string $language language code
+ * @return string menu string
+ */
+ public function getMenuText($language)
+ {
+ $menutext = $this->getLang('menu');
+ if (!$menutext) {
+ $info = $this->getInfo();
+ $menutext = $info['name'] . ' ...';
+ }
+ return $menutext;
+ }
+
+ /**
+ * Return the path to the icon being displayed in the main admin menu.
+ * By default it tries to find an 'admin.svg' file in the plugin directory.
+ * (Override this function for setting another image)
+ *
+ * Important: you have to return a single path, monochrome SVG icon! It has to be
+ * under 2 Kilobytes!
+ *
+ * We recommend icons from https://materialdesignicons.com/ or to use a matching
+ * style.
+ *
+ * @return string full path to the icon file
+ */
+ public function getMenuIcon()
+ {
+ $plugin = $this->getPluginName();
+ return DOKU_PLUGIN . $plugin . '/admin.svg';
+ }
+
+ /**
+ * Determine position in list in admin window
+ * Lower values are sorted up
+ *
+ * @return int
+ */
+ public function getMenuSort()
+ {
+ return 1000;
+ }
+
+ /**
+ * Carry out required processing
+ */
+ public function handle()
+ {
+ // some plugins might not need this
+ }
+
+ /**
+ * Output html of the admin page
+ */
+ abstract public function html();
+
+ /**
+ * Checks if access should be granted to this admin plugin
+ *
+ * @return bool true if the current user may access this admin plugin
+ */
+ public function isAccessibleByCurrentUser() {
+ $data = [];
+ $data['instance'] = $this;
+ $data['hasAccess'] = false;
+
+ $event = new Event('ADMINPLUGIN_ACCESS_CHECK', $data);
+ if($event->advise_before()) {
+ if ($this->forAdminOnly()) {
+ $data['hasAccess'] = auth_isadmin();
+ } else {
+ $data['hasAccess'] = auth_ismanager();
+ }
+ }
+ $event->advise_after();
+
+ return $data['hasAccess'];
+ }
+
+ /**
+ * Return true for access only by admins (config:superuser) or false if managers are allowed as well
+ *
+ * @return bool
+ */
+ public function forAdminOnly()
+ {
+ return true;
+ }
+
+ /**
+ * Return array with ToC items. Items can be created with the html_mktocitem()
+ *
+ * @see html_mktocitem()
+ * @see tpl_toc()
+ *
+ * @return array
+ */
+ public function getTOC()
+ {
+ return array();
+ }
+
+}
+
diff --git a/platform/www/inc/Extension/AuthPlugin.php b/platform/www/inc/Extension/AuthPlugin.php
new file mode 100644
index 0000000..4b75fba
--- /dev/null
+++ b/platform/www/inc/Extension/AuthPlugin.php
@@ -0,0 +1,461 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * Auth Plugin Prototype
+ *
+ * allows to authenticate users in a plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Jan Schumann <js@jschumann-it.com>
+ */
+abstract class AuthPlugin extends Plugin
+{
+ public $success = true;
+
+ /**
+ * Possible things an auth backend module may be able to
+ * do. The things a backend can do need to be set to true
+ * in the constructor.
+ */
+ protected $cando = array(
+ 'addUser' => false, // can Users be created?
+ 'delUser' => false, // can Users be deleted?
+ 'modLogin' => false, // can login names be changed?
+ 'modPass' => false, // can passwords be changed?
+ 'modName' => false, // can real names be changed?
+ 'modMail' => false, // can emails be changed?
+ 'modGroups' => false, // can groups be changed?
+ 'getUsers' => false, // can a (filtered) list of users be retrieved?
+ 'getUserCount' => false, // can the number of users be retrieved?
+ 'getGroups' => false, // can a list of available groups be retrieved?
+ 'external' => false, // does the module do external auth checking?
+ 'logout' => true, // can the user logout again? (eg. not possible with HTTP auth)
+ );
+
+ /**
+ * Constructor.
+ *
+ * Carry out sanity checks to ensure the object is
+ * able to operate. Set capabilities in $this->cando
+ * array here
+ *
+ * For future compatibility, sub classes should always include a call
+ * to parent::__constructor() in their constructors!
+ *
+ * Set $this->success to false if checks fail
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+ public function __construct()
+ {
+ // the base class constructor does nothing, derived class
+ // constructors do the real work
+ }
+
+ /**
+ * Available Capabilities. [ DO NOT OVERRIDE ]
+ *
+ * For introspection/debugging
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @return array
+ */
+ public function getCapabilities()
+ {
+ return array_keys($this->cando);
+ }
+
+ /**
+ * Capability check. [ DO NOT OVERRIDE ]
+ *
+ * Checks the capabilities set in the $this->cando array and
+ * some pseudo capabilities (shortcutting access to multiple
+ * ones)
+ *
+ * ususal capabilities start with lowercase letter
+ * shortcut capabilities start with uppercase letter
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $cap the capability to check
+ * @return bool
+ */
+ public function canDo($cap)
+ {
+ switch ($cap) {
+ case 'Profile':
+ // can at least one of the user's properties be changed?
+ return ($this->cando['modPass'] ||
+ $this->cando['modName'] ||
+ $this->cando['modMail']);
+ break;
+ case 'UserMod':
+ // can at least anything be changed?
+ return ($this->cando['modPass'] ||
+ $this->cando['modName'] ||
+ $this->cando['modMail'] ||
+ $this->cando['modLogin'] ||
+ $this->cando['modGroups'] ||
+ $this->cando['modMail']);
+ break;
+ default:
+ // print a helping message for developers
+ if (!isset($this->cando[$cap])) {
+ msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1);
+ }
+ return $this->cando[$cap];
+ }
+ }
+
+ /**
+ * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ]
+ *
+ * You should use this function instead of calling createUser, modifyUser or
+ * deleteUsers directly. The event handlers can prevent the modification, for
+ * example for enforcing a user name schema.
+ *
+ * @author Gabriel Birke <birke@d-scribe.de>
+ * @param string $type Modification type ('create', 'modify', 'delete')
+ * @param array $params Parameters for the createUser, modifyUser or deleteUsers method.
+ * The content of this array depends on the modification type
+ * @return bool|null|int Result from the modification function or false if an event handler has canceled the action
+ */
+ public function triggerUserMod($type, $params)
+ {
+ $validTypes = array(
+ 'create' => 'createUser',
+ 'modify' => 'modifyUser',
+ 'delete' => 'deleteUsers',
+ );
+ if (empty($validTypes[$type])) {
+ return false;
+ }
+
+ $result = false;
+ $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null);
+ $evt = new Event('AUTH_USER_CHANGE', $eventdata);
+ if ($evt->advise_before(true)) {
+ $result = call_user_func_array(array($this, $validTypes[$type]), $evt->data['params']);
+ $evt->data['modification_result'] = $result;
+ }
+ $evt->advise_after();
+ unset($evt);
+ return $result;
+ }
+
+ /**
+ * Log off the current user [ OPTIONAL ]
+ *
+ * Is run in addition to the ususal logoff method. Should
+ * only be needed when trustExternal is implemented.
+ *
+ * @see auth_logoff()
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function logOff()
+ {
+ }
+
+ /**
+ * Do all authentication [ OPTIONAL ]
+ *
+ * Set $this->cando['external'] = true when implemented
+ *
+ * If this function is implemented it will be used to
+ * authenticate a user - all other DokuWiki internals
+ * will not be used for authenticating (except this
+ * function returns null, in which case, DokuWiki will
+ * still run auth_login as a fallback, which may call
+ * checkPass()). If this function is not returning null,
+ * implementing checkPass() is not needed here anymore.
+ *
+ * The function can be used to authenticate against third
+ * party cookies or Apache auth mechanisms and replaces
+ * the auth_login() function
+ *
+ * The function will be called with or without a set
+ * username. If the Username is given it was called
+ * from the login form and the given credentials might
+ * need to be checked. If no username was given it
+ * the function needs to check if the user is logged in
+ * by other means (cookie, environment).
+ *
+ * The function needs to set some globals needed by
+ * DokuWiki like auth_login() does.
+ *
+ * @see auth_login()
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $user Username
+ * @param string $pass Cleartext Password
+ * @param bool $sticky Cookie should not expire
+ * @return bool true on successful auth,
+ * null on unknown result (fallback to checkPass)
+ */
+ public function trustExternal($user, $pass, $sticky = false)
+ {
+ /* some example:
+
+ global $USERINFO;
+ global $conf;
+ $sticky ? $sticky = true : $sticky = false; //sanity check
+
+ // do the checking here
+
+ // set the globals if authed
+ $USERINFO['name'] = 'FIXME';
+ $USERINFO['mail'] = 'FIXME';
+ $USERINFO['grps'] = array('FIXME');
+ $_SERVER['REMOTE_USER'] = $user;
+ $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
+ $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
+ $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
+ return true;
+
+ */
+ }
+
+ /**
+ * Check user+password [ MUST BE OVERRIDDEN ]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct
+ *
+ * May be ommited if trustExternal is used.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user the user name
+ * @param string $pass the clear text password
+ * @return bool
+ */
+ public function checkPass($user, $pass)
+ {
+ msg("no valid authorisation system in use", -1);
+ return false;
+ }
+
+ /**
+ * Return user info [ MUST BE OVERRIDDEN ]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email address of the user
+ * grps array list of groups the user is in
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user the user name
+ * @param bool $requireGroups whether or not the returned data must include groups
+ * @return false|array containing user data or false
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ if (!$this->cando['external']) msg("no valid authorisation system in use", -1);
+ return false;
+ }
+
+ /**
+ * Create a new User [implement only where required/possible]
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user HAS TO be added to the default group by this
+ * function!
+ *
+ * Set addUser capability when implemented
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user
+ * @param string $pass
+ * @param string $name
+ * @param string $mail
+ * @param null|array $grps
+ * @return bool|null
+ */
+ public function createUser($user, $pass, $name, $mail, $grps = null)
+ {
+ msg("authorisation method does not allow creation of new users", -1);
+ return null;
+ }
+
+ /**
+ * Modify user data [implement only where required/possible]
+ *
+ * Set the mod* capabilities according to the implemented features
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ public function modifyUser($user, $changes)
+ {
+ msg("authorisation method does not allow modifying of user data", -1);
+ return false;
+ }
+
+ /**
+ * Delete one or more users [implement only where required/possible]
+ *
+ * Set delUser capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param array $users
+ * @return int number of users deleted
+ */
+ public function deleteUsers($users)
+ {
+ msg("authorisation method does not allow deleting of users", -1);
+ return 0;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ * [should be implemented whenever retrieveUsers is implemented]
+ *
+ * Set getUserCount capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param array $filter array of field/pattern pairs, empty array for no filter
+ * @return int
+ */
+ public function getUserCount($filter = array())
+ {
+ msg("authorisation method does not provide user counts", -1);
+ return 0;
+ }
+
+ /**
+ * Bulk retrieval of user data [implement only where required/possible]
+ *
+ * Set getUsers capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned, 0 for unlimited
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array list of userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = 0, $filter = null)
+ {
+ msg("authorisation method does not support mass retrieval of user data", -1);
+ return array();
+ }
+
+ /**
+ * Define a group [implement only where required/possible]
+ *
+ * Set addGroup capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param string $group
+ * @return bool
+ */
+ public function addGroup($group)
+ {
+ msg("authorisation method does not support independent group creation", -1);
+ return false;
+ }
+
+ /**
+ * Retrieve groups [implement only where required/possible]
+ *
+ * Set getGroups capability when implemented
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param int $start
+ * @param int $limit
+ * @return array
+ */
+ public function retrieveGroups($start = 0, $limit = 0)
+ {
+ msg("authorisation method does not support group list retrieval", -1);
+ return array();
+ }
+
+ /**
+ * Return case sensitivity of the backend [OPTIONAL]
+ *
+ * When your backend is caseinsensitive (eg. you can login with USER and
+ * user) then you need to overwrite this method and return false
+ *
+ * @return bool
+ */
+ public function isCaseSensitive()
+ {
+ return true;
+ }
+
+ /**
+ * Sanitize a given username [OPTIONAL]
+ *
+ * This function is applied to any user name that is given to
+ * the backend and should also be applied to any user name within
+ * the backend before returning it somewhere.
+ *
+ * This should be used to enforce username restrictions.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user username
+ * @return string the cleaned username
+ */
+ public function cleanUser($user)
+ {
+ return $user;
+ }
+
+ /**
+ * Sanitize a given groupname [OPTIONAL]
+ *
+ * This function is applied to any groupname that is given to
+ * the backend and should also be applied to any groupname within
+ * the backend before returning it somewhere.
+ *
+ * This should be used to enforce groupname restrictions.
+ *
+ * Groupnames are to be passed without a leading '@' here.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $group groupname
+ * @return string the cleaned groupname
+ */
+ public function cleanGroup($group)
+ {
+ return $group;
+ }
+
+ /**
+ * Check Session Cache validity [implement only where required/possible]
+ *
+ * DokuWiki caches user info in the user's session for the timespan defined
+ * in $conf['auth_security_timeout'].
+ *
+ * This makes sure slow authentication backends do not slow down DokuWiki.
+ * This also means that changes to the user database will not be reflected
+ * on currently logged in users.
+ *
+ * To accommodate for this, the user manager plugin will touch a reference
+ * file whenever a change is submitted. This function compares the filetime
+ * of this reference file with the time stored in the session.
+ *
+ * This reference file mechanism does not reflect changes done directly in
+ * the backend's database through other means than the user manager plugin.
+ *
+ * Fast backends might want to return always false, to force rechecks on
+ * each page load. Others might want to use their own checking here. If
+ * unsure, do not override.
+ *
+ * @param string $user - The username
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool
+ */
+ public function useSessionCache($user)
+ {
+ global $conf;
+ return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'] . '/sessionpurge'));
+ }
+}
diff --git a/platform/www/inc/Extension/CLIPlugin.php b/platform/www/inc/Extension/CLIPlugin.php
new file mode 100644
index 0000000..8637ccf
--- /dev/null
+++ b/platform/www/inc/Extension/CLIPlugin.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * CLI plugin prototype
+ *
+ * Provides DokuWiki plugin functionality on top of php-cli
+ */
+abstract class CLIPlugin extends \splitbrain\phpcli\CLI implements PluginInterface
+{
+ use PluginTrait;
+}
diff --git a/platform/www/inc/Extension/Event.php b/platform/www/inc/Extension/Event.php
new file mode 100644
index 0000000..32f346c
--- /dev/null
+++ b/platform/www/inc/Extension/Event.php
@@ -0,0 +1,197 @@
+<?php
+// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
+
+namespace dokuwiki\Extension;
+
+/**
+ * The Action plugin event
+ */
+class Event
+{
+ /** @var string READONLY event name, objects must register against this name to see the event */
+ public $name = '';
+ /** @var mixed|null READWRITE data relevant to the event, no standardised format, refer to event docs */
+ public $data = null;
+ /**
+ * @var mixed|null READWRITE the results of the event action, only relevant in "_AFTER" advise
+ * event handlers may modify this if they are preventing the default action
+ * to provide the after event handlers with event results
+ */
+ public $result = null;
+ /** @var bool READONLY if true, event handlers can prevent the events default action */
+ public $canPreventDefault = true;
+
+ /** @var bool whether or not to carry out the default action associated with the event */
+ protected $runDefault = true;
+ /** @var bool whether or not to continue propagating the event to other handlers */
+ protected $mayContinue = true;
+
+ /**
+ * event constructor
+ *
+ * @param string $name
+ * @param mixed $data
+ */
+ public function __construct($name, &$data)
+ {
+
+ $this->name = $name;
+ $this->data =& $data;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->name;
+ }
+
+ /**
+ * advise all registered BEFORE handlers of this event
+ *
+ * if these methods are used by functions outside of this object, they must
+ * properly handle correct processing of any default action and issue an
+ * advise_after() signal. e.g.
+ * $evt = new dokuwiki\Plugin\Doku_Event(name, data);
+ * if ($evt->advise_before(canPreventDefault) {
+ * // default action code block
+ * }
+ * $evt->advise_after();
+ * unset($evt);
+ *
+ * @param bool $enablePreventDefault
+ * @return bool results of processing the event, usually $this->runDefault
+ */
+ public function advise_before($enablePreventDefault = true)
+ {
+ global $EVENT_HANDLER;
+
+ $this->canPreventDefault = $enablePreventDefault;
+ if ($EVENT_HANDLER !== null) {
+ $EVENT_HANDLER->process_event($this, 'BEFORE');
+ } else {
+ dbglog($this->name . ':BEFORE event triggered before event system was initialized');
+ }
+
+ return (!$enablePreventDefault || $this->runDefault);
+ }
+
+ /**
+ * advise all registered AFTER handlers of this event
+ *
+ * @param bool $enablePreventDefault
+ * @see advise_before() for details
+ */
+ public function advise_after()
+ {
+ global $EVENT_HANDLER;
+
+ $this->mayContinue = true;
+
+ if ($EVENT_HANDLER !== null) {
+ $EVENT_HANDLER->process_event($this, 'AFTER');
+ } else {
+ dbglog($this->name . ':AFTER event triggered before event system was initialized');
+ }
+ }
+
+ /**
+ * trigger
+ *
+ * - advise all registered (<event>_BEFORE) handlers that this event is about to take place
+ * - carry out the default action using $this->data based on $enablePrevent and
+ * $this->_default, all of which may have been modified by the event handlers.
+ * - advise all registered (<event>_AFTER) handlers that the event has taken place
+ *
+ * @param null|callable $action
+ * @param bool $enablePrevent
+ * @return mixed $event->results
+ * the value set by any <event>_before or <event> handlers if the default action is prevented
+ * or the results of the default action (as modified by <event>_after handlers)
+ * or NULL no action took place and no handler modified the value
+ */
+ public function trigger($action = null, $enablePrevent = true)
+ {
+
+ if (!is_callable($action)) {
+ $enablePrevent = false;
+ if ($action !== null) {
+ trigger_error(
+ 'The default action of ' . $this .
+ ' is not null but also not callable. Maybe the method is not public?',
+ E_USER_WARNING
+ );
+ }
+ }
+
+ if ($this->advise_before($enablePrevent) && is_callable($action)) {
+ $this->result = call_user_func_array($action, [&$this->data]);
+ }
+
+ $this->advise_after();
+
+ return $this->result;
+ }
+
+ /**
+ * stopPropagation
+ *
+ * stop any further processing of the event by event handlers
+ * this function does not prevent the default action taking place
+ */
+ public function stopPropagation()
+ {
+ $this->mayContinue = false;
+ }
+
+ /**
+ * may the event propagate to the next handler?
+ *
+ * @return bool
+ */
+ public function mayPropagate()
+ {
+ return $this->mayContinue;
+ }
+
+ /**
+ * preventDefault
+ *
+ * prevent the default action taking place
+ */
+ public function preventDefault()
+ {
+ $this->runDefault = false;
+ }
+
+ /**
+ * should the default action be executed?
+ *
+ * @return bool
+ */
+ public function mayRunDefault()
+ {
+ return $this->runDefault;
+ }
+
+ /**
+ * Convenience method to trigger an event
+ *
+ * Creates, triggers and destroys an event in one go
+ *
+ * @param string $name name for the event
+ * @param mixed $data event data
+ * @param callable $action (optional, default=NULL) default action, a php callback function
+ * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action
+ *
+ * @return mixed the event results value after all event processing is complete
+ * by default this is the return value of the default action however
+ * it can be set or modified by event handler hooks
+ */
+ static public function createAndTrigger($name, &$data, $action = null, $canPreventDefault = true)
+ {
+ $evt = new Event($name, $data);
+ return $evt->trigger($action, $canPreventDefault);
+ }
+}
diff --git a/platform/www/inc/Extension/EventHandler.php b/platform/www/inc/Extension/EventHandler.php
new file mode 100644
index 0000000..7bed0fe
--- /dev/null
+++ b/platform/www/inc/Extension/EventHandler.php
@@ -0,0 +1,108 @@
+<?php
+// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
+
+namespace dokuwiki\Extension;
+
+/**
+ * Controls the registration and execution of all events,
+ */
+class EventHandler
+{
+
+ // public properties: none
+
+ // private properties
+ protected $hooks = array(); // array of events and their registered handlers
+
+ /**
+ * event_handler
+ *
+ * constructor, loads all action plugins and calls their register() method giving them
+ * an opportunity to register any hooks they require
+ */
+ public function __construct()
+ {
+
+ // load action plugins
+ /** @var ActionPlugin $plugin */
+ $plugin = null;
+ $pluginlist = plugin_list('action');
+
+ foreach ($pluginlist as $plugin_name) {
+ $plugin = plugin_load('action', $plugin_name);
+
+ if ($plugin !== null) $plugin->register($this);
+ }
+ }
+
+ /**
+ * register_hook
+ *
+ * register a hook for an event
+ *
+ * @param string $event string name used by the event, (incl '_before' or '_after' for triggers)
+ * @param string $advise
+ * @param object $obj object in whose scope method is to be executed,
+ * if NULL, method is assumed to be a globally available function
+ * @param string $method event handler function
+ * @param mixed $param data passed to the event handler
+ * @param int $seq sequence number for ordering hook execution (ascending)
+ */
+ public function register_hook($event, $advise, $obj, $method, $param = null, $seq = 0)
+ {
+ $seq = (int)$seq;
+ $doSort = !isset($this->hooks[$event . '_' . $advise][$seq]);
+ $this->hooks[$event . '_' . $advise][$seq][] = array($obj, $method, $param);
+
+ if ($doSort) {
+ ksort($this->hooks[$event . '_' . $advise]);
+ }
+ }
+
+ /**
+ * process the before/after event
+ *
+ * @param Event $event
+ * @param string $advise BEFORE or AFTER
+ */
+ public function process_event($event, $advise = '')
+ {
+
+ $evt_name = $event->name . ($advise ? '_' . $advise : '_BEFORE');
+
+ if (!empty($this->hooks[$evt_name])) {
+ foreach ($this->hooks[$evt_name] as $sequenced_hooks) {
+ foreach ($sequenced_hooks as $hook) {
+ list($obj, $method, $param) = $hook;
+
+ if ($obj === null) {
+ $method($event, $param);
+ } else {
+ $obj->$method($event, $param);
+ }
+
+ if (!$event->mayPropagate()) return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if an event has any registered handlers
+ *
+ * When $advise is empty, both BEFORE and AFTER events will be considered,
+ * otherwise only the given advisory is checked
+ *
+ * @param string $name Name of the event
+ * @param string $advise BEFORE, AFTER or empty
+ * @return bool
+ */
+ public function hasHandlerForEvent($name, $advise = '')
+ {
+ if ($advise) {
+ return isset($this->hooks[$name . '_' . $advise]);
+ }
+
+ return isset($this->hooks[$name . '_BEFORE']) || isset($this->hooks[$name . '_AFTER']);
+ }
+}
diff --git a/platform/www/inc/Extension/Plugin.php b/platform/www/inc/Extension/Plugin.php
new file mode 100644
index 0000000..03637fe
--- /dev/null
+++ b/platform/www/inc/Extension/Plugin.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * DokuWiki Base Plugin
+ *
+ * Most plugin types inherit from this class
+ */
+abstract class Plugin implements PluginInterface
+{
+ use PluginTrait;
+}
diff --git a/platform/www/inc/Extension/PluginController.php b/platform/www/inc/Extension/PluginController.php
new file mode 100644
index 0000000..638fd39
--- /dev/null
+++ b/platform/www/inc/Extension/PluginController.php
@@ -0,0 +1,393 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * Class to encapsulate access to dokuwiki plugins
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+class PluginController
+{
+ /** @var array the types of plugins DokuWiki supports */
+ const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli'];
+
+ protected $listByType = [];
+ /** @var array all installed plugins and their enabled state [plugin=>enabled] */
+ protected $masterList = [];
+ protected $pluginCascade = ['default' => [], 'local' => [], 'protected' => []];
+ protected $lastLocalConfigFile = '';
+
+ /**
+ * Populates the master list of plugins
+ */
+ public function __construct()
+ {
+ $this->loadConfig();
+ $this->populateMasterList();
+ }
+
+ /**
+ * Returns a list of available plugins of given type
+ *
+ * @param $type string, plugin_type name;
+ * the type of plugin to return,
+ * use empty string for all types
+ * @param $all bool;
+ * false to only return enabled plugins,
+ * true to return both enabled and disabled plugins
+ *
+ * @return array of
+ * - plugin names when $type = ''
+ * - or plugin component names when a $type is given
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function getList($type = '', $all = false)
+ {
+
+ // request the complete list
+ if (!$type) {
+ return $all ? array_keys($this->masterList) : array_keys(array_filter($this->masterList));
+ }
+
+ if (!isset($this->listByType[$type]['enabled'])) {
+ $this->listByType[$type]['enabled'] = $this->getListByType($type, true);
+ }
+ if ($all && !isset($this->listByType[$type]['disabled'])) {
+ $this->listByType[$type]['disabled'] = $this->getListByType($type, false);
+ }
+
+ return $all
+ ? array_merge($this->listByType[$type]['enabled'], $this->listByType[$type]['disabled'])
+ : $this->listByType[$type]['enabled'];
+ }
+
+ /**
+ * Loads the given plugin and creates an object of it
+ *
+ * @param $type string type of plugin to load
+ * @param $name string name of the plugin to load
+ * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance
+ * @param $disabled bool true to load even disabled plugins
+ * @return PluginInterface|null the plugin object or null on failure
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ */
+ public function load($type, $name, $new = false, $disabled = false)
+ {
+
+ //we keep all loaded plugins available in global scope for reuse
+ global $DOKU_PLUGINS;
+
+ list($plugin, /* $component */) = $this->splitName($name);
+
+ // check if disabled
+ if (!$disabled && !$this->isEnabled($plugin)) {
+ return null;
+ }
+
+ $class = $type . '_plugin_' . $name;
+
+ //plugin already loaded?
+ if (!empty($DOKU_PLUGINS[$type][$name])) {
+ if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
+ return class_exists($class, true) ? new $class : null;
+ }
+
+ return $DOKU_PLUGINS[$type][$name];
+ }
+
+ //construct class and instantiate
+ if (!class_exists($class, true)) {
+
+ # the plugin might be in the wrong directory
+ $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
+ if ($inf['base'] && $inf['base'] != $plugin) {
+ msg(
+ sprintf(
+ "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
+ hsc($plugin),
+ hsc(
+ $inf['base']
+ )
+ ), -1
+ );
+ } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
+ msg(
+ sprintf(
+ "Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " .
+ 'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
+ ), -1
+ );
+ }
+ return null;
+ }
+
+ $DOKU_PLUGINS[$type][$name] = new $class;
+ return $DOKU_PLUGINS[$type][$name];
+ }
+
+ /**
+ * Whether plugin is disabled
+ *
+ * @param string $plugin name of plugin
+ * @return bool true disabled, false enabled
+ * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state
+ */
+ public function isDisabled($plugin)
+ {
+ dbg_deprecated('isEnabled()');
+ return !$this->isEnabled($plugin);
+ }
+
+ /**
+ * Check whether plugin is disabled
+ *
+ * @param string $plugin name of plugin
+ * @return bool true enabled, false disabled
+ */
+ public function isEnabled($plugin)
+ {
+ return !empty($this->masterList[$plugin]);
+ }
+
+ /**
+ * Disable the plugin
+ *
+ * @param string $plugin name of plugin
+ * @return bool true saving succeed, false saving failed
+ */
+ public function disable($plugin)
+ {
+ if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false;
+ $this->masterList[$plugin] = 0;
+ return $this->saveList();
+ }
+
+ /**
+ * Enable the plugin
+ *
+ * @param string $plugin name of plugin
+ * @return bool true saving succeed, false saving failed
+ */
+ public function enable($plugin)
+ {
+ if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false;
+ $this->masterList[$plugin] = 1;
+ return $this->saveList();
+ }
+
+ /**
+ * Returns cascade of the config files
+ *
+ * @return array with arrays of plugin configs
+ */
+ public function getCascade()
+ {
+ return $this->pluginCascade;
+ }
+
+ /**
+ * Read all installed plugins and their current enabled state
+ */
+ protected function populateMasterList()
+ {
+ if ($dh = @opendir(DOKU_PLUGIN)) {
+ $all_plugins = array();
+ while (false !== ($plugin = readdir($dh))) {
+ if ($plugin[0] === '.') continue; // skip hidden entries
+ if (is_file(DOKU_PLUGIN . $plugin)) continue; // skip files, we're only interested in directories
+
+ if (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 0) {
+ $all_plugins[$plugin] = 0;
+
+ } elseif (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 1) {
+ $all_plugins[$plugin] = 1;
+ } else {
+ $all_plugins[$plugin] = 1;
+ }
+ }
+ $this->masterList = $all_plugins;
+ if (!file_exists($this->lastLocalConfigFile)) {
+ $this->saveList(true);
+ }
+ }
+ }
+
+ /**
+ * Includes the plugin config $files
+ * and returns the entries of the $plugins array set in these files
+ *
+ * @param array $files list of files to include, latter overrides previous
+ * @return array with entries of the $plugins arrays of the included files
+ */
+ protected function checkRequire($files)
+ {
+ $plugins = array();
+ foreach ($files as $file) {
+ if (file_exists($file)) {
+ include_once($file);
+ }
+ }
+ return $plugins;
+ }
+
+ /**
+ * Save the current list of plugins
+ *
+ * @param bool $forceSave ;
+ * false to save only when config changed
+ * true to always save
+ * @return bool true saving succeed, false saving failed
+ */
+ protected function saveList($forceSave = false)
+ {
+ global $conf;
+
+ if (empty($this->masterList)) return false;
+
+ // Rebuild list of local settings
+ $local_plugins = $this->rebuildLocal();
+ if ($local_plugins != $this->pluginCascade['local'] || $forceSave) {
+ $file = $this->lastLocalConfigFile;
+ $out = "<?php\n/*\n * Local plugin enable/disable settings\n" .
+ " * Auto-generated through plugin/extension manager\n *\n" .
+ " * NOTE: Plugins will not be added to this file unless there " .
+ "is a need to override a default setting. Plugins are\n" .
+ " * enabled by default.\n */\n";
+ foreach ($local_plugins as $plugin => $value) {
+ $out .= "\$plugins['$plugin'] = $value;\n";
+ }
+ // backup current file (remove any existing backup)
+ if (file_exists($file)) {
+ $backup = $file . '.bak';
+ if (file_exists($backup)) @unlink($backup);
+ if (!@copy($file, $backup)) return false;
+ if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']);
+ }
+ //check if can open for writing, else restore
+ return io_saveFile($file, $out);
+ }
+ return false;
+ }
+
+ /**
+ * Rebuild the set of local plugins
+ *
+ * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
+ */
+ protected function rebuildLocal()
+ {
+ //assign to local variable to avoid overwriting
+ $backup = $this->masterList;
+ //Can't do anything about protected one so rule them out completely
+ $local_default = array_diff_key($backup, $this->pluginCascade['protected']);
+ //Diff between local+default and default
+ //gives us the ones we need to check and save
+ $diffed_ones = array_diff_key($local_default, $this->pluginCascade['default']);
+ //The ones which we are sure of (list of 0s not in default)
+ $sure_plugins = array_filter($diffed_ones, array($this, 'negate'));
+ //the ones in need of diff
+ $conflicts = array_diff_key($local_default, $diffed_ones);
+ //The final list
+ return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->pluginCascade['default']));
+ }
+
+ /**
+ * Build the list of plugins and cascade
+ *
+ */
+ protected function loadConfig()
+ {
+ global $config_cascade;
+ foreach (array('default', 'protected') as $type) {
+ if (array_key_exists($type, $config_cascade['plugins'])) {
+ $this->pluginCascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
+ }
+ }
+ $local = $config_cascade['plugins']['local'];
+ $this->lastLocalConfigFile = array_pop($local);
+ $this->pluginCascade['local'] = $this->checkRequire(array($this->lastLocalConfigFile));
+ if (is_array($local)) {
+ $this->pluginCascade['default'] = array_merge(
+ $this->pluginCascade['default'],
+ $this->checkRequire($local)
+ );
+ }
+ $this->masterList = array_merge(
+ $this->pluginCascade['default'],
+ $this->pluginCascade['local'],
+ $this->pluginCascade['protected']
+ );
+ }
+
+ /**
+ * Returns a list of available plugin components of given type
+ *
+ * @param string $type plugin_type name; the type of plugin to return,
+ * @param bool $enabled true to return enabled plugins,
+ * false to return disabled plugins
+ * @return array of plugin components of requested type
+ */
+ protected function getListByType($type, $enabled)
+ {
+ $master_list = $enabled
+ ? array_keys(array_filter($this->masterList))
+ : array_keys(array_filter($this->masterList, array($this, 'negate')));
+ $plugins = array();
+
+ foreach ($master_list as $plugin) {
+
+ if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) {
+ $plugins[] = $plugin;
+ continue;
+ }
+
+ $typedir = DOKU_PLUGIN . "$plugin/$type/";
+ if (is_dir($typedir)) {
+ if ($dp = opendir($typedir)) {
+ while (false !== ($component = readdir($dp))) {
+ if (strpos($component, '.') === 0 || strtolower(substr($component, -4)) !== '.php') continue;
+ if (is_file($typedir . $component)) {
+ $plugins[] = $plugin . '_' . substr($component, 0, -4);
+ }
+ }
+ closedir($dp);
+ }
+ }
+
+ }//foreach
+
+ return $plugins;
+ }
+
+ /**
+ * Split name in a plugin name and a component name
+ *
+ * @param string $name
+ * @return array with
+ * - plugin name
+ * - and component name when available, otherwise empty string
+ */
+ protected function splitName($name)
+ {
+ if (!isset($this->masterList[$name])) {
+ return explode('_', $name, 2);
+ }
+
+ return array($name, '');
+ }
+
+ /**
+ * Returns inverse boolean value of the input
+ *
+ * @param mixed $input
+ * @return bool inversed boolean value of input
+ */
+ protected function negate($input)
+ {
+ return !(bool)$input;
+ }
+}
diff --git a/platform/www/inc/Extension/PluginInterface.php b/platform/www/inc/Extension/PluginInterface.php
new file mode 100644
index 0000000..f2dbe86
--- /dev/null
+++ b/platform/www/inc/Extension/PluginInterface.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * DokuWiki Plugin Interface
+ *
+ * Defines the public contract all DokuWiki plugins will adhere to. The actual code
+ * to do so is defined in dokuwiki\Extension\PluginTrait
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+interface PluginInterface
+{
+ /**
+ * General Info
+ *
+ * Needs to return a associative array with the following values:
+ *
+ * base - the plugin's base name (eg. the directory it needs to be installed in)
+ * author - Author of the plugin
+ * email - Email address to contact the author
+ * date - Last modified date of the plugin in YYYY-MM-DD format
+ * name - Name of the plugin
+ * desc - Short description of the plugin (Text only)
+ * url - Website with more information on the plugin (eg. syntax description)
+ */
+ public function getInfo();
+
+ /**
+ * The type of the plugin inferred from the class name
+ *
+ * @return string plugin type
+ */
+ public function getPluginType();
+
+ /**
+ * The name of the plugin inferred from the class name
+ *
+ * @return string plugin name
+ */
+ public function getPluginName();
+
+ /**
+ * The component part of the plugin inferred from the class name
+ *
+ * @return string component name
+ */
+ public function getPluginComponent();
+
+ /**
+ * Access plugin language strings
+ *
+ * to try to minimise unnecessary loading of the strings when the plugin doesn't require them
+ * e.g. when info plugin is querying plugins for information about themselves.
+ *
+ * @param string $id id of the string to be retrieved
+ * @return string in appropriate language or english if not available
+ */
+ public function getLang($id);
+
+ /**
+ * retrieve a language dependent file and pass to xhtml renderer for display
+ * plugin equivalent of p_locale_xhtml()
+ *
+ * @param string $id id of language dependent wiki page
+ * @return string parsed contents of the wiki page in xhtml format
+ */
+ public function locale_xhtml($id);
+
+ /**
+ * Prepends appropriate path for a language dependent filename
+ * plugin equivalent of localFN()
+ *
+ * @param string $id id of localization file
+ * @param string $ext The file extension (usually txt)
+ * @return string wiki text
+ */
+ public function localFN($id, $ext = 'txt');
+
+ /**
+ * Reads all the plugins language dependent strings into $this->lang
+ * this function is automatically called by getLang()
+ *
+ * @todo this could be made protected and be moved to the trait only
+ */
+ public function setupLocale();
+
+ /**
+ * use this function to access plugin configuration variables
+ *
+ * @param string $setting the setting to access
+ * @param mixed $notset what to return if the setting is not available
+ * @return mixed
+ */
+ public function getConf($setting, $notset = false);
+
+ /**
+ * merges the plugin's default settings with any local settings
+ * this function is automatically called through getConf()
+ *
+ * @todo this could be made protected and be moved to the trait only
+ */
+ public function loadConfig();
+
+ /**
+ * Loads a given helper plugin (if enabled)
+ *
+ * @author Esther Brunner <wikidesign@gmail.com>
+ *
+ * @param string $name name of plugin to load
+ * @param bool $msg if a message should be displayed in case the plugin is not available
+ * @return PluginInterface|null helper plugin object
+ */
+ public function loadHelper($name, $msg = true);
+
+ /**
+ * email
+ * standardised function to generate an email link according to obfuscation settings
+ *
+ * @param string $email
+ * @param string $name
+ * @param string $class
+ * @param string $more
+ * @return string html
+ */
+ public function email($email, $name = '', $class = '', $more = '');
+
+ /**
+ * external_link
+ * standardised function to generate an external link according to conf settings
+ *
+ * @param string $link
+ * @param string $title
+ * @param string $class
+ * @param string $target
+ * @param string $more
+ * @return string
+ */
+ public function external_link($link, $title = '', $class = '', $target = '', $more = '');
+
+ /**
+ * output text string through the parser, allows dokuwiki markup to be used
+ * very ineffecient for small pieces of data - try not to use
+ *
+ * @param string $text wiki markup to parse
+ * @param string $format output format
+ * @return null|string
+ */
+ public function render_text($text, $format = 'xhtml');
+
+ /**
+ * Allow the plugin to prevent DokuWiki from reusing an instance
+ *
+ * @return bool false if the plugin has to be instantiated
+ */
+ public function isSingleton();
+}
+
+
+
diff --git a/platform/www/inc/Extension/PluginTrait.php b/platform/www/inc/Extension/PluginTrait.php
new file mode 100644
index 0000000..f1db0f5
--- /dev/null
+++ b/platform/www/inc/Extension/PluginTrait.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+/**
+ * Provides standard DokuWiki plugin behaviour
+ */
+trait PluginTrait
+{
+
+ protected $localised = false; // set to true by setupLocale() after loading language dependent strings
+ protected $lang = array(); // array to hold language dependent strings, best accessed via ->getLang()
+ protected $configloaded = false; // set to true by loadConfig() after loading plugin configuration variables
+ protected $conf = array(); // array to hold plugin settings, best accessed via ->getConf()
+
+ /**
+ * @see PluginInterface::getInfo()
+ */
+ public function getInfo()
+ {
+ $parts = explode('_', get_class($this));
+ $info = DOKU_PLUGIN . '/' . $parts[2] . '/plugin.info.txt';
+ if (file_exists($info)) return confToHash($info);
+
+ msg(
+ 'getInfo() not implemented in ' . get_class($this) . ' and ' . $info . ' not found.<br />' .
+ 'Verify you\'re running the latest version of the plugin. If the problem persists, send a ' .
+ 'bug report to the author of the ' . $parts[2] . ' plugin.', -1
+ );
+ return array(
+ 'date' => '0000-00-00',
+ 'name' => $parts[2] . ' plugin',
+ );
+ }
+
+ /**
+ * @see PluginInterface::isSingleton()
+ */
+ public function isSingleton()
+ {
+ return true;
+ }
+
+ /**
+ * @see PluginInterface::loadHelper()
+ */
+ public function loadHelper($name, $msg = true)
+ {
+ $obj = plugin_load('helper', $name);
+ if (is_null($obj) && $msg) msg("Helper plugin $name is not available or invalid.", -1);
+ return $obj;
+ }
+
+ // region introspection methods
+
+ /**
+ * @see PluginInterface::getPluginType()
+ */
+ public function getPluginType()
+ {
+ list($t) = explode('_', get_class($this), 2);
+ return $t;
+ }
+
+ /**
+ * @see PluginInterface::getPluginName()
+ */
+ public function getPluginName()
+ {
+ list(/* $t */, /* $p */, $n) = explode('_', get_class($this), 4);
+ return $n;
+ }
+
+ /**
+ * @see PluginInterface::getPluginComponent()
+ */
+ public function getPluginComponent()
+ {
+ list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_class($this), 4);
+ return (isset($c) ? $c : '');
+ }
+
+ // endregion
+ // region localization methods
+
+ /**
+ * @see PluginInterface::getLang()
+ */
+ public function getLang($id)
+ {
+ if (!$this->localised) $this->setupLocale();
+
+ return (isset($this->lang[$id]) ? $this->lang[$id] : '');
+ }
+
+ /**
+ * @see PluginInterface::locale_xhtml()
+ */
+ public function locale_xhtml($id)
+ {
+ return p_cached_output($this->localFN($id));
+ }
+
+ /**
+ * @see PluginInterface::localFN()
+ */
+ public function localFN($id, $ext = 'txt')
+ {
+ global $conf;
+ $plugin = $this->getPluginName();
+ $file = DOKU_CONF . 'plugin_lang/' . $plugin . '/' . $conf['lang'] . '/' . $id . '.' . $ext;
+ if (!file_exists($file)) {
+ $file = DOKU_PLUGIN . $plugin . '/lang/' . $conf['lang'] . '/' . $id . '.' . $ext;
+ if (!file_exists($file)) {
+ //fall back to english
+ $file = DOKU_PLUGIN . $plugin . '/lang/en/' . $id . '.' . $ext;
+ }
+ }
+ return $file;
+ }
+
+ /**
+ * @see PluginInterface::setupLocale()
+ */
+ public function setupLocale()
+ {
+ if ($this->localised) return;
+
+ global $conf, $config_cascade; // definitely don't invoke "global $lang"
+ $path = DOKU_PLUGIN . $this->getPluginName() . '/lang/';
+
+ $lang = array();
+
+ // don't include once, in case several plugin components require the same language file
+ @include($path . 'en/lang.php');
+ foreach ($config_cascade['lang']['plugin'] as $config_file) {
+ if (file_exists($config_file . $this->getPluginName() . '/en/lang.php')) {
+ include($config_file . $this->getPluginName() . '/en/lang.php');
+ }
+ }
+
+ if ($conf['lang'] != 'en') {
+ @include($path . $conf['lang'] . '/lang.php');
+ foreach ($config_cascade['lang']['plugin'] as $config_file) {
+ if (file_exists($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php')) {
+ include($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php');
+ }
+ }
+ }
+
+ $this->lang = $lang;
+ $this->localised = true;
+ }
+
+ // endregion
+ // region configuration methods
+
+ /**
+ * @see PluginInterface::getConf()
+ */
+ public function getConf($setting, $notset = false)
+ {
+
+ if (!$this->configloaded) {
+ $this->loadConfig();
+ }
+
+ if (isset($this->conf[$setting])) {
+ return $this->conf[$setting];
+ } else {
+ return $notset;
+ }
+ }
+
+ /**
+ * @see PluginInterface::loadConfig()
+ */
+ public function loadConfig()
+ {
+ global $conf;
+
+ $defaults = $this->readDefaultSettings();
+ $plugin = $this->getPluginName();
+
+ foreach ($defaults as $key => $value) {
+ if (isset($conf['plugin'][$plugin][$key])) continue;
+ $conf['plugin'][$plugin][$key] = $value;
+ }
+
+ $this->configloaded = true;
+ $this->conf =& $conf['plugin'][$plugin];
+ }
+
+ /**
+ * read the plugin's default configuration settings from conf/default.php
+ * this function is automatically called through getConf()
+ *
+ * @return array setting => value
+ */
+ protected function readDefaultSettings()
+ {
+
+ $path = DOKU_PLUGIN . $this->getPluginName() . '/conf/';
+ $conf = array();
+
+ if (file_exists($path . 'default.php')) {
+ include($path . 'default.php');
+ }
+
+ return $conf;
+ }
+
+ // endregion
+ // region output methods
+
+ /**
+ * @see PluginInterface::email()
+ */
+ public function email($email, $name = '', $class = '', $more = '')
+ {
+ if (!$email) return $name;
+ $email = obfuscate($email);
+ if (!$name) $name = $email;
+ $class = "class='" . ($class ? $class : 'mail') . "'";
+ return "<a href='mailto:$email' $class title='$email' $more>$name</a>";
+ }
+
+ /**
+ * @see PluginInterface::external_link()
+ */
+ public function external_link($link, $title = '', $class = '', $target = '', $more = '')
+ {
+ global $conf;
+
+ $link = htmlentities($link);
+ if (!$title) $title = $link;
+ if (!$target) $target = $conf['target']['extern'];
+ if ($conf['relnofollow']) $more .= ' rel="nofollow"';
+
+ if ($class) $class = " class='$class'";
+ if ($target) $target = " target='$target'";
+ if ($more) $more = " " . trim($more);
+
+ return "<a href='$link'$class$target$more>$title</a>";
+ }
+
+ /**
+ * @see PluginInterface::render_text()
+ */
+ public function render_text($text, $format = 'xhtml')
+ {
+ return p_render($format, p_get_instructions($text), $info);
+ }
+
+ // endregion
+}
diff --git a/platform/www/inc/Extension/RemotePlugin.php b/platform/www/inc/Extension/RemotePlugin.php
new file mode 100644
index 0000000..33bca98
--- /dev/null
+++ b/platform/www/inc/Extension/RemotePlugin.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+use dokuwiki\Remote\Api;
+use ReflectionException;
+use ReflectionMethod;
+
+/**
+ * Remote Plugin prototype
+ *
+ * Add functionality to the remote API in a plugin
+ */
+abstract class RemotePlugin extends Plugin
+{
+
+ private $api;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->api = new Api();
+ }
+
+ /**
+ * Get all available methods with remote access.
+ *
+ * By default it exports all public methods of a remote plugin. Methods beginning
+ * with an underscore are skipped.
+ *
+ * @return array Information about all provided methods. {@see dokuwiki\Remote\RemoteAPI}.
+ * @throws ReflectionException
+ */
+ public function _getMethods()
+ {
+ $result = array();
+
+ $reflection = new \ReflectionClass($this);
+ foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+ // skip parent methods, only methods further down are exported
+ $declaredin = $method->getDeclaringClass()->name;
+ if ($declaredin === 'dokuwiki\Extension\Plugin' || $declaredin === 'dokuwiki\Extension\RemotePlugin') {
+ continue;
+ }
+ $method_name = $method->name;
+ if (strpos($method_name, '_') === 0) {
+ continue;
+ }
+
+ // strip asterisks
+ $doc = $method->getDocComment();
+ $doc = preg_replace(
+ array('/^[ \t]*\/\*+[ \t]*/m', '/[ \t]*\*+[ \t]*/m', '/\*+\/\s*$/m', '/\s*\/\s*$/m'),
+ array('', '', '', ''),
+ $doc
+ );
+
+ // prepare data
+ $data = array();
+ $data['name'] = $method_name;
+ $data['public'] = 0;
+ $data['doc'] = $doc;
+ $data['args'] = array();
+
+ // get parameter type from doc block type hint
+ foreach ($method->getParameters() as $parameter) {
+ $name = $parameter->name;
+ $type = 'string'; // we default to string
+ if (preg_match('/^@param[ \t]+([\w|\[\]]+)[ \t]\$' . $name . '/m', $doc, $m)) {
+ $type = $this->cleanTypeHint($m[1]);
+ }
+ $data['args'][] = $type;
+ }
+
+ // get return type from doc block type hint
+ if (preg_match('/^@return[ \t]+([\w|\[\]]+)/m', $doc, $m)) {
+ $data['return'] = $this->cleanTypeHint($m[1]);
+ } else {
+ $data['return'] = 'string';
+ }
+
+ // add to result
+ $result[$method_name] = $data;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Matches the given type hint against the valid options for the remote API
+ *
+ * @param string $hint
+ * @return string
+ */
+ protected function cleanTypeHint($hint)
+ {
+ $types = explode('|', $hint);
+ foreach ($types as $t) {
+ if (substr($t, -2) === '[]') {
+ return 'array';
+ }
+ if ($t === 'boolean') {
+ return 'bool';
+ }
+ if (in_array($t, array('array', 'string', 'int', 'double', 'bool', 'null', 'date', 'file'))) {
+ return $t;
+ }
+ }
+ return 'string';
+ }
+
+ /**
+ * @return Api
+ */
+ protected function getApi()
+ {
+ return $this->api;
+ }
+
+}
diff --git a/platform/www/inc/Extension/SyntaxPlugin.php b/platform/www/inc/Extension/SyntaxPlugin.php
new file mode 100644
index 0000000..ea8f51b
--- /dev/null
+++ b/platform/www/inc/Extension/SyntaxPlugin.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace dokuwiki\Extension;
+
+use Doku_Handler;
+use Doku_Renderer;
+
+/**
+ * Syntax Plugin Prototype
+ *
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+abstract class SyntaxPlugin extends \dokuwiki\Parsing\ParserMode\Plugin
+{
+ use PluginTrait;
+
+ protected $allowedModesSetup = false;
+
+ /**
+ * Syntax Type
+ *
+ * Needs to return one of the mode types defined in $PARSER_MODES in Parser.php
+ *
+ * @return string
+ */
+ abstract public function getType();
+
+ /**
+ * Allowed Mode Types
+ *
+ * Defines the mode types for other dokuwiki markup that maybe nested within the
+ * plugin's own markup. Needs to return an array of one or more of the mode types
+ * defined in $PARSER_MODES in Parser.php
+ *
+ * @return array
+ */
+ public function getAllowedTypes()
+ {
+ return array();
+ }
+
+ /**
+ * Paragraph Type
+ *
+ * Defines how this syntax is handled regarding paragraphs. This is important
+ * for correct XHTML nesting. Should return one of the following:
+ *
+ * 'normal' - The plugin can be used inside paragraphs
+ * 'block' - Open paragraphs need to be closed before plugin output
+ * 'stack' - Special case. Plugin wraps other paragraphs.
+ *
+ * @see Doku_Handler_Block
+ *
+ * @return string
+ */
+ public function getPType()
+ {
+ return 'normal';
+ }
+
+ /**
+ * Handler to prepare matched data for the rendering process
+ *
+ * This function can only pass data to render() via its return value - render()
+ * may be not be run during the object's current life.
+ *
+ * Usually you should only need the $match param.
+ *
+ * @param string $match The text matched by the patterns
+ * @param int $state The lexer state for the match
+ * @param int $pos The character position of the matched text
+ * @param Doku_Handler $handler The Doku_Handler object
+ * @return bool|array Return an array with all data you want to use in render, false don't add an instruction
+ */
+ abstract public function handle($match, $state, $pos, Doku_Handler $handler);
+
+ /**
+ * Handles the actual output creation.
+ *
+ * The function must not assume any other of the classes methods have been run
+ * during the object's current life. The only reliable data it receives are its
+ * parameters.
+ *
+ * The function should always check for the given output format and return false
+ * when a format isn't supported.
+ *
+ * $renderer contains a reference to the renderer object which is
+ * currently handling the rendering. You need to use it for writing
+ * the output. How this is done depends on the renderer used (specified
+ * by $format
+ *
+ * The contents of the $data array depends on what the handler() function above
+ * created
+ *
+ * @param string $format output format being rendered
+ * @param Doku_Renderer $renderer the current renderer object
+ * @param array $data data created by handler()
+ * @return boolean rendered correctly? (however, returned value is not used at the moment)
+ */
+ abstract public function render($format, Doku_Renderer $renderer, $data);
+
+ /**
+ * There should be no need to override this function
+ *
+ * @param string $mode
+ * @return bool
+ */
+ public function accepts($mode)
+ {
+
+ if (!$this->allowedModesSetup) {
+ global $PARSER_MODES;
+
+ $allowedModeTypes = $this->getAllowedTypes();
+ foreach ($allowedModeTypes as $mt) {
+ $this->allowedModes = array_merge($this->allowedModes, $PARSER_MODES[$mt]);
+ }
+
+ $idx = array_search(substr(get_class($this), 7), (array)$this->allowedModes);
+ if ($idx !== false) {
+ unset($this->allowedModes[$idx]);
+ }
+ $this->allowedModesSetup = true;
+ }
+
+ return parent::accepts($mode);
+ }
+}