summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2019-11-05 18:01:37 +0100
committerSimon Rettberg2019-11-05 18:01:37 +0100
commit50d28a0ad8dcf5d9fe697278b6ea05aa00f8fb87 (patch)
tree0418c774e8cbda6376f2f8a699af6e67a9055235
parent[permissionmanager] Fix creating bogus negative cache entries (diff)
downloadslx-admin-50d28a0ad8dcf5d9fe697278b6ea05aa00f8fb87.tar.gz
slx-admin-50d28a0ad8dcf5d9fe697278b6ea05aa00f8fb87.tar.xz
slx-admin-50d28a0ad8dcf5d9fe697278b6ea05aa00f8fb87.zip
[baseconfig] Overhaul hook system
This enables us to finally properly show the inheritance flow of all the config variables when editing the baseconfig for a certain location or machine.
-rw-r--r--modules-available/baseconfig/api.inc.php175
-rw-r--r--modules-available/baseconfig/inc/baseconfig.inc.php148
-rw-r--r--modules-available/baseconfig/inc/configholder.inc.php137
-rw-r--r--modules-available/baseconfig/lang/de/module.json4
-rw-r--r--modules-available/baseconfig/lang/de/template-tags.json3
-rw-r--r--modules-available/baseconfig/lang/en/module.json4
-rw-r--r--modules-available/baseconfig/lang/en/template-tags.json3
-rw-r--r--modules-available/baseconfig/page.inc.php67
-rw-r--r--modules-available/baseconfig/templates/_page.html108
-rw-r--r--modules-available/locations/baseconfig/getconfig.inc.php25
-rw-r--r--modules-available/locations/baseconfig/hook.json2
-rw-r--r--modules-available/locations/inc/locationhooks.inc.php28
-rw-r--r--modules-available/statistics/baseconfig/getconfig.inc.php3
-rw-r--r--modules-available/statistics/baseconfig/hook.json2
-rw-r--r--modules-available/statistics/inc/statisticshooks.inc.php20
15 files changed, 449 insertions, 280 deletions
diff --git a/modules-available/baseconfig/api.inc.php b/modules-available/baseconfig/api.inc.php
index aab03b5a..350c6173 100644
--- a/modules-available/baseconfig/api.inc.php
+++ b/modules-available/baseconfig/api.inc.php
@@ -1,125 +1,5 @@
<?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;
-}
-
-class ConfigHolder
-{
- private static $config = [];
-
- private static $context = '';
-
- private static $postHooks = [];
-
- public static function setContext($name)
- {
- self::$context = $name;
- }
-
- public static function addArray($array, $prio = 0)
- {
- foreach ($array as $key => $value) {
- self::add($key, $value, $prio);
- }
- }
-
- public static function add($key, $value, $prio = 0)
- {
- if (!isset(self::$config[$key])) {
- self::$config[$key] = [];
- }
- $new = [
- 'prio' => $prio,
- 'value' => $value,
- 'context' => self::$context,
- ];
- if (empty(self::$config[$key]) || self::$config[$key][0]['prio'] > $prio) {
- // Existing is higher, append new one
- array_push(self::$config[$key], $new);
- } else {
- // New one has highest prio or matches existing, put in front
- array_unshift(self::$config[$key], $new);
- }
- }
-
- public static function get($key)
- {
- if (!isset(self::$config[$key]))
- return false;
- return self::$config[$key][0]['value'];
- }
-
- /**
- * @param callable $func
- */
- public static function addPostHook($func)
- {
- self::$postHooks[] = array('context' => self::$context, 'function' => $func);
- }
-
- public static function applyPostHooks()
- {
- foreach (self::$postHooks as $hook) {
- self::$context = $hook['context'] . ':post';
- $hook['function']();
- }
- self::$postHooks = [];
- }
-
- public static function getConfig()
- {
- self::applyPostHooks();
- $ret = [];
- foreach (self::$config as $key => $list) {
- if ($list[0]['value'] === false)
- continue;
- $ret[$key] = $list[0]['value'];
- }
- return $ret;
- }
-
- public static function outputConfig()
- {
- self::applyPostHooks();
- foreach (self::$config as $key => $list) {
- echo str_pad('# ' . $key . ' ', 35, '#', STR_PAD_BOTH), "\n";
- foreach ($list as $pos => $item) {
- if ($pos != 0 || $item['value'] === false) {
- echo '# (', $item['context'], ':', $item['prio'], ')';
- if ($pos == 0) {
- echo " <disabled this setting>\n";
- } else {
- echo " <overridden>\n";
- }
- } else {
- echo $key, "='", escape($item['value']), "'\n";
- echo '# (', $item['context'], ':', $item['prio'], ") <active>\n";
- }
- }
- }
- }
-
-}
-
-/**
- * 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
@@ -127,58 +7,19 @@ function escape($string)
* global setting.
*/
-function handleModule($name, $ip, $uuid) // Pass ip and uuid instead of global to make them read only
-{
- // Module has getconfig hook
- $file = 'modules/' . $name . '/baseconfig/getconfig.inc.php';
- if (!is_file($file))
- return;
- // Properly registered and can be activated
- $mod = Module::get($name);
- if ($mod === false)
- return;
- if (!$mod->activate(1, false))
- return;
- // Process dependencies first
- foreach ($mod->getDependencies() as $dep) {
- handleModule($dep, $ip, $uuid);
- }
- ConfigHolder::setContext($name);
- (function($file, $ip, $uuid) {
- include_once($file);
- })($file, $ip, $uuid);
-}
-
-// 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);
- ConfigHolder::setContext($out[1]);
- handleModule($out[1], $ip, $uuid);
-}
-
-// Rest is handled by module
-$defaults = BaseConfigUtil::getVariables();
-// Dump global config from DB
-ConfigHolder::setContext('<global>');
-$res = Database::simpleQuery('SELECT setting, value FROM setting_global');
-while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if (!isset($defaults[$row['setting']]))
- continue; // Setting is not defined in any <module>/baseconfig/settings.json
- ConfigHolder::add($row['setting'], $row['value'], -1);
-}
+// Prepare ConfigHolder from request data
+BaseConfig::prepareFromRequest();
-// Fallback to default values from json files
-ConfigHolder::setContext('<default>');
-foreach ($defaults as $setting => $value) {
- ConfigHolder::add($setting, $value['defaultvalue'], -1000);
-}
+ConfigHolder::add('SLX_NOW', time(), PHP_INT_MAX);
// All done, now output
-ConfigHolder::add('SLX_NOW', time(), PHP_INT_MAX);
+ConfigHolder::applyPostHooks();
ConfigHolder::outputConfig();
// 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');
+if (file_exists('client_config_additional.php')) {
+ echo "########## client_config_additional.php:\n";
+ @include('client_config_additional.php');
+}
diff --git a/modules-available/baseconfig/inc/baseconfig.inc.php b/modules-available/baseconfig/inc/baseconfig.inc.php
new file mode 100644
index 00000000..064e0f89
--- /dev/null
+++ b/modules-available/baseconfig/inc/baseconfig.inc.php
@@ -0,0 +1,148 @@
+<?php
+
+class BaseConfig
+{
+
+ private static $modulesDone = [];
+
+ /**
+ * @var array key-value-pairs of override vars, can be accessed by hooks
+ */
+ private static $overrides = [];
+
+ /**
+ * Fill the ConfigHolder with values from various hooks, while taking
+ * into account UUID and IP-address of the client making the current
+ * HTTP request.
+ */
+ public static function prepareFromRequest()
+ {
+ $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;
+ }
+ // 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);
+ ConfigHolder::setContext($out[1]);
+ self::handleModule($out[1], $ip, $uuid, false);
+ }
+ self::commonBase();
+ }
+
+ /**
+ * Fill the ConfigHolder with data from various hooks that supply
+ * static overrides for config variables. The overrides can be used
+ * to make the hooks behave in certain ways, by setting specific values like
+ * 'locationid'
+ * @param array $overrides key value pairs of overrides
+ */
+ public static function prepareWithOverrides($overrides)
+ {
+ self::$overrides = $overrides;
+ $ip = $uuid = false;
+ if (self::hasOverride('ip')) {
+ $ip = self::getOverride('ip');
+ }
+ if (self::hasOverride('uuid')) {
+ $uuid = self::getOverride('uuid');
+ }
+ // Handle only static hooks that don't dynamically generate output
+ foreach (glob('modules/*/baseconfig/hook.json') as $file) {
+ preg_match('#^modules/([^/]+)/#', $file, $out);
+ ConfigHolder::setContext($out[1]);
+ self::handleModule($out[1], $ip, $uuid, true);
+ }
+ self::commonBase();
+ }
+
+ /**
+ * Just fill the ConfigHolder with the defaults from all the json files
+ * that define config variables.
+ */
+ public static function prepareDefaults()
+ {
+ $defaults = BaseConfigUtil::getVariables();
+ self::addDefaults($defaults);
+ }
+
+ private static function commonBase()
+ {
+ $defaults = BaseConfigUtil::getVariables();
+
+ // Dump global config from DB
+ ConfigHolder::setContext('<global>', function($id) {
+ return [
+ 'name' => Dictionary::translate('source-global', true),
+ 'locationid' => 0,
+ ];
+ });
+ $res = Database::simpleQuery('SELECT setting, value, displayvalue FROM setting_global');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (!isset($defaults[$row['setting']]))
+ continue; // Setting is not defined in any <module>/baseconfig/settings.json
+ ConfigHolder::add($row['setting'], $row, -1);
+ }
+
+ // Fallback to default values from json files
+ self::addDefaults($defaults);
+
+ }
+
+ private static function addDefaults($defaults)
+ {
+ ConfigHolder::setContext('<default>', function($id) {
+ return [
+ 'name' => Dictionary::translate('source-default', true),
+ 'locationid' => 0,
+ ];
+ });
+ foreach ($defaults as $setting => $value) {
+ ConfigHolder::add($setting, $value['defaultvalue'], -1000);
+ }
+ }
+
+ private static function handleModule($name, $ip, $uuid, $needJsonHook) // Pass ip and uuid instead of global to make them read only
+ {
+ if (isset(self::$modulesDone[$name]))
+ return;
+ self::$modulesDone[$name] = true;
+ // Module has getconfig hook
+ $file = 'modules/' . $name . '/baseconfig/getconfig.inc.php';
+ if (!is_file($file))
+ return;
+ // We want only static hooks that have a json config (currently used for displaying inheritance tree)
+ if ($needJsonHook && !is_file('modules/' . $name . '/baseconfig/hook.json'))
+ return;
+ // Properly registered and can be activated
+ $mod = Module::get($name);
+ if ($mod === false)
+ return;
+ if (!$mod->activate(1, false))
+ return;
+ // Process dependencies first
+ foreach ($mod->getDependencies() as $dep) {
+ self::handleModule($dep, $ip, $uuid, $needJsonHook);
+ }
+ ConfigHolder::setContext($name);
+ (function ($file, $ip, $uuid) {
+ include_once($file);
+ })($file, $ip, $uuid);
+ }
+
+ public static function hasOverride($key)
+ {
+ return array_key_exists($key, self::$overrides);
+ }
+
+ public static function getOverride($key)
+ {
+ return self::$overrides[$key];
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/inc/configholder.inc.php b/modules-available/baseconfig/inc/configholder.inc.php
new file mode 100644
index 00000000..e5856128
--- /dev/null
+++ b/modules-available/baseconfig/inc/configholder.inc.php
@@ -0,0 +1,137 @@
+<?php
+
+class ConfigHolder
+{
+ private static $config = [];
+
+ private static $context = '';
+
+ private static $postHooks = [];
+
+ public static function setContext($id, $resolver = false)
+ {
+ $tmp = ['id' => $id, 'resolver' => $resolver];
+ self::$context =& $tmp;
+ }
+
+ /**
+ * @param string $key config variable name
+ * @param false|string|array $value false to unset, string value, or array with keys value and displayvalue
+ * @param int $prio priority of this value, in case the same key gets set multiple times
+ */
+ public static function add($key, $value, $prio = 0)
+ {
+ if (!isset(self::$config[$key])) {
+ self::$config[$key] = [];
+ }
+ $new = [
+ 'prio' => $prio,
+ 'context' => &self::$context,
+ ];
+ if (is_array($value)) {
+ $new['value'] = $value['value'];
+ $new['displayvalue'] = $value['displayvalue'];
+ } else {
+ $new['value'] = $value;
+ }
+ if (empty(self::$config[$key]) || self::$config[$key][0]['prio'] > $prio) {
+ // Existing is higher, append new one
+ array_push(self::$config[$key], $new);
+ } else {
+ // New one has highest prio or matches existing, put in front
+ array_unshift(self::$config[$key], $new);
+ }
+ }
+
+ public static function get($key)
+ {
+ if (!isset(self::$config[$key]))
+ return false;
+ return self::$config[$key][0]['value'];
+ }
+
+ /**
+ * @param callable $func
+ */
+ public static function addPostHook($func)
+ {
+ self::$postHooks[] = array('context' => &self::$context, 'function' => $func);
+ }
+
+ public static function applyPostHooks()
+ {
+ foreach (self::$postHooks as $hook) {
+ $newContext = $hook['context'];
+ $newContext['post'] = true;
+ self::$context =& $newContext;
+ $hook['function']();
+ }
+ self::$postHooks = [];
+ }
+
+ public static function getRecursiveConfig($prettyPrint = true)
+ {
+ $ret = [];
+ foreach (self::$config as $key => $list) {
+ $last = false;
+ foreach ($list as $entry) {
+ if ($last !== false && $last['context']['id'] === '<global>'
+ && $entry['context']['id'] === '<default>' && $last['value'] === $entry['value'])
+ continue;
+ $cb =& $entry['context']['resolver'];
+ $valueKey = 'value';
+ if ($prettyPrint && is_callable($cb)) {
+ $data = $cb($entry['context']['id']);
+ $name = $data['name'];
+ if (isset($data['locationid']) && isset($entry['displayvalue'])
+ && User::hasPermission('.baseconfig.view', $data['locationid'])) {
+ $valueKey = 'displayvalue';
+ }
+ } else {
+ $name = $entry['context']['id'];
+ }
+ $ret[$key][] = ['name' => $name, 'value' => $entry[$valueKey]];
+ $last = $entry;
+ }
+ }
+ return $ret;
+ }
+
+ public static function outputConfig()
+ {
+ foreach (self::$config as $key => $list) {
+ echo str_pad('# ' . $key . ' ', 35, '#', STR_PAD_BOTH), "\n";
+ foreach ($list as $pos => $item) {
+ $ctx = $item['context']['id'];
+ if (isset($item['context']['post'])) {
+ $ctx .= '[post-hook]';
+ }
+ $ctx .= ' :: ' . $item['prio'];
+ if ($pos != 0 || $item['value'] === false) {
+ echo '# (', $ctx, ')';
+ if ($pos == 0) {
+ echo " <disabled this setting>\n";
+ } else {
+ echo " <overridden>\n";
+ }
+ } else {
+ echo $key, "='", self::escape($item['value']), "'\n";
+ echo '# (', $ctx, ") <active>\n";
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ private static function escape($string)
+ {
+ return str_replace("'", "'\"'\"'", $string);
+ }
+
+}
diff --git a/modules-available/baseconfig/lang/de/module.json b/modules-available/baseconfig/lang/de/module.json
index 44b51ec3..f7dbf53a 100644
--- a/modules-available/baseconfig/lang/de/module.json
+++ b/modules-available/baseconfig/lang/de/module.json
@@ -1,3 +1,5 @@
{
- "module_name": "Konfigurationsvariablen"
+ "module_name": "Konfigurationsvariablen",
+ "source-default": "Auslieferungszustand",
+ "source-global": "Global"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/template-tags.json b/modules-available/baseconfig/lang/de/template-tags.json
index dfaecc96..1ce62c87 100644
--- a/modules-available/baseconfig/lang/de/template-tags.json
+++ b/modules-available/baseconfig/lang/de/template-tags.json
@@ -1,8 +1,7 @@
{
"lang_basicConfiguration": "Basiskonfiguration",
"lang_clientRelatedConfig": "Die Optionen auf dieser Seite beziehen sich auf das Verhalten der bwLehrpool-Clients.",
- "lang_defaultValue": "Standard",
"lang_editOverrideNotice": "Sie bearbeiten die Einstellungen f\u00fcr einen Unterbereich",
"lang_enableOverride": "\u00dcberschreiben",
- "lang_inheritSource": "Geerbt von"
+ "lang_showParents": "Geerbte Werte"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/module.json b/modules-available/baseconfig/lang/en/module.json
index 9ad9d10f..97e19f92 100644
--- a/modules-available/baseconfig/lang/en/module.json
+++ b/modules-available/baseconfig/lang/en/module.json
@@ -1,3 +1,5 @@
{
- "module_name": "Config Variables"
+ "module_name": "Config Variables",
+ "source-default": "Factory default",
+ "source-global": "Global"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/template-tags.json b/modules-available/baseconfig/lang/en/template-tags.json
index 471fef35..9ac578e7 100644
--- a/modules-available/baseconfig/lang/en/template-tags.json
+++ b/modules-available/baseconfig/lang/en/template-tags.json
@@ -1,8 +1,7 @@
{
"lang_basicConfiguration": "Basic Configuration",
"lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
- "lang_defaultValue": "Default",
"lang_editOverrideNotice": "You're editing the settings of a sub-section",
"lang_enableOverride": "Override",
- "lang_inheritSource": "Inherited from"
+ "lang_showParents": "Inherited values"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/page.inc.php b/modules-available/baseconfig/page.inc.php
index 0c11d5e1..a931d3f3 100644
--- a/modules-available/baseconfig/page.inc.php
+++ b/modules-available/baseconfig/page.inc.php
@@ -127,59 +127,47 @@ class Page_BaseConfig extends Page
$where = '';
$params = array();
}
+ $parents = $this->getInheritanceData();
// List config options
$settings = array();
- $vars = BaseConfigUtil::getVariables();
+ $varsFromJson = BaseConfigUtil::getVariables();
// Remember missing variables
- $missing = $vars;
+ $missing = $varsFromJson;
// Populate structure with existing config from db
- $this->fillSettings($vars, $settings, $missing, $this->qry_extra['table'], $fields, $where, $params, false);
- if (isset($this->qry_extra['getfallback']) && !empty($missing)) {
- $method = explode('::', $this->qry_extra['getfallback']);
- $fieldValue = $this->qry_extra['field_value'];
- $tries = 0;
- while (++$tries < 100 && !empty($missing)) {
- $ret = call_user_func($method, $fieldValue);
- if ($ret === false)
- break;
- $fieldValue = $ret['value'];
- $params = array('field_value' => $fieldValue);
- $this->fillSettings($vars, $settings, $missing, $this->qry_extra['table'], $fields, $where, $params, $ret['display']);
- }
- }
- if ($this->targetModule !== false && !empty($missing)) {
- $this->fillSettings($vars, $settings, $missing, 'setting_global', '', '', array(), 'Global');
- }
+ $this->fillSettings($varsFromJson, $settings, $missing, $this->qry_extra['table'], $fields, $where, $params, false);
// Add entries that weren't in the db (global), setup override checkbox (module specific)
- foreach ($vars as $key => $var) {
+ foreach ($varsFromJson as $key => $var) {
if ($this->targetModule !== false && !isset($settings[$var['catid']]['settings'][$key])) {
// Module specific - value is not set in DB
- $settings[$var['catid']]['settings'][$key] = $var + array(
+ $settings[$var['catid']]['settings'][$key] = 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]['defaultvalue'])) {
- $settings[$var['catid']]['settings'][$key]['defaultvalue'] = $var['defaultvalue'];
+ $entry =& $settings[$var['catid']]['settings'][$key];
+ if (!isset($entry['displayvalue'])) {
+ if (isset($parents[$key][0]['value'])) {
+ $entry['displayvalue'] = $parents[$key][0]['value'];
+ } else {
+ $entry['displayvalue'] = $var['defaultvalue'];
+ }
}
- if (!isset($settings[$var['catid']]['settings'][$key]['shadows'])) {
- $settings[$var['catid']]['settings'][$key]['shadows'] = isset($var['shadows']) ? $var['shadows'] : null;
+ if (!isset($entry['shadows'])) {
+ $entry['shadows'] = isset($var['shadows']) ? $var['shadows'] : null;
}
- $settings[$var['catid']]['settings'][$key] += array(
+ $entry += array(
'item' => $this->makeInput(
$var['validator'],
$key,
- $settings[$var['catid']]['settings'][$key]['displayvalue'],
- $settings[$var['catid']]['settings'][$key]['shadows'],
+ $entry['displayvalue'],
+ $entry['shadows'],
$editForbidden
),
'description' => Util::markup(Dictionary::translateFileModule($var['module'], 'config-variables', $key)),
'setting' => $key,
+ 'tree' => isset($parents[$key]) ? $parents[$key] : false,
);
}
- //die();
+ unset($entry);
// Sort categories
@@ -216,10 +204,7 @@ class Page_BaseConfig extends Page
continue;
}
unset($missing[$row['setting']]);
- if ($sourceName !== false) {
- $row['defaultvalue'] = '';
- $row['defaultsource'] = $sourceName;
- } elseif ($this->targetModule !== false) {
+ if ($this->targetModule !== false) {
$row['checked'] = 'checked';
}
$row += $vars[$row['setting']];
@@ -280,6 +265,16 @@ class Page_BaseConfig extends Page
$func = explode('::', $this->qry_extra['locationResolver']);
return (int)call_user_func($func, $this->qry_extra['field_value']);
}
+
+ private function getInheritanceData()
+ {
+ if (!isset($this->qry_extra['getInheritance']) || !isset($this->qry_extra['field_value'])) {
+ BaseConfig::prepareDefaults();
+ return ConfigHolder::getRecursiveConfig(true);
+ }
+ $func = explode('::', $this->qry_extra['getInheritance']);
+ return call_user_func($func, $this->qry_extra['field_value']);
+ }
/**
* Create html snippet for setting, based on given validator
diff --git a/modules-available/baseconfig/templates/_page.html b/modules-available/baseconfig/templates/_page.html
index 7f7c33d0..1f9bcafb 100644
--- a/modules-available/baseconfig/templates/_page.html
+++ b/modules-available/baseconfig/templates/_page.html
@@ -18,52 +18,53 @@
<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>
- <div class="slx-default">
- {{#defaultvalue}}{{lang_defaultValue}}:{{/defaultvalue}}
- {{defaultvalue}}
- </div>
- {{#override}}
- <div class="checkbox">
- <input name="override[{{setting}}]" class="override-cb" id="CB_{{setting}}" type="checkbox" {{checked}} {{edit_disabled}}>
- <label for="CB_{{setting}}">
- {{lang_enableOverride}}
- </label>
- </div>
- {{/override}}
+ <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="checkbox">
+ <input name="override[{{setting}}]" class="override-cb" id="CB_{{setting}}" type="checkbox" {{checked}} {{edit_disabled}}>
+ <label for="CB_{{setting}}">
+ {{lang_enableOverride}}
+ </label>
</div>
- <div class="col-md-5 config-container">
- {{{item}}}
- <div class="slx-default">
- {{#defaultsource}}{{lang_inheritSource}}:{{/defaultsource}}
- {{defaultsource}}
- </div>
+ {{/override}}
+ </div>
+ <div class="col-md-5 config-container">
+ {{{item}}}
+ <div class="slx-default">
+ {{#tree.0}}
+ <a href="#tree-{{setting}}" data-toggle="collapse">{{lang_showParents}}</a>
+ <div class="hidden" id="default-{{setting}}">{{value}}</div>
+ {{/tree.0}}
</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 class="slx-default collapse text-nowrap" id="tree-{{setting}}">
+ {{#tree}}
+ <div class="slx-strike"><b>{{name}}</b>: {{value}}</div>
+ {{/tree}}
</div>
</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 class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal">&times;</button>
- <h4 class="modal-title"><b>{{setting}}</b></h4>
+ </div>
+ <div class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{setting}}</b></h4>
- </div>
- <div class="modal-body">{{{description}}}</div>
</div>
+ <div class="modal-body">{{{description}}}</div>
</div>
</div>
- {{/settings}}
</div>
+ {{/settings}}
</div>
</div>
{{/categories}}
@@ -135,25 +136,44 @@ document.addEventListener("DOMContentLoaded", function () {
var updateCheckbox = function() {
var id = '#CB_' + $(this).attr('id');
- $(id).prop('checked', true);
+ var $cb = $(id).prop('checked', true);
+ if ($cb.length > 0) {
+ syncCheckbox.call($cb[0]);
+ }
+ };
+ var syncCheckbox = function() {
+ var setting = this.id.substr(3);
+ var $itm = $('#tree-' + setting + ' > div:first-child');
+ if (this.checked) {
+ $itm.addClass('slx-strike');
+ } else {
+ $itm.removeClass('slx-strike');
+ }
};
var $cont = $('.config-container');
$cont.find('select, input').on('change', updateCheckbox);
$cont.find('input').on('input', updateCheckbox);
$('.override-cb').on('change', function() {
- if (this.checked) return;
- var input = document.getElementById(this.id.substr(3));
+ var setting = this.id.substr(3);
+ syncCheckbox.call(this);
+ var input = document.getElementById(setting);
if (!input) return;
+ var defaults = this.checked ? false : ('' + $('#default-' + setting).text());
if (input.tagName.toUpperCase() === 'SELECT') {
+ var items = defaults === false ? false : defaults.split(/\s+/);
$(input).find('option').each(function() {
- $(this).prop('selected', this.defaultSelected);
+ $(this).prop('selected', items === false ? this.defaultSelected : (items.indexOf(this.value) !== -1));
});
$(input).filter('.multilist').multiselect('refresh');
} else if (input.type && input.type.toUpperCase() === 'CHECKBOX') {
- $(input).prop('checked', input.defaultChecked);
- } else if (input.defaultValue !== undefined) {
- $(input).val(input.defaultValue);
- }
+ $(input).prop('checked', defaults === false ? input.defaultChecked : !!defaults);
+ } else {
+ $(input).val(defaults === false ? input.defaultValue : defaults);
+ } // TODO: Make this work for multiinput/selectize (or get rid of them)
+ $allShadowingFields.each(updateShadows);
+ }).each(syncCheckbox);
+ window.addEventListener('unload', function() {
+ $('.multilist').multiselect('refresh');
});
});
</script>
diff --git a/modules-available/locations/baseconfig/getconfig.inc.php b/modules-available/locations/baseconfig/getconfig.inc.php
index f21503f1..26e43ed8 100644
--- a/modules-available/locations/baseconfig/getconfig.inc.php
+++ b/modules-available/locations/baseconfig/getconfig.inc.php
@@ -2,7 +2,9 @@
// Location handling: figure out location
$locationId = false;
-if (Request::any('force', 0, 'int') === 1 && Request::any('module', false, 'string') === 'locations') {
+if (BaseConfig::hasOverride('locationid')) {
+ $locationId = BaseConfig::getOverride('locationid');
+} elseif (Request::any('force', 0, 'int') === 1 && Request::any('module', false, 'string') === 'locations') {
// Force location for testing, but require logged in admin
if (User::load()) {
$locationId = Request::any('value', 0, 'int');
@@ -10,6 +12,8 @@ if (Request::any('force', 0, 'int') === 1 && Request::any('module', false, 'stri
}
if ($locationId === false) {
+ if (!$ip) // Required at this point, bail out if not given
+ return;
$locationId = Location::getFromIpAndUuid($ip, $uuid);
}
@@ -23,19 +27,30 @@ if ($locationId !== false) {
// Query location specific settings (from bottom to top)
if (!empty($matchingLocations)) {
// First get all settings for all locations we're in
- $list = implode(',', $matchingLocations);
- $res = Database::simpleQuery("SELECT locationid, setting, value FROM setting_location WHERE locationid IN ($list)");
+ $res = Database::simpleQuery("SELECT locationid, setting, value, displayvalue
+ FROM setting_location WHERE locationid IN (:list)",
+ ['list' => $matchingLocations]);
$tmp = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $tmp[(int)$row['locationid']][$row['setting']] = $row['value'];
+ $tmp[(int)$row['locationid']][$row['setting']] = $row; // Put whole row so we have value and displayvalue
}
+ // Callback for pretty printing
+ $cb = function($id) {
+ if (substr($id, 0, 9) !== 'location-')
+ return ['name' => $id, 'locationid' => 0];
+ $lid = (int)substr($id, 9);
+ return [
+ 'name' => Location::getName($lid),
+ 'locationid' => $lid,
+ ];
+ };
// $matchingLocations contains the location ids sorted from closest to furthest, so we use it to make sure the order
// in which they override is correct (closest setting wins, e.g. room setting beats department setting)
$prio = count($matchingLocations) + 1;
foreach ($matchingLocations as $lid) {
if (!isset($tmp[$lid]))
continue;
- ConfigHolder::setContext('location-' . $lid);
+ ConfigHolder::setContext('location-' . $lid, $cb);
foreach ($tmp[$lid] as $setting => $value) {
ConfigHolder::add($setting, $value, $prio);
}
diff --git a/modules-available/locations/baseconfig/hook.json b/modules-available/locations/baseconfig/hook.json
index 1d69647e..fabb4686 100644
--- a/modules-available/locations/baseconfig/hook.json
+++ b/modules-available/locations/baseconfig/hook.json
@@ -3,5 +3,5 @@
"field": "locationid",
"locationResolver": "LocationHooks::baseconfigLocationResolver",
"tostring": "Location::getName",
- "getfallback": "LocationHooks::getBaseconfigParent"
+ "getInheritance": "LocationHooks::baseconfigInheritance"
} \ No newline at end of file
diff --git a/modules-available/locations/inc/locationhooks.inc.php b/modules-available/locations/inc/locationhooks.inc.php
index c15c34ab..5ce3bbfe 100644
--- a/modules-available/locations/inc/locationhooks.inc.php
+++ b/modules-available/locations/inc/locationhooks.inc.php
@@ -4,28 +4,26 @@ class LocationHooks
{
/**
- * Used for baseconfig hook
- * @param $locationId
- * @return bool|array ('value' => x, 'display' => y), false if no parent or unknown id
+ * Resolve baseconfig id to locationid -- noop in this case
*/
- public static function getBaseconfigParent($locationId)
+ public static function baseconfigLocationResolver($id)
{
- $locationId = (int)$locationId;
- $assoc = Location::getLocationsAssoc();
- if (!isset($assoc[$locationId]))
- return false;
- $locationId = (int)$assoc[$locationId]['parentlocationid'];
- if (!isset($assoc[$locationId]))
- return false;
- return array('value' => $locationId, 'display' => $assoc[$locationId]['locationname']);
+ return $id;
}
/**
- * Resolve baseconfig id to locationid -- noop in this case
+ * Hook to get inheritance tree for all config vars
+ * @param int $id Locationid currently being edited
*/
- public static function baseconfigLocationResolver($id)
+ public static function baseconfigInheritance($id)
{
- return $id;
+ $locs = Location::getLocationsAssoc();
+ if ($locs === false || !isset($locs[$id]))
+ return [];
+ BaseConfig::prepareWithOverrides([
+ 'locationid' => $locs[$id]['parentlocationid']
+ ]);
+ return ConfigHolder::getRecursiveConfig(true);
}
} \ No newline at end of file
diff --git a/modules-available/statistics/baseconfig/getconfig.inc.php b/modules-available/statistics/baseconfig/getconfig.inc.php
index ea351d15..053827db 100644
--- a/modules-available/statistics/baseconfig/getconfig.inc.php
+++ b/modules-available/statistics/baseconfig/getconfig.inc.php
@@ -8,6 +8,9 @@ if (Request::any('force', 0, 'int') === 1 && Request::any('module', false, 'stri
}
}
+if (!$uuid) // Required at this point, bail out if not given
+ return;
+
// Query machine specific settings
$res = Database::simpleQuery("SELECT setting, value FROM setting_machine WHERE machineuuid = :uuid", ['uuid' => $uuid]);
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
diff --git a/modules-available/statistics/baseconfig/hook.json b/modules-available/statistics/baseconfig/hook.json
index da28ad5d..4a406653 100644
--- a/modules-available/statistics/baseconfig/hook.json
+++ b/modules-available/statistics/baseconfig/hook.json
@@ -3,5 +3,5 @@
"field": "machineuuid",
"locationResolver": "StatisticsHooks::baseconfigLocationResolver",
"tostring": "StatisticsHooks::getBaseconfigName",
- "getfallback": "StatisticsHooks::getBaseconfigParent"
+ "getInheritance": "StatisticsHooks::baseconfigInheritance"
} \ No newline at end of file
diff --git a/modules-available/statistics/inc/statisticshooks.inc.php b/modules-available/statistics/inc/statisticshooks.inc.php
index ead4917b..746bdabf 100644
--- a/modules-available/statistics/inc/statisticshooks.inc.php
+++ b/modules-available/statistics/inc/statisticshooks.inc.php
@@ -21,11 +21,6 @@ class StatisticsHooks
return self::$row['hostname'] ? self::$row['hostname'] : self::$row['clientip'];
}
- public static function getBaseconfigParent($machineuuid)
- {
- return false; // TODO
- }
-
public static function baseconfigLocationResolver($machineuuid)
{
self::getRow($machineuuid);
@@ -34,4 +29,19 @@ class StatisticsHooks
return (int)self::$row['locationid'];
}
+ /**
+ * Hook to get inheritance tree for all config vars
+ * @param int $machineuuid MachineUUID currently being edited
+ */
+ public static function baseconfigInheritance($machineuuid)
+ {
+ self::getRow($machineuuid);
+ if (self::$row === false)
+ return [];
+ BaseConfig::prepareWithOverrides([
+ 'locationid' => self::$row['locationid']
+ ]);
+ return ConfigHolder::getRecursiveConfig(true);
+ }
+
} \ No newline at end of file