diff options
author | Simon Rettberg | 2019-11-05 18:01:37 +0100 |
---|---|---|
committer | Simon Rettberg | 2019-11-05 18:01:37 +0100 |
commit | 50d28a0ad8dcf5d9fe697278b6ea05aa00f8fb87 (patch) | |
tree | 0418c774e8cbda6376f2f8a699af6e67a9055235 | |
parent | [permissionmanager] Fix creating bogus negative cache entries (diff) | |
download | slx-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.php | 175 | ||||
-rw-r--r-- | modules-available/baseconfig/inc/baseconfig.inc.php | 148 | ||||
-rw-r--r-- | modules-available/baseconfig/inc/configholder.inc.php | 137 | ||||
-rw-r--r-- | modules-available/baseconfig/lang/de/module.json | 4 | ||||
-rw-r--r-- | modules-available/baseconfig/lang/de/template-tags.json | 3 | ||||
-rw-r--r-- | modules-available/baseconfig/lang/en/module.json | 4 | ||||
-rw-r--r-- | modules-available/baseconfig/lang/en/template-tags.json | 3 | ||||
-rw-r--r-- | modules-available/baseconfig/page.inc.php | 67 | ||||
-rw-r--r-- | modules-available/baseconfig/templates/_page.html | 108 | ||||
-rw-r--r-- | modules-available/locations/baseconfig/getconfig.inc.php | 25 | ||||
-rw-r--r-- | modules-available/locations/baseconfig/hook.json | 2 | ||||
-rw-r--r-- | modules-available/locations/inc/locationhooks.inc.php | 28 | ||||
-rw-r--r-- | modules-available/statistics/baseconfig/getconfig.inc.php | 3 | ||||
-rw-r--r-- | modules-available/statistics/baseconfig/hook.json | 2 | ||||
-rw-r--r-- | modules-available/statistics/inc/statisticshooks.inc.php | 20 |
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">×</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">×</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 |