$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, '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; foreach (array_unique($GLOBALS['cache_html']['resources'][$type]) as $content) { if (is_array($content)) { // For meta tags foreach ($content as $param => $value) { $template = str_replace('%%STRING_' . $param . '%%', $value, $template); } $resource_string .= $template; } else { $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'])); } 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 = isset($_SERVER['HTTP_CONTENT_TYPE']) ? $_SERVER['HTTP_CONTENT_TYPE'] : $_SERVER['CONTENT_TYPE']; // https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection // XSS script regex // 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 = '
'; foreach ($args['bars'] as $bar) { $output .= '
'; $total += $bar['percent']; } $left = '100' - $total; if ($left > 0) { $output .= '
'; } if ($left >= 0) { $output .= '
' . $args['text'] . '
'; } foreach ($args['bars'] as $bar) { $output .= '
' . $bar['text'] . '
'; } # if ($left > '0') { $output .= '
'.$args['text'].'
'; } $output .= '
'; return $output; } // Legacy function // DO NOT USE THIS. Please replace instances of it with percentage_bar from above. // TESTME needs unit testing // DOCME needs phpdoc block function print_percentage_bar($width, $height, $percent, $left_text, $left_colour, $left_background, $right_text, $right_colour, $right_background) { if ($percent > "100") { $size_percent = "100"; } else { $size_percent = $percent; } $percentage_bar['border'] = "#" . $left_background; $percentage_bar['bg'] = "#" . $right_background; $percentage_bar['width'] = $width; $percentage_bar['text'] = $right_text; $percentage_bar['bars'][0] = ['percent' => $size_percent, 'colour' => '#' . $left_background, 'text' => $left_text]; $output = percentage_bar($percentage_bar); return $output; } // TESTME needs unit testing // DOCME needs phpdoc block function device_link_class($device) { if (isset($device['status']) && $device['status'] == '0') { $class = "red"; } else { $class = ""; } if ((isset($device['ignore']) && $device['ignore'] == '1') || (!is_null($device['ignore_until']) && strtotime($device['ignore_until']) > time())) { $class = "grey"; if (isset($device['status']) && $device['status'] == '1') { $class = "green"; } } if (isset($device['disabled']) && $device['disabled'] == '1') { $class = "grey"; } return $class; } /** * Return cached locations list * * If filter used, return locations available only for specified params. * Without filter return all available locations (cached) * * @param array $filter * * @return array */ // TESTME needs unit testing function get_locations($filter = []) { $where_array = []; foreach ($filter as $var => $value) { switch ($var) { case 'location_lat': case 'location_lon': case 'location_country': case 'location_state': case 'location_county': case 'location_city': // Check geo params only when GEO enabled globally if ($GLOBALS['config']['geocoding']['enable']) { $where_array[$var] = generate_query_values_ng($value, $var); } break; case 'location': $where_array[$var] = generate_query_values_ng($value, $var); break; } } if (!safe_empty($where_array)) { // Return only founded locations $locations = dbFetchColumn("SELECT DISTINCT `location` FROM `devices_locations`" . generate_where_clause($GLOBALS['cache']['where']['devices_permitted'], $where_array)); } else { $locations = empty($GLOBALS['cache']['device_locations']) ? [] : array_keys($GLOBALS['cache']['device_locations']); } sort($locations); return $locations; } // Note, by default text NOT escaped. // TESTME needs unit testing // DOCME needs phpdoc block function generate_ap_link($args, $text = NULL, $type = NULL, $escape = FALSE) { humanize_port($args); if (!$text) { $text = escape_html($args['port_label']); } if ($type) { $args['graph_type'] = $type; } if (!isset($args['graph_type'])) { $args['graph_type'] = 'port_bits'; } if (!isset($args['hostname']) && $args['device_id']) { $args = array_merge($args, device_by_id_cache($args['device_id'])); } $content = "
" . $args['text'] . " - " . escape_html($args['port_label']) . "
"; if ($args['ifAlias']) { $content .= escape_html($args['ifAlias']) . "
"; } $content .= "
"; $graph_array['type'] = $args['graph_type']; $graph_array['legend'] = "yes"; $graph_array['height'] = "100"; $graph_array['width'] = "340"; $graph_array['to'] = get_time(); $graph_array['from'] = get_time('day'); $graph_array['id'] = $args['accesspoint_id']; $content .= generate_graph_tag($graph_array); $graph_array['from'] = get_time('week'); $content .= generate_graph_tag($graph_array); $graph_array['from'] = get_time('month'); $content .= generate_graph_tag($graph_array); $graph_array['from'] = get_time('year'); $content .= generate_graph_tag($graph_array); $content .= "
"; $url = generate_ap_url($args); if (port_permitted($args['interface_id'], $args['device_id'])) { return overlib_link($url, $text, $content, $class, $escape); } return $text; } /** * Returns TRUE if the device is marked as ignored in the cache. * * @param $device_id * * @return bool */ function device_is_ignored($device_id) { return isset($GLOBALS['cache']['devices']['ignored']) && in_array($device_id, $GLOBALS['cache']['devices']['ignored'], TRUE); } // 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_count($GLOBALS['permissions']['device'])) { $query_permitted[] = generate_query_values_ng(array_keys($GLOBALS['permissions']['device']), $column); // $query_permitted[] = " $column IN (". // implode(',', array_keys($GLOBALS['permissions']['device'])). // ')'; } else { // Exclude all entries, because there is no permitted devices $query_permitted[] = ' 0'; } } // Also don't show ignored and disabled devices (except on 'device' and 'devices' pages) $devices_excluded = []; if (strpos($page, 'device') !== 0) { if ($options['hide_ignored'] && safe_count($GLOBALS['cache']['devices']['ignored'])) { $devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['ignored']); } if ($options['hide_disabled'] && safe_count($GLOBALS['cache']['devices']['disabled'])) { $devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['disabled']); } } if (!safe_empty($devices_excluded)) { // Set query with excluded devices $query_permitted[] = generate_query_values_ng(array_unique($devices_excluded), $column, '!='); // $query_permitted[] = " $column NOT IN (". // implode(',', array_unique($devices_excluded)). // ')'; } // 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': $column = '`port_id`'; if (isset($options['port_table'])) { $column = '`' . $options['port_table'] . '`.' . $column; } // 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_count($GLOBALS['permissions']['port'])) { $query_permitted[] = generate_query_values_ng(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_count($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_count($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_count($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_count($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_count($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_ng(array_unique($ports_excluded), $column, '!='); // $query_permitted[] = $column . " NOT IN (". // implode(',', array_unique($ports_excluded)). // ')'; } // 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() $column = '`sensor_id`'; if (isset($options['sensor_table'])) { $column = '`' . $options['sensor_table'] . '`.' . $column; } // 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_count($GLOBALS['permissions']['sensor'])) { $query_permitted = generate_query_values_ng(array_keys((array)$GLOBALS['permissions']['sensor']), $column); // $query_permitted .= " $column IN ("; // $query_permitted .= implode(',', array_keys($GLOBALS['permissions']['sensor'])); // $query_permitted .= ')'; } else { // Exclude all entries, because there are no permitted entities $query_permitted = '0'; } $query_part[] = $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_count($GLOBALS['permissions']['alert'])) { $query_permitted = generate_query_values_ng(array_keys((array)$GLOBALS['permissions']['alert']), $column); // $query_permitted .= " $column IN ("; // $query_permitted .= implode(',', array_keys($GLOBALS['permissions']['alert'])); // $query_permitted .= ')'; } 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-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])) { continue; } // skip empty key-value pair $content[$key] = $tmp['value'][$i]; } $ok = TRUE; //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(); } /** * 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']) && strpos($content, '/') === FALSE) { $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; } /** * 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') { // FIXME handle severity parameter with colour or icon? $ui_alerts = '<div width="100%" class="alert alert-' . $severity . '">'; if (!safe_empty($title)) { $ui_alerts .= '<h4>' . $title . '</h4>'; } $ui_alerts .= $text . '</div>'; $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']) && is_alpha($html) && is_file($GLOBALS['config']['html_dir'] . "/includes/panels/" . $html . ".inc.php")) { $panel_file = $GLOBALS['config']['html_dir'] . "/includes/panels/" . $html . ".inc.php"; ob_start(); include($panel_file); $html = ob_get_clean(); } $GLOBALS['cache_html']['page_panel'] = $html; } /** * 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 = []; 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"); return $GradientColors; } function c_len($val) { return (strlen($val) == 6 ? TRUE : FALSE); } 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(substr($hex, 0, 1), 2) . str_repeat(substr($hex, 1, 1), 2) . str_repeat(substr($hex, 2, 1), 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 ? '<em class="text-danger">$1</em>' : '<em class="text-danger">$0</em>'; } $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; if (isset($cache['type_class'][$group][$type])) { return $cache['type_class'][$group][$type]['class']; } $classes = ['primary', 'success', 'warning', 'error', 'suppressed']; if (isset($cache['type_class'][$group]['NEXT'])) { $next = $cache['type_class'][$group]['NEXT']; } else { $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 '<span class="label label-' . get_type_class($type, $group) . '">' . $type . '</span>'; } /** * 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