summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Schulthess2017-01-19 15:39:51 +0100
committerChristoph Schulthess2017-01-19 15:39:51 +0100
commit8daf28a8fe9c920d1dfb9cb8e465c0d2b3f63afc (patch)
treeba98d1ee20a798c60f15208619600f9dba83a27b
parentdeactivated query (diff)
downloadslx-admin-8daf28a8fe9c920d1dfb9cb8e465c0d2b3f63afc.tar.gz
slx-admin-8daf28a8fe9c920d1dfb9cb8e465c0d2b3f63afc.tar.xz
slx-admin-8daf28a8fe9c920d1dfb9cb8e465c0d2b3f63afc.zip
preliminary debugrequest version
-rw-r--r--apis/debugrequest.php14
-rw-r--r--modules-available/debugconfig/api.inc.php115
-rw-r--r--modules-available/debugconfig/config.json4
-rw-r--r--modules-available/debugconfig/inc/baseconfigutil.inc.php83
-rw-r--r--modules-available/debugconfig/inc/validator.inc.php106
-rw-r--r--modules-available/debugconfig/install.inc.php58
-rw-r--r--modules-available/debugconfig/lang/de/messages.json5
-rw-r--r--modules-available/debugconfig/lang/de/module.json3
-rw-r--r--modules-available/debugconfig/lang/de/template-tags.json7
-rw-r--r--modules-available/debugconfig/lang/en/messages.json5
-rw-r--r--modules-available/debugconfig/lang/en/module.json3
-rw-r--r--modules-available/debugconfig/lang/en/template-tags.json7
-rw-r--r--modules-available/debugconfig/lang/pt/messages.json3
-rw-r--r--modules-available/debugconfig/lang/pt/module.json3
-rw-r--r--modules-available/debugconfig/lang/pt/template-tags.json3
-rw-r--r--modules-available/debugconfig/page.inc.php342
-rw-r--r--modules-available/debugconfig/templates/_page.html122
17 files changed, 876 insertions, 7 deletions
diff --git a/apis/debugrequest.php b/apis/debugrequest.php
index 8e0fd78b..4449e6eb 100644
--- a/apis/debugrequest.php
+++ b/apis/debugrequest.php
@@ -1,19 +1,19 @@
<?php
-
/*$debug_settings = Property::getRemoteDebuggingConfig();
if ($debug_settings["status"] !== "enabled") {
http_response_code(403);
exit;
}*/
-$debug_request = json_decode(file_get_contents("php://input"), true);
-//$validclient = Database::queryFirst("SELECT machineuuid, clientip FROM machine WHERE machineuuid = :uuid AND clientip = :ip", array(":uuid" => $debug_request["uuid"], ":ip" => \'$_SERVER["REMOTE_ADDR"]\'));
-$validclient = true;
+$debug_request = split(":", file_get_contents("php://input"), 2);
+$uuid = $debug_request[0];
+$port = 5900 + $debug_request[1];
+$validclient = Database::queryFirst("SELECT machineuuid, clientip FROM machine WHERE machineuuid = :uuid AND clientip = :ip", array(":uuid" => "$debug_request[0]", ":ip" => $_SERVER["REMOTE_ADDR"]));
+//$validclient = Database::simpleQuery("SELECT machineuuid, clientip FROM machine LIMIT 1;");
if ($validclient == false) {
http_response_code(400);
} else {
http_response_code(200);
- //$data = json_encode(array("ip" => $_SERVER["REMOTE_ADDR"], "port" => 5900 + $debug_request["display"]));
- //Taskmanager::submit('relay', $data, true);
+ $data = json_encode(array("ip" => $_SERVER["REMOTE_ADDR"], "port" => 5900 + $debug_request["display"]));
+ Taskmanager::submit('relay', $data, true);
}
-//echo file_get_contents("php://input");
diff --git a/modules-available/debugconfig/api.inc.php b/modules-available/debugconfig/api.inc.php
new file mode 100644
index 00000000..af780d99
--- /dev/null
+++ b/modules-available/debugconfig/api.inc.php
@@ -0,0 +1,115 @@
+<?php
+
+$ip = $_SERVER['REMOTE_ADDR'];
+if (substr($ip, 0, 7) === '::ffff:') {
+ $ip = substr($ip, 7);
+}
+
+$uuid = Request::any('uuid', false, 'string');
+if ($uuid !== false && strlen($uuid) !== 36) {
+ $uuid = false;
+}
+
+/**
+ * Escape given string so it is a valid string in sh that can be surrounded
+ * by single quotes ('). This basically turns _'_ into _'"'"'_
+ *
+ * @param string $string input
+ * @return string escaped sh string
+ */
+function escape($string)
+{
+ return str_replace("'", "'\"'\"'", $string);
+}
+
+/*
+ * We gather all config variables here. First, let other modules generate
+ * their desired config vars. Afterwards, add the global config vars from
+ * db. If a variable is already set, it will not be overridden by the
+ * global setting.
+ */
+
+$configVars = array();
+function handleModule($file, $ip, $uuid) // Pass ip and uuid instead of global to make them read only
+{
+ global $configVars;
+ include_once $file;
+}
+
+// Handle any hooks by other modules first
+// other modules should generally only populate $configVars
+foreach (glob('modules/*/baseconfig/getconfig.inc.php') as $file) {
+ preg_match('#^modules/([^/]+)/#', $file, $out);
+ $mod = Module::get($out[1]);
+ if ($mod === false)
+ continue;
+ $mod->activate();
+ foreach ($mod->getDependencies() as $dep) {
+ $depFile = 'modules/' . $dep . '/baseconfig/getconfig.inc.php';
+ if (file_exists($depFile) && Module::isAvailable($dep)) {
+ handleModule($depFile, $ip, $uuid);
+ }
+ }
+ handleModule($file, $ip, $uuid);
+}
+
+// Rest is handled by module
+$defaults = BaseConfigUtil::getVariables();
+
+// Dump global config from DB
+$res = Database::simpleQuery('SELECT setting, value, enabled FROM setting_global');
+while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (isset($configVars[$row['setting']]) // Already set by a hook above, ignore
+ || !isset($defaults[$row['setting']])) // Setting is not defined in any <module>/baseconfig/settings.json
+ continue;
+ if ($row['enabled'] != 1) {
+ // Setting is disabled
+ $configVars[$row['setting']] = false;
+ } else {
+ $configVars[$row['setting']] = $row['value'];
+ }
+}
+
+// Fallback to default values from json files
+foreach ($defaults as $setting => $value) {
+ if (isset($configVars[$setting])) {
+ if ($configVars[$setting] === false) {
+ unset($configVars[$setting]);
+ }
+ } else {
+ $configVars[$setting] = $value['defaultvalue'];
+ }
+}
+
+// All done, now output
+
+if (Request::any('save') === 'true') {
+ // output AND save to disk: Generate contents
+ $lines = '';
+ foreach ($configVars as $setting => $value) {
+ $lines .= $setting . "='" . escape($value) . "'\n";
+ }
+ // Save to all the locations
+ $data = Property::getVersionCheckInformation();
+ if (is_array($data) && isset($data['systems'])) {
+ foreach ($data['systems'] as $system) {
+ $path = CONFIG_HTTP_DIR . '/' . $system['id'] . '/config';
+ if (file_put_contents($path, $lines) > 0) {
+ echo "# Saved config to $path\n";
+ } else {
+ echo "# Error saving config to $path\n";
+ }
+ }
+ }
+ // Output to browser
+ echo $lines;
+} else {
+ // Only output to client
+ foreach ($configVars as $setting => $value) {
+ echo $setting, "='", escape($value), "'\n";
+ }
+}
+
+// For quick testing or custom extensions: Include external file that should do nothing
+// more than outputting more key-value-pairs. It's expected in the webroot of slxadmin
+if (file_exists('client_config_additional.php')) @include('client_config_additional.php');
diff --git a/modules-available/debugconfig/config.json b/modules-available/debugconfig/config.json
new file mode 100644
index 00000000..e4d906e1
--- /dev/null
+++ b/modules-available/debugconfig/config.json
@@ -0,0 +1,4 @@
+{
+ "category": "main.settings-client",
+ "dependencies" : ["js_selectize", "bootstrap_multiselect"]
+}
diff --git a/modules-available/debugconfig/inc/baseconfigutil.inc.php b/modules-available/debugconfig/inc/baseconfigutil.inc.php
new file mode 100644
index 00000000..3039ea12
--- /dev/null
+++ b/modules-available/debugconfig/inc/baseconfigutil.inc.php
@@ -0,0 +1,83 @@
+<?php
+
+class BaseConfigUtil
+{
+
+ /**
+ * Return all config variables to be handled directly by the baseconfig edit module.
+ * The array will contain a list of mapping of type:
+ * VARNAME => array(
+ * catid => xx,
+ * defaultvalue => xx,
+ * permissions => xx,
+ * validator => xx,
+ * )
+ *
+ * @param \Module $module optional, only consider given module, not all enabled modules
+ * @return array all known config variables
+ */
+ public static function getVariables($module = false)
+ {
+ $settings = array();
+ if ($module === false) {
+ $module = '*';
+ } else {
+ $module = $module->getIdentifier();
+ }
+ foreach (glob("modules/{$module}/baseconfig/settings.json", GLOB_NOSORT) as $file) {
+ $data = json_decode(file_get_contents($file), true);
+ if (!is_array($data))
+ continue;
+ preg_match('#^modules/([^/]+)/#', $file, $out);
+ foreach ($data as &$entry) {
+ $entry['module'] = $out[1];
+ }
+ $settings += $data;
+ }
+ return $settings;
+ }
+
+ public static function getCategories($module = false)
+ {
+ $categories = array();
+ if ($module === false) {
+ $module = '*';
+ } else {
+ $module = $module->getIdentifier();
+ }
+ foreach (glob("modules/{$module}/baseconfig/categories.json", GLOB_NOSORT) as $file) {
+ $data = json_decode(file_get_contents($file), true);
+ if (!is_array($data))
+ continue;
+ preg_match('#^modules/([^/]+)/#', $file, $out);
+ foreach ($data as &$entry) {
+ $entry = array('module' => $out[1], 'sortpos' => $entry);
+ }
+ $categories += $data;
+ }
+ return $categories;
+ }
+
+ /**
+ * Mark variables that would be shadowed according to the given values.
+ *
+ * @param $vars list of vars as obtained from BaseConfigUtil::getVariables()
+ * @param $values key-value-pairs of variable assignments to work with
+ */
+ public static function markShadowedVars(&$vars, $values) {
+ foreach ($vars as $key => &$var) {
+ if (!isset($var['shadows']))
+ continue;
+ foreach ($var['shadows'] as $triggerVal => $destSettings) {
+ if (isset($values[$key]) && $values[$key] !== $triggerVal)
+ continue;
+ foreach ($destSettings as $destSetting) {
+ if (isset($vars[$destSetting])) {
+ $vars[$destSetting]['shadowed'] = true;
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/modules-available/debugconfig/inc/validator.inc.php b/modules-available/debugconfig/inc/validator.inc.php
new file mode 100644
index 00000000..ec7b95aa
--- /dev/null
+++ b/modules-available/debugconfig/inc/validator.inc.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * This class contains all the helper functions that
+ * can be referenced by a config setting. Every function
+ * here is supposed to validate the given config value
+ * and either return the validated and possibly sanitized
+ * value, or false to indicate that the given value is invalid.
+ * The passed value is a reference, as it can also be modified
+ * by the validator to tweak the value that is being
+ * displayed in the web interface, compared to the returned
+ * value, which will only be used by the client directly,
+ * and is not displayed by the web interface.
+ */
+class Validator
+{
+
+ public static function validate($condition, &$displayValue)
+ {
+ if (empty($condition))
+ return $displayValue;
+ $data = explode(':', $condition, 2);
+ switch ($data[0]) {
+ case 'regex':
+ if (preg_match($data[1], $displayValue))
+ return $displayValue;
+ return false;
+ case 'list':
+ return self::validateList($data[1], $displayValue);
+ case 'function':
+ return self::$data[1]($displayValue);
+ case 'multilist':
+ return self::validateMultiList($data[1], $displayValue);
+ case 'multiinput':
+ return self::validateMultiInput($data[1], $displayValue);
+ default:
+ Util::traceError('Unknown validation method: ' . $data[0]);
+ }
+ return false; // make code inspector happy - doesn't know traceError doesn't return
+ }
+
+
+ /**
+ * Validate linux password. If already in $6$ hash form,
+ * the unchanged value will be returned.
+ * if empty, an empty string will also be returned.
+ * Otherwise it it assumed that the value is a plain text
+ * password that is supposed to be hashed.
+ */
+ private static function linuxPassword(&$displayValue)
+ {
+ if (empty($displayValue))
+ return '';
+ if (preg_match('/^\$[156]\$.+\$./', $displayValue))
+ return $displayValue;
+ return Crypto::hash6($displayValue);
+ }
+
+ /**
+ * "Fix" network share path for SMB shares where a backslash
+ * is used instead of a slash.
+ * @param string $displayValue network path
+ * @return string cleaned up path
+ */
+ private static function networkShare(&$displayValue)
+ {
+ $displayValue = trim($displayValue);
+ if (substr($displayValue, 0, 2) === '\\\\')
+ $displayValue = str_replace('\\', '/', $displayValue);
+ $returnValue = $displayValue;
+ if (substr($returnValue, 0, 2) === '//')
+ $returnValue = str_replace(' ', '\\040', $returnValue);
+ return $returnValue;
+ }
+
+ /**
+ * Validate value against list.
+ * @param string $list The list as a string of items, separated by "|"
+ * @param string $displayValue The value to validate
+ * @return boolean|string The value, if in list, false otherwise
+ */
+ private static function validateList($list, &$displayValue)
+ {
+ $list = explode('|', $list);
+ if (in_array($displayValue, $list))
+ return $displayValue;
+ return false;
+ }
+ private static function validateMultiList($list, &$displayValue)
+ {
+ $allowedValues = explode('|', $list);
+ $values = [];
+ foreach ($displayValue as $v) {
+ if (in_array($v, $allowedValues)) {
+ $values[] = $v;
+ }
+ }
+ $displayValue = implode(' ', $values);
+ return $displayValue;
+ }
+
+ private static function validateMultiInput(&$list, &$displayValue)
+ {
+ return $displayValue;
+ }
+}
diff --git a/modules-available/debugconfig/install.inc.php b/modules-available/debugconfig/install.inc.php
new file mode 100644
index 00000000..b4eada5d
--- /dev/null
+++ b/modules-available/debugconfig/install.inc.php
@@ -0,0 +1,58 @@
+<?php
+
+$res = array();
+
+$res[] = tableCreate('setting_global', "
+ `setting` varchar(28) NOT NULL,
+ `value` text NOT NULL,
+ `displayvalue` text NOT NULL,
+ `enabled` tinyint(1) UNSIGNED NOT NULL DEFAULT '1',
+ PRIMARY KEY (`setting`)
+");
+
+// Update path
+
+// Add toggle field
+
+if (!tableHasColumn('setting_global', 'enabled')) {
+ if (tableHasColumn('setting_global', 'toggle')) {
+ $ret = Database::exec("ALTER TABLE `setting_global` CHANGE `toggle` `enabled` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1'");
+ } else {
+ $ret = Database::exec("ALTER TABLE `setting_global` ADD COLUMN `enabled` tinyint(1) UNSIGNED NOT NULL DEFAULT '1'");
+ }
+ if ($ret === false) {
+ finalResponse(UPDATE_FAILED, 'Adding enabled to setting_global failed: ' . Database::lastError());
+ }
+}
+
+// Add displayvalue field
+
+if (!tableHasColumn('setting_global', 'displayvalue')) {
+ Database::exec("ALTER TABLE `setting_global` ADD `displayvalue` TEXT NOT NULL");
+ Database::exec("UPDATE `setting_global` SET `displayvalue` = `value`");
+ $res[] = UPDATE_DONE;
+}
+
+// Delete old tables
+
+/*
+Keep disabled for a while, in case some customer made unexpected important changes etc...
+
+if (tableExists('setting')) {
+ Database::exec('DROP TABLE setting');
+}
+if (tableExists('setting_distro')) {
+ Database::exec('DROP TABLE setting_distro');
+}
+if (tableExists('cat_setting')) {
+ Database::exec('DROP TABLE cat_setting');
+}
+*/
+
+// Create response for browser
+
+if (in_array(UPDATE_DONE, $res)) {
+ finalResponse(UPDATE_DONE, 'Tables created successfully');
+}
+
+finalResponse(UPDATE_NOOP, 'Everything already up to date');
diff --git a/modules-available/debugconfig/lang/de/messages.json b/modules-available/debugconfig/lang/de/messages.json
new file mode 100644
index 00000000..a0b8cdd4
--- /dev/null
+++ b/modules-available/debugconfig/lang/de/messages.json
@@ -0,0 +1,5 @@
+{
+ "invalid-hook": "Ung\u00fcltiger Hook: {{0}}",
+ "no-module-hook": "Modul {{0}} hat keinen Hook",
+ "settings-updated": "Einstellungen wurden aktualisiert"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/de/module.json b/modules-available/debugconfig/lang/de/module.json
new file mode 100644
index 00000000..461bebdb
--- /dev/null
+++ b/modules-available/debugconfig/lang/de/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "KonfigurationsVariablen"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/de/template-tags.json b/modules-available/debugconfig/lang/de/template-tags.json
new file mode 100644
index 00000000..cdd54f6a
--- /dev/null
+++ b/modules-available/debugconfig/lang/de/template-tags.json
@@ -0,0 +1,7 @@
+{
+ "lang_basicConfiguration": "Basiskonfiguration",
+ "lang_clientRelatedConfig": "Die Optionen auf dieser Seite beziehen sich auf das Verhalten der bwLehrpool-Clients.",
+ "lang_editOverrideNotice": "Sie bearbeiten die Einstellungen f\u00fcr einen Unterbereich",
+ "lang_enableOverride": "\u00dcberschreiben",
+ "lang_settingActive": "Einstellung aktiv"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/en/messages.json b/modules-available/debugconfig/lang/en/messages.json
new file mode 100644
index 00000000..0e2ac9fe
--- /dev/null
+++ b/modules-available/debugconfig/lang/en/messages.json
@@ -0,0 +1,5 @@
+{
+ "invalid-hook": "Module {{0}} has an invalid baseconfig hook",
+ "no-module-hook": "Module {{0}} doesn't have a baseconfig hook",
+ "settings-updated": "Settings have been updated"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/en/module.json b/modules-available/debugconfig/lang/en/module.json
new file mode 100644
index 00000000..48591f88
--- /dev/null
+++ b/modules-available/debugconfig/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Config variables"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/en/template-tags.json b/modules-available/debugconfig/lang/en/template-tags.json
new file mode 100644
index 00000000..8e75e5ac
--- /dev/null
+++ b/modules-available/debugconfig/lang/en/template-tags.json
@@ -0,0 +1,7 @@
+{
+ "lang_basicConfiguration": "Basic Configuration",
+ "lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
+ "lang_editOverrideNotice": "You're editing the settings of a sub-section",
+ "lang_enableOverride": "Override",
+ "lang_settingActive": "Setting active"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/pt/messages.json b/modules-available/debugconfig/lang/pt/messages.json
new file mode 100644
index 00000000..f5cb96eb
--- /dev/null
+++ b/modules-available/debugconfig/lang/pt/messages.json
@@ -0,0 +1,3 @@
+{
+ "settings-updated": "As configura\u00e7\u00f5es foram atualizadas"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/pt/module.json b/modules-available/debugconfig/lang/pt/module.json
new file mode 100644
index 00000000..dca9eb8b
--- /dev/null
+++ b/modules-available/debugconfig/lang/pt/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Vari\u00e1veis"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/lang/pt/template-tags.json b/modules-available/debugconfig/lang/pt/template-tags.json
new file mode 100644
index 00000000..14367665
--- /dev/null
+++ b/modules-available/debugconfig/lang/pt/template-tags.json
@@ -0,0 +1,3 @@
+{
+ "lang_basicConfiguration": "Configura\u00e7\u00e3o B\u00e1sica"
+} \ No newline at end of file
diff --git a/modules-available/debugconfig/page.inc.php b/modules-available/debugconfig/page.inc.php
new file mode 100644
index 00000000..5e99f2a0
--- /dev/null
+++ b/modules-available/debugconfig/page.inc.php
@@ -0,0 +1,342 @@
+<?php
+
+class Page_BaseConfig extends Page
+{
+ private $qry_extra = array();
+ private $categories;
+
+ /**
+ * @var bool|string in case we're in module mode, set to the id of the module
+ */
+ private $targetModule = false;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ // Determine if we're setting global or module specific
+ $this->getModuleSpecific();
+
+ $newValues = Request::post('setting');
+ if (is_array($newValues)) {
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=baseconfig');
+ }
+ // Build variables for specific sub-settings
+ if ($this->targetModule === false) {
+ // We're editing global settings - use the 'enabled' field
+ $qry_insert = ', enabled';
+ $qry_values = ', :enabled';
+ $qry_update = ', enabled = :enabled';
+ $params = array();
+ } elseif (empty($this->qry_extra['field'])) {
+ // Module specific, but module doesn't have an extra field
+ $qry_insert = '';
+ $qry_values = '';
+ $qry_update = '';
+ } else {
+ // Module with extra field
+ $qry_insert = ', ' . $this->qry_extra['field'];
+ $qry_values = ', :field_value';
+ $qry_update = '';
+ $params = array('field_value' => $this->qry_extra['field_value']);
+ $delExtra = " AND {$this->qry_extra['field']} = :field_value ";
+ $delParams = array('field_value' => $this->qry_extra['field_value']);
+ // Not editing global settings
+ if ($this->getCurrentModuleName() === false) {
+ Message::addError('main.value-invalid', $this->qry_extra['field'], $this->qry_extra['field_value']);
+ Util::redirect('?do=BaseConfig');
+ }
+ }
+ // Honor override/enabled checkbox
+ $override = Request::post('override', array());
+ // Load all existing config options to validate input
+ $vars = BaseConfigUtil::getVariables();
+ // First, handle shadowing so we don't create warnings for empty fields
+ BaseConfigUtil::markShadowedVars($vars, $newValues);
+ // Validate input
+ foreach ($vars as $key => $var) {
+ if (isset($var['shadowed']))
+ continue;
+ if ($this->targetModule === false) {
+ // Global mode
+ $params['enabled'] = (is_array($override) && isset($override[$key]) && $override[$key] === 'on') ? 1 : 0;
+ } else {
+ // Module mode
+ if (is_array($override) && (!isset($override[$key]) || $override[$key] !== 'on')) {
+ // override not set - delete
+ $delParams['key'] = $key;
+ Database::exec("DELETE FROM {$this->qry_extra['table']} WHERE setting = :key $delExtra", $delParams);
+ continue;
+ }
+ }
+ $validator = $var['validator'];
+ $displayValue = (isset($newValues[$key]) ? $newValues[$key] : '');
+ // Validate data first!
+ $mangledValue = Validator::validate($validator, $displayValue);
+ if ($mangledValue === false) {
+ Message::addWarning('main.value-invalid', $key, $displayValue);
+ continue;
+ }
+ // Now put into DB
+ Database::exec("INSERT INTO {$this->qry_extra['table']} (setting, value, displayvalue $qry_insert)"
+ . " VALUES (:key, :value, :displayvalue $qry_values)"
+ . " ON DUPLICATE KEY UPDATE value = :value, displayvalue = :displayvalue $qry_update",
+ array(
+ 'key' => $key,
+ 'value' => $mangledValue,
+ 'displayvalue' => $displayValue
+ ) + $params
+ );
+ }
+ Message::addSuccess('settings-updated');
+ if ($this->targetModule === false) {
+ Util::redirect('?do=BaseConfig');
+ } elseif (empty($this->qry_extra['field'])) {
+ Util::redirect('?do=BaseConfig&module=' . $this->targetModule);
+ } else {
+ Util::redirect('?do=BaseConfig&module=' . $this->targetModule . '&' . $this->qry_extra['field'] . '=' . $this->qry_extra['field_value']);
+ }
+ }
+ // Load categories so we can define them as sub menu items
+ $this->categories = BaseConfigUtil::getCategories();
+ asort($this->categories, SORT_DESC);
+ foreach ($this->categories as $catid => $val) {
+ Dashboard::addSubmenu(
+ '#category_' . $catid,
+ Dictionary::translateFileModule($this->categories[$catid]['module'], 'config-variable-categories', $catid, true)
+ );
+ }
+ }
+
+ protected function doRender()
+ {
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main');
+ }
+ // Check if valid submodule mode, store name if any
+ if ($this->targetModule !== false) {
+ $this->qry_extra['subheading'] = $this->getCurrentModuleName();
+ if ($this->qry_extra['subheading'] === false) {
+ Message::addError('main.value-invalid', $this->qry_extra['field'], $this->qry_extra['field_value']);
+ Util::redirect('?do=BaseConfig');
+ }
+ }
+ // List config options
+ $settings = array();
+ $vars = BaseConfigUtil::getVariables();
+ // Get stuff that's set in DB already
+ if ($this->targetModule === false) {
+ $fields = ', enabled';
+ $where = '';
+ $params = array();
+ } elseif (isset($this->qry_extra['field'])) {
+ $fields = '';
+ $where = " WHERE {$this->qry_extra['field']} = :field_value";
+ $params = array('field_value' => $this->qry_extra['field_value']);
+ } else {
+ $fields = '';
+ $where = '';
+ $params = array();
+ }
+ // Populate structure with existing config from db
+ $res = Database::simpleQuery("SELECT setting, value, displayvalue $fields FROM {$this->qry_extra['table']} "
+ . " {$where} ORDER BY setting ASC", $params);
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (!isset($vars[$row['setting']]) || !is_array($vars[$row['setting']])) {
+ $unknown[] = $row['setting'];
+ continue;
+ }
+ $row += $vars[$row['setting']];
+ if (!isset($row['catid'])) {
+ $row['catid'] = 'unknown';
+ }
+ $settings[$row['catid']]['settings'][$row['setting']] = $row;
+ }
+ // Add entries that weren't in the db (global), setup override checkbox (module specific)
+ foreach ($vars as $key => $var) {
+ if ($this->targetModule === false) {
+ // Global settings - honor enabled field in db
+ if (!isset($settings[$var['catid']]['settings'][$key]['enabled']) || $settings[$var['catid']]['settings'][$key]['enabled'] == 1) {
+ $settings[$var['catid']]['settings'][$key]['checked'] = 'checked';
+ }
+ } elseif (isset($settings[$var['catid']]['settings'][$key])) {
+ // Module specific - value is set in DB
+ $settings[$var['catid']]['settings'][$key]['checked'] = 'checked';
+ } else {
+ // Module specific - value is not set in DB
+ $settings[$var['catid']]['settings'][$key] = $var + array(
+ 'setting' => $key
+ );
+ }
+ if (!isset($settings[$var['catid']]['settings'][$key]['displayvalue'])) {
+ $settings[$var['catid']]['settings'][$key]['displayvalue'] = $var['defaultvalue'];
+ }
+ if (!isset($settings[$var['catid']]['settings'][$key]['shadows'])) {
+ $settings[$var['catid']]['settings'][$key]['shadows'] = null;
+ }
+ //echo "<pre>";
+ //var_dump($settings[$var['catid']]['settings'][$key]);
+ //echo "</pre>";
+ $settings[$var['catid']]['settings'][$key] += array(
+ 'item' => $this->makeInput(
+ $var['validator'],
+ $key,
+ $settings[$var['catid']]['settings'][$key]['displayvalue'],
+ $settings[$var['catid']]['settings'][$key]['shadows']
+ ),
+ 'description' => Util::markup(Dictionary::translateFileModule($var['module'], 'config-variables', $key)),
+ 'setting' => $key,
+ );
+ }
+ //die();
+
+
+ // Sort categories
+ $sortvals = array();
+ foreach ($settings as $catid => &$setting) {
+ $sortvals[] = isset($this->categories[$catid]) ? (int)$this->categories[$catid]['sortpos'] : 99999;
+ $setting['category_id'] = $catid;
+ $setting['category_name'] = Dictionary::translateFileModule($this->categories[$catid]['module'], 'config-variable-categories', $catid);
+ if ($setting['category_name'] === false) {
+ $setting['category_name'] = $catid;
+ }
+ ksort($setting['settings']);
+ $setting['settings'] = array_values($setting['settings']);
+ }
+ unset($setting);
+ array_multisort($sortvals, SORT_ASC, SORT_NUMERIC, $settings);
+ Render::addTemplate('_page', array(
+ 'override' => $this->targetModule !== false,
+ 'categories' => array_values($settings),
+ 'target_module' => $this->targetModule,
+ ) + $this->qry_extra);
+ Module::isAvailable('bootstrap_switch');
+ }
+
+ private function getCurrentModuleName()
+ {
+ if (isset($this->qry_extra['tostring'])) {
+ $method = explode('::', $this->qry_extra['tostring']);
+ return call_user_func($method, $this->qry_extra['field_value']);
+ }
+ if (isset($this->qry_extra['field'])) {
+ return $this->targetModule . ' // ' . $this->qry_extra['field'] . '=' . $this->qry_extra['field_value'];
+ }
+ return $this->targetModule;
+ }
+
+ private function getModuleSpecific()
+ {
+ $module = Request::any('module', '', 'string');
+ if ($module === '') {
+ $this->qry_extra = array(
+ 'table' => 'setting_global',
+ );
+ return;
+ }
+ //\\//\\//\\
+ if (!Module::isAvailable($module)) {
+ Message::addError('main.no-such-module', $module);
+ Util::redirect('?do=baseconfig');
+ }
+ $file = 'modules/' . $module . '/baseconfig/hook.json';
+ if (!file_exists($file)) {
+ Message::addError('no-module-hook', $module);
+ Util::redirect('?do=baseconfig');
+ }
+ $hook = json_decode(file_get_contents($file), true);
+ if (empty($hook['table'])) {
+ Message::addError('invalid-hook', $module);
+ Util::redirect('?do=baseconfig');
+ }
+ if (isset($hook['field'])) {
+ $hook['field_value'] = Request::any($hook['field'], '0', 'string');
+ }
+ $this->targetModule = $module;
+ $this->qry_extra = $hook;
+ }
+
+ /**
+ * Create html snippet for setting, based on given validator
+ * @param type $validator
+ * @return boolean
+ */
+ private function makeInput($validator, $setting, $current, $shadows)
+ {
+ /* for the html snippet we need: */
+ $args = array('class' => 'form-control', 'name' => "setting[$setting]", 'id' => $setting);
+ if (!empty($shadows)) {
+ $args['data-shadows'] = json_encode($shadows);
+ }
+ $inner = "";
+ /* -- */
+
+ $parts = explode(':', $validator, 2);
+
+ if ($parts[0] === 'list') {
+
+ $items = explode('|', $parts[1]);
+ foreach ($items as $item) {
+ if ($item === $current) {
+ $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
+ } else {
+ $inner .= "<option value=\"$item\"> $item </option>";
+ }
+ }
+
+ $tag = 'select';
+ unset($args['type']);
+ $current = '';
+
+ } elseif ($parts[0] == 'multilist') {
+
+ $items = explode('|', $parts[1]);
+ $args['multiple'] = 'multiple';
+ $args['class'] .= " multilist";
+ $args['name'] .= '[]';
+
+ $selected = explode(' ', $current);
+
+ foreach ($items as $item) {
+ if (in_array($item, $selected)) {
+ $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
+ } else {
+ $inner .= "<option value=\"$item\"> $item </option>";
+ }
+ }
+ $tag = 'select';
+ unset($args['type']);
+ $current = '';
+ } else {
+ // Everything else is a text input for now
+ $tag = 'input';
+ $args['value'] = $current;
+ $args['type'] = 'text';
+ /* Password field guessing */
+ if (stripos($validator, 'password') !== false) {
+ $args['type'] = Property::getPasswordFieldType();
+ }
+ }
+
+ /* multiinput: enter multiple free-form strings*/
+ if ($validator === 'multiinput') {
+ $args['class'] .= " multiinput";
+ }
+
+ $output = "<$tag ";
+ foreach ($args as $key => $val) {
+ $output .= "$key=\"" . htmlspecialchars($val) . '" ';
+ }
+ if (empty($inner)) {
+ $output .= '/>';
+ } else {
+ $output .= '>' . $inner . "</$tag>";
+ }
+
+ return $output;
+ }
+
+}
diff --git a/modules-available/debugconfig/templates/_page.html b/modules-available/debugconfig/templates/_page.html
new file mode 100644
index 00000000..7f380495
--- /dev/null
+++ b/modules-available/debugconfig/templates/_page.html
@@ -0,0 +1,122 @@
+<h1>{{lang_basicConfiguration}}</h1>
+{{#override}}
+<h2>{{subheading}}</h2>
+<div class="alert alert-info">{{lang_editOverrideNotice}}</div>
+{{/override}}
+<p>{{lang_clientRelatedConfig}}</p>
+<form action="?do=BaseConfig" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ {{#override}}
+ <input name="module" type="hidden" value="{{target_module}}">
+ <input name="{{field}}" type="hidden" value="{{field_value}}">
+ {{/override}}
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
+ <input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
+ {{#categories}}
+ <div class="panel panel-default">
+ <div class="panel-heading" role="tab" id="heading{{category_id}}">
+ <a name="category_{{category_id}}"></a>
+ {{category_name}}
+ </div>
+ <div class="panel-body">
+ <div class="list-group">
+ {{#settings}}
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-md-5 slx-cfg-toggle">
+ <div>{{setting}}</div>
+ {{^override}}
+ <div class="slx-default">
+ {{defaultvalue}}
+ </div>
+ <input class="bs-switch" name="override[{{setting}}]" id="CB_{{setting}}" type="checkbox" {{checked}}> <label for="CB_{{setting}}">{{lang_settingActive}}</label>
+ {{/override}}
+ {{#override}}
+ <input class="bs-switch" name="override[{{setting}}]" id="CB_{{setting}}" type="checkbox" {{checked}}> <label for="CB_{{setting}}">{{lang_enableOverride}}</label>
+ {{/override}}
+ </div>
+ <div class="col-md-5">
+ {{{item}}}
+ </div>
+ <div class="col-md-2">
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-{{setting}}"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ </div>
+ </div>
+ <div class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{setting}}</div>
+ <div class="modal-body">{{{description}}}</div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+ </div>
+ {{/settings}}
+ </div>
+ </div>
+ </div>
+ {{/categories}}
+ <button class="btn btn-primary" type="submit">{{lang_save}}</button>
+ <button class="btn btn-default" type="reset">{{lang_reset}}</button>
+ {{^override}}
+ <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}">Download</a>
+ {{/override}}
+ {{#override}}
+ <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}&amp;module={{target_module}}&amp;value={{field_value}}&amp;force=1">Download</a>
+ {{/override}}
+</form>
+
+<script type="text/javascript">
+
+function updateShadows(e) {
+ var rules = $(e).data('shadows');
+ if (!rules) return;
+ var currentValue = $(e).val();
+ for (var triggerVal in rules) {
+ var targets = rules[triggerVal];
+ for (var i = 0; i < targets.length; ++i) {
+ var target = targets[i];
+ var inp = $('#' + target);
+ var selitem = inp.data('selitem');
+
+ if (currentValue === triggerVal) {
+ inp.prop('disabled', true);
+ if (selitem) selitem.disable();
+ $('#' + target + '.multilist').multiselect('disable');
+ } else {
+ inp.prop('disabled', false);
+ if (selitem) selitem.enable();
+ $('#' + target + '.multilist').multiselect('enable');
+ }
+ }
+ }
+}
+
+
+document.addEventListener("DOMContentLoaded", function () {
+ /* apply selectize on all multiinput-selectize inputs */
+ $("input.multiinput").each(function (idx, elem) {
+ var e = $(elem);
+ e.data('selitem', e.selectize({
+ delimiter: ' ',
+ create: true,
+ plugins: ['restore_on_backspace', 'remove_button'],
+ maxItems: 10000
+
+ })[0].selectize);
+ });
+
+ var $multilists = $("select.multilist");
+ $multilists.multiselect({
+ includeSelectAllOption: true,
+ buttonWidth: '100%',
+ buttonClass: 'form-control'
+ });
+
+ /* data-shadowing bindings */
+ $allShadowingFields = $('[data-shadows]');
+ $allShadowingFields.on('change', function (event) { updateShadows(event.target); });
+ $allShadowingFields.each(function (idx, elem) { updateShadows(elem); });
+});
+</script>