$field_array) { // loop through each sub-field that we are going to check for variables foreach ($check_fields as $field_to_check) { if (isset($field_array[$field_to_check]) && is_array($form_array[$field_name][$field_to_check])) { // if the field/sub-field combination is an array, resolve it recursively $form_array[$field_name][$field_to_check] = inject_form_variables($form_array[$field_name][$field_to_check], $arg1); } elseif (isset($field_array[$field_to_check]) && !is_array($field_array[$field_to_check])) { $count = 0; /* loop through the $field_to_check and replace up to three times * for each arg1:arg2:arg3 variables. */ while (true) { $matches = []; // if (preg_match('/\|(arg[123]):([a-zA-Z0-9_]*)\|/', $form_array[$field_name][$field_to_check], $matches)) { if (preg_match('/\|(arg[123]):([a-zA-Z0-9_]*)\|/', $field_array[$field_to_check], $matches)) { $string = $field_array[$field_to_check]; $matches0 = $matches[0]; $matches1 = $matches[1]; $matches2 = $matches[2]; // an empty field name in the variable means don't treat this as an array if ($matches2 == '') { if (is_array(${$matches1})) { // the existing value is already an array, leave it alone $form_array[$field_name][$field_to_check] = ${$matches1}; } else { // the existing value is probably a single variable $form_array[$field_name][$field_to_check] = str_replace($matches0, ${$matches1}, $field_array[$field_to_check]); } } else { /* copy the value down from the array/key specified in the variable * replace up to three times for arg1:arg2:arg3 variables */ if (is_array(${$matches1})) { $array = ${$matches1}; if (isset($array[$matches2]) && $array[$matches2] != '') { $string = str_replace($matches0, $array[$matches2], $string); } else { $string = str_replace($matches0, '', $string); } } // Double check to see if the replacement went as planned $matches = []; preg_match('/\|(arg[123]):([a-zA-Z0-9_]*)\|/', $string, $matches); if (!cacti_sizeof($matches)) { $form_array[$field_name][$field_to_check] = $string; // No more arg[123] break; } else { if ($field_to_check == 'sql') { // Update the form value with the modified string // Then update the field array string value to recheck $form_array[$field_name][$field_to_check] = $string; $field_array[$field_to_check] = $string; } elseif (isset($form_array[$field_name]['default'])) { // We did not find a match for this field value, use the default $form_array[$field_name][$field_to_check] = $form_array[$field_name]['default']; } else { // We found no value, found no default, set to empty string $form_array[$field_name][$field_to_check] = ''; } } } /* if there are no more arg's, break. Since some arg's * might not ever be replaced, continue counting till 3 as * in the special case of 'sql' for example. */ $additional = preg_match('/\|(arg[123]):([a-zA-Z0-9_]*)\|/', $string); if (empty($additional)) { break; } if ($count >= 3) { break; } else { $count++; } } else { // No arg[123] found break; } } } } } } return $form_array; } /** * Generates a table row with alternating row colors and returns the current color. * * This function prints an HTML table row (``) with a class that alternates between * 'odd', 'even', and 'even-alternate' based on the provided row value. It also returns * the current color used for the row. * * @param string $row_color1 The color for odd rows. * @param string $row_color2 The color for even rows. If empty or 'E5E5E5', the class 'even' is used. * @param int $row_value The current row number, used to determine if the row is odd or even. * @param string $row_id Optional. The ID to assign to the row. If not provided, no ID is assigned. * * @return string The current color used for the row. */ function form_alternate_row_color(string $row_color1, string $row_color2, int $row_value, string $row_id = '') : string { if ($row_value % 2 == 1) { $class = 'odd'; $current_color = $row_color1; } else { if ($row_color2 == '' || $row_color2 == 'E5E5E5') { $class = 'even'; } else { $class = 'even-alternate'; } $current_color = $row_color1; } if ($row_id != '') { print "\n"; } else { print "\n"; } return $current_color; } /** * Generates an HTML table row with alternating classes for styling. * * @param string $row_id The ID to assign to the table row. If not provided, no ID will be assigned. * @param bool $light If true, the row will use the 'even-alternate' class for even rows. * @param bool $disabled If true, the row will not be selectable. * * @return void */ function form_alternate_row(string $row_id = '', bool $light = false, bool $disabled = false) : void { static $i = 1; if ($i % 2 == 1) { $class = 'odd'; } elseif ($light) { $class = 'even-alternate'; } else { $class = 'even'; } $i++; if ($row_id != '' && !$disabled && !str_starts_with($row_id, 'row_')) { print "\n"; } elseif (str_starts_with($row_id, 'row_')) { print "\n"; } elseif ($row_id != '') { print "\n"; } else { print "\n"; } } /** * A wrapper to form_selectable_ecell that escapes the contents * * This function creates a selectable table cell with the provided contents, * ensuring that the contents are properly escaped to prevent XSS attacks. * * @param mixed $contents The content to be displayed inside the cell. * @param mixed $id The ID attribute for the cell. * @param mixed $width The width of the cell. Default is an empty string. * @param string $styleclass The style or class attribute for the cell. Default is an empty string. * @param string $title The title attribute for the cell. Default is an empty string. * * @return bool */ function form_selectable_ecell(mixed $contents, mixed $id, mixed $width = '', string $styleclass = '', string $title = '') : bool { return form_selectable_cell(htmle($contents), $id, $width, $styleclass, $title); } /** * Format's a table row such that it can be highlighted using cacti's js actions * * @param mixed $contents The content to be placed inside the table cell. * @param mixed $id The ID attribute for the table cell (not used in the function). * @param mixed $width The width of the table cell. Default is an empty string. * @param string $styleclass The style or class attribute for the table cell. * Default is an empty string. If it contains a colon (:), * it is treated as a style attribute; otherwise, as a class attribute. * @param string $title The tooltip text for the table cell. Default is an empty string. * * @return bool False if an error is encountered */ function form_selectable_cell(mixed $contents, mixed $id, mixed $width = '', string $styleclass = '', string $title = '') : bool { global $tableCount; static $tableColumns = null; $table_id = form_get_table_id(); if (!isset($tableColumns[$table_id])) { $tableColumns[$table_id] = json_decode(read_user_setting("visible_columns_{$table_id}{$tableCount[$table_id]}"), true); } static $col_num = null; static $col_id = null; static $logged = null; if ($col_num === null) { $col_num = 0; $col_id = $id; } elseif ($col_id != $id) { $col_num = 0; $col_id = $id; } else { $col_num++; } if (isset($tableColumns[$table_id]) && cacti_sizeof($tableColumns[$table_id])) { $columns = array_keys($tableColumns[$table_id]); // Check if the column is visible if (isset($columns[$col_num])) { if ($tableColumns[$table_id][$columns[$col_num]] !== true) { return false; } } elseif (isset($columns["autocol$col_num"])) { // @phpstan-ignore-line if ($tableColumns[$table_id][$columns["autocol$col_num"]] !== true) { return false; } } elseif ($col_num < cacti_sizeof($tableColumns)) { // We can have last item colspan > 1. So, only log if column count is larger cacti_log("The table with the Table ID $table_id is not using form_selectable_cell() correctly"); $logged[$table_id] = true; } } $output = ''; if ($styleclass != '') { if (!str_contains($styleclass, ':')) { $output = "class='nowrap " . $styleclass . "'"; if ($width != '') { $output .= " style='width:$width;'"; } } else { $output = "class='nowrap' style='" . $styleclass; if ($width != '') { $output .= ";width:$width;"; } $output .= "'"; } } else { $output = 'class="nowrap"'; if ($width != '') { $output .= " style='width:$width;'"; } } if ($title != '') { $wrapper = "" . $contents . ''; } else { $wrapper = $contents; } print "\t' . $wrapper . "\n"; return true; } function form_get_table_id(mixed $increment = false) : string { static $table_count = 0; if ($increment) { $table_count++; } if (isset_request_var('action') && get_nfilter_request_var('action') != '' && isset_request_var('tab') && get_nfilter_request_var('tab') != '') { return basename(get_current_page(), '.php') . ':' . $table_count . ':action-tab-' . get_nfilter_request_var('action') . '-' . get_nfilter_request_var('tab') . ':'; } if (isset_request_var('action') && get_nfilter_request_var('action') != '') { return basename(get_current_page(), '.php') . ':' . $table_count . ':action-' . get_nfilter_request_var('action') . ':'; } if (isset_request_var('tab') && get_nfilter_request_var('tab') != '') { return basename(get_current_page(), '.php') . ':tab-' . get_nfilter_request_var('tab') . ':'; } else { return basename(get_current_page(), '.php') . ':' . $table_count; } } /** * Format's a table row such that it can be highlighted using cacti's js actions * * @param mixed $contents The content to be placed inside the table cell. * @param string $table_id The ID attribute for the table cell (not used in the function). * @param string $columnid The width of the table cell. Default is an empty string. * @param string $styleclass The style or class attribute for the table cell. * Default is an empty string. If it contains a colon (:), it is treated as a style * attribute; otherwise, as a class attribute. * @param string $title The tooltip text for the table cell. Default is an empty string. * * @return bool False if errors are encountered */ function form_selectable_vcell(mixed $contents, string $table_id = '', string $columnid = '', string $styleclass = '', string $title = '') : bool { global $tableCount; static $tableColumns = null; if ($table_id == '') { $table_id = form_get_table_id(); } if (!isset($tableColumns[$table_id])) { $tableColumns[$table_id] = json_decode(read_user_setting("visible_columns_{$table_id}{$tableCount[$table_id]}"), true); } if (isset($tableColumns[$table_id]) && cacti_sizeof($tableColumns[$table_id])) { if (!$tableColumns[$table_id][$columnid]) { return false; } } $output = ''; $width = ''; // Width was undefined, adding this until we know what was intended if ($styleclass != '') { if (!str_contains($styleclass, ':')) { $output = "class='nowrap " . $styleclass . "'"; if ($width != '') { $output .= " style='width:$width;'"; } } else { $output = "class='nowrap' style='" . $styleclass; if ($width != '') { $output .= ";width:$width;"; } $output .= "'"; } } else { $output = 'class="nowrap"'; if ($width != '') { $output .= " style='width:$width;'"; } } if ($title != '') { $wrapper = "" . $contents . ''; } else { $wrapper = $contents; } print "\t' . $wrapper . "\n"; return true; } function form_process_visible_display_text(string $table_id, array $display_text) : array { global $tableCount; static $tableColumns = null; /** * We have to support more than one table per page * so maintain a column count per page and store settings * accordingly. */ $table_id = form_get_table_id(true); if (!isset($tableColumns[$table_id])) { $tableCount[$table_id] = 0; // this reset/clear functionality can be removed before production if (isset_request_var('clear') || isset_request_var('reset')) { db_execute_prepared('DELETE FROM settings_user WHERE user_id = ? AND name = ?', [$_SESSION[SESS_USER_ID], "visible_columns_{$table_id}{$tableCount[$table_id]}"]); $tableColumns[$table_id] = []; } else { $tableColumns[$table_id] = json_decode(read_user_setting("visible_columns_{$table_id}{$tableCount[$table_id]}"), true); } } else { $tableCount[$table_id]++; // this reset/clear functionality can be removed before production if (isset_request_var('clear') || isset_request_var('reset')) { db_execute_prepared('DELETE FROM settings_user WHERE user_id = ? AND name = ?', [$_SESSION[SESS_USER_ID], "visible_columns_{$table_id}{$tableCount[$table_id]}"]); $tableColumns[$table_id] = []; } else { $tableColumns[$table_id] = json_decode(read_user_setting("visible_columns_{$table_id}{$tableCount[$table_id]}"), true); } } // reset if the developer is making changes to the page layout if (cacti_sizeof($tableColumns[$table_id]) && cacti_sizeof($display_text) != cacti_sizeof($tableColumns[$table_id])) { cacti_log('WARNING: Detected a change in base table topology', false, 'DEVELOP', POLLER_VERBOSITY_MEDIUM); $tableColumns[$table_id] = []; } if (isset_request_var('columns_add')) { $columns = get_nfilter_request_var('columns_add'); if (is_array($columns)) { foreach ($columns as $column) { if (isset($tableCount[$table_id][$column])) { $tableColumns[$table_id][$column] = true; } } } else { $tableColumns[$table_id][$columns] = true; } } if (isset_request_var('columns_remove')) { $columns = get_nfilter_request_var('columns_remove'); if (is_array($columns)) { foreach ($columns as $column) { if (isset($tableCount[$table_id][$column])) { $tableColumns[$table_id][$column] = false; } } } else { $tableColumns[$table_id][$columns] = false; } } if (!cacti_sizeof($tableColumns[$table_id])) { $initialize = true; } else { $initialize = false; } $return_array = []; $coldata = []; if (cacti_sizeof($display_text)) { foreach ($display_text as $id => $column) { // Convert the array to a standard array if (is_numeric($id)) { $id = "autocol$id"; if (isset($column['display'])) { $return_array[$id] = $column; } else { if (is_array($column)) { $return_array[$id]['display'] = $column[0]; if (isset($column[1])) { $return_array[$id]['sort'] = $column[1]; } } else { $return_array[$id]['display'] = $column; } } } elseif (isset($column['display'])) { $return_array[$id] = $column; } else { if (is_array($column)) { $return_array[$id]['display'] = $column[0]; if (isset($column[1])) { $return_array[$id]['sort'] = $column[1]; } } else { $return_array[$id]['display'] = $column; } } if (isset($tableColumns[$table_id][$id]) && $tableColumns[$table_id][$id] == true) { $return_array[$id]['visible'] = true; $coldata[$id] = true; } elseif (isset($column['nohide']) && $column['nohide'] === true) { $return_array[$id]['visible'] = true; $coldata[$id] = true; } elseif ($initialize) { if (isset($column['default'])) { if ($column['default'] === true) { $return_array[$id]['visible'] = true; $coldata[$id] = true; } else { $return_array[$id]['visible'] = false; $coldata[$id] = false; } } else { $return_array[$id]['visible'] = true; $coldata[$id] = true; } } else { $return_array[$id]['visible'] = false; $coldata[$id] = false; } } } if ($initialize) { set_user_setting("visible_columns_{$table_id}{$tableCount[$table_id]}", json_encode($coldata)); } // cacti_log(json_encode($return_array)); return $return_array; } /** * Format's a tables checkbox form element so that the cacti js actions work on it * * @param string $title The title attribute for the checkbox, used for accessibility. * @param mixed $id The unique identifier for the checkbox input element. * @param bool $disabled Whether the checkbox should be disabled. Default is false. * @param bool $checked Whether the checkbox should be checked. Default is false. * * @return void */ function form_checkbox_cell(string $title, mixed $id, bool $disabled = false, bool $checked = false) : void { print "\t\n"; print "\t\t\n"; print "\t\n"; } /** * Ends a table row that is started with form_alternate_row * * @return void */ function form_end_row() : void { print "\n"; } /** * Returns the boolean equivalent of an HTML checkbox value * * This function checks if the given string is equal to 'on' and returns true if it is, * otherwise it returns false. * * @param string $html_boolean The string representation of a boolean value. * * @return bool Returns true if the input string is 'on', otherwise false. */ function html_boolean(string $html_boolean) : bool { return ($html_boolean == 'on'); } /** * Returns the natural language equivalent of an HTML checkbox value * * This function takes an HTML boolean value (typically 'on' or 'off') and * returns a user-friendly string indicating whether the value is selected or not. * * @param string $html_boolean The HTML boolean value to convert. Expected values are 'on' or 'off'. * * @return string Returns 'Selected' if the input is 'on', otherwise returns 'Not Selected'. */ function html_boolean_friendly(string $html_boolean) : string { if ($html_boolean == 'on') { return __('Selected'); } else { return __('Not Selected'); } } /** * Finds the proper CSS padding to apply based on the current client browser in use * * This function currently returns an empty string, indicating no specific style is applied. * * @return string An empty string representing the checkbox style. */ function get_checkbox_style() : string { return ''; } /** * Sets the required 'action' request variable * * @param string $default The default action is not set * * @return void */ function set_default_action(string $default = '') : void { if (!isset_request_var('action')) { set_request_var('action', $default); } elseif (is_array(get_nfilter_request_var('action'))) { if (read_config_option('log_validation') == 'on') { cacti_log('WARNING: Request variable \'action\' was passed as array in ' . $_SERVER['SCRIPT_NAME'] . '.', false, 'WEBUI'); } set_request_var('action', $_REQUEST['action'][0]); } else { set_request_var('action', $_REQUEST['action']); } } /** * alias of unset_request_var * * @param string $variable * * @return void */ function unsrv(string $variable) : void { unset_request_var($variable); } /** * Unsets the request variable. * * @param string $variable * * @return void */ function unset_request_var(string $variable) : void { global $_CACTI_REQUEST; if (isset($_CACTI_REQUEST[$variable])) { unset($_CACTI_REQUEST[$variable]); } if (isset($_REQUEST[$variable])) { unset($_REQUEST[$variable]); } } /** * alias of isset_request_var() * * @param string $variable * * @return bool */ function isrv(string $variable) : bool { return isset_request_var($variable); } /** * checks to see if the $_REQUEST variable is set. * * @param string $variable * * @return bool */ function isset_request_var(string $variable) : bool { return isset($_REQUEST[$variable]); } /** * alias of isempty_request_var() * * @param string $variable * * @return bool */ function ierv(string $variable) : bool { return isempty_request_var($variable); } /** * checks to see if the $_REQUEST variableis empty. * * @param string $variable * * @return bool */ function isempty_request_var(string $variable) : bool { if (isset_request_var($variable)) { $value = $_REQUEST[$variable]; if (!empty($value)) { return false; } } return true; } /** * alias of set_request_var() * * @param string $variable The variable to set * @param mixed $value The value to set the variable to * * @return void */ function srv(string $variable, mixed $value) : void { set_request_var($variable, $value); } /** * Sets a given $_REQUEST variable and Cacti global. * * @param string $variable The variable to set * @param mixed $value The value to set the variable to * * @return void */ function set_request_var(string $variable, mixed $value) : void { global $_CACTI_REQUEST; $_CACTI_REQUEST[$variable] = $value; $_REQUEST[$variable] = $value; $_POST[$variable] = $value; $_GET[$variable] = $value; } /** * returns the current value of a PHP $_REQUEST variable, optionally * returning a default value if the request variable does not exist. When Cacti * has 'log_validation' set on, it will log all instances where a request variable * has not first been filtered. * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function get_request_var(string $name, mixed $default = '') : mixed { global $_CACTI_REQUEST; $log_validation = read_config_option('log_validation'); if (isset($_CACTI_REQUEST[$name])) { return $_CACTI_REQUEST[$name]; } if (isset_request_var($name)) { if ($log_validation == 'on') { html_log_input_error($name); } set_request_var($name, $_REQUEST[$name]); return $_REQUEST[$name]; } else { return $default; } } /** * alias of get_request_var() * * @deprecated v1.0 * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function get_request_var_request(string $name, mixed $default = '') : mixed { return get_request_var($name, $default); } /** * alias of get_request_var() * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function grv(string $name, mixed $default = '') : mixed { return get_request_var($name, $default); } /** * alias of get_filter_request_var() * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param int $filter the filter mode to use * @param array $options used to pass to filter_var function or to hold the * default value to be returned * * @return mixed */ function gfrv(string $name, int $filter = FILTER_VALIDATE_INT, array $options = []) : mixed { return get_filter_request_var($name, $filter, $options); } /** * returns the current value of a PHP $_REQUEST variable and also * sanitizing the value using the filter. It will also optionally * return a default value if the request variable does not exist * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param int $filter the filter mode to use * @param array $options used to pass to filter_var function or to hold the * default value to be returned * * @return mixed */ function get_filter_request_var(string $name, int $filter = FILTER_VALIDATE_INT, array $options = []) : mixed { $custom_error = 'Unknown Error'; if (isset_request_var($name)) { if (isempty_request_var($name)) { set_request_var($name, get_nfilter_request_var($name)); return get_request_var($name); } if (get_nfilter_request_var($name) == 'undefined') { if (isset($options['default'])) { set_request_var($name, $options['default']); return $options['default']; } else { set_request_var($name, ''); return ''; } } else { if (get_nfilter_request_var($name) == '0') { $value = '0'; } elseif (get_nfilter_request_var($name) == 'undefined') { if (isset($options['default'])) { $value = $options['default']; } else { $value = ''; } } elseif ($filter == FILTER_VALIDATE_IS_REGEX) { if (is_base64_encoded($_REQUEST[$name])) { $_REQUEST[$name] = mb_convert_encoding(base64_decode($_REQUEST[$name], true), 'UTF-8'); } $valid = validate_is_regex($_REQUEST[$name]); if ($valid === true) { $value = $_REQUEST[$name]; } else { $value = false; $custom_error = $valid; } } elseif ($filter == FILTER_VALIDATE_IS_NUMERIC_ARRAY) { $valid = true; if (is_array($_REQUEST[$name])) { foreach ($_REQUEST[$name] as $number) { if (!is_numeric($number)) { $valid = false; break; } } } else { $valid = false; } if ($valid == true) { $value = $_REQUEST[$name]; } else { $value = false; } } elseif ($filter == FILTER_VALIDATE_IS_NUMERIC_LIST) { $valid = true; $values = preg_split('/,/', $_REQUEST[$name], -1, PREG_SPLIT_NO_EMPTY); foreach (($values ?: []) as $number) { if (!is_numeric($number)) { $valid = false; break; } } if ($valid == true) { $value = $_REQUEST[$name]; } else { $value = false; } } elseif (!cacti_sizeof($options)) { $value = filter_var($_REQUEST[$name], $filter); } else { $value = filter_var($_REQUEST[$name], $filter, $options); } } if ($value === null && $options['default'] === null) { $value = ''; } if ($value === false) { if ($filter == FILTER_VALIDATE_IS_REGEX) { raise_message('custom', __esc('The regular expression "%s" is not valid. Error is %s', (string) get_nfilter_request_var($name), (string) $custom_error), MESSAGE_LEVEL_ERROR); set_request_var($name, ''); } else { die_html_input_error($name, get_nfilter_request_var($name)); } } else { set_request_var($name, $value); return $value; } } else { if (isset($options['default'])) { set_request_var($name, $options['default']); return $options['default']; } else { return null; } } return null; } /** * alias of get_nfilter_request_var() * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function gnrv(string $name, mixed $default = '') : mixed { return get_nfilter_request_var($name, $default); } /** * returns the current value of a PHP $_REQUEST variable, optionally * returning a default value if the request variable does not exist, * but without using any of the filtering checks. * * This should only be used when the request variable has already been * vetted via other filer request var functoins * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function get_nfilter_request_var(string $name, mixed $default = '') : mixed { global $_CACTI_REQUEST; if (isset($_CACTI_REQUEST[$name])) { return $_CACTI_REQUEST[$name]; } if (isset($_REQUEST[$name])) { return $_REQUEST[$name]; } else { return $default; } } /** * alias of get_nfilteR_request_var * * @deprecated v1.0 * * @param string $name the name of the request variable. this should be a * valid key in the $_REQUEST array * @param mixed $default the value to return if the specified name does not * exist in the $_REQUEST array * * @return mixed */ function get_request_var_post(string $name, mixed $default = '') : mixed { return get_nfilter_request_var($name, $default); } /** * validate, sanitize, and store request variables into the * custom $_CACTI_REQUEST and desired session variables for * Cacti filtering. * * @param array $filters - An array keyed with the filter methods. * @param string $sess_prefix - A string to use to prefix the session * variable. * * Valid filter include those from PHP filter_var() function syntax. * The format of the array is: * array( * 'varA' => array( * 'filter' => value, * 'pageset' => true, (optional) * 'session' => sess_name, (optional) * 'options' => mixed, * 'default' => value), * 'varB' => array( * 'filter' => value, * 'pageset' => true, (optional) * 'session' => sess_name, (optional) * 'options' => mixed, * 'default' => value), * ... * ); * * The 'pageset' attribute is optional, and when set, any changes * between what the page returns and what is set in the session * result in the page number being returned to 1. * * The 'session' attribute is also optional, and when set, all * changes will be stored to the session variable defined and * not to session_prefix . '_' . $variable as the default. This * allows for the concept of global session variables such as * 'sess_default_rows'. * * Validation 'filter' follow PHP conventions including: * * FILTER_VALIDATE_BOOLEAN - Validate that the variable is boolean * FILTER_VALIDATE_EMAIL - Validate that the variable is an email * FILTER_VALIDATE_FLOAT - Validate that the variable is a float * FILTER_VALIDATE_INT - Validate that the variable is an integer * FILTER_VALIDATE_IP - Validate that the variable is an IP address * FILTER_VALIDATE_MAC - Validate that the variable is a MAC Address * FILTER_VALIDATE_REGEXP - Validate against a REGEX * FILTER_VALIDATE_URL - Validate that the variable is a valid URL * FILTER_VALIDATE_IS_REGEX - Validate if a filter variable is a valid regex * FILTER_VALIDATE_IS_NUMERIC_ARRAY - Validate if a filter variable is a numeric array * FILTER_VALIDATE_IS_NUMERIC_LIST - Validate if a filter variable is a comma delimited list of numbers * * Sanitization 'filters' follow PHP conventions including: * * FILTER_SANITIZE_EMAIL - Sanitize the email address * FILTER_SANITIZE_ENCODED - URL-encode string * FILTER_SANITIZE_MAGIC_QUOTES - Apply addslashes() * FILTER_SANITIZE_NUMBER_FLOAT - Remove all non float values * FILTER_SANITIZE_NUMBER_INT - Remove everything non int * FILTER_SANITIZE_SPECIAL_CHARS - Escape special chars * FILTER_SANITIZE_FULL_SPECIAL_CHARS - Equivalent to htmlspecialchars adding ENT_QUOTES * FILTER_SANITIZE_STRING - Strip tags, optionally strip or encode special chars * FILTER_SANITIZE_URL - Remove all characters except letters, digits, etc. * FILTER_UNSAFE_RAW - Nothing and optional strip or encode * * @return void */ function validate_store_request_vars(array $filters, string $sess_prefix = '') : void { $changed = 0; $custom_error = ''; $session_variable = 'sess_fallback'; if (cacti_sizeof($filters)) { foreach ($filters as $variable => $options) { // Establish the session variable first if ($sess_prefix != '') { if (isset($options['session'])) { $session_variable = $options['session']; } elseif ($variable != 'rows') { $session_variable = $sess_prefix . '_' . $variable; } else { $session_variable = 'sess_default_rows'; } // Check for special cases 'clear' and 'reset' if (isset_request_var('clear')) { kill_session_var($session_variable); unset_request_var($variable); } elseif (isset_request_var('reset')) { kill_session_var($session_variable); } elseif (isset($options['pageset']) && $options['pageset'] == true) { $changed += check_changed($variable, (string) $session_variable); } } if (!isset_request_var($variable)) { if ($sess_prefix != '' && isset($_SESSION[$session_variable])) { set_request_var($variable, $_SESSION[$session_variable]); } elseif (isset($options['default'])) { set_request_var($variable, $options['default']); } else { cacti_log("WARNING: Filter Variable: $variable, Must have a default and none is set", false, 'FILTER'); cacti_debug_backtrace('FILTER'); set_request_var($variable, ''); } } else { if (get_nfilter_request_var($variable) == '0') { $value = '0'; } elseif (get_nfilter_request_var($variable) == 'undefined') { if (isset($options['default'])) { $value = $options['default']; } else { $value = ''; } } elseif (isempty_request_var($variable)) { $value = ''; } elseif ($options['filter'] == FILTER_VALIDATE_IS_REGEX) { if (is_base64_encoded($_REQUEST[$variable])) { $_REQUEST[$variable] = mb_convert_encoding(base64_decode($_REQUEST[$variable], true), 'UTF-8'); } $valid = validate_is_regex($_REQUEST[$variable]); if ($valid === true) { $value = $_REQUEST[$variable]; } else { $value = false; $custom_error = $valid; } } elseif ($options['filter'] == FILTER_VALIDATE_IS_NUMERIC_ARRAY) { $valid = true; if (is_array($_REQUEST[$variable])) { foreach ($_REQUEST[$variable] as $number) { if (!is_numeric($number)) { $valid = false; break; } } } else { $valid = false; } if ($valid == true) { $value = $_REQUEST[$variable]; } else { $value = false; } } elseif ($options['filter'] == FILTER_VALIDATE_IS_NUMERIC_LIST) { $valid = true; $values = preg_split('/,/', $_REQUEST[$variable], -1, PREG_SPLIT_NO_EMPTY); foreach (($values ?: []) as $number) { if (!is_numeric($number)) { $valid = false; break; } } if ($valid == true) { $value = $_REQUEST[$variable]; } else { $value = false; } } elseif (!isset($options['options'])) { $value = filter_var($_REQUEST[$variable], $options['filter']); } else { // Handle FILTER_VALIDATE_REGEXP specially to ensure proper delimiters if ($options['filter'] == FILTER_VALIDATE_REGEXP && isset($options['options']['regexp'])) { $regex = $options['options']['regexp']; // Only add delimiters if they're not already present if (!preg_match('/^\/.*\/[imsuxADJUX]*$/', $regex)) { $options['options']['regexp'] = '/' . $regex . '/'; } } // Special handling for graph_template_id to allow -1 if ($variable === 'graph_template_id' && get_nfilter_request_var($variable) == '-1') { $value = '-1'; } else { $value = filter_var($_REQUEST[$variable], $options['filter'], $options['options']); } } if ($value === false) { if ($options['filter'] == FILTER_VALIDATE_IS_REGEX) { raise_message('custom', __('The regular expression "%s" is not valid. Error is %s', htmle((string) get_nfilter_request_var($variable)), htmle((string) $custom_error)), MESSAGE_LEVEL_ERROR); set_request_var($variable, ''); } else { die_html_input_error($variable, get_nfilter_request_var($variable), htmle((string) $custom_error)); } } else { set_request_var($variable, $value); } } if ($sess_prefix != '') { if (isset_request_var($variable)) { $_SESSION[$session_variable] = get_request_var($variable); } elseif (isset($_SESSION[$session_variable])) { set_request_var($variable, $_SESSION[$session_variable]); } } } update_order_string(); } if ($changed) { set_request_var('page', 1); set_request_var('changed', 1); $_SESSION[$sess_prefix . '_page'] = 1; } elseif (!isset_request_var('page') && isset($_SESSION[$sess_prefix . '_page'])) { set_request_var('page', $_SESSION[$sess_prefix . '_page']); } } /** * Creates a sort string for standard Cacti tables * * This function manages the sorting order for data displayed on a page. It can update the order string * in place or based on request variables. The function handles sorting by different columns, including * special handling for IP addresses. * * @param bool $inplace If true, updates the order string in place using the current session data. * * @return void */ function update_order_string(bool $inplace = false) : void { $page = get_order_string_page(false); $order = ''; if (!str_contains(get_request_var('sort_column'), '(') && !str_contains(get_request_var('sort_column'), '`')) { $del = '`'; } else { $del = ''; } $database = get_mysql_info(); $natural = false; if ($database['database'] == 'MariaDB') { if (cacti_version_compare($database['version'], '10.7', '>')) { $natural = true; } } if ($inplace) { $_SESSION['sort_string'][$page] = 'ORDER BY '; foreach ($_SESSION['sort_data'][$page] as $column => $direction) { /* Re-validate keys at read time; write-time sanitization may have * been bypassed by older session data stored before this fix. */ $column = sanitize_sql_column($column, ''); if ($column === '') { continue; } $direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC'; if ($column == 'ip' || $column == 'ip_address') { $order .= ($order != '' ? ', ' : '') . 'INET_ATON(' . $column . ') ' . $direction; } elseif ($column == 'hostname' && $natural) { $order .= ($order != '' ? ', ' : '') . 'NATURAL_SORT_KEY(' . $column . ') ' . $direction; } else { $order .= ($order != '' ? ', ' : '') . $column . ' ' . $direction; } } $_SESSION['sort_string'][$page] .= $order; } else { if (isset_request_var('clear')) { unset($_SESSION['sort_data'][$page]); unset($_SESSION['sort_string'][$page]); } elseif (isset_request_var('add') && get_nfilter_request_var('add') == 'reset') { unset($_SESSION['sort_data'][$page]); unset($_SESSION['sort_string'][$page]); $safe_direction = strtoupper(get_request_var('sort_direction')) === 'DESC' ? 'DESC' : 'ASC'; $sort_column = sanitize_sql_column(get_request_var('sort_column'), ''); if ($sort_column === '') { return; } $_SESSION['sort_data'][$page][$sort_column] = $safe_direction; $column = $sort_column; $direction = $safe_direction; if (!str_contains($sort_column, '(') && !str_contains($sort_column, '`')) { $del = '`'; } else { $del = ''; } if ($column == 'ip' || $column == 'ip_address') { $_SESSION['sort_string'][$page] = 'ORDER BY INET_ATON(' . $column . ') ' . $direction; } elseif ($column == 'hostname' && $natural) { $_SESSION['sort_string'][$page] = 'ORDER BY NATURAL_SORT_KEY(' . $del . implode($del . '.' . $del, explode('.', $sort_column)) . $del . ') ' . $direction; } else { $_SESSION['sort_string'][$page] = 'ORDER BY ' . $del . implode($del . '.' . $del, explode('.', $sort_column)) . $del . ' ' . $direction; } } elseif (isset_request_var('sort_column')) { if (isset_request_var('reset')) { unset($_SESSION['sort_data'][$page]); unset($_SESSION['sort_string'][$page]); } $sort_column = sanitize_sql_column(get_request_var('sort_column'), ''); if ($sort_column === '') { return; } $_SESSION['sort_data'][$page][$sort_column] = strtoupper(get_nfilter_request_var('sort_direction')) === 'DESC' ? 'DESC' : 'ASC'; $_SESSION['sort_string'][$page] = 'ORDER BY '; foreach ($_SESSION['sort_data'][$page] as $column => $direction) { if (!str_contains($column, '(') && !str_contains($column, '`')) { $del = '`'; } else { $del = ''; break; } } foreach ($_SESSION['sort_data'][$page] as $column => $direction) { $column = sanitize_sql_column($column, ''); if ($column === '') { continue; } $direction = strtoupper((string)$direction) === 'DESC' ? 'DESC' : 'ASC'; if ($column == 'ip' || $column == 'ip_address') { $order .= ($order != '' ? ', ' : '') . 'INET_ATON(' . $column . ') ' . $direction; } elseif ($column == 'hostname' && $natural) { $order .= ($order != '' ? ', ' : '') . 'NATURAL_SORT_KEY(' . $del . implode($del . '.' . $del, explode('.', $column)) . $del . ') ' . $direction; } else { $order .= ($order != '' ? ', ' : '') . $del . implode($del . '.' . $del, explode('.', $column)) . $del . ' ' . $direction; } } $_SESSION['sort_string'][$page] .= $order; } else { unset($_SESSION['sort_data'][$page]); unset($_SESSION['sort_string'][$page]); } } } /** * Generates an SQL ORDER BY clause based on the current sorting preferences. * * This function constructs an ORDER BY clause using the sort column and sort direction * specified in the request variables. It also ensures that the column name is properly * delimited to prevent SQL injection. * * @return string The generated ORDER BY clause. */ function get_order_string() : string { $page = get_order_string_page(true); if (isset($_SESSION['sort_string'][$page])) { return $_SESSION['sort_string'][$page]; } /* Allowlist: identifiers are word chars and dots only; backtick in * the payload would escape the quoting even with $del='`'. */ $sort_column = sanitize_sql_column(get_request_var('sort_column'), ''); if ($sort_column === '') { return ''; } $sort_dir = strtoupper(get_request_var('sort_direction')) === 'DESC' ? 'DESC' : 'ASC'; if (!str_contains($sort_column, '(') && !str_contains($sort_column, '`')) { $del = '`'; } else { $del = ''; } return 'ORDER BY ' . $del . implode($del . '.' . $del, explode('.', $sort_column)) . $del . ' ' . $sort_dir; } /** * Whitelist of the dsstats measure columns that get_dsstats_order_string() * is allowed to sort by. This is the single source of truth for both the * Assert\Choice constraint's `choices` list below and the fallback default, * so a new measure only needs to be added in one place. * * Kept as plain const arrays rather than a backed enum so this file stays * parseable on PHP 8.0 (see .github/workflows/rocky-linux.yml, which lints * against Rocky Linux 9's stock php-cli package). */ const DSSTATS_MEASURES = ['average', 'peak', 'p25n', 'p50n', 'p75n', 'p90n', 'p95n', 'sum']; const DSSTATS_MEASURE_DEFAULT = 'average'; /** * Builds the validated ORDER BY clause used by get_allowed_graphs() when * sorting graphs by a dsstats measure (see the 'rs' derived table it joins * in). The measure and order values come from unauthenticated request * variables ('measure', 'graph_order'), so they are checked against the * actual data_source_stats_* columns and asc/desc before being concatenated * into SQL, the same way get_order_string() checks sort_column/sort_direction. * * @param array $sql_order Expects 'measure' and 'order' keys; see * get_allowed_graphs() for the full array shape. * * @return string The 'ORDER BY rs. ' clause. */ function get_dsstats_order_string(array $sql_order) : string { $measure = isset($sql_order['measure']) && is_scalar($sql_order['measure']) && CactiValidator::isValid($sql_order['measure'], [new Assert\Choice(choices: DSSTATS_MEASURES)]) ? $sql_order['measure'] : DSSTATS_MEASURE_DEFAULT; $order = isset($sql_order['order']) && is_scalar($sql_order['order']) ? strtoupper((string) $sql_order['order']) : ''; $direction = CactiValidator::isValid($order, [new Assert\Choice(choices: ['ASC', 'DESC'])]) ? $order : 'ASC'; return 'ORDER BY rs.' . $measure . ' ' . $direction; } /** * Removes a specified column from the order string in the session data. * * This function retrieves the current page's order string and checks if the specified * column exists in the session's sort data for that page. If the column is found, it * removes the column from the sort data and updates the order string. * * @param string $column The name of the column to be removed from the order string. * * @return void */ function remove_column_from_order_string(string $column) : void { $page = get_order_string_page(false); if (isset($_SESSION['sort_data'][$page][$column])) { unset($_SESSION['sort_data'][$page][$column]); update_order_string(true); } } /** * Generates a unique order string for the current page. * * This function creates a unique identifier for the current page by combining * a static page count, the current page name, and optional request variables * such as 'action' and 'tab'. The page count is incremented with each call to * ensure uniqueness. * * @param bool $increment Increment the page counter if true * * @return string A unique order string for the current page. */ function get_order_string_page(bool $increment = true) : string { static $page_count = 0; $page = $page_count . '_' . str_replace('.php', '', get_current_page()); if (isset_request_var('action')) { $page .= '_' . get_nfilter_request_var('action'); } if (isset_request_var('tab')) { $page .= '_' . get_nfilter_request_var('tab'); } if ($increment == true) { $page_count++; } return $page; } /** * Validate the redirect url provider by the HTTP_REFERER from PHP * * @param string $url The regular expression to validate. * @param string $default The URL to travel to upon failure * * @return string The validated URL, or the provided $default if invalid */ function validate_redirect_url($url = '', $default = 'index.php') { if ($url === '') { return $default; } $url = trim($url); // Decode the url to make it readable if encoded if (is_urlencoded($url)) { $url = urldecode($url); } // reject URLs with protocol schemes (external redirects, javascript:, data:) $bad_strings = [ 'javascript:', 'data:', 'vbscript:', 'mailto:', 'file:' ]; foreach ($bad_strings as $bstring) { if (stripos($url, $bstring) !== false) { return $default; } } // reject protocol-relative URLs if (strpos($url, '//') === 0) { return $default; } // reject URLs with newlines (header injection) if (preg_match('/[\r\n]/', $url)) { return $default; } // reject path traversal sequences if (stripos($url, '..') !== false) { return $default; } // Prevent referring off site $ref_host = parse_url($url, PHP_URL_HOST); $srv_host = null; if (isset($_SERVER['SERVER_NAME']) && $_SERVER['SERVER_NAME'] != '') { $srv_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); } if ($ref_host === null || ($srv_host !== null && $ref_host === $srv_host)) { $ref_path = parse_url($url, PHP_URL_PATH) ?: ''; $ref_query = parse_url($url, PHP_URL_QUERY); $safe = sanitize_uri($ref_path . ($ref_query !== null ? '?' . $ref_query : '')); return $safe; } else { return $default; } } /** * Builds a forced-HTTPS redirect using a server-configured host name. * * @param string $server_name The web server's configured name, optionally * followed by ':port' (IPv6 literals must be * bracketed first, e.g. '[::1]:8080'). * @param string $request_uri The requested local path and query string. * @param string $default_path A local fallback when the request URI is invalid. * * @psalm-taint-escape header * * @return string A safe absolute HTTPS URL, or an empty string for an invalid host. */ function cacti_build_https_redirect_url(string $server_name, string $request_uri, string $default_path = '/') : string { $server_name = trim($server_name); if ($server_name === '') { return ''; } /* split an optional port off first; a bracketed IPv6 literal keeps its * embedded colons and is only split on a ']:port' suffix, so a bare, * unbracketed IPv6 address (multiple colons, no brackets) is never * mistaken for a host:port pair */ if ($server_name[0] === '[') { $close = strpos($server_name, ']'); if ($close === false) { return ''; } $host = substr($server_name, 1, $close - 1); $port = ''; if (isset($server_name[$close + 1])) { if ($server_name[$close + 1] !== ':') { return ''; } $port = substr($server_name, $close + 2); } } elseif (substr_count($server_name, ':') === 1) { [$host, $port] = explode(':', $server_name, 2); } else { $host = $server_name; $port = ''; } if ($port !== '' && (!ctype_digit($port) || (int) $port < 1 || (int) $port > 65535)) { return ''; } if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { $host = '[' . $host . ']'; } elseif (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) { return ''; } $path = validate_redirect_url($request_uri, $default_path); $path = '/' . ltrim($path, '/'); return 'https://' . $host . ($port !== '' ? ':' . $port : '') . $path; } /** * Resolves a trusted host for the force_https redirect from the admin-configured * base_url, in preference to the request's Host/SERVER_NAME. * * SERVER_NAME mirrors the client Host header under the common Apache default * UseCanonicalName Off, so validating its format alone (see * cacti_build_https_redirect_url()) still lets an attacker redirect to another * valid-looking hostname. base_url is the trusted source Cacti already uses for * absolute URLs, so it is preferred when configured. * * @return string A bare host, or 'host:port' when base_url configures a * non-default port, safe to pass to * cacti_build_https_redirect_url(), or '' when base_url is unset. */ function cacti_force_https_host() : string { $base = trim((string) read_config_option('base_url')); if ($base === '') { return ''; } /* base_url is stored in scheme-less form (e.g. 'monitor.example/cacti') * until the first linked graph report is rendered, see the same check in * lib/reports.php; parse_url() cannot extract a host without a scheme, so * add one for parsing purposes only. */ $parseable = (substr($base, 0, 4) === 'http') ? $base : 'http://' . $base; $host = parse_url($parseable, PHP_URL_HOST); $port = parse_url($parseable, PHP_URL_PORT); if (!is_string($host) || $host === '') { return ''; } /* bracket a literal IPv6 host before appending a port so the two remain * unambiguous when cacti_build_https_redirect_url() splits them back apart */ if (strpos($host, ':') !== false) { $host = '[' . $host . ']'; } return ($port !== null && $port !== false) ? $host . ':' . $port : $host; } /** * Validates if the given string is a valid regular expression. * * This function checks if the provided regular expression is valid and safe to use. * It prevents exploits by limiting the length of the regular expression to 50 bytes * and disallowing the use of the semicolon character. * * @param string $regex The regular expression to validate. * * @return bool|string Returns true if the regular expression is valid, otherwise returns an error message string. */ function validate_is_regex(string $regex) : bool|string { if ($regex == '') { return true; } /** * Prevent exploits from encoded Regular expressions that can cause * injections in MariaDB and MySQL. We do this by limiting the * length of the regular expression to 50 bytes or less. */ if (strlen($regex) > 50) { return __('Cacti regular expressions are limited to 50 characters only for security reasons.'); } if (str_contains($regex, ';')) { return __('Cacti regular expressions can not includes the semi-color character.'); } restore_error_handler(); $track_errors = ini_get('track_errors'); ini_set('track_errors', 1); if (@preg_match("'" . $regex . "'", '') !== false) { ini_set('track_errors', $track_errors); return true; } $last_error = error_get_last(); $php_error = trim(str_replace('preg_match():', '', $last_error['message'])); ini_set('track_errors', $track_errors); $errors = [ PREG_INTERNAL_ERROR => __('There was an internal error!'), PREG_BACKTRACK_LIMIT_ERROR => __('Backtrack limit was exhausted!'), PREG_RECURSION_LIMIT_ERROR => __('Recursion limit was exhausted!'), PREG_BAD_UTF8_ERROR => __('Bad UTF-8 error!'), PREG_BAD_UTF8_OFFSET_ERROR => __('Bad UTF-8 offset error!'), ]; $error = preg_last_error(); if (!defined('IN_CACTI_INSTALL')) { set_error_handler('CactiErrorHandler'); } if (empty($error)) { return $php_error; } else { return $errors[$error]; } } /** * Loads the current session value for a given request variable. * * This function checks if a request variable is set. If it is, the value is stored in the session. * If the request variable is not set but the session variable is, the session value is set as the request variable. * If neither is set, the request variable is set to a default value. * * @param string $request_var_name The name of the request variable to check. * @param string $session_var_name The name of the session variable to store the value. * @param mixed $default_value The default value to set if neither the request nor session variable is set. * * @return void */ function load_current_session_value(string $request_var_name, string $session_var_name, mixed $default_value) : void { if (isset_request_var($request_var_name)) { $_SESSION[$session_var_name] = get_request_var($request_var_name); } elseif (isset($_SESSION[$session_var_name])) { set_request_var($request_var_name, $_SESSION[$session_var_name]); } else { set_request_var($request_var_name, $default_value); } } /** * Get the colored device status as an HTML span element. * * This function returns an HTML span element with a class and text representing * the status of a device. The status can be 'Disabled', 'Down (Thold)', 'Down', * 'Recovering', 'Up', 'Error', or 'Unknown'. * * @param bool $disabled Indicates if the device is disabled. * @param int $status The current status of the device. * @param int $thold_failure_count Optional. The threshold failure count. Default is -1. * @param int $status_event_count Optional. The status event count. Default is -1. * * @return string The HTML span element with the appropriate class and status text. */ function get_colored_device_status(bool $disabled, int $status, int $thold_failure_count = -1, int $status_event_count = -1) : string { if ($disabled) { return "" . __('Disabled') . ''; } else { if ($status != HOST_RECOVERING && $thold_failure_count > 0) { if ($status_event_count >= $thold_failure_count) { return "" . __('Down (Thold)') . ''; } } return match ($status) { HOST_DOWN => "" . __('Down') . '', HOST_RECOVERING => "" . __('Recovering') . '', HOST_UP => "" . __('Up') . '', HOST_ERROR => "" . __('Error') . '', default => "" . __('Unknown') . '', }; } } /** * Given a device's status, return the colored text in HTML format suitable for display * * @param bool $disabled When true, the device is disabled, false is it is not * @param string $site_name The name of the site to display * * @return string Returns a string containing html that represents the site's current * status and name */ function get_colored_site_status(bool $disabled, string|null $site_name) : string { $class = ''; if ($disabled) { $class = 'deviceDown'; } return "" . __esc($site_name) . ''; } /** * Determine the correct graph start time selected using the timespan selector * * @return mixed The current graph start time if set and numeric, otherwise a default timespan value. */ function get_current_graph_start() : mixed { if (isset($_SESSION['sess_current_timespan_begin_now']) && is_numeric($_SESSION['sess_current_timespan_begin_now'])) { return $_SESSION['sess_current_timespan_begin_now']; } else { return '-' . DEFAULT_TIMESPAN; } } /** * Determine the correct graph end time selected using the timespan selector * * @return mixed The current graph end time if set and numeric, otherwise '0'. */ function get_current_graph_end() : mixed { if (isset($_SESSION['sess_current_timespan_end_now']) && is_numeric($_SESSION['sess_current_timespan_end_now'])) { return $_SESSION['sess_current_timespan_end_now']; } else { return '0'; } } /** * Generates an HTML tooltip element with the provided text. * * This function creates a tooltip element using a div with the class * "cactiTooltipHint" and a FontAwesome question-circle icon. The tooltip * text is included within a span element that is initially hidden. * * @param string $text The text to be displayed inside the tooltip. * * @return string The HTML string for the tooltip element if text is provided, * otherwise an empty string. */ function display_tooltip(string $text) : string { if ($text != '') { return '
' . $text . "
\n"; } else { return ''; } } /** * Generates a paginated list of links for navigating through pages. * * @param int $current_page The current page number. * @param int $pages_per_screen The number of pages to display in the pagination control. * @param int $rows_per_page The number of rows per page. * @param int $total_rows The total number of rows. * @param string $url The base URL for the pagination links. * @param string $page_var The query parameter name for the page number (default is 'page'). * @param string $return_to The ID of the HTML element to update with the new page content (default is ''). * * @return string The HTML for the pagination control. */ function get_page_list(int $current_page, int $pages_per_screen, int $rows_per_page, int $total_rows, string $url, string $page_var = 'page', string $return_to = '') : string { if (!preg_match('/^[A-Za-z_$][A-Za-z0-9_$]*$/', $page_var)) { $page_var = 'page'; } // By current design, $pages_per_screen means number of page no in mid of nav bar // when $total_pages is larger than $pages_per_screen + 2(first and last) // So actual $pages_per_screen should be $pages_per_screen+2 $pages_per_screen += 2; $url_page_select = "'; if ($return_to == '') { $return_to = 'main'; } $url_json = cacti_js_encode($url . $page_var); $return_to_json = cacti_js_encode($return_to); $url_page_select .= ""; return $url_page_select; }