mirror of
https://github.com/Cacti/plugin_monitor.git
synced 2026-10-25 04:27:36 +09:00
* fix: require POST for state-changing monitor actions csrf-magic only validates POST, so save, saveDb, remove, the mute toggles and the card-order save now fall through to the normal page draw when sent by GET, and monitor.js sends remove and the mute toggles as POST with the CSRF token. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: save dashboards only as their owner saveSettings() took the owner from an unvalidated user request variable and let sql_save() overwrite any dashboard id. It now always uses the session user and refuses to update a dashboard the user does not own, matching removeDashboard() and saveCardOrder(). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
528 lines
15 KiB
PHP
528 lines
15 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. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Test bootstrap.
|
|
*
|
|
* Monitor's sources expect to be included by Cacti, which has already
|
|
* defined the db_*, request-variable, and logging helpers as plain global
|
|
* functions. Nothing here talks to a database or a network: each Cacti
|
|
* function is declared as a stub that records the call in
|
|
* $GLOBALS['__test_db_calls'] and hands back a safe default.
|
|
*
|
|
* The CI workflow checks out a pinned Cacti release next to this plugin so
|
|
* Pest runs against Cacti's own Composer-managed vendor tree (Pest/PHPUnit)
|
|
* instead of a vendor tree local to this plugin. The version check below
|
|
* makes sure that checkout actually matches what tests/.cacti-version
|
|
* expects before any plugin source is loaded.
|
|
*
|
|
* Guarding every declaration with function_exists() keeps this file usable
|
|
* if a future integration suite loads real Cacti first.
|
|
*/
|
|
|
|
$cacti_root = dirname(__DIR__, 3);
|
|
$autoload = $cacti_root . '/include/vendor/autoload.php';
|
|
$version = $cacti_root . '/include/cacti_version';
|
|
$expected = __DIR__ . '/.cacti-version';
|
|
|
|
if (!is_readable($autoload)) {
|
|
throw new RuntimeException("Cacti Composer autoloader is not readable: $autoload");
|
|
}
|
|
|
|
if (!is_readable($version)) {
|
|
throw new RuntimeException("Cacti version file is not readable: $version");
|
|
}
|
|
|
|
if (!is_readable($expected)) {
|
|
throw new RuntimeException("Expected Cacti version file is not readable: $expected");
|
|
}
|
|
|
|
$cacti_version = trim((string) file_get_contents($version));
|
|
$expected_version = trim((string) file_get_contents($expected));
|
|
|
|
if ($cacti_version === '') {
|
|
throw new RuntimeException("Cacti version file is empty: $version");
|
|
}
|
|
|
|
if ($expected_version === '') {
|
|
throw new RuntimeException("Expected Cacti version file is empty: $expected");
|
|
}
|
|
|
|
// The CI workflow tracks a moving branch (1.2.x or develop) rather than a pinned release, so any actual version is accepted.
|
|
if (!in_array($expected_version, array('1.2.x', 'develop'), true) && $cacti_version !== $expected_version) {
|
|
throw new RuntimeException("Expected Cacti $expected_version, found $cacti_version in $version");
|
|
}
|
|
|
|
require_once $autoload;
|
|
|
|
/*
|
|
* base_path has to point at the Cacti root two levels above this plugin:
|
|
* monitor's source files build include paths from it at runtime.
|
|
*/
|
|
$GLOBALS['config'] = array(
|
|
'base_path' => $cacti_root,
|
|
'url_path' => '/cacti/',
|
|
'cacti_version' => $cacti_version,
|
|
'cacti_server_os' => 'unix',
|
|
);
|
|
|
|
$GLOBALS['__test_db_calls'] = array();
|
|
$GLOBALS['__test_request'] = array();
|
|
$GLOBALS['__test_db_fixtures'] = array();
|
|
|
|
if (!function_exists('get_md5_include_js')) {
|
|
function get_md5_include_js($path, $async = false) {
|
|
return "<script type='text/javascript' src='" . $path . "'></script>";
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_md5_include_css')) {
|
|
function get_md5_include_css($path) {
|
|
return "<link href='" . $path . "' type='text/css' rel='stylesheet'>";
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_selected_theme')) {
|
|
function get_selected_theme() {
|
|
return $GLOBALS['__test_selected_theme'] ?? 'modern';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('monitor_test_mock_db')) {
|
|
function monitor_test_mock_db($fn, $match, $result) {
|
|
$GLOBALS['__test_db_fixtures'][] = array('fn' => $fn, 'match' => $match, 'result' => $result);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('monitor_test_reset_db_mocks')) {
|
|
function monitor_test_reset_db_mocks() {
|
|
$GLOBALS['__test_db_fixtures'] = array();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('monitor_test_db_result')) {
|
|
function monitor_test_db_result($fn, $sql, $params, $default) {
|
|
foreach (array_reverse($GLOBALS['__test_db_fixtures']) as $fixture) {
|
|
if ($fixture['fn'] !== $fn) {
|
|
continue;
|
|
}
|
|
|
|
$match = $fixture['match'];
|
|
|
|
if (is_callable($match)) {
|
|
if (!$match($sql, $params)) {
|
|
continue;
|
|
}
|
|
} elseif (strpos($sql, $match) === false) {
|
|
continue;
|
|
}
|
|
|
|
$result = $fixture['result'];
|
|
|
|
return is_callable($result) ? $result($sql, $params) : $result;
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_execute')) {
|
|
function db_execute($sql) {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'db_execute', 'sql' => $sql, 'params' => array());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_execute_prepared')) {
|
|
function db_execute_prepared($sql, $params = array()) {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'db_execute_prepared', 'sql' => $sql, 'params' => $params);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_assoc')) {
|
|
function db_fetch_assoc($sql) {
|
|
return monitor_test_db_result('db_fetch_assoc', $sql, array(), array());
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_assoc_prepared')) {
|
|
function db_fetch_assoc_prepared($sql, $params = array()) {
|
|
return monitor_test_db_result('db_fetch_assoc_prepared', $sql, $params, array());
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_row')) {
|
|
function db_fetch_row($sql) {
|
|
return array();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_row_prepared')) {
|
|
function db_fetch_row_prepared($sql, $params = array()) {
|
|
return array();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_cell')) {
|
|
function db_fetch_cell($sql) {
|
|
return monitor_test_db_result('db_fetch_cell', $sql, array(), '');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_cell_prepared')) {
|
|
function db_fetch_cell_prepared($sql, $params = array()) {
|
|
return monitor_test_db_result('db_fetch_cell_prepared', $sql, $params, '');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_index_exists')) {
|
|
function db_index_exists($table, $index) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_column_exists')) {
|
|
function db_column_exists($table, $column) {
|
|
return monitor_test_db_result('db_column_exists', $table . '.' . $column, array(), false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_is_enabled')) {
|
|
function api_plugin_is_enabled($plugin) {
|
|
return monitor_test_db_result('api_plugin_is_enabled', $plugin, array(), false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_user_realm_auth')) {
|
|
function api_plugin_user_realm_auth($file) {
|
|
return monitor_test_db_result('api_plugin_user_realm_auth', $file, array(), false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('array_rekey')) {
|
|
function array_rekey($array, $key, $value_array) {
|
|
$return_array = array();
|
|
|
|
if (!empty($array)) {
|
|
foreach ($array as $item) {
|
|
$item_key = $item[$key];
|
|
|
|
if (is_array($value_array)) {
|
|
foreach ($value_array as $value) {
|
|
$return_array[$item_key][$value] = $item[$value];
|
|
}
|
|
} else {
|
|
$return_array[$item_key] = $item[$value_array];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $return_array;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_table_exists')) {
|
|
function db_table_exists($table) {
|
|
return monitor_test_db_result('db_table_exists', $table, array(), false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_db_add_column')) {
|
|
function api_plugin_db_add_column($plugin, $table, $data) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_add_column')) {
|
|
function db_add_column($table, $data) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_remove_column')) {
|
|
function db_remove_column($table, $column) {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'db_remove_column', 'table' => $table, 'column' => $column, 'sql' => $table);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_db_table_create')) {
|
|
function api_plugin_db_table_create($plugin, $table, $data) {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'api_plugin_db_table_create', 'plugin' => $plugin, 'table' => $table, 'data' => $data, 'sql' => $table);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_update_table')) {
|
|
function db_update_table($table, $data) {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'db_update_table', 'table' => $table, 'data' => $data, 'sql' => $table);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$GLOBALS['__test_registered_hooks'] = array();
|
|
|
|
if (!function_exists('api_plugin_register_hook')) {
|
|
function api_plugin_register_hook($plugin, $hook, $function, $file, $subtype = '') {
|
|
$GLOBALS['__test_registered_hooks'][] = array(
|
|
'name' => $plugin,
|
|
'hook' => $hook,
|
|
'function' => $function,
|
|
'file' => $file,
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$GLOBALS['__test_registered_realms'] = array();
|
|
|
|
if (!function_exists('api_plugin_register_realm')) {
|
|
function api_plugin_register_realm($plugin, $file, $description, $enabled) {
|
|
$GLOBALS['__test_registered_realms'][] = array(
|
|
'name' => $plugin,
|
|
'file' => $file,
|
|
'description' => $description,
|
|
'enabled' => $enabled,
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$GLOBALS['__test_exec_calls'] = array();
|
|
|
|
if (!function_exists('exec_background')) {
|
|
function exec_background($command, $args = '') {
|
|
$GLOBALS['__test_exec_calls'][] = array('command' => $command, 'args' => $args);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('read_config_option')) {
|
|
function read_config_option($name, $force = false) {
|
|
return isset($GLOBALS['__test_config_options'][$name]) ? $GLOBALS['__test_config_options'][$name] : '';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('set_config_option')) {
|
|
function set_config_option($name, $value) {
|
|
$GLOBALS['__test_config_options'][$name] = $value;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('read_user_setting')) {
|
|
function read_user_setting($name, $default = '', $user = 0) {
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('set_user_setting')) {
|
|
function set_user_setting($name, $value, $user = 0) {
|
|
}
|
|
}
|
|
|
|
if (!function_exists('html_escape')) {
|
|
function html_escape($string) {
|
|
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('__')) {
|
|
function __($text, $domain = '') {
|
|
return $text;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('__esc')) {
|
|
function __esc($text, $domain = '') {
|
|
return htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_log')) {
|
|
function cacti_log($message, $also_print = false, $log_type = '', $level = 0) {
|
|
$args = func_get_args();
|
|
$GLOBALS["__test_cacti_log"][] = isset($args[0]) ? (string) $args[0] : "";
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_sizeof')) {
|
|
function cacti_sizeof($array) {
|
|
return is_array($array) ? count($array) : 0;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('is_realm_allowed')) {
|
|
function is_realm_allowed($realm) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('raise_message')) {
|
|
function raise_message($id, $text = '', $level = 0) {
|
|
$GLOBALS['__test_messages'][] = array('id' => $id, 'text' => $text, 'level' => $level);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_request_var')) {
|
|
function get_request_var($name) {
|
|
return $GLOBALS['__test_request'][$name] ?? '';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('isset_request_var')) {
|
|
function isset_request_var($name) {
|
|
return isset($GLOBALS['__test_request'][$name]);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('validate_store_request_vars')) {
|
|
function validate_store_request_vars($filters, $sess_prefix = '') {
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_nfilter_request_var')) {
|
|
function get_nfilter_request_var($name) {
|
|
return get_request_var($name);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_filter_request_var')) {
|
|
function get_filter_request_var($name) {
|
|
return get_request_var($name);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('set_request_var')) {
|
|
function set_request_var($name, $value) {
|
|
$GLOBALS['__test_request'][$name] = $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the stubbed request vars a test needs, resetting the rest.
|
|
*
|
|
* @param array $vars
|
|
*
|
|
* @return void
|
|
*/
|
|
function test_set_request(array $vars): void {
|
|
$GLOBALS['__test_request'] = $vars;
|
|
}
|
|
|
|
// validateRequestVars() is not stubbed here: includes/controller.php declares
|
|
// it unconditionally, so a global stub would fatal with a redeclare error
|
|
// once a test loads that file via loadPluginSource()/monitor_test_load().
|
|
|
|
if (!function_exists('form_input_validate')) {
|
|
function form_input_validate($value, $name, $regex, $optional, $error) {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('is_error_message')) {
|
|
function is_error_message() {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('sql_save')) {
|
|
function sql_save($array, $table, $key = 'id') {
|
|
$GLOBALS['__test_db_calls'][] = array('fn' => 'sql_save', 'table' => $table, 'data' => $array);
|
|
|
|
return isset($array['id']) ? $array['id'] : 1;
|
|
}
|
|
}
|
|
|
|
if (!defined('CACTI_PATH_BASE')) {
|
|
define('CACTI_PATH_BASE', $GLOBALS['config']['base_path']);
|
|
}
|
|
|
|
if (!defined('POLLER_VERBOSITY_LOW')) {
|
|
define('POLLER_VERBOSITY_LOW', 2);
|
|
}
|
|
|
|
if (!defined('POLLER_VERBOSITY_MEDIUM')) {
|
|
define('POLLER_VERBOSITY_MEDIUM', 3);
|
|
}
|
|
|
|
if (!defined('POLLER_VERBOSITY_DEBUG')) {
|
|
define('POLLER_VERBOSITY_DEBUG', 5);
|
|
}
|
|
|
|
if (!defined('POLLER_VERBOSITY_NONE')) {
|
|
define('POLLER_VERBOSITY_NONE', 6);
|
|
}
|
|
|
|
if (!defined('FILTER_VALIDATE_IS_REGEX')) {
|
|
define('FILTER_VALIDATE_IS_REGEX', 99999);
|
|
}
|
|
|
|
if (!defined('MESSAGE_LEVEL_INFO')) {
|
|
define('MESSAGE_LEVEL_INFO', 1);
|
|
}
|
|
|
|
if (!defined('MESSAGE_LEVEL_ERROR')) {
|
|
define('MESSAGE_LEVEL_ERROR', 3);
|
|
}
|
|
|
|
if (!function_exists('plugin_test_read_source')) {
|
|
function plugin_test_read_source($relative_file) {
|
|
$path = realpath(__DIR__ . '/../' . $relative_file);
|
|
if ($path === false) {
|
|
throw new RuntimeException("Unable to resolve required file: {$relative_file}");
|
|
}
|
|
|
|
$contents = file_get_contents($path);
|
|
if ($contents === false) {
|
|
throw new RuntimeException("Unable to read required file: {$relative_file}");
|
|
}
|
|
|
|
return $contents;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a plugin source file at global scope.
|
|
*
|
|
* Some plugin files define data as file-scope variables that the rest of
|
|
* the plugin reads as globals, and they read $config while doing so.
|
|
* Requiring them from inside a method would make both halves of that
|
|
* method-local, so the require happens here and any variable the file
|
|
* introduced is published to $GLOBALS.
|
|
*
|
|
* @param string $path Absolute path to the file.
|
|
*
|
|
* @return void
|
|
*/
|
|
function monitor_test_load($path) {
|
|
global $config;
|
|
|
|
$__before = get_defined_vars();
|
|
|
|
require_once $path;
|
|
|
|
foreach (get_defined_vars() as $__name => $__value) {
|
|
if (!array_key_exists($__name, $__before) && strncmp($__name, '__', 2) !== 0) {
|
|
$GLOBALS[$__name] = $__value;
|
|
}
|
|
}
|
|
}
|