$item) {
$path = $config['html_dir'] . '/includes/entities/' . $entity_type . '.inc.php';
if (is_file($path)) {
include_once($path);
}
}
/**
* Callback function for replacing strings in the HTML buffer at the end of the script execution.
*
* @param string $buffer HTML buffer obtained from ob_start()
*
* @return string Modified buffer
*/
function html_callback($buffer)
{
global $config;
global $cache_html;
// Do not disclose version to unauthorized requests
$version_param = $_SESSION['authenticated'] ? '?v=' . OBSERVIUM_VERSION : '';
// Define template strings for registered CSS/JS links and other elements
$templates = [
'css' => ' ' . PHP_EOL,
'style' => ' ' . PHP_EOL,
'js' => ' ' . PHP_EOL,
'script' => ' ' . PHP_EOL,
// key-value
'meta-equiv' => ' ' . PHP_EOL,
'meta' => ' ' . PHP_EOL,
];
// Process and replace resources in the buffer
foreach ($templates as $type => $template) {
$uppercase_type = strtoupper($type);
if (isset($GLOBALS['cache_html']['resources'][$type])) {
$resource_string = '' . PHP_EOL;
if ($type === 'meta-equiv' || $type === 'meta') {
foreach ($GLOBALS['cache_html']['resources'][$type] as $name => $content) {
//bdump($content);
$resource_string .= str_replace([ '%%STRING_name%%', '%%STRING_content%%' ], [ $name, $content ], $template);
}
} else {
foreach (array_unique($GLOBALS['cache_html']['resources'][$type]) as $content) {
$resource_string .= str_replace('%%STRING%%', $content, $template);
}
}
$resource_string .= ' ' . PHP_EOL;
$buffer = str_replace('' . PHP_EOL, $resource_string, $buffer);
} else {
// Clean template string
$buffer = str_replace('', '', $buffer);
}
}
// Replace placeholders in the buffer with actual values
$replacements = [
'##TITLE##' => html_callback_build_title(),
'##PAGE_PANEL##' => $GLOBALS['cache_html']['page_panel'],
'##UI_ALERTS##' => implode(PHP_EOL, (array)$GLOBALS['cache_html']['ui_alerts']),
];
// Return the modified HTML page source
return array_str_replace($replacements, $buffer, TRUE);
}
/**
* Set the title of the page based on various criteria.
*/
function html_callback_build_title()
{
global $config;
global $vars;
global $cache_html;
if (!is_array($cache_html['title'])) {
// Title not set by any page, fall back to nicecase'd page name:
if ($vars['page'] && $_SESSION['authenticated']) {
$cache_html['title'] = [nicecase($vars['page'])];
} else {
// Main page or no page specified, leave the title empty
$cache_html['title'] = [];
}
}
// If a suffix is set, append it to the title
if ($config['page_title_suffix']) {
$cache_html['title'][] = $config['page_title_suffix'];
}
// If a prefix is set, prepend it to the title
if ($config['page_title_prefix']) {
array_unshift($cache_html['title'], $config['page_title_prefix']);
}
// Build the title with separators
return escape_html(implode($config['page_title_separator'], $cache_html['title']));
}
/**
* Register an HTML resource
*
* Registers resource for use later (will be re-inserted via output buffer handler)
* CSS and JS files default to the css/ and js/ directories respectively.
* Scripts are inserted literally as passed in $name.
*
* @param string $type Type of resource (css/js/script)
* @param string $content Filename or script content or array (for meta)
*/
// TESTME needs unit testing
function register_html_resource($type, $content)
{
// If no path specified, default to subdirectory of resource type (for CSS and JS only)
$type = strtolower($type);
if (in_array($type, [ 'css', 'js' ]) && !str_contains($content, '/')) {
$content = $type . '/' . $content;
}
// Insert into global variable, used in html callback function
$GLOBALS['cache_html']['resources'][$type][] = $content;
}
/**
* Register an HTML title section
*
* Registers title section for use in the html
tag.
* Calls can be stacked, and will be concatenated later by the HTML callback function.
*
* @param string $title Section title content
*/
// TESTME needs unit testing
function register_html_title($title)
{
$GLOBALS['cache_html']['title'][] = $title;
}
function register_html_meta($name, $content, $tag = 'name') {
if (safe_empty($content) || !is_alpha($name)) {
return;
}
if ($tag !== 'name') {
// http-equiv is multiplied
$GLOBALS['cache_html']['resources']['meta-equiv'][$name] = escape_html($content);
} else {
$GLOBALS['cache_html']['resources']['meta'][$name] = escape_html($content);
}
}
/**
* Register an HTML alert block displayed in top of page.
*
* @param string $text Alert message
* @param string $title Alert title if passed
* @param string $severity Severity in list: info, danger, warning, success, recovery, suppressed, delay, disabled
*/
function register_html_alert($text, $title = NULL, $severity = 'info') {
if (!$GLOBALS['config']['web_show_notifications']) {
// suppress web ui alerts
return;
}
// FIXME handle severity parameter with colour or icon?
$ui_alerts = '
';
if (!safe_empty($title)) {
$ui_alerts .= '
' . $title . '
';
}
$ui_alerts .= $text . '
';
$GLOBALS['cache_html']['ui_alerts'][] = $ui_alerts;
}
/**
* Register an HTML panel section
*
* Registers left panel section.
* Calls can be stacked, and will be concatenated later by the HTML callback function.
*
* @param string $html Section panel content
*/
// TESTME needs unit testing
function register_html_panel($html = '') {
if (!isset($GLOBALS['cache_html']['page_panel']) && (empty($html) || $html === 'default')) {
// register default (ajax) panel
// Load a default panel after whole page (only when visible)
// $config['html_dir'] . "/includes/panels/default.inc.php"
register_html_resource('script', "$(document).ready(function (e) { $('#myAffix:visible[data-panel=default]').load('/ajax/panel.php').attr('data-panel', 'loaded'); });");
// load when become visible
register_html_resource('script', "$(window).resize(function (e) { $('#myAffix:visible[data-panel=default]').load('/ajax/panel.php').attr('data-panel', 'loaded'); });");
return;
}
// Just rename data attrib for panel placeholder from default
register_html_resource('script', "$(document).ready(function (e) { $('#myAffix[data-panel=default]').attr('data-panel', 'register'); });");
$GLOBALS['cache_html']['page_panel'] = $html;
}
function http_match_referer($pattern) {
if ($_SERVER['HTTP_SEC_FETCH_SITE'] !== 'same-origin') {
return FALSE;
}
if (is_array_list($pattern)) {
$match = FALSE;
foreach ($pattern as $patt) {
if ($match = preg_match($patt, $_SERVER['HTTP_REFERER'])) {
break;
}
}
} else {
$match = preg_match($pattern, $_SERVER['HTTP_REFERER']);
}
return (bool)$match;
}
/**
* Parse $_GET, $_POST and REQUEST_URI into $vars array
*
* @param array|string $vars_order Request variables order (POST, URI, GET)
* @param boolean $auth this var or ($_SESSION['authenticated']) used for allow to use var_decode()
*
* @return array array of vars
*/
function get_vars($vars_order = [], $auth = FALSE) {
if (is_string($vars_order)) {
$vars_order = explode(' ', $vars_order);
} elseif (empty($vars_order) || !is_array($vars_order)) {
$vars_order = [ 'POST', 'URI', 'GET' ]; // Default order
}
// Content-Type=>application/x-www-form-urlencoded
$content_type = $_SERVER['HTTP_CONTENT_TYPE'] ?? $_SERVER['CONTENT_TYPE'];
// Allow using var_decode(), this prevents to use potentially unsafe serialize functions
$auth = $auth || $_SESSION['authenticated'];
$vars = [];
foreach ($vars_order as $order) {
$order = strtoupper($order);
switch ($order) {
case 'JSON':
//r(getallheaders());
//exit;
// https://stackoverflow.com/questions/8893574/php-php-input-vs-post
if (!in_array($content_type, ['application/x-www-form-urlencoded', 'multipart/form-data-encoded'])) {
//$json = @json_decode(trim(file_get_contents("php://input")), TRUE, 512, OBS_JSON_DECODE);
$json = safe_json_decode(trim(file_get_contents("php://input")));
if (is_array_assoc($json)) {
//$vars = array_merge_indexed($vars, $json);
$vars = $json; // Currently just override $vars, see ajax actions
//$vars_got['JSON'] = 1;
}
}
break;
case 'POST':
// Parse POST variables into $vars
foreach ($_POST as $name => $value) {
// Var names sanitize
if (!preg_match(OBS_PATTERN_VAR_NAME, $name)) {
continue;
}
if (!isset($vars[$name])) {
$vars[$name] = $auth ? var_decode($value) : $value;
if (is_string($vars[$name]) && preg_match(OBS_PATTERN_XSS, $vars[$name])) {
// Prevent any
STATE;
return $state;
}
/**
* Generate Percentage Bar
*
* This function generates an Observium percentage bar from a supplied array of arguments.
* It is possible to draw a bar that does not work at all,
* So care should be taken to make sure values are valid.
*
* @param array $args
*
* @return string
*/
// TESTME needs unit testing
function percentage_bar($args)
{
if (strlen($args['bg'])) {
$style .= 'background-color:' . $args['bg'] . ';';
}
if (strlen($args['border'])) {
$style .= 'border-color:' . $args['border'] . ';';
}
if (strlen($args['width'])) {
$style .= 'width:' . $args['width'] . ';';
}
if (strlen($args['text_c'])) {
$style_b .= 'color:' . $args['text_c'] . ';';
}
$total = '0';
$output = '
";
$url = generate_ap_url($args);
if (port_permitted($args['interface_id'], $args['device_id'])) {
return overlib_link($url, $text, $content, $class, $escape);
}
return $text;
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function generate_ap_url($ap, $vars = [])
{
return generate_url(['page' => 'device', 'device' => $ap['device_id'], 'tab' => 'accesspoint', 'ap' => $ap['accesspoint_id']], $vars);
}
/**
* Generate SQL WHERE string with check permissions and ignores for device_id, port_id and other
*
* Note, this function uses comparison operator IN. Max number of values in the IN list
* is limited by the 'max_allowed_packet' option (default: 1048576)
*
* Usage examples:
* generate_query_permitted()
* ' AND `device_id` IN (1,4,8,33) AND `device_id` NOT IN (66) AND (`device_id` != '' AND `device_id` IS NOT NULL) '
* generate_query_permitted(array('device'), array('device_table' => 'D'))
* ' AND `D`.`device_id` IN (1,4,8,33) AND `D`.`device_id` NOT IN (66) AND (`D`.`device_id` != '' AND `D`.`device_id` IS NOT NULL) '
* generate_query_permitted(array('device', 'port'), array('port_table' => 'I')) ==
* ' AND `device_id` IN (1,4,8,33) AND `device_id` NOT IN (66) AND (`device_id` != '' AND `device_id` IS NOT NULL)
* AND `I`.`port_id` IN (1,4,8,33) AND `I`.`port_id` NOT IN (66) AND (`I`.`port_id` != '' AND `I`.`port_id` IS NOT NULL) '
* generate_query_permitted(array('device', 'port'), array('port_table' => 'I', 'hide_ignored' => TRUE))
* This additionaly exclude all ignored devices and ports
*
* @param array|string $type_array Array with permission types, currently allowed 'devices', 'ports'
* @param array $options Options for each permission type: device_table, port_table, hide_ignored, hide_disabled
*
* @return string
* @uses html/includes/cache-data.inc.php
* @global integer $_SESSION ['userlevel']
* @global boolean $GLOBALS ['config']['web_show_disabled']
* @global array $GLOBALS ['permissions']
* @global array $GLOBALS ['cache']['devices']
* @global array $GLOBALS ['cache']['ports']
* @global string $GLOBALS ['vars']['page']
*/
// TESTME needs unit testing
function generate_query_permitted_ng($type_array = [ 'device' ], $options = []) {
if (!is_array($type_array)) {
$type_array = [ $type_array ];
}
$user_limited = $_SESSION['userlevel'] < 5;
$page = $GLOBALS['vars']['page'];
// If device IDs stored in SESSION use it (used in ajax)
//if (!isset($GLOBALS['cache']['devices']) && isset($_SESSION['cache']['devices']))
//{
// $GLOBALS['cache']['devices'] = $_SESSION['cache']['devices'];
//}
if (!isset($GLOBALS['permissions'])) {
if (is_graph() || is_api()) {
// Do not broke graph/api output
print_debug("Function " . __FUNCTION__ . "() on page '$page' called before include cache-data.inc.php or something wrong with caching permissions.");
} else {
// Note, this function must used after load permissions list!
print_error("Function " . __FUNCTION__ . "() on page '$page' called before include cache-data.inc.php or something wrong with caching permissions.");
}
}
// Use option hide_disabled if passed or use config
$options['hide_disabled'] = $options['hide_disabled'] ?? !$GLOBALS['config']['web_show_disabled'];
//$query_permitted = '';
$query_part = [];
foreach ($type_array as $type) {
switch ($type) {
// Devices permission query
case 'device':
case 'devices':
$column = '`device_id`';
$query_permitted = [];
if (isset($options['device_table'])) {
$column = '`' . $options['device_table'] . '`.' . $column;
}
// Show only permitted devices
if ($user_limited) {
if (!safe_empty($GLOBALS['permissions']['device'])) {
$query_permitted[] = generate_query_values(array_keys($GLOBALS['permissions']['device']), $column);
} else {
// Exclude all entries, because there are no permitted devices
$query_permitted[] = ' 0';
}
}
// Also don't show ignored and disabled devices (except on 'device' and 'devices' pages)
$devices_excluded = [];
if (!str_starts_with($page, 'device')) {
if ($options['hide_ignored'] && !safe_empty($GLOBALS['cache']['devices']['ignored'])) {
$devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['ignored']);
}
if ($options['hide_disabled'] && !safe_empty($GLOBALS['cache']['devices']['disabled'])) {
$devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['disabled']);
}
}
if (!safe_empty($devices_excluded)) {
//sort($devices_excluded, SORT_NUMERIC);
//r($devices_excluded);
// Set query with excluded devices
$query_permitted[] = generate_query_values($devices_excluded, $column, '!=');
}
// At the end excluded entries with empty/null device_id (wrong entries)
//$query_permitted[] = " ($column != '' AND $column IS NOT NULL)";
$query_permitted[] = " $column IS NOT NULL"; // Note: SELECT '' = 0; is TRUE
$query_part[] = implode(" AND ", $query_permitted);
unset($query_permitted);
break;
// Ports permission query
case 'port':
case 'ports':
$table = isset($options['port_table']) ? '`' . $options['port_table'] . '`.' : '';
if (isset($options['entity'])) {
$query_permitted[] = generate_query_values('port', $table.'entity_type');
$column = $table.'`entity_id`';
} else {
$column = $table.'`port_id`';
}
// If port IDs stored in SESSION use it (used in ajax)
//if (!isset($GLOBALS['cache']['ports']) && isset($_SESSION['cache']['ports']))
//{
// $GLOBALS['cache']['ports'] = $_SESSION['cache']['ports'];
//}
// Show only permitted ports
if ($user_limited) {
if (!safe_empty($GLOBALS['permissions']['port'])) {
$query_permitted[] = generate_query_values(array_keys($GLOBALS['permissions']['port']), $column);
// $query_permitted[] = " $column IN (" .
// implode(',', array_keys($GLOBALS['permissions']['port'])) .
// ')';
} else {
// Exclude all entries, because there is no permitted ports
$query_permitted[] = '0';
}
}
$ports_excluded = [];
// Don't show ports with disabled polling.
if (!safe_empty($GLOBALS['cache']['ports']['poll_disabled'])) {
$ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['poll_disabled']);
//foreach ($GLOBALS['cache']['ports']['poll_disabled'] as $entry)
//{
// $ports_excluded[] = $entry;
//}
//$ports_excluded = array_unique($ports_excluded);
}
// Don't show deleted ports (except on 'deleted-ports' page)
if ($page !== 'deleted-ports' && !safe_empty($GLOBALS['cache']['ports']['deleted'])) {
$ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['deleted']);
//foreach ($GLOBALS['cache']['ports']['deleted'] as $entry)
//{
// $ports_excluded[] = $entry;
//}
//$ports_excluded = array_unique($ports_excluded);
}
if ($page !== 'device' && !in_array('device', $type_array)) {
// Don't show ports for disabled devices (except on 'device' page or if 'device' permissions already queried)
if ($options['hide_disabled'] && !$user_limited && !safe_empty($GLOBALS['cache']['ports']['device_disabled'])) {
$ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['device_disabled']);
//foreach ($GLOBALS['cache']['ports']['device_disabled'] as $entry)
//{
// $ports_excluded[] = $entry;
//}
//$ports_excluded = array_unique($ports_excluded);
}
// Don't show ports for ignored devices (except on 'device' page)
if ($options['hide_ignored'] && !safe_empty($GLOBALS['cache']['ports']['device_ignored'])) {
$ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['device_ignored']);
//foreach ($GLOBALS['cache']['ports']['device_ignored'] as $entry)
//{
// $ports_excluded[] = $entry;
//}
//$ports_excluded = array_unique($ports_excluded);
}
}
// Don't show ignored ports (only on some pages!)
if (($page === 'overview' || $options['hide_ignored']) && !safe_empty($GLOBALS['cache']['ports']['ignored'])) {
$ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['ignored']);
//foreach ($GLOBALS['cache']['ports']['ignored'] as $entry)
//{
// $ports_excluded[] = $entry;
//}
//$ports_excluded = array_unique($ports_excluded);
}
unset($entry);
if (!safe_empty($ports_excluded)) {
// Set query with excluded ports
$query_permitted[] = generate_query_values($ports_excluded, $column, '!=');
}
// At the end excluded entries with empty/null port_id (wrong entries)
if (!isset($options['port_null']) || !$options['port_null']) {
//$query_permitted[] = "($column != '' AND $column IS NOT NULL)";
$query_permitted[] = "$column IS NOT NULL";
} elseif (!$user_limited && safe_count($query_permitted)) {
// FIXME. derp code, need rewrite
//$query_permitted[] = safe_count($query_permitted) ? "OR $column IS NULL" : "$column IS NULL";
$query_permitted[] = "OR $column IS NULL";
}
$query_permitted = implode(" AND ", (array)$query_permitted);
if (!safe_empty($query_permitted)) {
$query_part[] = str_replace(" AND OR ", ' OR ', $query_permitted);
}
unset($query_permitted);
break;
case 'sensor':
case 'sensors':
// For sensors
// FIXME -- this is easily generifyable, just use translate_table_array()
$table = isset($options['sensor_table']) ? '`' . $options['sensor_table'] . '`.' : '';
if (isset($options['entity'])) {
$query_permitted[] = generate_query_values('sensor', $table.'entity_type');
$column = $table.'`entity_id`';
} else {
$column = $table.'`sensor_id`';
}
// If IDs stored in SESSION use it (used in ajax)
//if (!isset($GLOBALS['cache']['sensors']) && isset($_SESSION['cache']['sensors']))
//{
// $GLOBALS['cache']['sensors'] = $_SESSION['cache']['sensors'];
//}
// Show only permitted entities
if ($user_limited) {
if (!safe_empty($GLOBALS['permissions']['sensor'])) {
$query_permitted[] = generate_query_values(array_keys((array)$GLOBALS['permissions']['sensor']), $column);
} else {
// Exclude all entries, because there are no permitted entities
$query_permitted[] = '0';
}
$query_permitted = implode(" AND ", (array)$query_permitted);
if (!safe_empty($query_permitted)) {
$query_part[] = str_replace(" AND OR ", ' OR ', $query_permitted);
}
unset($query_permitted);
}
break;
case 'alert':
case 'alerts':
// For generic alert
$column = '`alert_table_id`';
// Show only permitted entities
if ($user_limited) {
if (!safe_empty($GLOBALS['permissions']['alert'])) {
$query_permitted = generate_query_values(array_keys((array)$GLOBALS['permissions']['alert']), $column);
} else {
// Exclude all entries, because there are no permitted entities
$query_permitted = '0';
}
$query_part[] = $query_permitted;
unset($query_permitted);
}
break;
case 'bill':
case 'bills':
// For bills
break;
}
}
if (!safe_empty($query_part)) {
//r($query_part);
if ($user_limited) {
// Limited user must use OR for include multiple entities
$query_permitted = "((" . implode(") OR (", $query_part) . "))";
} else {
// Unlimited used must use AND for exclude multiple hidden entities
$query_permitted = "((" . implode(") AND (", $query_part) . "))";
}
// Append leading AND if option requested
if ($options['leading_and']) {
$query_permitted = ' AND ' . $query_permitted;
}
}
//r($query_permitted);
return !safe_empty($query_permitted) ? $query_permitted . ' ' : '';
}
/**
* Compat function for old usages, call to generate_query_permitted_ng()
*
* @param $type_array
* @param $options
*
* @return string
*/
function generate_query_permitted($type_array = ['device'], $options = [])
{
$options['leading_and'] = TRUE;
return generate_query_permitted_ng($type_array, $options);
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function dashboard_exists($dash_id)
{
return dbExist('dashboards', '`dash_id` = ?', [$dash_id]);
//return count(dbFetchRow("SELECT * FROM `dashboards` WHERE `dash_id` = ?", array($dash_id)));
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function get_user_prefs($user_id)
{
$prefs = [];
foreach (dbFetchRows("SELECT * FROM `users_prefs` WHERE `user_id` = ?", [$user_id]) as $entry) {
$prefs[$entry['pref']] = $entry;
}
return $prefs;
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function get_user_pref($user_id, $pref)
{
if ($entry = dbFetchRow("SELECT `value` FROM `users_prefs` WHERE `user_id` = ? AND `pref` = ?", [$user_id, $pref])) {
return $entry['value'];
}
return NULL;
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function set_user_pref($user_id, $pref, $value)
{
//if (dbFetchCell("SELECT COUNT(*) FROM `users_prefs` WHERE `user_id` = ? AND `pref` = ?", array($user_id, $pref)))
if (dbExist('users_prefs', '`user_id` = ? AND `pref` = ?', [$user_id, $pref])) {
$id = dbUpdate(['value' => $value], 'users_prefs', '`user_id` = ? AND `pref` = ?', [$user_id, $pref]);
} else {
$id = dbInsert(['user_id' => $user_id, 'pref' => $pref, 'value' => $value], 'users_prefs');
}
return $id;
}
// TESTME needs unit testing
// DOCME needs phpdoc block
function del_user_pref($user_id, $pref)
{
return dbDelete('users_prefs', "`user_id` = ? AND `pref` = ?", [$user_id, $pref]);
}
/**
* Load user-specific configuration settings and merge them with global configuration.
*
* This function fetches user-specific preferences from the database and merges
* them with the global configuration. Only settings allowed by the configuration
* definitions are considered. The resulting configuration is stored in the
* $load_config parameter.
*
* @param array $load_config A reference to the array where the merged configuration should be stored.
* @param int $user_id The ID of the user whose preferences should be loaded.
*
* @return void
*/
function load_user_config(&$load_config, $user_id)
{
global $config;
if (!$prefs = dbFetchRows("SELECT * FROM `users_prefs` WHERE `user_id` = ? AND `pref` NOT IN (?, ?)", [$user_id, 'atom_key', 'api_key'])) {
// No user prefs set
return FALSE;
}
// Always use global config here!
include($config['install_dir'] . '/includes/config-variables.inc.php');
foreach ($prefs as $item) {
if (!isset($config_variable[$item['pref']]['useredit']) ||
!$config_variable[$item['pref']]['useredit']) {
// Load only permitted settings
print_debug("User [$user_id] setting '{$item['pref']}' not permitted by definitions.");
continue;
}
// Convert boo|bee|baa config value into $config['boo']['bee']['baa']
$tree = explode('|', $item['pref']);
set_nested_value($load_config, $tree, safe_unserialize($item['value']));
}
}
/**
* Set a value in a nested array, given a list of keys.
*
* This function sets a value in a nested array, creating intermediate arrays as
* necessary. The keys are specified as an array, where each element represents a
* level in the nested array. The value is set at the position specified by the
* last key.
*
* @param array $array A reference to the array in which the value should be set.
* @param array $keys An array of keys specifying the position in the nested array.
* @param mixed $value The value to set in the nested array.
*
* @return void
*/
function set_nested_value(&$array, $keys, $value)
{
$last_key = array_pop($keys);
foreach ($keys as $key) {
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[$last_key] = $value;
}
function process_sql_vars($vars)
{
global $config;
// Always use global config here!
include($config['install_dir'] . '/includes/config-variables.inc.php');
$deletes = [];
$sets = [];
$errors = [];
$set_attribs = []; // set obs_attribs
// Submit button pressed
foreach ($vars as $varname => $value) {
if (str_starts_with($varname, 'varset_')) {
$varname = substr($varname, 7);
$sqlname = str_replace('__', '|', $varname);
$sqlset = get_var_true($value); // value sets in sql
$content = $vars[$varname];
$confname = '$config[\'' . implode("']['", explode('|', $sqlname)) . '\']';
$section = $config_variable[$sqlname]['section'];
if ($vars[$varname . '_custom']) {
$ok = FALSE;
if (isset($config_variable[$sqlname]['edition']) && $config_variable[$sqlname]['edition'] !== OBSERVIUM_EDITION) {
// Skip variables not allowed for current Observium edition
continue;
}
if (isset($config_sections[$section]['edition']) && $config_sections[$section]['edition'] !== OBSERVIUM_EDITION) {
// Skip sections not allowed for current Observium edition
continue;
}
// Split enum|foo|bar into enum foo|bar
[$vartype, $varparams] = explode('|', $config_variable[$sqlname]['type'], 2);
$params = [];
// If a callback function is defined, use this to fill params.
if ($config_variable[$sqlname]['params_call'] && function_exists($config_variable[$sqlname]['params_call'])) {
$params = call_user_func($config_variable[$sqlname]['params_call']);
// Else if the params are defined directly, use these.
} elseif (is_array($config_variable[$sqlname]['params'])) {
$params = $config_variable[$sqlname]['params'];
} elseif (!empty($varparams)) {
// Else use parameters specified in variable type (e.g. enum|1|2|5|10)
foreach (explode('|', $varparams) as $param) {
$params[$param] = [ 'name' => nicecase($param) ];
}
}
switch ($vartype) {
case 'int':
case 'integer':
case 'float':
if (is_numeric($content)) {
$ok = TRUE;
} else {
$errors[] = $config_variable[$sqlname]['name'] . " ($confname) should be of numeric type. Setting '" . escape_html($content) . "' ignored.";
}
break;
case 'bool':
case 'boolean':
switch ($content) {
case 'on':
case '1':
$content = 1;
$ok = TRUE;
break;
case 'off': // Won't actually happen. When "unchecked" the field is simply not transmitted...
case '0':
case '': // ... which we catch here.
$content = 0;
$ok = TRUE;
break;
default:
$ok = FALSE;
$errors[] = $config_variable[$sqlname]['name'] . " ($confname) should be of type bool. Setting '" . escape_html($content) . "' ignored.";
}
break;
case 'enum':
if (!array_key_exists($content, $params)) {
$ok = FALSE;
$errors[] = $config_variable[$sqlname]['name'] . " ($confname) should be one of " . implode(', ', $params) . ". Setting '" . escape_html($content) . "' ignored.";
} else {
$ok = TRUE;
}
break;
case 'enum-array':
//r($content);
//r($params);
foreach ($content as $value) {
// Check all values
if (!array_key_exists($value, $params)) {
$ok = FALSE;
$errors[] = $config_variable[$sqlname]['name'] . " ($confname) all values should be one of this list " . implode(', ', $params) . ". Settings '" . implode(', ', $content) . "' ignored.";
break;
}
$ok = TRUE;
}
break;
case 'enum-list':
//r($content);
//r($params);
if (isset($content['value'])) {
$content = array_filter(array_unique($content['value']), static function ($value) { return !safe_empty($value); });
$ok = !safe_empty($content);
//r($content);
}
break;
case 'enum-key-value':
//r($content);
//r($params);
if (isset($content['key'], $content['value'])) {
$tmp = $content;
$content = [];
foreach ($tmp['key'] as $i => $key) {
if (safe_empty($key) && safe_empty($tmp['value'][$i])) {
// skip an empty key-value pair
continue;
}
$content[$key] = $tmp['value'][$i];
}
$ok = !safe_empty($content);
//r($content);
}
break;
case 'enum-freeinput':
//r($content);
//r($params);
// FIXME, need validate values
if (is_null($content)) {
// Empty array allowed, for override defaults
$content = [];
$ok = TRUE;
}
foreach ($content as $value) {
$ok = TRUE;
}
break;
case 'password':
case 'string':
$ok = TRUE;
break;
default:
$ok = FALSE;
$errors[] = $config_variable[$sqlname]['name'] . " ($confname) is of unknown type (" . $config_variable[$sqlname]['type'] . ")";
break;
}
if ($ok) {
$sets[$sqlname] = $content;
// Set an obs_attrib, example for syslog trigger
//r($config_variable[$sqlname]);
if (isset($config_variable[$sqlname]['set_attrib']) && !safe_empty($config_variable[$sqlname]['set_attrib'])) {
$set_attribs[$config_variable[$sqlname]['set_attrib']] = get_time();
}
}
} elseif ($sqlset) {
$deletes[] = $sqlname;
// Set an obs_attrib, example for syslog trigger
//r($config_variable[$sqlname]);
if (isset($config_variable[$sqlname]['set_attrib']) && !safe_empty($config_variable[$sqlname]['set_attrib'])) {
$set_attribs[$config_variable[$sqlname]['set_attrib']] = get_time();
}
}
}
}
return [ 'sets' => $sets, 'set_attribs' => $set_attribs, 'deletes' => $deletes, 'errors' => $errors ];
}
/**
* Convert amqp|conn|host into returning value of $arrayvar['amqp']['conn']['host']
*
* @param string $sqlname Variable name
* @param array $arrayvar Array where to see param
* @param Boolean $try_isset If True, return isset($sqlname) check, else return variable content
*
* @return mixed
*/
function sql_to_array($sqlname, $arrayvar, $try_isset = TRUE)
{
[$key, $pop_sqlname] = explode('|', $sqlname, 2);
if (!is_array($arrayvar)) {
return FALSE;
}
$isset = array_key_exists($key, $arrayvar);
if (safe_empty($pop_sqlname)) {
// Reached the variable, return its content, or FALSE if it's not set
if ($try_isset) {
return $isset;
}
return $isset ? $arrayvar[$key] : NULL;
}
if ($isset) {
// Recurse to lower level
return sql_to_array($pop_sqlname, $arrayvar[$key], $try_isset);
}
return FALSE;
}
/**
* Darkens or lightens a colour
* Found via http://codepad.org/MTGLWVd0
*
* First argument is the colour in hex, second argument is how dark it should be 1=same, 2=50%
*
* @param string $rgb
* @param int $darker
*
* @return string
*/
function darken_color($rgb, $darker = 2)
{
if (strpos($rgb, '#') !== FALSE) {
$hash = '#';
$rgb = str_replace('#', '', $rgb);
} else {
$hash = '';
}
$len = strlen($rgb);
if ($len == 6) {
} // Passed RGB
elseif ($len == 8) {
// Passed RGBA, remove alpha channel
$rgb = substr($rgb, 0, 6);
} else {
$rgb = FALSE;
}
if ($rgb === FALSE) {
return $hash . '000000';
}
$darker = ($darker > 1) ? $darker : 1;
[$R16, $G16, $B16] = str_split($rgb, 2);
$R = sprintf("%02X", floor(hexdec($R16) / $darker));
$G = sprintf("%02X", floor(hexdec($G16) / $darker));
$B = sprintf("%02X", floor(hexdec($B16) / $darker));
return $hash . $R . $G . $B;
}
function json_output($status, $message)
{
header("Content-type: application/json; charset=utf-8");
echo safe_json_encode(["status" => $status, "message" => $message]);
exit();
}
/**
* Redirect to specified URL
*
* @param string $url Redirecting URL
*/
function redirect_to_url($url)
{
if (safe_empty($url) || $url === '#') {
return;
} // Empty url, do not redirect
$parse = parse_url($url);
//r($url);
if (!isset($parse['scheme']) && !str_starts($url, '/')) {
// When this is not full url or not started with /
$url = '/' . $url;
}
if (headers_sent()) {
// HTML headers already sent, use JS than
register_html_resource('script', "location.href='$url'");
} else {
// Just use headers
header('Location: ' . $url);
}
}
function generate_colour_gradient($start_colour, $end_colour, $steps)
{
if ($steps < 4) {
$steps = 4;
}
$FromRGB['r'] = hexdec(substr($start_colour, 0, 2));
$FromRGB['g'] = hexdec(substr($start_colour, 2, 2));
$FromRGB['b'] = hexdec(substr($start_colour, 4, 2));
$ToRGB['r'] = hexdec(substr($end_colour, 0, 2));
$ToRGB['g'] = hexdec(substr($end_colour, 2, 2));
$ToRGB['b'] = hexdec(substr($end_colour, 4, 2));
$StepRGB['r'] = ($FromRGB['r'] - $ToRGB['r']) / ($steps - 1);
$StepRGB['g'] = ($FromRGB['g'] - $ToRGB['g']) / ($steps - 1);
$StepRGB['b'] = ($FromRGB['b'] - $ToRGB['b']) / ($steps - 1);
$GradientColors = [];
$GradientColors[] = $start_colour; // Hack because array starts at 0, but we count from 1.
for ($i = 0; $i < $steps; $i++) {
$RGB['r'] = floor($FromRGB['r'] - ($StepRGB['r'] * $i));
$RGB['g'] = floor($FromRGB['g'] - ($StepRGB['g'] * $i));
$RGB['b'] = floor($FromRGB['b'] - ($StepRGB['b'] * $i));
$HexRGB['r'] = sprintf('%02x', ($RGB['r']));
$HexRGB['g'] = sprintf('%02x', ($RGB['g']));
$HexRGB['b'] = sprintf('%02x', ($RGB['b']));
$GradientColors[] = implode(NULL, $HexRGB);
}
$GradientColors = array_filter($GradientColors, "c_len");
unset($GradientColors[0]); // Remove the placeholder array position 0 because it's not used.
//r($GradientColors);
return $GradientColors;
}
function c_len($val)
{
return strlen($val) === 6;
}
function adjust_colour_brightness($hex, $steps)
{
// Steps should be between -255 and 255. Negative = darker, positive = lighter
$steps = max(-255, min(255, $steps));
// Normalize into a six character long hex string
$hex = str_replace('#', '', $hex);
if (strlen($hex) === 3) {
$hex = str_repeat($hex[0], 2) . str_repeat($hex[1], 2) . str_repeat($hex[2], 2);
}
// Split into three parts: R, G and B
$color_parts = str_split($hex, 2);
$return = '';
foreach ($color_parts as $color) {
$color = hexdec($color); // Convert to decimal
$color = max(0, min(255, $color + $steps)); // Adjust color
$return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
}
return $return;
}
/**
* Highlight (or replace with specific strings) part of string.
* Optionally can search full words of search strings.
*
* @param string $text Text where need highlight search string
* @param array $search Search array. Can be string, simple array or array with 'search', 'replace' pairs
* @param string $replace Default is just
* @param bool $words If True search full words
*
* @return string
*/
function html_highlight($text, $search = [], $replace = '', $words = FALSE)
{
if (empty($replace)) {
// Default is highlight as danger class
$replace = $words ? '$1' : '$0';
}
$entries = [];
foreach ((array)$search as $entry) {
if (isset($entry['search'])) {
if (!isset($entry['replace'])) {
$entry['replace'] = $replace;
}
$text = html_highlight($text, $entry['search'], $entry['replace'], $words);
continue;
}
if (strlen($entry) == 0) {
continue;
}
$entry = preg_quote($entry, '%');
// allow limited regex patterns in search strings (currently only for interfaces links)
//$patterns = [ '\\\\d\+' => '\d+', ];
$entries[] = str_replace('\\\\d\+', '\d+', $entry);
}
if (!count($entries)) {
return $text;
}
$search_pattern = '(' . implode('|', $entries) . ')';
if ($words) {
// Search full words
$search_pattern = str_replace('(?:', '(', OBS_PATTERN_START) .
$search_pattern .
str_replace('(?:', '(', OBS_PATTERN_END);
// append start and end in pattern search
$replace = '$1' . $replace . '$3';
} else {
// Search any search string
$search_pattern = '%' . $search_pattern . '%i';
}
return preg_replace($search_pattern, $replace, $text);
}
/**
* Silly class to assign and remember a unique class for a type.
*
* @param string $type
* @param string $group
*
* @return string
*/
function get_type_class($type, $group = "unknown") {
global $cache;
// Short-circuit if hardcoded classes exist for this
if (isset($GLOBALS['config']['type_class'][$group][$type]['class'])) {
return $GLOBALS['config']['type_class'][$group][$type]['class'];
}
if (isset($cache['type_class'][$group][$type])) {
return $cache['type_class'][$group][$type]['class'];
}
// all known label classes
// $classes = [ 'default', 'primary', 'success', 'info', 'warning', 'important',
// 'error', 'danger', 'suppressed', 'delayed', 'inverse', 'rainbow' ];
// available label classes for cycling
$classes = [ 'primary', 'success', 'info', 'warning', 'important', 'suppressed', 'default' ];
$next = $cache['type_class'][$group]['_NEXT_'] ?? 0;
$cache['type_class'][$group][$type]['class'] = $classes[$next];
if (isset($classes[$next + 1])) {
$next++;
} else {
$next = 0;
}
$cache['type_class'][$group]['_NEXT_'] = $next;
return $cache['type_class'][$group][$type]['class'];
}
/**
* Silly class to return a label using persistent class for a certain string/type within a given group
*
* @param string $type
* @param string $group
*
* @return string
*/
function get_type_class_label($type, $group = "unknown") {
return '' . $type . '';
}
/**
* Get a value from the array by traversing the keys.
*
* @param array $array The array to get the value from.
* @param array $keys An array of keys to traverse.
*
* @return mixed|null The value at the specified keys or null if not found.
*/
function get_value_by_keys(&$array, $keys)
{
$key = array_shift($keys);
if (empty($keys)) {
return $array[$key] ?? NULL;
}
return isset($array[$key]) ? get_value_by_keys($array[$key], $keys) : NULL;
}
/**
* Set a value in the array by traversing the keys.
*
* @param array $array The array to set the value in.
* @param array $keys An array of keys to traverse.
* @param mixed $value The value to set.
*/
function set_value_by_keys(&$array, $keys, $value)
{
$key = array_shift($keys);
if (empty($keys)) {
$array[$key] = $value;
} else {
if (!isset($array[$key])) {
$array[$key] = [];
}
set_value_by_keys($array[$key], $keys, $value);
}
}
/**
* Unset a value in the array by traversing the keys.
*
* @param array $array The array to unset the value in.
* @param array $keys An array of keys to traverse.
*/
function unset_value_by_keys(&$array, $keys)
{
$key = array_shift($keys);
if (empty($keys)) {
unset($array[$key]);
} else {
if (isset($array[$key])) {
unset_value_by_keys($array[$key], $keys);
}
}
}
// EOF