summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/config
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib/plugins/config')
-rw-r--r--platform/www/lib/plugins/config/admin.php282
-rw-r--r--platform/www/lib/plugins/config/admin.svg1
-rw-r--r--platform/www/lib/plugins/config/core/ConfigParser.php90
-rw-r--r--platform/www/lib/plugins/config/core/Configuration.php219
-rw-r--r--platform/www/lib/plugins/config/core/Loader.php269
-rw-r--r--platform/www/lib/plugins/config/core/Setting/Setting.php294
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingArray.php105
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php60
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingCompression.php21
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php33
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php23
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingEmail.php58
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingFieldset.php17
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingHidden.php10
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingImConvert.php28
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingLicense.php23
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php163
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php71
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoClass.php12
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php13
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php11
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNumeric.php42
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php25
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingOnoff.php57
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingPassword.php39
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingRegex.php34
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingRenderer.php56
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingSavedir.php26
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingSepchar.php18
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingString.php32
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingUndefined.php40
-rw-r--r--platform/www/lib/plugins/config/core/Writer.php116
-rw-r--r--platform/www/lib/plugins/config/images/danger.pngbin0 -> 637 bytes
-rw-r--r--platform/www/lib/plugins/config/images/security.pngbin0 -> 682 bytes
-rw-r--r--platform/www/lib/plugins/config/images/warning.pngbin0 -> 606 bytes
-rw-r--r--platform/www/lib/plugins/config/lang/en/intro.txt7
-rw-r--r--platform/www/lib/plugins/config/lang/en/lang.php277
-rw-r--r--platform/www/lib/plugins/config/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/config/settings/config.metadata.php245
-rw-r--r--platform/www/lib/plugins/config/style.css167
40 files changed, 2991 insertions, 0 deletions
diff --git a/platform/www/lib/plugins/config/admin.php b/platform/www/lib/plugins/config/admin.php
new file mode 100644
index 0000000..219612c
--- /dev/null
+++ b/platform/www/lib/plugins/config/admin.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * Configuration Manager admin plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+
+use dokuwiki\plugin\config\core\Configuration;
+use dokuwiki\plugin\config\core\Setting\Setting;
+use dokuwiki\plugin\config\core\Setting\SettingFieldset;
+use dokuwiki\plugin\config\core\Setting\SettingHidden;
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_config extends DokuWiki_Admin_Plugin {
+
+ const IMGDIR = DOKU_BASE . 'lib/plugins/config/images/';
+
+ /** @var Configuration */
+ protected $configuration;
+
+ /** @var bool were there any errors in the submitted data? */
+ protected $hasErrors = false;
+
+ /** @var bool have the settings translations been loaded? */
+ protected $promptsLocalized = false;
+
+
+ /**
+ * handle user request
+ */
+ public function handle() {
+ global $ID, $INPUT;
+
+ // always initialize the configuration
+ $this->configuration = new Configuration();
+
+ if(!$INPUT->bool('save') || !checkSecurityToken()) {
+ return;
+ }
+
+ // don't go any further if the configuration is locked
+ if($this->configuration->isLocked()) return;
+
+ // update settings and redirect of successful
+ $ok = $this->configuration->updateSettings($INPUT->arr('config'));
+ if($ok) { // no errors
+ try {
+ if($this->configuration->hasChanged()) {
+ $this->configuration->save();
+ } else {
+ $this->configuration->touch();
+ }
+ msg($this->getLang('updated'), 1);
+ } catch(Exception $e) {
+ msg($this->getLang('error'), -1);
+ }
+ send_redirect(wl($ID, array('do' => 'admin', 'page' => 'config'), true, '&'));
+ } else {
+ $this->hasErrors = true;
+ }
+ }
+
+ /**
+ * output appropriate html
+ */
+ public function html() {
+ $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
+ global $lang;
+ global $ID;
+
+ $this->setupLocale(true);
+
+ echo $this->locale_xhtml('intro');
+
+ echo '<div id="config__manager">';
+
+ if($this->configuration->isLocked()) {
+ echo '<div class="info">' . $this->getLang('locked') . '</div>';
+ }
+
+ // POST to script() instead of wl($ID) so config manager still works if
+ // rewrite config is broken. Add $ID as hidden field to remember
+ // current ID in most cases.
+ echo '<form id="dw__configform" action="' . script() . '" method="post">';
+ echo '<div class="no"><input type="hidden" name="id" value="' . $ID . '" /></div>';
+ formSecurityToken();
+ $this->printH1('dokuwiki_settings', $this->getLang('_header_dokuwiki'));
+
+ $in_fieldset = false;
+ $first_plugin_fieldset = true;
+ $first_template_fieldset = true;
+ foreach($this->configuration->getSettings() as $setting) {
+ if(is_a($setting, SettingHidden::class)) {
+ continue;
+ } else if(is_a($setting, settingFieldset::class)) {
+ // config setting group
+ if($in_fieldset) {
+ echo '</table>';
+ echo '</div>';
+ echo '</fieldset>';
+ } else {
+ $in_fieldset = true;
+ }
+ if($first_plugin_fieldset && $setting->getType() == 'plugin') {
+ $this->printH1('plugin_settings', $this->getLang('_header_plugin'));
+ $first_plugin_fieldset = false;
+ } else if($first_template_fieldset && $setting->getType() == 'template') {
+ $this->printH1('template_settings', $this->getLang('_header_template'));
+ $first_template_fieldset = false;
+ }
+ echo '<fieldset id="' . $setting->getKey() . '">';
+ echo '<legend>' . $setting->prompt($this) . '</legend>';
+ echo '<div class="table">';
+ echo '<table class="inline">';
+ } else {
+ // config settings
+ list($label, $input) = $setting->html($this, $this->hasErrors);
+
+ $class = $setting->isDefault()
+ ? ' class="default"'
+ : ($setting->isProtected() ? ' class="protected"' : '');
+ $error = $setting->hasError()
+ ? ' class="value error"'
+ : ' class="value"';
+ $icon = $setting->caution()
+ ? '<img src="' . self::IMGDIR . $setting->caution() . '.png" ' .
+ 'alt="' . $setting->caution() . '" title="' . $this->getLang($setting->caution()) . '" />'
+ : '';
+
+ echo '<tr' . $class . '>';
+ echo '<td class="label">';
+ echo '<span class="outkey">' . $setting->getPrettyKey() . '</span>';
+ echo $icon . $label;
+ echo '</td>';
+ echo '<td' . $error . '>' . $input . '</td>';
+ echo '</tr>';
+ }
+ }
+
+ echo '</table>';
+ echo '</div>';
+ if($in_fieldset) {
+ echo '</fieldset>';
+ }
+
+ // show undefined settings list
+ $undefined_settings = $this->configuration->getUndefined();
+ if($allow_debug && !empty($undefined_settings)) {
+ /**
+ * Callback for sorting settings
+ *
+ * @param Setting $a
+ * @param Setting $b
+ * @return int if $a is lower/equal/higher than $b
+ */
+ function settingNaturalComparison($a, $b) {
+ return strnatcmp($a->getKey(), $b->getKey());
+ }
+
+ usort($undefined_settings, 'settingNaturalComparison');
+ $this->printH1('undefined_settings', $this->getLang('_header_undefined'));
+ echo '<fieldset>';
+ echo '<div class="table">';
+ echo '<table class="inline">';
+ foreach($undefined_settings as $setting) {
+ list($label, $input) = $setting->html($this);
+ echo '<tr>';
+ echo '<td class="label">' . $label . '</td>';
+ echo '<td>' . $input . '</td>';
+ echo '</tr>';
+ }
+ echo '</table>';
+ echo '</div>';
+ echo '</fieldset>';
+ }
+
+ // finish up form
+ echo '<p>';
+ echo '<input type="hidden" name="do" value="admin" />';
+ echo '<input type="hidden" name="page" value="config" />';
+
+ if(!$this->configuration->isLocked()) {
+ echo '<input type="hidden" name="save" value="1" />';
+ echo '<button type="submit" name="submit" accesskey="s">' . $lang['btn_save'] . '</button>';
+ echo '<button type="reset">' . $lang['btn_reset'] . '</button>';
+ }
+
+ echo '</p>';
+
+ echo '</form>';
+ echo '</div>';
+ }
+
+ /**
+ * @param bool $prompts
+ */
+ public function setupLocale($prompts = false) {
+ parent::setupLocale();
+ if(!$prompts || $this->promptsLocalized) return;
+ $this->lang = array_merge($this->lang, $this->configuration->getLangs());
+ $this->promptsLocalized = true;
+ }
+
+ /**
+ * Generates a two-level table of contents for the config plugin.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ *
+ * @return array
+ */
+ public function getTOC() {
+ $this->setupLocale(true);
+
+ $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
+ $toc = array();
+ $check = false;
+
+ // gather settings data into three sub arrays
+ $labels = ['dokuwiki' => [], 'plugin' => [], 'template' => []];
+ foreach($this->configuration->getSettings() as $setting) {
+ if(is_a($setting, SettingFieldset::class)) {
+ $labels[$setting->getType()][] = $setting;
+ }
+ }
+
+ // top header
+ $title = $this->getLang('_configuration_manager');
+ $toc[] = html_mktocitem(sectionID($title, $check), $title, 1);
+
+ // main entries
+ foreach(['dokuwiki', 'plugin', 'template'] as $section) {
+ if(empty($labels[$section])) continue; // no entries, skip
+
+ // create main header
+ $toc[] = html_mktocitem(
+ $section . '_settings',
+ $this->getLang('_header_' . $section),
+ 1
+ );
+
+ // create sub headers
+ foreach($labels[$section] as $setting) {
+ /** @var SettingFieldset $setting */
+ $name = $setting->prompt($this);
+ $toc[] = html_mktocitem($setting->getKey(), $name, 2);
+ }
+ }
+
+ // undefined settings if allowed
+ if(count($this->configuration->getUndefined()) && $allow_debug) {
+ $toc[] = html_mktocitem('undefined_settings', $this->getLang('_header_undefined'), 1);
+ }
+
+ return $toc;
+ }
+
+ /**
+ * @param string $id
+ * @param string $text
+ */
+ protected function printH1($id, $text) {
+ echo '<h1 id="' . $id . '">' . $text . '</h1>';
+ }
+
+ /**
+ * Adds a translation to this plugin's language array
+ *
+ * Used by some settings to set up dynamic translations
+ *
+ * @param string $key
+ * @param string $value
+ */
+ public function addLang($key, $value) {
+ if(!$this->localised) $this->setupLocale();
+ $this->lang[$key] = $value;
+ }
+}
diff --git a/platform/www/lib/plugins/config/admin.svg b/platform/www/lib/plugins/config/admin.svg
new file mode 100644
index 0000000..ced9871
--- /dev/null
+++ b/platform/www/lib/plugins/config/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/config/core/ConfigParser.php b/platform/www/lib/plugins/config/core/ConfigParser.php
new file mode 100644
index 0000000..9e79b96
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/ConfigParser.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+/**
+ * A naive PHP file parser
+ *
+ * This parses our very simple config file in PHP format. We use this instead of simply including
+ * the file, because we want to keep expressions such as 24*60*60 as is.
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+class ConfigParser {
+ /** @var string variable to parse from the file */
+ protected $varname = 'conf';
+ /** @var string the key to mark sub arrays */
+ protected $keymarker = Configuration::KEYMARKER;
+
+ /**
+ * Parse the given PHP file into an array
+ *
+ * When the given files does not exist, this returns an empty array
+ *
+ * @param string $file
+ * @return array
+ */
+ public function parse($file) {
+ if(!file_exists($file)) return array();
+
+ $config = array();
+ $contents = @php_strip_whitespace($file);
+ $pattern = '/\$' . $this->varname . '\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$' . $this->varname . '|$))/s';
+ $matches = array();
+ preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER);
+
+ for($i = 0; $i < count($matches); $i++) {
+ $value = $matches[$i][2];
+
+ // merge multi-dimensional array indices using the keymarker
+ $key = preg_replace('/.\]\[./', $this->keymarker, $matches[$i][1]);
+
+ // handle arrays
+ if(preg_match('/^array ?\((.*)\)/', $value, $match)) {
+ $arr = explode(',', $match[1]);
+
+ // remove quotes from quoted strings & unescape escaped data
+ $len = count($arr);
+ for($j = 0; $j < $len; $j++) {
+ $arr[$j] = trim($arr[$j]);
+ $arr[$j] = $this->readValue($arr[$j]);
+ }
+
+ $value = $arr;
+ } else {
+ $value = $this->readValue($value);
+ }
+
+ $config[$key] = $value;
+ }
+
+ return $config;
+ }
+
+ /**
+ * Convert php string into value
+ *
+ * @param string $value
+ * @return bool|string
+ */
+ protected function readValue($value) {
+ $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s';
+ $unescape_pairs = array(
+ '\\\\' => '\\',
+ '\\\'' => '\'',
+ '\\"' => '"'
+ );
+
+ if($value == 'true') {
+ $value = true;
+ } elseif($value == 'false') {
+ $value = false;
+ } else {
+ // remove quotes from quoted strings & unescape escaped data
+ $value = preg_replace($removequotes_pattern, '$2', $value);
+ $value = strtr($value, $unescape_pairs);
+ }
+ return $value;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Configuration.php b/platform/www/lib/plugins/config/core/Configuration.php
new file mode 100644
index 0000000..c58645c
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Configuration.php
@@ -0,0 +1,219 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+use dokuwiki\plugin\config\core\Setting\Setting;
+use dokuwiki\plugin\config\core\Setting\SettingNoClass;
+use dokuwiki\plugin\config\core\Setting\SettingNoDefault;
+use dokuwiki\plugin\config\core\Setting\SettingNoKnownClass;
+use dokuwiki\plugin\config\core\Setting\SettingUndefined;
+
+/**
+ * Holds all the current settings and proxies the Loader and Writer
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class Configuration {
+
+ const KEYMARKER = '____';
+
+ /** @var Setting[] metadata as array of Settings objects */
+ protected $settings = array();
+ /** @var Setting[] undefined and problematic settings */
+ protected $undefined = array();
+
+ /** @var array all metadata */
+ protected $metadata;
+ /** @var array all default settings */
+ protected $default;
+ /** @var array all local settings */
+ protected $local;
+ /** @var array all protected settings */
+ protected $protected;
+
+ /** @var bool have the settings been changed since loading from disk? */
+ protected $changed = false;
+
+ /** @var Loader */
+ protected $loader;
+ /** @var Writer */
+ protected $writer;
+
+ /**
+ * ConfigSettings constructor.
+ */
+ public function __construct() {
+ $this->loader = new Loader(new ConfigParser());
+ $this->writer = new Writer();
+
+ $this->metadata = $this->loader->loadMeta();
+ $this->default = $this->loader->loadDefaults();
+ $this->local = $this->loader->loadLocal();
+ $this->protected = $this->loader->loadProtected();
+
+ $this->initSettings();
+ }
+
+ /**
+ * Get all settings
+ *
+ * @return Setting[]
+ */
+ public function getSettings() {
+ return $this->settings;
+ }
+
+ /**
+ * Get all unknown or problematic settings
+ *
+ * @return Setting[]
+ */
+ public function getUndefined() {
+ return $this->undefined;
+ }
+
+ /**
+ * Have the settings been changed since loading from disk?
+ *
+ * @return bool
+ */
+ public function hasChanged() {
+ return $this->changed;
+ }
+
+ /**
+ * Check if the config can be written
+ *
+ * @return bool
+ */
+ public function isLocked() {
+ return $this->writer->isLocked();
+ }
+
+ /**
+ * Update the settings using the data provided
+ *
+ * @param array $input as posted
+ * @return bool true if all updates went through, false on errors
+ */
+ public function updateSettings($input) {
+ $ok = true;
+
+ foreach($this->settings as $key => $obj) {
+ $value = isset($input[$key]) ? $input[$key] : null;
+ if($obj->update($value)) {
+ $this->changed = true;
+ }
+ if($obj->hasError()) $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Save the settings
+ *
+ * This save the current state as defined in this object, including the
+ * undefined settings
+ *
+ * @throws \Exception
+ */
+ public function save() {
+ // only save the undefined settings that have not been handled in settings
+ $undefined = array_diff_key($this->undefined, $this->settings);
+ $this->writer->save(array_merge($this->settings, $undefined));
+ }
+
+ /**
+ * Touch the settings
+ *
+ * @throws \Exception
+ */
+ public function touch() {
+ $this->writer->touch();
+ }
+
+ /**
+ * Load the extension language strings
+ *
+ * @return array
+ */
+ public function getLangs() {
+ return $this->loader->loadLangs();
+ }
+
+ /**
+ * Initalizes the $settings and $undefined properties
+ */
+ protected function initSettings() {
+ $keys = array_merge(
+ array_keys($this->metadata),
+ array_keys($this->default),
+ array_keys($this->local),
+ array_keys($this->protected)
+ );
+ $keys = array_unique($keys);
+
+ foreach($keys as $key) {
+ $obj = $this->instantiateClass($key);
+
+ if($obj->shouldHaveDefault() && !isset($this->default[$key])) {
+ $this->undefined[$key] = new SettingNoDefault($key);
+ }
+
+ $d = isset($this->default[$key]) ? $this->default[$key] : null;
+ $l = isset($this->local[$key]) ? $this->local[$key] : null;
+ $p = isset($this->protected[$key]) ? $this->protected[$key] : null;
+
+ $obj->initialize($d, $l, $p);
+ }
+ }
+
+ /**
+ * Instantiates the proper class for the given config key
+ *
+ * The class is added to the $settings or $undefined arrays and returned
+ *
+ * @param string $key
+ * @return Setting
+ */
+ protected function instantiateClass($key) {
+ if(isset($this->metadata[$key])) {
+ $param = $this->metadata[$key];
+ $class = $this->determineClassName(array_shift($param), $key); // first param is class
+ $obj = new $class($key, $param);
+ $this->settings[$key] = $obj;
+ } else {
+ $obj = new SettingUndefined($key);
+ $this->undefined[$key] = $obj;
+ }
+ return $obj;
+ }
+
+ /**
+ * Return the class to load
+ *
+ * @param string $class the class name as given in the meta file
+ * @param string $key the settings key
+ * @return string
+ */
+ protected function determineClassName($class, $key) {
+ // try namespaced class first
+ if(is_string($class)) {
+ $modern = str_replace('_', '', ucwords($class, '_'));
+ $modern = '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting' . $modern;
+ if($modern && class_exists($modern)) return $modern;
+ // try class as given
+ if(class_exists($class)) return $class;
+ // class wasn't found add to errors
+ $this->undefined[$key] = new SettingNoKnownClass($key);
+ } else {
+ // no class given, add to errors
+ $this->undefined[$key] = new SettingNoClass($key);
+ }
+ return '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting';
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Loader.php b/platform/www/lib/plugins/config/core/Loader.php
new file mode 100644
index 0000000..90ad0f5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Loader.php
@@ -0,0 +1,269 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+use dokuwiki\Extension\Event;
+
+/**
+ * Configuration loader
+ *
+ * Loads configuration meta data and settings from the various files. Honors the
+ * configuration cascade and installed plugins.
+ */
+class Loader {
+ /** @var ConfigParser */
+ protected $parser;
+
+ /** @var string[] list of enabled plugins */
+ protected $plugins;
+ /** @var string current template */
+ protected $template;
+
+ /**
+ * Loader constructor.
+ * @param ConfigParser $parser
+ * @triggers PLUGIN_CONFIG_PLUGINLIST
+ */
+ public function __construct(ConfigParser $parser) {
+ global $conf;
+ $this->parser = $parser;
+ $this->plugins = plugin_list();
+ $this->template = $conf['template'];
+ // allow plugins to remove configurable plugins
+ Event::createAndTrigger('PLUGIN_CONFIG_PLUGINLIST', $this->plugins);
+ }
+
+ /**
+ * Read the settings meta data
+ *
+ * Reads the main file, plugins and template settings meta data
+ *
+ * @return array
+ */
+ public function loadMeta() {
+ // load main file
+ $meta = array();
+ include DOKU_PLUGIN . 'config/settings/config.metadata.php';
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $meta = array_merge(
+ $meta,
+ $this->loadExtensionMeta(
+ DOKU_PLUGIN . $plugin . '/conf/metadata.php',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $meta = array_merge(
+ $meta,
+ $this->loadExtensionMeta(
+ tpl_incdir() . '/conf/metadata.php',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $meta;
+ }
+
+ /**
+ * Read the default values
+ *
+ * Reads the main file, plugins and template defaults
+ *
+ * @return array
+ */
+ public function loadDefaults() {
+ // load main files
+ global $config_cascade;
+ $conf = $this->loadConfigs($config_cascade['main']['default']);
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $conf = array_merge(
+ $conf,
+ $this->loadExtensionConf(
+ DOKU_PLUGIN . $plugin . '/conf/default.php',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $conf = array_merge(
+ $conf,
+ $this->loadExtensionConf(
+ tpl_incdir() . '/conf/default.php',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $conf;
+ }
+
+ /**
+ * Reads the language strings
+ *
+ * Only reads extensions, main one is loaded the usual way
+ *
+ * @return array
+ */
+ public function loadLangs() {
+ $lang = array();
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $lang = array_merge(
+ $lang,
+ $this->loadExtensionLang(
+ DOKU_PLUGIN . $plugin . '/',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $lang = array_merge(
+ $lang,
+ $this->loadExtensionLang(
+ tpl_incdir() . '/',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $lang;
+ }
+
+ /**
+ * Read the local settings
+ *
+ * @return array
+ */
+ public function loadLocal() {
+ global $config_cascade;
+ return $this->loadConfigs($config_cascade['main']['local']);
+ }
+
+ /**
+ * Read the protected settings
+ *
+ * @return array
+ */
+ public function loadProtected() {
+ global $config_cascade;
+ return $this->loadConfigs($config_cascade['main']['protected']);
+ }
+
+ /**
+ * Read the config values from the given files
+ *
+ * @param string[] $files paths to config php's
+ * @return array
+ */
+ protected function loadConfigs($files) {
+ $conf = array();
+ foreach($files as $file) {
+ $conf = array_merge($conf, $this->parser->parse($file));
+ }
+ return $conf;
+ }
+
+ /**
+ * Read settings file from an extension
+ *
+ * This is used to read the settings.php files of plugins and templates
+ *
+ * @param string $file php file to read
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionMeta($file, $type, $extname) {
+ if(!file_exists($file)) return array();
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // include file
+ $meta = array();
+ include $file;
+ if(empty($meta)) return array();
+
+ // read data
+ $data = array();
+ $data[$prefix . $type . '_settings_name'] = ['fieldset'];
+ foreach($meta as $key => $value) {
+ if($value[0] == 'fieldset') continue; //plugins only get one fieldset
+ $data[$prefix . $key] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read a default file from an extension
+ *
+ * This is used to read the default.php files of plugins and templates
+ *
+ * @param string $file php file to read
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionConf($file, $type, $extname) {
+ if(!file_exists($file)) return array();
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // parse file
+ $conf = $this->parser->parse($file);
+ if(empty($conf)) return array();
+
+ // read data
+ $data = array();
+ foreach($conf as $key => $value) {
+ $data[$prefix . $key] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read the language file of an extension
+ *
+ * @param string $dir directory of the extension
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionLang($dir, $type, $extname) {
+ global $conf;
+ $ll = $conf['lang'];
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // include files
+ $lang = array();
+ if(file_exists($dir . 'lang/en/settings.php')) {
+ include $dir . 'lang/en/settings.php';
+ }
+ if($ll != 'en' && file_exists($dir . 'lang/' . $ll . '/settings.php')) {
+ include $dir . 'lang/' . $ll . '/settings.php';
+ }
+
+ // set up correct keys
+ $strings = array();
+ foreach($lang as $key => $val) {
+ $strings[$prefix . $key] = $val;
+ }
+
+ // add fieldset key
+ $strings[$prefix . $type . '_settings_name'] = ucwords(str_replace('_', ' ', $extname));
+
+ return $strings;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/Setting.php b/platform/www/lib/plugins/config/core/Setting/Setting.php
new file mode 100644
index 0000000..d64f684
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/Setting.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+use dokuwiki\plugin\config\core\Configuration;
+
+/**
+ * Class Setting
+ */
+class Setting {
+ /** @var string unique identifier of this setting */
+ protected $key = '';
+
+ /** @var mixed the default value of this setting */
+ protected $default = null;
+ /** @var mixed the local value of this setting */
+ protected $local = null;
+ /** @var mixed the protected value of this setting */
+ protected $protected = null;
+
+ /** @var array valid alerts, images matching the alerts are in the plugin's images directory */
+ static protected $validCautions = array('warning', 'danger', 'security');
+
+ protected $pattern = '';
+ protected $error = false; // only used by those classes which error check
+ protected $input = null; // only used by those classes which error check
+ protected $caution = null; // used by any setting to provide an alert along with the setting
+
+ /**
+ * Constructor.
+ *
+ * The given parameters will be set up as class properties
+ *
+ * @see initialize() to set the actual value of the setting
+ *
+ * @param string $key
+ * @param array|null $params array with metadata of setting
+ */
+ public function __construct($key, $params = null) {
+ $this->key = $key;
+
+ if(is_array($params)) {
+ foreach($params as $property => $value) {
+ $property = trim($property, '_'); // we don't use underscores anymore
+ $this->$property = $value;
+ }
+ }
+ }
+
+ /**
+ * Set the current values for the setting $key
+ *
+ * This is used to initialize the setting with the data read form the config files.
+ *
+ * @see update() to set a new value
+ * @param mixed $default default setting value
+ * @param mixed $local local setting value
+ * @param mixed $protected protected setting value
+ */
+ public function initialize($default = null, $local = null, $protected = null) {
+ $this->default = $this->cleanValue($default);
+ $this->local = $this->cleanValue($local);
+ $this->protected = $this->cleanValue($protected);
+ }
+
+ /**
+ * update changed setting with validated user provided value $input
+ * - if changed value fails validation check, save it to $this->input (to allow echoing later)
+ * - if changed value passes validation check, set $this->local to the new value
+ *
+ * @param mixed $input the new value
+ * @return boolean true if changed, false otherwise
+ */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+ $input = $this->cleanValue($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ // validate new value
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ // update local copy of this setting with new value
+ $this->local = $input;
+
+ // setting ready for update
+ return true;
+ }
+
+ /**
+ * Clean a value read from a config before using it internally
+ *
+ * Default implementation returns $value as is. Subclasses can override.
+ * Note: null should always be returned as null!
+ *
+ * This is applied in initialize() and update()
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function cleanValue($value) {
+ return $value;
+ }
+
+ /**
+ * Should this type of config have a default?
+ *
+ * @return bool
+ */
+ public function shouldHaveDefault() {
+ return true;
+ }
+
+ /**
+ * Get this setting's unique key
+ *
+ * @return string
+ */
+ public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * Get the key of this setting marked up human readable
+ *
+ * @param bool $url link to dokuwiki.org manual?
+ * @return string
+ */
+ public function getPrettyKey($url = true) {
+ $out = str_replace(Configuration::KEYMARKER, "»", $this->key);
+ if($url && !strstr($out, '»')) {//provide no urls for plugins, etc.
+ if($out == 'start') {
+ // exception, because this config name is clashing with our actual start page
+ return '<a href="http://www.dokuwiki.org/config:startpage">' . $out . '</a>';
+ } else {
+ return '<a href="http://www.dokuwiki.org/config:' . $out . '">' . $out . '</a>';
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Returns setting key as an array key separator
+ *
+ * This is used to create form output
+ *
+ * @return string key
+ */
+ public function getArrayKey() {
+ return str_replace(Configuration::KEYMARKER, "']['", $this->key);
+ }
+
+ /**
+ * What type of configuration is this
+ *
+ * Returns one of
+ *
+ * 'plugin' for plugin configuration
+ * 'template' for template configuration
+ * 'dokuwiki' for core configuration
+ *
+ * @return string
+ */
+ public function getType() {
+ if(substr($this->getKey(), 0, 10) == 'plugin' . Configuration::KEYMARKER) {
+ return 'plugin';
+ } else if(substr($this->getKey(), 0, 7) == 'tpl' . Configuration::KEYMARKER) {
+ return 'template';
+ } else {
+ return 'dokuwiki';
+ }
+ }
+
+ /**
+ * Build html for label and input of setting
+ *
+ * @param \admin_plugin_config $plugin object of config plugin
+ * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
+ * @return string[] with content array(string $label_html, string $input_html)
+ */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = formText($value);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<textarea rows="3" cols="40" id="config___' . $key .
+ '" name="config[' . $key . ']" class="edit" ' . $disable . '>' . $value . '</textarea>';
+ return array($label, $input);
+ }
+
+ /**
+ * Should the current local value be saved?
+ *
+ * @see out() to run when this returns true
+ * @return bool
+ */
+ public function shouldBeSaved() {
+ if($this->isProtected()) return false;
+ if($this->local === null) return false;
+ if($this->default == $this->local) return false;
+ return true;
+ }
+
+ /**
+ * Generate string to save local setting value to file according to $fmt
+ *
+ * @see shouldBeSaved() to check if this should be called
+ * @param string $var name of variable
+ * @param string $fmt save format
+ * @return string
+ */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $tr = array("\\" => '\\\\', "'" => '\\\''); // escape the value
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = '" . strtr(cleanText($this->local), $tr) . "';\n";
+
+ return $out;
+ }
+
+ /**
+ * Returns the localized prompt
+ *
+ * @param \admin_plugin_config $plugin object of config plugin
+ * @return string text
+ */
+ public function prompt(\admin_plugin_config $plugin) {
+ $prompt = $plugin->getLang($this->key);
+ if(!$prompt) $prompt = htmlspecialchars(str_replace(array('____', '_'), ' ', $this->key));
+ return $prompt;
+ }
+
+ /**
+ * Is setting protected
+ *
+ * @return bool
+ */
+ public function isProtected() {
+ return !is_null($this->protected);
+ }
+
+ /**
+ * Is setting the default?
+ *
+ * @return bool
+ */
+ public function isDefault() {
+ return !$this->isProtected() && is_null($this->local);
+ }
+
+ /**
+ * Has an error?
+ *
+ * @return bool
+ */
+ public function hasError() {
+ return $this->error;
+ }
+
+ /**
+ * Returns caution
+ *
+ * @return false|string caution string, otherwise false for invalid caution
+ */
+ public function caution() {
+ if(empty($this->caution)) return false;
+ if(!in_array($this->caution, Setting::$validCautions)) {
+ throw new \RuntimeException(
+ 'Invalid caution string (' . $this->caution . ') in metadata for setting "' . $this->key . '"'
+ );
+ }
+ return $this->caution;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingArray.php b/platform/www/lib/plugins/config/core/Setting/SettingArray.php
new file mode 100644
index 0000000..c48dc76
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingArray.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_array
+ */
+class SettingArray extends Setting {
+
+ /**
+ * Create an array from a string
+ *
+ * @param string $string
+ * @return array
+ */
+ protected function fromString($string) {
+ $array = explode(',', $string);
+ $array = array_map('trim', $array);
+ $array = array_filter($array);
+ $array = array_unique($array);
+ return $array;
+ }
+
+ /**
+ * Create a string from an array
+ *
+ * @param array $array
+ * @return string
+ */
+ protected function fromArray($array) {
+ return join(', ', (array) $array);
+ }
+
+ /**
+ * update setting with user provided value $input
+ * if value fails error check, save it
+ *
+ * @param string $input
+ * @return bool true if changed, false otherwise (incl. on error)
+ */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $input = $this->fromString($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ foreach($input as $item) {
+ if($this->pattern && !preg_match($this->pattern, $item)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+ }
+
+ $this->local = $input;
+ return true;
+ }
+
+ /**
+ * Escaping
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function escape($string) {
+ $tr = array("\\" => '\\\\', "'" => '\\\'');
+ return "'" . strtr(cleanText($string), $tr) . "'";
+ }
+
+ /** @inheritdoc */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $vals = array_map(array($this, 'escape'), $this->local);
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = array(" . join(', ', $vals) . ");\n";
+ return $out;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = htmlspecialchars($this->fromArray($value));
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php b/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php
new file mode 100644
index 0000000..3a6df6f
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_authtype
+ */
+class SettingAuthtype extends SettingMultichoice {
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ /** @var $plugin_controller \dokuwiki\Extension\PluginController */
+ global $plugin_controller;
+
+ // retrieve auth types provided by plugins
+ foreach($plugin_controller->getList('auth') as $plugin) {
+ $this->choices[] = $plugin;
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ /** @var $plugin_controller \dokuwiki\Extension\PluginController */
+ global $plugin_controller;
+
+ // is an update possible/requested?
+ $local = $this->local; // save this, parent::update() may change it
+ if(!parent::update($input)) return false; // nothing changed or an error caught by parent
+ $this->local = $local; // restore original, more error checking to come
+
+ // attempt to load the plugin
+ $auth_plugin = $plugin_controller->load('auth', $input);
+
+ // @TODO: throw an error in plugin controller instead of returning null
+ if(is_null($auth_plugin)) {
+ $this->error = true;
+ msg('Cannot load Auth Plugin "' . $input . '"', -1);
+ return false;
+ }
+
+ // verify proper instantiation (is this really a plugin?) @TODO use instanceof? implement interface?
+ if(is_object($auth_plugin) && !method_exists($auth_plugin, 'getPluginName')) {
+ $this->error = true;
+ msg('Cannot create Auth Plugin "' . $input . '"', -1);
+ return false;
+ }
+
+ // did we change the auth type? logout
+ global $conf;
+ if($conf['authtype'] != $input) {
+ msg('Authentication system changed. Please re-login.');
+ auth_logoff();
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingCompression.php b/platform/www/lib/plugins/config/core/Setting/SettingCompression.php
new file mode 100644
index 0000000..f97d828
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingCompression.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_compression
+ */
+class SettingCompression extends SettingMultichoice {
+
+ protected $choices = array('0'); // 0 = no compression, always supported
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+
+ // populate _choices with the compression methods supported by this php installation
+ if(function_exists('gzopen')) $this->choices[] = 'gz';
+ if(function_exists('bzopen')) $this->choices[] = 'bz2';
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php b/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php
new file mode 100644
index 0000000..dfb27f5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_dirchoice
+ */
+class SettingDirchoice extends SettingMultichoice {
+
+ protected $dir = '';
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+
+ // populate $this->_choices with a list of directories
+ $list = array();
+
+ if($dh = @opendir($this->dir)) {
+ while(false !== ($entry = readdir($dh))) {
+ if($entry == '.' || $entry == '..') continue;
+ if($this->pattern && !preg_match($this->pattern, $entry)) continue;
+
+ $file = (is_link($this->dir . $entry)) ? readlink($this->dir . $entry) : $this->dir . $entry;
+ if(is_dir($file)) $list[] = $entry;
+ }
+ closedir($dh);
+ }
+ sort($list);
+ $this->choices = $list;
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php b/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php
new file mode 100644
index 0000000..2553175
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_disableactions
+ */
+class SettingDisableactions extends SettingMulticheckbox {
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ global $lang;
+
+ // make some language adjustments (there must be a better way)
+ // transfer some DokuWiki language strings to the plugin
+ $plugin->addLang($this->key . '_revisions', $lang['btn_revs']);
+ foreach($this->choices as $choice) {
+ if(isset($lang['btn_' . $choice])) $plugin->addLang($this->key . '_' . $choice, $lang['btn_' . $choice]);
+ }
+
+ return parent::html($plugin, $echo);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingEmail.php b/platform/www/lib/plugins/config/core/Setting/SettingEmail.php
new file mode 100644
index 0000000..25a0c0e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingEmail.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_email
+ */
+class SettingEmail extends SettingString {
+ protected $multiple = false;
+ protected $placeholders = false;
+
+ /** @inheritdoc */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+ if($input === '') {
+ $this->local = $input;
+ return true;
+ }
+ $mail = $input;
+
+ if($this->placeholders) {
+ // replace variables with pseudo values
+ $mail = str_replace('@USER@', 'joe', $mail);
+ $mail = str_replace('@NAME@', 'Joe Schmoe', $mail);
+ $mail = str_replace('@MAIL@', 'joe@example.com', $mail);
+ }
+
+ // multiple mail addresses?
+ if($this->multiple) {
+ $mails = array_filter(array_map('trim', explode(',', $mail)));
+ } else {
+ $mails = array($mail);
+ }
+
+ // check them all
+ foreach($mails as $mail) {
+ // only check the address part
+ if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)) {
+ $addr = $matches[2];
+ } else {
+ $addr = $mail;
+ }
+
+ if(!mail_isvalid($addr)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php b/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php
new file mode 100644
index 0000000..4e86189
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * A do-nothing class used to detect the 'fieldset' type.
+ *
+ * Used to start a new settings "display-group".
+ */
+class SettingFieldset extends Setting {
+
+ /** @inheritdoc */
+ public function shouldHaveDefault() {
+ return false;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingHidden.php b/platform/www/lib/plugins/config/core/Setting/SettingHidden.php
new file mode 100644
index 0000000..ca8a03e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingHidden.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_hidden
+ */
+class SettingHidden extends Setting {
+ // Used to explicitly ignore a setting in the configuration manager.
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php b/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php
new file mode 100644
index 0000000..8740d94
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_im_convert
+ */
+class SettingImConvert extends SettingString {
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $input = trim($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if($input && !file_exists($input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingLicense.php b/platform/www/lib/plugins/config/core/Setting/SettingLicense.php
new file mode 100644
index 0000000..8dacf8e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingLicense.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_license
+ */
+class SettingLicense extends SettingMultichoice {
+
+ protected $choices = array(''); // none choosen
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ global $license;
+
+ foreach($license as $key => $data) {
+ $this->choices[] = $key;
+ $this->lang[$this->key . '_o_' . $key] = $data['name']; // stored in setting
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php b/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php
new file mode 100644
index 0000000..df212cc
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_multicheckbox
+ */
+class SettingMulticheckbox extends SettingString {
+
+ protected $choices = array();
+ protected $combine = array();
+ protected $other = 'always';
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ // split any combined values + convert from array to comma separated string
+ $input = ($input) ? $input : array();
+ $input = $this->array2str($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+
+ // convert from comma separated list into array + combine complimentary actions
+ $value = $this->str2array($value);
+ $default = $this->str2array($this->default);
+
+ $input = '';
+ foreach($this->choices as $choice) {
+ $idx = array_search($choice, $value);
+ $idx_default = array_search($choice, $default);
+
+ $checked = ($idx !== false) ? 'checked="checked"' : '';
+
+ // @todo ideally this would be handled using a second class of "default"
+ $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : "";
+
+ $prompt = ($plugin->getLang($this->key . '_' . $choice) ?
+ $plugin->getLang($this->key . '_' . $choice) : htmlspecialchars($choice));
+
+ $input .= '<div class="selection' . $class . '">' . "\n";
+ $input .= '<label for="config___' . $key . '_' . $choice . '">' . $prompt . "</label>\n";
+ $input .= '<input id="config___' . $key . '_' . $choice . '" name="config[' . $key .
+ '][]" type="checkbox" class="checkbox" value="' . $choice . '" ' . $disable . ' ' . $checked . "/>\n";
+ $input .= "</div>\n";
+
+ // remove this action from the disabledactions array
+ if($idx !== false) unset($value[$idx]);
+ if($idx_default !== false) unset($default[$idx_default]);
+ }
+
+ // handle any remaining values
+ if($this->other != 'never') {
+ $other = join(',', $value);
+ // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists')
+ // use != 'exists' rather than == 'always' to ensure invalid values default to 'always'
+ if($this->other != 'exists' || $other) {
+
+ $class = (
+ (count($default) == count($value)) &&
+ (count($value) == count(array_intersect($value, $default)))
+ ) ?
+ " selectiondefault" : "";
+
+ $input .= '<div class="other' . $class . '">' . "\n";
+ $input .= '<label for="config___' . $key . '_other">' .
+ $plugin->getLang($key . '_other') .
+ "</label>\n";
+ $input .= '<input id="config___' . $key . '_other" name="config[' . $key .
+ '][other]" type="text" class="edit" value="' . htmlspecialchars($other) .
+ '" ' . $disable . " />\n";
+ $input .= "</div>\n";
+ }
+ }
+ $label = '<label>' . $this->prompt($plugin) . '</label>';
+ return array($label, $input);
+ }
+
+ /**
+ * convert comma separated list to an array and combine any complimentary values
+ *
+ * @param string $str
+ * @return array
+ */
+ protected function str2array($str) {
+ $array = explode(',', $str);
+
+ if(!empty($this->combine)) {
+ foreach($this->combine as $key => $combinators) {
+ $idx = array();
+ foreach($combinators as $val) {
+ if(($idx[] = array_search($val, $array)) === false) break;
+ }
+
+ if(count($idx) && $idx[count($idx) - 1] !== false) {
+ foreach($idx as $i) unset($array[$i]);
+ $array[] = $key;
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * convert array of values + other back to a comma separated list, incl. splitting any combined values
+ *
+ * @param array $input
+ * @return string
+ */
+ protected function array2str($input) {
+
+ // handle other
+ $other = trim($input['other']);
+ $other = !empty($other) ? explode(',', str_replace(' ', '', $input['other'])) : array();
+ unset($input['other']);
+
+ $array = array_unique(array_merge($input, $other));
+
+ // deconstruct any combinations
+ if(!empty($this->combine)) {
+ foreach($this->combine as $key => $combinators) {
+
+ $idx = array_search($key, $array);
+ if($idx !== false) {
+ unset($array[$idx]);
+ $array = array_merge($array, $combinators);
+ }
+ }
+ }
+
+ return join(',', array_unique($array));
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php b/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php
new file mode 100644
index 0000000..3a50857
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_multichoice
+ */
+class SettingMultichoice extends SettingString {
+ protected $choices = array();
+ public $lang; //some custom language strings are stored in setting
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+ $nochoice = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = ' disabled="disabled"';
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+
+ // ensure current value is included
+ if(!in_array($value, $this->choices)) {
+ $this->choices[] = $value;
+ }
+ // disable if no other choices
+ if(!$this->isProtected() && count($this->choices) <= 1) {
+ $disable = ' disabled="disabled"';
+ $nochoice = $plugin->getLang('nochoice');
+ }
+
+ $key = htmlspecialchars($this->key);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+
+ $input = "<div class=\"input\">\n";
+ $input .= '<select class="edit" id="config___' . $key . '" name="config[' . $key . ']"' . $disable . '>' . "\n";
+ foreach($this->choices as $choice) {
+ $selected = ($value == $choice) ? ' selected="selected"' : '';
+ $option = $plugin->getLang($this->key . '_o_' . $choice);
+ if(!$option && isset($this->lang[$this->key . '_o_' . $choice])) {
+ $option = $this->lang[$this->key . '_o_' . $choice];
+ }
+ if(!$option) $option = $choice;
+
+ $choice = htmlspecialchars($choice);
+ $option = htmlspecialchars($option);
+ $input .= ' <option value="' . $choice . '"' . $selected . ' >' . $option . '</option>' . "\n";
+ }
+ $input .= "</select> $nochoice \n";
+ $input .= "</div>\n";
+
+ return array($label, $input);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if(!in_array($input, $this->choices)) return false;
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php b/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php
new file mode 100644
index 0000000..8efff21
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_no_class
+ * A do-nothing class used to detect settings with a missing setting class.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoClass extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_class';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php b/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php
new file mode 100644
index 0000000..07b8412
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_no_default
+ *
+ * A do-nothing class used to detect settings with no default value.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoDefault extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_default';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php b/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php
new file mode 100644
index 0000000..3c527e1
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * A do-nothing class used to detect settings with a missing setting class.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoKnownClass extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_known_class';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php b/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php
new file mode 100644
index 0000000..8a6b179
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_numeric
+ */
+class SettingNumeric extends SettingString {
+ // This allows for many PHP syntax errors...
+ // var $_pattern = '/^[-+\/*0-9 ]*$/';
+ // much more restrictive, but should eliminate syntax errors.
+ protected $pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/';
+ protected $min = null;
+ protected $max = null;
+
+ /** @inheritdoc */
+ public function update($input) {
+ $local = $this->local;
+ $valid = parent::update($input);
+ if($valid && !(is_null($this->min) && is_null($this->max))) {
+ $numeric_local = (int) eval('return ' . $this->local . ';');
+ if((!is_null($this->min) && $numeric_local < $this->min) ||
+ (!is_null($this->max) && $numeric_local > $this->max)) {
+ $this->error = true;
+ $this->input = $input;
+ $this->local = $local;
+ $valid = false;
+ }
+ }
+ return $valid;
+ }
+
+ /** @inheritdoc */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $local = $this->local === '' ? "''" : $this->local;
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = " . $local . ";\n";
+
+ return $out;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php b/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php
new file mode 100644
index 0000000..a486e18
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_numericopt
+ */
+class SettingNumericopt extends SettingNumeric {
+ // just allow an empty config
+ protected $pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/';
+
+ /**
+ * @inheritdoc
+ * Empty string is valid for numericopt
+ */
+ public function update($input) {
+ if($input === '') {
+ if($input == $this->local) return false;
+ $this->local = $input;
+ return true;
+ }
+
+ return parent::update($input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php b/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php
new file mode 100644
index 0000000..780778b
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_onoff
+ */
+class SettingOnoff extends SettingNumeric {
+
+ /**
+ * We treat the strings 'false' and 'off' as false
+ * @inheritdoc
+ */
+ protected function cleanValue($value) {
+ if($value === null) return null;
+
+ if(is_string($value)) {
+ if(strtolower($value) === 'false') return 0;
+ if(strtolower($value) === 'off') return 0;
+ if(trim($value) === '') return 0;
+ }
+
+ return (int) (bool) $value;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = ' disabled="disabled"';
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+
+ $key = htmlspecialchars($this->key);
+ $checked = ($value) ? ' checked="checked"' : '';
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<div class="input"><input id="config___' . $key . '" name="config[' . $key .
+ ']" type="checkbox" class="checkbox" value="1"' . $checked . $disable . '/></div>';
+ return array($label, $input);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $input = ($input) ? 1 : 0;
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingPassword.php b/platform/www/lib/plugins/config/core/Setting/SettingPassword.php
new file mode 100644
index 0000000..9d9c533
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingPassword.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_password
+ */
+class SettingPassword extends SettingString {
+
+ protected $code = 'plain'; // mechanism to be used to obscure passwords
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+ if(!$input) return false;
+
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = conf_encodeString($input, $this->code);
+ return true;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ $disable = $this->isProtected() ? 'disabled="disabled"' : '';
+
+ $key = htmlspecialchars($this->key);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" autocomplete="off" type="password" class="edit" value="" ' . $disable . ' />';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingRegex.php b/platform/www/lib/plugins/config/core/Setting/SettingRegex.php
new file mode 100644
index 0000000..b38f0a5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingRegex.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_regex
+ */
+class SettingRegex extends SettingString {
+
+ protected $delimiter = '/'; // regex delimiter to be used in testing input
+ protected $pregflags = 'ui'; // regex pattern modifiers to be used in testing input
+
+ /** @inheritdoc */
+ public function update($input) {
+
+ // let parent do basic checks, value, not changed, etc.
+ $local = $this->local;
+ if(!parent::update($input)) return false;
+ $this->local = $local;
+
+ // see if the regex compiles and runs (we don't check for effectiveness)
+ $regex = $this->delimiter . $input . $this->delimiter . $this->pregflags;
+ $lastError = error_get_last();
+ @preg_match($regex, 'testdata');
+ if(preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) {
+ $this->input = $input;
+ $this->error = true;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php b/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php
new file mode 100644
index 0000000..37ba9c7
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * additional setting classes specific to these settings
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_renderer
+ */
+class SettingRenderer extends SettingMultichoice {
+ protected $prompts = array();
+ protected $format = null;
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ $format = $this->format;
+
+ foreach(plugin_list('renderer') as $plugin) {
+ $renderer = plugin_load('renderer', $plugin);
+ if(method_exists($renderer, 'canRender') && $renderer->canRender($format)) {
+ $this->choices[] = $plugin;
+
+ $info = $renderer->getInfo();
+ $this->prompts[$plugin] = $info['name'];
+ }
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ // make some language adjustments (there must be a better way)
+ // transfer some plugin names to the config plugin
+ foreach($this->choices as $choice) {
+ if(!$plugin->getLang($this->key . '_o_' . $choice)) {
+ if(!isset($this->prompts[$choice])) {
+ $plugin->addLang(
+ $this->key . '_o_' . $choice,
+ sprintf($plugin->getLang('renderer__core'), $choice)
+ );
+ } else {
+ $plugin->addLang(
+ $this->key . '_o_' . $choice,
+ sprintf($plugin->getLang('renderer__plugin'), $this->prompts[$choice])
+ );
+ }
+ }
+ }
+ return parent::html($plugin, $echo);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php b/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php
new file mode 100644
index 0000000..43e428d
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_savedir
+ */
+class SettingSavedir extends SettingString {
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if(!init_path($input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php b/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php
new file mode 100644
index 0000000..57cd0ae
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_sepchar
+ */
+class SettingSepchar extends SettingMultichoice {
+
+ /** @inheritdoc */
+ public function __construct($key, $param = null) {
+ $str = '_-.';
+ for($i = 0; $i < strlen($str); $i++) $this->choices[] = $str[$i];
+
+ // call foundation class constructor
+ parent::__construct($key, $param);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingString.php b/platform/www/lib/plugins/config/core/Setting/SettingString.php
new file mode 100644
index 0000000..b819407
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingString.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_string
+ */
+class SettingString extends Setting {
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = htmlspecialchars($value);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php b/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php
new file mode 100644
index 0000000..fa46a9f
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+use dokuwiki\plugin\config\core\Configuration;
+
+/**
+ * A do-nothing class used to detect settings with no metadata entry.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingUndefined extends SettingHidden {
+
+ protected $errorMessage = '_msg_setting_undefined';
+
+ /** @inheritdoc */
+ public function shouldHaveDefault() {
+ return false;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ // determine the name the meta key would be called
+ if(preg_match(
+ '/^(?:plugin|tpl)' . Configuration::KEYMARKER . '.*?' . Configuration::KEYMARKER . '(.*)$/',
+ $this->getKey(),
+ $undefined_setting_match
+ )) {
+ $undefined_setting_key = $undefined_setting_match[1];
+ } else {
+ $undefined_setting_key = $this->getKey();
+ }
+
+ $label = '<span title="$meta[\'' . $undefined_setting_key . '\']">$' .
+ 'conf' . '[\'' . $this->getArrayKey() . '\']</span>';
+ $input = $plugin->getLang($this->errorMessage);
+
+ return array($label, $input);
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Writer.php b/platform/www/lib/plugins/config/core/Writer.php
new file mode 100644
index 0000000..56de621
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Writer.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+use dokuwiki\plugin\config\core\Setting\Setting;
+
+/**
+ * Writes the settings to the correct local file
+ */
+class Writer {
+ /** @var string header info */
+ protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings';
+
+ /** @var string the file where the config will be saved to */
+ protected $savefile;
+
+ /**
+ * Writer constructor.
+ */
+ public function __construct() {
+ global $config_cascade;
+ $this->savefile = end($config_cascade['main']['local']);
+ }
+
+ /**
+ * Save the given settings
+ *
+ * @param Setting[] $settings
+ * @throws \Exception
+ */
+ public function save($settings) {
+ global $conf;
+ if($this->isLocked()) throw new \Exception('no save');
+
+ // backup current file (remove any existing backup)
+ if(file_exists($this->savefile)) {
+ if(file_exists($this->savefile . '.bak.php')) @unlink($this->savefile . '.bak.php');
+ if(!io_rename($this->savefile, $this->savefile . '.bak.php')) throw new \Exception('no backup');
+ }
+
+ if(!$fh = @fopen($this->savefile, 'wb')) {
+ io_rename($this->savefile . '.bak.php', $this->savefile); // problem opening, restore the backup
+ throw new \Exception('no save');
+ }
+
+ $out = $this->getHeader();
+ foreach($settings as $setting) {
+ if($setting->shouldBeSaved()) {
+ $out .= $setting->out('conf', 'php');
+ }
+ }
+
+ fwrite($fh, $out);
+ fclose($fh);
+ if($conf['fperm']) chmod($this->savefile, $conf['fperm']);
+ $this->opcacheUpdate($this->savefile);
+ }
+
+ /**
+ * Update last modified time stamp of the config file
+ *
+ * Will invalidate all DokuWiki caches
+ *
+ * @throws \Exception when the config isn't writable
+ */
+ public function touch() {
+ if($this->isLocked()) throw new \Exception('no save');
+ @touch($this->savefile);
+ $this->opcacheUpdate($this->savefile);
+ }
+
+ /**
+ * Invalidate the opcache of the given file
+ *
+ * @todo this should probably be moved to core
+ * @param string $file
+ */
+ protected function opcacheUpdate($file) {
+ if(!function_exists('opcache_invalidate')) return;
+ opcache_invalidate($file);
+ }
+
+ /**
+ * Configuration is considered locked if there is no local settings filename
+ * or the directory its in is not writable or the file exists and is not writable
+ *
+ * @return bool true: locked, false: writable
+ */
+ public function isLocked() {
+ if(!$this->savefile) return true;
+ if(!is_writable(dirname($this->savefile))) return true;
+ if(file_exists($this->savefile) && !is_writable($this->savefile)) return true;
+ return false;
+ }
+
+ /**
+ * Returns the PHP intro header for the config file
+ *
+ * @return string
+ */
+ protected function getHeader() {
+ return join(
+ "\n",
+ array(
+ '<?php',
+ '/*',
+ ' * ' . $this->header,
+ ' * Auto-generated by config plugin',
+ ' * Run for user: ' . $_SERVER['REMOTE_USER'],
+ ' * Date: ' . date('r'),
+ ' */',
+ '',
+ ''
+ )
+ );
+ }
+}
diff --git a/platform/www/lib/plugins/config/images/danger.png b/platform/www/lib/plugins/config/images/danger.png
new file mode 100644
index 0000000..da06924
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/danger.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/images/security.png b/platform/www/lib/plugins/config/images/security.png
new file mode 100644
index 0000000..3ee8476
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/security.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/images/warning.png b/platform/www/lib/plugins/config/images/warning.png
new file mode 100644
index 0000000..c1af79f
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/warning.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/lang/en/intro.txt b/platform/www/lib/plugins/config/lang/en/intro.txt
new file mode 100644
index 0000000..0108987
--- /dev/null
+++ b/platform/www/lib/plugins/config/lang/en/intro.txt
@@ -0,0 +1,7 @@
+====== Configuration Manager ======
+
+Use this page to control the settings of your DokuWiki installation. For help on individual settings refer to [[doku>config]]. For more details about this plugin see [[doku>plugin:config]].
+
+Settings shown with a light red background are protected and can not be altered with this plugin. Settings shown with a blue background are the default values and settings shown with a white background have been set locally for this particular installation. Both blue and white settings can be altered.
+
+Remember to press the **Save** button before leaving this page otherwise your changes will be lost.
diff --git a/platform/www/lib/plugins/config/lang/en/lang.php b/platform/www/lib/plugins/config/lang/en/lang.php
new file mode 100644
index 0000000..fb8186c
--- /dev/null
+++ b/platform/www/lib/plugins/config/lang/en/lang.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * english language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Matthias Schulte <dokuwiki@lupo49.de>
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ */
+
+// for admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'Configuration Settings';
+
+$lang['error'] = 'Settings not updated due to an invalid value, please review your changes and resubmit.
+ <br />The incorrect value(s) will be shown surrounded by a red border.';
+$lang['updated'] = 'Settings updated successfully.';
+$lang['nochoice'] = '(no other choices available)';
+$lang['locked'] = 'The settings file can not be updated, if this is unintentional, <br />
+ ensure the local settings file name and permissions are correct.';
+
+$lang['danger'] = 'Danger: Changing this option could make your wiki and the configuration menu inaccessible.';
+$lang['warning'] = 'Warning: Changing this option could cause unintended behaviour.';
+$lang['security'] = 'Security Warning: Changing this option could present a security risk.';
+
+/* --- Config Setting Headers --- */
+$lang['_configuration_manager'] = 'Configuration Manager'; //same as heading in intro.txt
+$lang['_header_dokuwiki'] = 'DokuWiki';
+$lang['_header_plugin'] = 'Plugin';
+$lang['_header_template'] = 'Template';
+$lang['_header_undefined'] = 'Undefined Settings';
+
+/* --- Config Setting Groups --- */
+$lang['_basic'] = 'Basic';
+$lang['_display'] = 'Display';
+$lang['_authentication'] = 'Authentication';
+$lang['_anti_spam'] = 'Anti-Spam';
+$lang['_editing'] = 'Editing';
+$lang['_links'] = 'Links';
+$lang['_media'] = 'Media';
+$lang['_notifications'] = 'Notification';
+$lang['_syndication'] = 'Syndication (RSS)';
+$lang['_advanced'] = 'Advanced';
+$lang['_network'] = 'Network';
+
+/* --- Undefined Setting Messages --- */
+$lang['_msg_setting_undefined'] = 'No setting metadata.';
+$lang['_msg_setting_no_class'] = 'No setting class.';
+$lang['_msg_setting_no_known_class'] = 'Setting class not available.';
+$lang['_msg_setting_no_default'] = 'No default value.';
+
+/* -------------------- Config Options --------------------------- */
+
+/* Basic Settings */
+$lang['title'] = 'Wiki title aka. your wiki\'s name';
+$lang['start'] = 'Page name to use as the starting point for each namespace';
+$lang['lang'] = 'Interface language';
+$lang['template'] = 'Template aka. the design of the wiki.';
+$lang['tagline'] = 'Tagline (if template supports it)';
+$lang['sidebar'] = 'Sidebar page name (if template supports it), empty field disables the sidebar';
+$lang['license'] = 'Under which license should your content be released?';
+$lang['savedir'] = 'Directory for saving data';
+$lang['basedir'] = 'Server path (eg. <code>/dokuwiki/</code>). Leave blank for autodetection.';
+$lang['baseurl'] = 'Server URL (eg. <code>http://www.yourserver.com</code>). Leave blank for autodetection.';
+$lang['cookiedir'] = 'Cookie path. Leave blank for using baseurl.';
+$lang['dmode'] = 'Directory creation mode';
+$lang['fmode'] = 'File creation mode';
+$lang['allowdebug'] = 'Allow debug. <b>Disable if not needed!</b>';
+
+/* Display Settings */
+$lang['recent'] = 'Number of entries per page in the recent changes';
+$lang['recent_days'] = 'How many recent changes to keep (days)';
+$lang['breadcrumbs'] = 'Number of "trace" breadcrumbs. Set to 0 to disable.';
+$lang['youarehere'] = 'Use hierarchical breadcrumbs (you probably want to disable the above option then)';
+$lang['fullpath'] = 'Reveal full path of pages in the footer';
+$lang['typography'] = 'Do typographical replacements';
+$lang['dformat'] = 'Date format (see PHP\'s <a href="http://php.net/strftime">strftime</a> function)';
+$lang['signature'] = 'What to insert with the signature button in the editor';
+$lang['showuseras'] = 'What to display when showing the user that last edited a page';
+$lang['toptoclevel'] = 'Top level for table of contents';
+$lang['tocminheads'] = 'Minimum amount of headlines that determines whether the TOC is built';
+$lang['maxtoclevel'] = 'Maximum level for table of contents';
+$lang['maxseclevel'] = 'Maximum section edit level';
+$lang['camelcase'] = 'Use CamelCase for links';
+$lang['deaccent'] = 'How to clean pagenames';
+$lang['useheading'] = 'Use first heading for pagenames';
+$lang['sneaky_index'] = 'By default, DokuWiki will show all namespaces in the sitemap. Enabling this option will hide those where the user doesn\'t have read permissions. This might result in hiding of accessable subnamespaces which may make the index unusable with certain ACL setups.';
+$lang['hidepages'] = 'Hide pages matching this regular expression from search, the sitemap and other automatic indexes';
+
+/* Authentication Settings */
+$lang['useacl'] = 'Use access control lists';
+$lang['autopasswd'] = 'Autogenerate passwords';
+$lang['authtype'] = 'Authentication backend';
+$lang['passcrypt'] = 'Password encryption method';
+$lang['defaultgroup']= 'Default group, all new users will be placed in this group';
+$lang['superuser'] = 'Superuser - group, user or comma separated list user1,@group1,user2 with full access to all pages and functions regardless of the ACL settings';
+$lang['manager'] = 'Manager - group, user or comma separated list user1,@group1,user2 with access to certain management functions';
+$lang['profileconfirm'] = 'Confirm profile changes with password';
+$lang['rememberme'] = 'Allow permanent login cookies (remember me)';
+$lang['disableactions'] = 'Disable DokuWiki actions';
+$lang['disableactions_check'] = 'Check';
+$lang['disableactions_subscription'] = 'Subscribe/Unsubscribe';
+$lang['disableactions_wikicode'] = 'View source/Export Raw';
+$lang['disableactions_profile_delete'] = 'Delete Own Account';
+$lang['disableactions_other'] = 'Other actions (comma separated)';
+$lang['disableactions_rss'] = 'XML Syndication (RSS)';
+$lang['auth_security_timeout'] = 'Authentication Security Timeout (seconds)';
+$lang['securecookie'] = 'Should cookies set via HTTPS only be sent via HTTPS by the browser? Disable this option when only the login of your wiki is secured with SSL but browsing the wiki is done unsecured.';
+$lang['remote'] = 'Enable the remote API system. This allows other applications to access the wiki via XML-RPC or other mechanisms.';
+$lang['remoteuser'] = 'Restrict remote API access to the comma separated groups or users given here. Leave empty to give access to everyone.';
+
+/* Anti-Spam Settings */
+$lang['usewordblock']= 'Block spam based on wordlist';
+$lang['relnofollow'] = 'Use rel="ugc nofollow" on external links';
+$lang['indexdelay'] = 'Time delay before indexing (sec)';
+$lang['mailguard'] = 'Obfuscate email addresses';
+$lang['iexssprotect']= 'Check uploaded files for possibly malicious JavaScript or HTML code';
+
+/* Editing Settings */
+$lang['usedraft'] = 'Automatically save a draft while editing';
+$lang['htmlok'] = 'Allow embedded HTML';
+$lang['phpok'] = 'Allow embedded PHP';
+$lang['locktime'] = 'Maximum age for lock files (sec)';
+$lang['cachetime'] = 'Maximum age for cache (sec)';
+
+/* Link settings */
+$lang['target____wiki'] = 'Target window for internal links';
+$lang['target____interwiki'] = 'Target window for interwiki links';
+$lang['target____extern'] = 'Target window for external links';
+$lang['target____media'] = 'Target window for media links';
+$lang['target____windows'] = 'Target window for windows links';
+
+/* Media Settings */
+$lang['mediarevisions'] = 'Enable Mediarevisions?';
+$lang['refcheck'] = 'Check if a media file is still in use before deleting it';
+$lang['gdlib'] = 'GD Lib version';
+$lang['im_convert'] = 'Path to ImageMagick\'s convert tool';
+$lang['jpg_quality'] = 'JPG compression quality (0-100)';
+$lang['fetchsize'] = 'Maximum size (bytes) fetch.php may download from external URLs, eg. to cache and resize external images.';
+
+/* Notification Settings */
+$lang['subscribers'] = 'Allow users to subscribe to page changes by email';
+$lang['subscribe_time'] = 'Time after which subscription lists and digests are sent (sec); This should be smaller than the time specified in recent_days.';
+$lang['notify'] = 'Always send change notifications to this email address';
+$lang['registernotify'] = 'Always send info on newly registered users to this email address';
+$lang['mailfrom'] = 'Sender email address to use for automatic mails';
+$lang['mailreturnpath'] = 'Recipient email address for non delivery notifications';
+$lang['mailprefix'] = 'Email subject prefix to use for automatic mails. Leave blank to use the wiki title';
+$lang['htmlmail'] = 'Send better looking, but larger in size HTML multipart emails. Disable for plain text only mails.';
+
+/* Syndication Settings */
+$lang['sitemap'] = 'Generate Google sitemap this often (in days). 0 to disable';
+$lang['rss_type'] = 'XML feed type';
+$lang['rss_linkto'] = 'XML feed links to';
+$lang['rss_content'] = 'What to display in the XML feed items?';
+$lang['rss_update'] = 'XML feed update interval (sec)';
+$lang['rss_show_summary'] = 'XML feed show summary in title';
+$lang['rss_show_deleted'] = 'XML feed Show deleted feeds';
+$lang['rss_media'] = 'What kind of changes should be listed in the XML feed?';
+$lang['rss_media_o_both'] = 'both';
+$lang['rss_media_o_pages'] = 'pages';
+$lang['rss_media_o_media'] = 'media';
+
+
+/* Advanced Options */
+$lang['updatecheck'] = 'Check for updates and security warnings? DokuWiki needs to contact update.dokuwiki.org for this feature.';
+$lang['userewrite'] = 'Use nice URLs';
+$lang['useslash'] = 'Use slash as namespace separator in URLs';
+$lang['sepchar'] = 'Page name word separator';
+$lang['canonical'] = 'Use fully canonical URLs';
+$lang['fnencode'] = 'Method for encoding non-ASCII filenames.';
+$lang['autoplural'] = 'Check for plural forms in links';
+$lang['compression'] = 'Compression method for attic files';
+$lang['gzip_output'] = 'Use gzip Content-Encoding for xhtml';
+$lang['compress'] = 'Compact CSS and javascript output';
+$lang['cssdatauri'] = 'Size in bytes up to which images referenced in CSS files should be embedded right into the stylesheet to reduce HTTP request header overhead. <code>400</code> to <code>600</code> bytes is a good value. Set <code>0</code> to disable.';
+$lang['send404'] = 'Send "HTTP 404/Page Not Found" for non existing pages';
+$lang['broken_iua'] = 'Is the ignore_user_abort function broken on your system? This could cause a non working search index. IIS+PHP/CGI is known to be broken. See <a href="http://bugs.dokuwiki.org/?do=details&amp;task_id=852">Bug 852</a> for more info.';
+$lang['xsendfile'] = 'Use the X-Sendfile header to let the webserver deliver static files? Your webserver needs to support this.';
+$lang['renderer_xhtml'] = 'Renderer to use for main (xhtml) wiki output';
+$lang['renderer__core'] = '%s (dokuwiki core)';
+$lang['renderer__plugin'] = '%s (plugin)';
+$lang['search_nslimit'] = 'Limit the search to the current X namespaces. When a search is executed from a page within a deeper namespace, the first X namespaces will be added as filter';
+$lang['search_fragment'] = 'Specify the default fragment search behavior';
+$lang['search_fragment_o_exact'] = 'exact';
+$lang['search_fragment_o_starts_with'] = 'starts with';
+$lang['search_fragment_o_ends_with'] = 'ends with';
+$lang['search_fragment_o_contains'] = 'contains';
+$lang['trustedproxy'] = 'Trust forwarding proxies matching this regular expression about the true client IP they report. The default matches local networks. Leave empty to trust no proxy.';
+
+$lang['_feature_flags'] = 'Feature Flags';
+$lang['defer_js'] = 'Defer javascript to be execute after the page\'s HTML has been parsed. Improves perceived page speed but could break a small number of plugins.';
+
+/* Network Options */
+$lang['dnslookups'] = 'DokuWiki will lookup hostnames for remote IP addresses of users editing pages. If you have a slow or non working DNS server or don\'t want this feature, disable this option';
+$lang['jquerycdn'] = 'Should the jQuery and jQuery UI script files be loaded from a CDN? This adds additional HTTP requests, but files may load faster and users may have them cached already.';
+
+/* jQuery CDN options */
+$lang['jquerycdn_o_0'] = 'No CDN, local delivery only';
+$lang['jquerycdn_o_jquery'] = 'CDN at code.jquery.com';
+$lang['jquerycdn_o_cdnjs'] = 'CDN at cdnjs.com';
+
+/* Proxy Options */
+$lang['proxy____host'] = 'Proxy servername';
+$lang['proxy____port'] = 'Proxy port';
+$lang['proxy____user'] = 'Proxy user name';
+$lang['proxy____pass'] = 'Proxy password';
+$lang['proxy____ssl'] = 'Use SSL to connect to proxy';
+$lang['proxy____except'] = 'Regular expression to match URLs for which the proxy should be skipped.';
+
+/* License Options */
+$lang['license_o_'] = 'None chosen';
+
+/* typography options */
+$lang['typography_o_0'] = 'none';
+$lang['typography_o_1'] = 'excluding single quotes';
+$lang['typography_o_2'] = 'including single quotes (might not always work)';
+
+/* userewrite options */
+$lang['userewrite_o_0'] = 'none';
+$lang['userewrite_o_1'] = '.htaccess';
+$lang['userewrite_o_2'] = 'DokuWiki internal';
+
+/* deaccent options */
+$lang['deaccent_o_0'] = 'off';
+$lang['deaccent_o_1'] = 'remove accents';
+$lang['deaccent_o_2'] = 'romanize';
+
+/* gdlib options */
+$lang['gdlib_o_0'] = 'GD Lib not available';
+$lang['gdlib_o_1'] = 'Version 1.x';
+$lang['gdlib_o_2'] = 'Autodetection';
+
+/* rss_type options */
+$lang['rss_type_o_rss'] = 'RSS 0.91';
+$lang['rss_type_o_rss1'] = 'RSS 1.0';
+$lang['rss_type_o_rss2'] = 'RSS 2.0';
+$lang['rss_type_o_atom'] = 'Atom 0.3';
+$lang['rss_type_o_atom1'] = 'Atom 1.0';
+
+/* rss_content options */
+$lang['rss_content_o_abstract'] = 'Abstract';
+$lang['rss_content_o_diff'] = 'Unified Diff';
+$lang['rss_content_o_htmldiff'] = 'HTML formatted diff table';
+$lang['rss_content_o_html'] = 'Full HTML page content';
+
+/* rss_linkto options */
+$lang['rss_linkto_o_diff'] = 'difference view';
+$lang['rss_linkto_o_page'] = 'the revised page';
+$lang['rss_linkto_o_rev'] = 'list of revisions';
+$lang['rss_linkto_o_current'] = 'the current page';
+
+/* compression options */
+$lang['compression_o_0'] = 'none';
+$lang['compression_o_gz'] = 'gzip';
+$lang['compression_o_bz2'] = 'bz2';
+
+/* xsendfile header */
+$lang['xsendfile_o_0'] = "don't use";
+$lang['xsendfile_o_1'] = 'Proprietary lighttpd header (before release 1.5)';
+$lang['xsendfile_o_2'] = 'Standard X-Sendfile header';
+$lang['xsendfile_o_3'] = 'Proprietary Nginx X-Accel-Redirect header';
+
+/* Display user info */
+$lang['showuseras_o_loginname'] = 'Login name';
+$lang['showuseras_o_username'] = "User's full name";
+$lang['showuseras_o_username_link'] = "User's full name as interwiki user link";
+$lang['showuseras_o_email'] = "User's e-mail addresss (obfuscated according to mailguard setting)";
+$lang['showuseras_o_email_link'] = "User's e-mail addresss as a mailto: link";
+
+/* useheading options */
+$lang['useheading_o_0'] = 'Never';
+$lang['useheading_o_navigation'] = 'Navigation Only';
+$lang['useheading_o_content'] = 'Wiki Content Only';
+$lang['useheading_o_1'] = 'Always';
+
+$lang['readdircache'] = 'Maximum age for readdir cache (sec)';
diff --git a/platform/www/lib/plugins/config/plugin.info.txt b/platform/www/lib/plugins/config/plugin.info.txt
new file mode 100644
index 0000000..ddd7265
--- /dev/null
+++ b/platform/www/lib/plugins/config/plugin.info.txt
@@ -0,0 +1,7 @@
+base config
+author Christopher Smith
+email chris@jalakai.co.uk
+date 2015-07-18
+name Configuration Manager
+desc Manage Dokuwiki's Configuration Settings
+url http://dokuwiki.org/plugin:config
diff --git a/platform/www/lib/plugins/config/settings/config.metadata.php b/platform/www/lib/plugins/config/settings/config.metadata.php
new file mode 100644
index 0000000..6fdd64d
--- /dev/null
+++ b/platform/www/lib/plugins/config/settings/config.metadata.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Metadata for configuration manager plugin
+ *
+ * Note: This file is loaded in Loader::loadMeta().
+ *
+ * Format:
+ * $meta[<setting name>] = array(<handler class id>,<param name> => <param value>);
+ *
+ * <handler class id> is the handler class name without the "setting_" prefix
+ *
+ * Defined classes (see core/Setting/*):
+ * Generic
+ * -------------------------------------------
+ * '' - default class ('setting'), textarea, minimal input validation, setting output in quotes
+ * 'string' - single line text input, minimal input validation, setting output in quotes
+ * 'numeric' - text input, accepts numbers and arithmetic operators, setting output without quotes
+ * if given the '_min' and '_max' parameters are used for validation
+ * 'numericopt' - like above, but accepts empty values
+ * 'onoff' - checkbox input, setting output 0|1
+ * 'multichoice' - select input (single choice), setting output with quotes, required _choices parameter
+ * 'email' - text input, input must conform to email address format, supports optional '_multiple'
+ * parameter for multiple comma separated email addresses
+ * 'password' - password input, minimal input validation, setting output text in quotes, maybe encoded
+ * according to the _code parameter
+ * 'dirchoice' - as multichoice, selection choices based on folders found at location specified in _dir
+ * parameter (required). A pattern can be used to restrict the folders to only those which
+ * match the pattern.
+ * 'multicheckbox'- a checkbox for each choice plus an "other" string input, config file setting is a comma
+ * separated list of checked choices
+ * 'fieldset' - used to group configuration settings, but is not itself a setting. To make this clear in
+ * the language files the keys for this type should start with '_'.
+ * 'array' - a simple (one dimensional) array of string values, shown as comma separated list in the
+ * config manager but saved as PHP array(). Values may not contain commas themselves.
+ * _pattern matching on the array values supported.
+ * 'regex' - regular expression string, normally without delimiters; as for string, in addition tested
+ * to see if will compile & run as a regex. in addition to _pattern, also accepts _delimiter
+ * (default '/') and _pregflags (default 'ui')
+ *
+ * Single Setting
+ * -------------------------------------------------
+ * 'savedir' - as 'setting', input tested against initpath() (inc/init.php)
+ * 'sepchar' - as multichoice, selection constructed from string of valid values
+ * 'authtype' - as 'setting', input validated against a valid php file at expected location for auth files
+ * 'im_convert' - as 'setting', input must exist and be an im_convert module
+ * 'disableactions' - as 'setting'
+ * 'compression' - no additional parameters. checks php installation supports possible compression alternatives
+ * 'licence' - as multichoice, selection constructed from licence strings in language files
+ * 'renderer' - as multichoice, selection constructed from enabled renderer plugins which canRender()
+ * 'authtype' - as multichoice, selection constructed from the enabled auth plugins
+ *
+ * Any setting commented or missing will use 'setting' class - text input, minimal validation, quoted output
+ *
+ * Defined parameters:
+ * '_caution' - no value (default) or 'warning', 'danger', 'security'. display an alert along with the setting
+ * '_pattern' - string, a preg pattern. input is tested against this pattern before being accepted
+ * optional all classes, except onoff & multichoice which ignore it
+ * '_choices' - array of choices. used to populate a selection box. choice will be replaced by a localised
+ * language string, indexed by <setting name>_o_<choice>, if one exists
+ * required by 'multichoice' & 'multicheckbox' classes, ignored by others
+ * '_dir' - location of directory to be used to populate choice list
+ * required by 'dirchoice' class, ignored by other classes
+ * '_combine' - complimentary output setting values which can be combined into a single display checkbox
+ * optional for 'multicheckbox', ignored by other classes
+ * '_code' - encoding method to use, accepted values: 'base64','uuencode','plain'. defaults to plain.
+ * '_min' - minimum numeric value, optional for 'numeric' and 'numericopt', ignored by others
+ * '_max' - maximum numeric value, optional for 'numeric' and 'numericopt', ignored by others
+ * '_delimiter' - string, default '/', a single character used as a delimiter for testing regex input values
+ * '_pregflags' - string, default 'ui', valid preg pattern modifiers used when testing regex input values, for more
+ * information see http://php.net/manual/en/reference.pcre.pattern.modifiers.php
+ * '_multiple' - bool, allow multiple comma separated email values; optional for 'email', ignored by others
+ * '_other' - how to handle other values (not listed in _choices). accepted values: 'always','exists','never'
+ * default value 'always'. 'exists' only shows 'other' input field when the setting contains value(s)
+ * not listed in choices (e.g. due to manual editing or update changing _choices). This is safer than
+ * 'never' as it will not discard unknown/other values.
+ * optional for 'multicheckbox', ignored by others
+ *
+ * The order of the settings influences the order in which they apppear in the config manager
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+$meta['_basic'] = array('fieldset');
+$meta['title'] = array('string');
+$meta['start'] = array('string','_caution' => 'warning','_pattern' => '!^[^:;/]+$!'); // don't accept namespaces
+$meta['lang'] = array('dirchoice','_dir' => DOKU_INC.'inc/lang/');
+$meta['template'] = array('dirchoice','_dir' => DOKU_INC.'lib/tpl/','_pattern' => '/^[\w-]+$/');
+$meta['tagline'] = array('string');
+$meta['sidebar'] = array('string');
+$meta['license'] = array('license');
+$meta['savedir'] = array('savedir','_caution' => 'danger');
+$meta['basedir'] = array('string','_caution' => 'danger');
+$meta['baseurl'] = array('string','_caution' => 'danger');
+$meta['cookiedir'] = array('string','_caution' => 'danger');
+$meta['dmode'] = array('numeric','_pattern' => '/0[0-7]{3,4}/'); // only accept octal representation
+$meta['fmode'] = array('numeric','_pattern' => '/0[0-7]{3,4}/'); // only accept octal representation
+$meta['allowdebug'] = array('onoff','_caution' => 'security');
+
+$meta['_display'] = array('fieldset');
+$meta['recent'] = array('numeric');
+$meta['recent_days'] = array('numeric');
+$meta['breadcrumbs'] = array('numeric','_min' => 0);
+$meta['youarehere'] = array('onoff');
+$meta['fullpath'] = array('onoff','_caution' => 'security');
+$meta['typography'] = array('multichoice','_choices' => array(0,1,2));
+$meta['dformat'] = array('string');
+$meta['signature'] = array('string');
+$meta['showuseras'] = array(
+ 'multichoice',
+ '_choices' => array('loginname', 'username', 'username_link', 'email', 'email_link')
+);
+$meta['toptoclevel'] = array('multichoice','_choices' => array(1,2,3,4,5)); // 5 toc levels
+$meta['tocminheads'] = array('multichoice','_choices' => array(0,1,2,3,4,5,10,15,20));
+$meta['maxtoclevel'] = array('multichoice','_choices' => array(0,1,2,3,4,5));
+$meta['maxseclevel'] = array('multichoice','_choices' => array(0,1,2,3,4,5)); // 0 for no sec edit buttons
+$meta['camelcase'] = array('onoff','_caution' => 'warning');
+$meta['deaccent'] = array('multichoice','_choices' => array(0,1,2),'_caution' => 'warning');
+$meta['useheading'] = array('multichoice','_choices' => array(0,'navigation','content',1));
+$meta['sneaky_index'] = array('onoff');
+$meta['hidepages'] = array('regex');
+
+$meta['_authentication'] = array('fieldset');
+$meta['useacl'] = array('onoff','_caution' => 'danger');
+$meta['autopasswd'] = array('onoff');
+$meta['authtype'] = array('authtype','_caution' => 'danger');
+$meta['passcrypt'] = array('multichoice','_choices' => array(
+ 'smd5','md5','apr1','sha1','ssha','lsmd5','crypt','mysql','my411','kmd5','pmd5','hmd5',
+ 'mediawiki','bcrypt','djangomd5','djangosha1','djangopbkdf2_sha1','djangopbkdf2_sha256',
+ 'sha512','argon2i','argon2id'
+));
+$meta['defaultgroup']= array('string');
+$meta['superuser'] = array('string','_caution' => 'danger');
+$meta['manager'] = array('string');
+$meta['profileconfirm'] = array('onoff');
+$meta['rememberme'] = array('onoff');
+$meta['disableactions'] = array(
+ 'disableactions',
+ '_choices' => array(
+ 'backlink',
+ 'index',
+ 'recent',
+ 'revisions',
+ 'search',
+ 'subscription',
+ 'register',
+ 'resendpwd',
+ 'profile',
+ 'profile_delete',
+ 'edit',
+ 'wikicode',
+ 'check',
+ 'rss'
+ ),
+ '_combine' => array(
+ 'subscription' => array('subscribe', 'unsubscribe'),
+ 'wikicode' => array('source', 'export_raw')
+ )
+);
+$meta['auth_security_timeout'] = array('numeric');
+$meta['securecookie'] = array('onoff');
+$meta['remote'] = array('onoff','_caution' => 'security');
+$meta['remoteuser'] = array('string');
+
+$meta['_anti_spam'] = array('fieldset');
+$meta['usewordblock']= array('onoff');
+$meta['relnofollow'] = array('onoff');
+$meta['indexdelay'] = array('numeric');
+$meta['mailguard'] = array('multichoice','_choices' => array('visible','hex','none'));
+$meta['iexssprotect']= array('onoff','_caution' => 'security');
+
+$meta['_editing'] = array('fieldset');
+$meta['usedraft'] = array('onoff');
+$meta['htmlok'] = array('onoff','_caution' => 'security');
+$meta['phpok'] = array('onoff','_caution' => 'security');
+$meta['locktime'] = array('numeric');
+$meta['cachetime'] = array('numeric');
+
+$meta['_links'] = array('fieldset');
+$meta['target____wiki'] = array('string');
+$meta['target____interwiki'] = array('string');
+$meta['target____extern'] = array('string');
+$meta['target____media'] = array('string');
+$meta['target____windows'] = array('string');
+
+$meta['_media'] = array('fieldset');
+$meta['mediarevisions'] = array('onoff');
+$meta['gdlib'] = array('multichoice','_choices' => array(0,1,2));
+$meta['im_convert'] = array('im_convert');
+$meta['jpg_quality'] = array('numeric','_pattern' => '/^100$|^[1-9]?[0-9]$/'); //(0-100)
+$meta['fetchsize'] = array('numeric');
+$meta['refcheck'] = array('onoff');
+
+$meta['_notifications'] = array('fieldset');
+$meta['subscribers'] = array('onoff');
+$meta['subscribe_time'] = array('numeric');
+$meta['notify'] = array('email', '_multiple' => true);
+$meta['registernotify'] = array('email', '_multiple' => true);
+$meta['mailfrom'] = array('email', '_placeholders' => true);
+$meta['mailreturnpath'] = array('email', '_placeholders' => true);
+$meta['mailprefix'] = array('string');
+$meta['htmlmail'] = array('onoff');
+
+$meta['_syndication'] = array('fieldset');
+$meta['sitemap'] = array('numeric');
+$meta['rss_type'] = array('multichoice','_choices' => array('rss','rss1','rss2','atom','atom1'));
+$meta['rss_linkto'] = array('multichoice','_choices' => array('diff','page','rev','current'));
+$meta['rss_content'] = array('multichoice','_choices' => array('abstract','diff','htmldiff','html'));
+$meta['rss_media'] = array('multichoice','_choices' => array('both','pages','media'));
+$meta['rss_update'] = array('numeric');
+$meta['rss_show_summary'] = array('onoff');
+$meta['rss_show_deleted'] = array('onoff');
+
+$meta['_advanced'] = array('fieldset');
+$meta['updatecheck'] = array('onoff');
+$meta['userewrite'] = array('multichoice','_choices' => array(0,1,2),'_caution' => 'danger');
+$meta['useslash'] = array('onoff');
+$meta['sepchar'] = array('sepchar','_caution' => 'warning');
+$meta['canonical'] = array('onoff');
+$meta['fnencode'] = array('multichoice','_choices' => array('url','safe','utf-8'),'_caution' => 'warning');
+$meta['autoplural'] = array('onoff');
+$meta['compress'] = array('onoff');
+$meta['cssdatauri'] = array('numeric','_pattern' => '/^\d+$/');
+$meta['gzip_output'] = array('onoff');
+$meta['send404'] = array('onoff');
+$meta['compression'] = array('compression','_caution' => 'warning');
+$meta['broken_iua'] = array('onoff');
+$meta['xsendfile'] = array('multichoice','_choices' => array(0,1,2,3),'_caution' => 'warning');
+$meta['renderer_xhtml'] = array('renderer','_format' => 'xhtml','_choices' => array('xhtml'),'_caution' => 'warning');
+$meta['readdircache'] = array('numeric');
+$meta['search_nslimit'] = array('numeric', '_min' => 0);
+$meta['search_fragment'] = array('multichoice','_choices' => array('exact', 'starts_with', 'ends_with', 'contains'),);
+$meta['trustedproxy'] = array('regex');
+
+$meta['_feature_flags'] = ['fieldset'];
+$meta['defer_js'] = ['onoff'];
+
+$meta['_network'] = array('fieldset');
+$meta['dnslookups'] = array('onoff');
+$meta['jquerycdn'] = array('multichoice', '_choices' => array(0,'jquery', 'cdnjs'));
+$meta['proxy____host'] = array('string','_pattern' => '#^(|[a-z0-9\-\.+]+)$#i');
+$meta['proxy____port'] = array('numericopt');
+$meta['proxy____user'] = array('string');
+$meta['proxy____pass'] = array('password','_code' => 'base64');
+$meta['proxy____ssl'] = array('onoff');
+$meta['proxy____except'] = array('string');
diff --git a/platform/www/lib/plugins/config/style.css b/platform/www/lib/plugins/config/style.css
new file mode 100644
index 0000000..054021e
--- /dev/null
+++ b/platform/www/lib/plugins/config/style.css
@@ -0,0 +1,167 @@
+/* plugin:configmanager */
+#config__manager div.success,
+#config__manager div.error,
+#config__manager div.info {
+ background-position: 0.5em;
+ padding: 0.5em;
+ text-align: center;
+}
+
+#config__manager fieldset {
+ margin: 1em;
+ width: auto;
+ margin-bottom: 2em;
+ background-color: __background_alt__;
+ color: __text__;
+ padding: 0 1em;
+}
+[dir=rtl] #config__manager fieldset {
+ clear: both;
+}
+#config__manager legend {
+ font-size: 1.25em;
+}
+
+#config__manager form { }
+#config__manager table {
+ margin: 1em 0;
+ width: 100%;
+}
+
+#config__manager fieldset td {
+ text-align: left;
+}
+[dir=rtl] #config__manager fieldset td {
+ text-align: right;
+}
+#config__manager fieldset td.value {
+ /* fixed data column width */
+ width: 31em;
+}
+
+[dir=rtl] #config__manager label {
+ text-align: right;
+}
+[dir=rtl] #config__manager td.value input.checkbox {
+ float: right;
+ padding-left: 0;
+ padding-right: 0.7em;
+}
+[dir=rtl] #config__manager td.value label {
+ float: left;
+}
+
+#config__manager td.label {
+ padding: 0.8em 0 0.6em 1em;
+ vertical-align: top;
+}
+[dir=rtl] #config__manager td.label {
+ padding: 0.8em 1em 0.6em 0;
+}
+
+#config__manager td.label label {
+ clear: left;
+ display: block;
+}
+[dir=rtl] #config__manager td.label label {
+ clear: right;
+}
+#config__manager td.label img {
+ padding: 0 10px;
+ vertical-align: middle;
+ float: right;
+}
+[dir=rtl] #config__manager td.label img {
+ float: left;
+}
+
+#config__manager td.label span.outkey {
+ font-size: 70%;
+ margin-top: -1.7em;
+ margin-left: -1em;
+ display: block;
+ background-color: __background__;
+ color: __text_neu__;
+ float: left;
+ padding: 0 0.1em;
+ position: relative;
+ z-index: 1;
+}
+[dir=rtl] #config__manager td.label span.outkey {
+ float: right;
+ margin-right: 1em;
+}
+
+#config__manager td input.edit {
+ width: 30em;
+}
+#config__manager td .input {
+ width: 30.8em;
+}
+#config__manager td select.edit { }
+#config__manager td textarea.edit {
+ width: 27.5em;
+ height: 4em;
+}
+
+#config__manager td textarea.edit:focus {
+ height: 10em;
+}
+
+#config__manager tr .input,
+#config__manager tr input,
+#config__manager tr textarea,
+#config__manager tr select {
+ background-color: #fff;
+ color: #000;
+}
+
+#config__manager tr.default .input,
+#config__manager tr.default input,
+#config__manager tr.default textarea,
+#config__manager tr.default select,
+#config__manager .selectiondefault {
+ background-color: #ccddff;
+ color: #000;
+}
+
+#config__manager tr.protected .input,
+#config__manager tr.protected input,
+#config__manager tr.protected textarea,
+#config__manager tr.protected select,
+#config__manager tr.protected .selection {
+ background-color: #ffcccc!important;
+ color: #000 !important;
+}
+
+#config__manager td.error { background-color: red; color: #000; }
+
+#config__manager .selection {
+ width: 14.8em;
+ float: left;
+ margin: 0 0.3em 2px 0;
+}
+[dir=rtl] #config__manager .selection {
+ width: 14.8em;
+ float: right;
+ margin: 0 0 2px 0.3em;
+}
+
+#config__manager .selection label {
+ float: right;
+ width: 14em;
+ font-size: 90%;
+}
+
+
+#config__manager .other {
+ clear: both;
+ padding-top: 0.5em;
+}
+
+#config__manager .other label {
+ padding-left: 2px;
+ font-size: 90%;
+}
+
+/* end plugin:configmanager */