mirror of
https://github.com/Cacti/cacti.git
synced 2026-11-03 02:27:13 +09:00
* security: consolidate verified GHSA fixes for develop (batch 4) Develop counterparts of the 1.2.x batch-4 fixes in #8144, re-verified against current develop HEAD. - GHSA-4rmr-wvjq-qxc2: include/auth.php Basic Auth and guest branches fall through to the per-page realm check instead of returning early. - GHSA-j67j-wpm4-9g3x: the pre-dispatch update_policy branch in user_admin.php and user_group_admin.php requires a CSRF-token POST. The denylist -> allowlist redesign is tracked for 1.3 in #8143. - GHSA-2p3x-f5jm-rg6p: the View-Graphs -> poller RCE chain is broken by the realm check (4rmr), the update_policy guard (j67j) and shell-free webhits (x5pw). - GHSA-x5pw-cg7j-mj4r: scripts/webhits.pl runs wc through a shell-free list-form open. - GHSA-9737-rqh3-h7fh: names interpolated into raise_message() now use __esc() across the UI; URL- and HTML-bearing messages are excluded. - GHSA-9x9h-2577-9w86: the colordropdown input is built as a node and the label is no longer round-tripped through parseHTML (the list item already used createTextNode on develop). - GHSA-h96j-8xhc-2p3w: remote_agent.php authorizes on $_SERVER['REMOTE_ADDR'], not get_client_addr(), which honours client-settable proxy headers. Regression guards under tests/Unit/Security/{Auth,Csrf,Xss,ShellInjection}. php -l, check-changelog.py and the sink/architectural baselines all pass. * chore: reference PR #8145 on the batch-4 1.3.0-dev CHANGELOG entries * security: resolve develop merge - keep id-only xmlerror unescaped (matches #8141 h745 test) * security(GHSA-j67j): use REQUEST_METHOD POST guard instead of raw \ (hardening-pattern-guard) * security(GHSA-9737): fix review findings - drop double-encoding in __esc, escape missed name sinks, correct plugins format-arg count
2020 lines
65 KiB
PHP
2020 lines
65 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
|
|
require_once __DIR__ . '/CactiValidator.php';
|
|
|
|
/**
|
|
* Replaces all variables contained in $form_array with their actual values
|
|
*
|
|
* This function recursively processes the form array and replaces placeholders
|
|
* with corresponding values from the provided arguments. It supports up to three
|
|
* levels of argument replacement (arg1, arg2, arg3).
|
|
*
|
|
* @param array $form_array The form array to process. This array is passed by reference.
|
|
* @param mixed $arg1 Represents the |arg1:| variable (see include/global_form.php for more details)
|
|
* @param mixed $arg2 Represents the |arg2:| variable (see include/global_form.php for more details)
|
|
* @param mixed $arg3 Represents the |arg3:| variable (see include/global_form.php for more details)
|
|
* @param mixed $arg4 Represents the |arg4:| variable (see include/global_form.php for more details)
|
|
*
|
|
* @return array The processed form array with injected variables.
|
|
*/
|
|
function inject_form_variables(array &$form_array, mixed $arg1 = [], mixed $arg2 = [], mixed $arg3 = [], mixed $arg4 = []) : array {
|
|
$check_fields = ['id', 'value', 'array', 'friendly_name', 'description', 'sql', 'sql_print', 'form_id', 'items', 'tree_id'];
|
|
|
|
// loop through each available field
|
|
if (cacti_sizeof($form_array)) {
|
|
foreach ($form_array as $field_name => $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 (`<tr>`) 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 "<tr class='$class selectable tableRow' id='$row_id'>\n";
|
|
} else {
|
|
print "<tr class='$class tableRow'>\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 "<tr class='$class selectable tableRow' id='$row_id'>\n";
|
|
} elseif (str_starts_with($row_id, 'row_')) {
|
|
print "<tr class='$class tableRow' id='$row_id'>\n";
|
|
} elseif ($row_id != '') {
|
|
print "<tr class='$class tableRow' id='$row_id'>\n";
|
|
} else {
|
|
print "<tr class='$class tableRow'>\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 = "<span class='cactiTooltipHint' style='padding:0px;margin:0px;' title='" . str_replace(['"', "'"], '', $title) . "'>" . $contents . '</span>';
|
|
} else {
|
|
$wrapper = $contents;
|
|
}
|
|
|
|
print "\t<td " . $output . '>' . $wrapper . "</td>\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 = "<span class='cactiTooltipHint' style='padding:0px;margin:0px;' title='" . str_replace(['"', "'"], '', $title) . "'>" . $contents . '</span>';
|
|
} else {
|
|
$wrapper = $contents;
|
|
}
|
|
|
|
print "\t<td " . $output . '>' . $wrapper . "</td>\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 string $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, string $id, bool $disabled = false, bool $checked = false) : void {
|
|
print "\t<td class='checkbox' style='width:1%;'>\n";
|
|
print "\t\t<input type='checkbox' title='" . htmle($title) . "' class='checkbox" . ($disabled ? ' disabled' : '') . "' " . ($disabled ? " disabled='disabled'" : '') . ($checked ? " checked='checked'" : '') . " id='chk_" . $id . "' name='chk_" . $id . "'><label class='formCheckboxLabel' for='chk_" . $id . "'></label>\n";
|
|
print "\t</td>\n";
|
|
}
|
|
|
|
/**
|
|
* Ends a table row that is started with form_alternate_row
|
|
*
|
|
* @return void
|
|
*/
|
|
function form_end_row() : void {
|
|
print "</tr>\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.<measure> <ASC|DESC>' 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 "<span class='deviceDisabled'>" . __('Disabled') . '</span>';
|
|
} else {
|
|
if ($status != HOST_RECOVERING && $thold_failure_count > 0) {
|
|
if ($status_event_count >= $thold_failure_count) {
|
|
return "<span class='deviceDown'>" . __('Down (Thold)') . '</span>';
|
|
}
|
|
}
|
|
|
|
return match ($status) {
|
|
HOST_DOWN => "<span class='deviceDown'>" . __('Down') . '</span>',
|
|
HOST_RECOVERING => "<span class='deviceRecovering'>" . __('Recovering') . '</span>',
|
|
HOST_UP => "<span class='deviceUp'>" . __('Up') . '</span>',
|
|
HOST_ERROR => "<span class='deviceError'>" . __('Error') . '</span>',
|
|
default => "<span class='deviceUnknown'>" . __('Unknown') . '</span>',
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 "<span class='$class'>" . __esc($site_name) . '</span>';
|
|
}
|
|
|
|
/**
|
|
* 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 '<div class="cactiTooltipHint ti ti-help"><span style="display:none;">' . $text . "</span></div>\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 = "<ul class='pagination'>";
|
|
|
|
if (str_contains($url, '?')) {
|
|
$url .= '&';
|
|
} else {
|
|
$url .= '?';
|
|
}
|
|
|
|
$url_ellipsis = '<li><span>...</span></li>';
|
|
|
|
if ($rows_per_page <= 0) {
|
|
$total_pages = 0;
|
|
} else {
|
|
$total_pages = ceil($total_rows / $rows_per_page);
|
|
}
|
|
|
|
$start_page = max(1, ($current_page - floor(($pages_per_screen - 1) / 2)));
|
|
$end_page = min($total_pages, ($current_page + floor(($pages_per_screen - 1) / 2)));
|
|
|
|
if ($total_pages <= $pages_per_screen) {
|
|
$start_page = 2;
|
|
$end_page = $total_pages - 1;
|
|
} else {
|
|
$start_page = max(2, ($current_page - floor(($pages_per_screen - 3) / 2)));
|
|
// When current_page > (pages_per_screen - 1) / 2
|
|
$end_page = min($total_pages - 1, ($current_page + floor(($pages_per_screen - 3) / 2)));
|
|
|
|
// adjust if we are close to the beginning of the page list
|
|
if ($current_page <= ceil(($pages_per_screen) / 2)) {
|
|
$end_page += ($pages_per_screen - $end_page - 1);
|
|
}
|
|
|
|
// adjust if we are close to the end of the page list
|
|
if (($total_pages - $current_page) < ceil(($pages_per_screen) / 2)) {
|
|
$start_page -= (($pages_per_screen - ($end_page - $start_page)) - 3);
|
|
}
|
|
|
|
// stay within limits
|
|
$start_page = max(2, $start_page);
|
|
$end_page = min($total_pages - 1, $end_page);
|
|
}
|
|
|
|
if ($total_pages > 0) {
|
|
if ($current_page == 1) {
|
|
$url_page_select .= "<li><a href='#' class='active' onClick='goto$page_var(1);return false'>1</a></li>";
|
|
} else {
|
|
$url_page_select .= "<li><a href='#' onClick='goto$page_var(1);return false'>1</a></li>";
|
|
}
|
|
}
|
|
|
|
for ($page_number = 0; (($page_number + $start_page) <= $end_page); $page_number++) {
|
|
$page = $page_number + $start_page;
|
|
|
|
if ($page_number < $pages_per_screen) {
|
|
if ($page_number == 0 && $start_page > 2) {
|
|
$url_page_select .= $url_ellipsis;
|
|
}
|
|
|
|
if ($current_page == $page) {
|
|
$url_page_select .= "<li><a href='#' class='active' onClick='goto$page_var($page);return false'>$page</a></li>";
|
|
} else {
|
|
$url_page_select .= "<li><a href='#' onClick='goto$page_var($page);return false'>$page</a></li>";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($total_pages - 1 > $end_page) {
|
|
$url_page_select .= $url_ellipsis;
|
|
}
|
|
|
|
if ($total_pages > 1) {
|
|
if ($current_page == $total_pages) {
|
|
$url_page_select .= "<li><a href='#' class='active' onClick='goto$page_var($total_pages);return false'>$total_pages</a></li>";
|
|
} else {
|
|
$url_page_select .= "<li><a href='#' onClick='goto$page_var($total_pages);return false'>$total_pages</a></li>";
|
|
}
|
|
}
|
|
|
|
$url_page_select .= '</ul>';
|
|
|
|
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 .= "<script type='text/javascript'>
|
|
function goto$page_var(pageNo) {
|
|
if (typeof url_graph === 'function') {
|
|
var url_add=url_graph('')
|
|
} else {
|
|
var url_add='';
|
|
};
|
|
|
|
strURL = $url_json + '=' + pageNo + url_add;
|
|
|
|
loadUrl({
|
|
url: strURL,
|
|
elementId: $return_to_json,
|
|
});
|
|
}</script>";
|
|
|
|
return $url_page_select;
|
|
}
|