mirror of
https://github.com/Cacti/plugin_thold.git
synced 2026-10-23 03:27:05 +09:00
* INFO: set compat to 1.2.32 * docs: update Copilot instructions Cacti baseline to 1.2.32 (matches INFO compat) * INFO: back off compat to 1.2.29 * Add manifest.json + upgrade-time file pruning * Prune phpunit.xml on upgrade and ignore markdown-lint configs phpunit.xml ships in the repo for CI but is a dev-only test artifact, so it is removed from manifest.json 'expected', pruned from installs on upgrade (like tests/), and excluded from the manifest drift check. The retired markdown-lint configs (.mdlrc, .md_style.rb) are deleted, and .md* is now ignored like .git*: protected from pruning and excluded from drift if it reappears. * Move thold_functions.php and thold_webapi.php into includes/; flatten css The main threshold library becomes includes/functions.php and the web API helper becomes includes/webapi.php; every caller (setup.php, includes/*, the CLI/poller/notify entry points and the test suite) loads them from includes/. Each per-theme stylesheet css/<theme>/main.css becomes css/<theme>.css so users can drop their own css/<theme>.css into the css base, and the page_head hook looks there. Old paths are tombstoned so existing installs drop the stale copies on upgrade. * Regenerate cacti.pot for the includes/ file relocations Moving the plugin's library files into includes/ changed the source-reference lines in the translation template; regenerate it so the 'Verify translation template' CI check passes. * Harden the hook-include test and make RPN integer ops explicit The HookFunctionsIncludeTest invokes real hooks with placeholder input purely to execute their relocated include_once lines; it now swallows engine notices/warnings/deprecations during those calls (and restores the error handler and config afterward) so it cannot trip the suite's failOnDeprecation or leak state into other tests. The RPN % and ^ operators now cast their operands to int explicitly, matching their long-standing integer-truncation behaviour while silencing the implicit float-to-int conversion deprecation. * Harden prune against tampered-manifest path traversal The upgrade-time prune now rejects any tombstone containing '.'/'..' segments (which could escape the plugin directory or resolve to its root) and treats ancestors of whitelist entries as protected, so a tombstone on a parent directory can no longer delete a whitelisted file beneath it. * docs: update and align the Project Structure block Point the structure tree at the relocated files (libraries now under includes/, data files under docs/, stylesheets under css/) and align the trailing '# ...' comments to a single column so they no longer drift right. * docs/tests: standardize test header and correct manifest-contract notes - Use the full license header (from the plugin's own setup.php) in tests/Unit/PruneFilesTest.php instead of the abbreviated copyright banner. - Document that the manifest drift check and the upgrade-time prune also handle phpunit.xml and .md* files, matching the implemented behavior. * refactor: rename prune helpers to the plugin-slug function prefix The repository naming contract reserves the plugin_<name>_ prefix for plugin lifecycle / hook-registration functions; all other functions use the plain <name>_ prefix. Rename the internal upgrade helpers accordingly: plugin_<name>_prune_files() -> <name>_prune_files() plugin_<name>_rmtree() -> <name>_rmtree() The call site, unit tests, and the copilot-instructions.md references are updated to match. No behavioral change. * chore: set the Cacti compatibility floor to 1.2.29 Align the compat metadata (and the contributor-guide references) with the team decision to standardize the minimum supported Cacti version at 1.2.29; the previously proposed 1.2.32 floor was not adopted. --------- Co-authored-by: Copilot <copilot@users.noreply.github.com>
771 lines
23 KiB
PHP
771 lines
23 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.
|
|
*
|
|
* thold'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 shim over CactiStubs, which records the call and hands back
|
|
* whatever the test programmed.
|
|
*
|
|
* 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, ['1.2.x', 'develop'], true) && $cacti_version !== $expected_version) {
|
|
throw new RuntimeException("Expected Cacti $expected_version, found $cacti_version in $version");
|
|
}
|
|
|
|
require_once $autoload;
|
|
require_once __DIR__ . '/Helpers/CactiStubs.php';
|
|
require_once __DIR__ . '/TestCase.php';
|
|
require_once __DIR__ . '/Helpers/ThresholdOutcome.php';
|
|
require_once __DIR__ . '/Helpers/ThresholdScenario.php';
|
|
|
|
if (!defined('POLLER_VERBOSITY_MEDIUM')) {
|
|
define('POLLER_VERBOSITY_MEDIUM', 3);
|
|
}
|
|
|
|
/*
|
|
* base_path has to point at the Cacti root two levels above this plugin:
|
|
* includes/functions.php builds include paths from it at runtime.
|
|
*/
|
|
$GLOBALS['config'] = [
|
|
'base_path' => $cacti_root,
|
|
'url_path' => '/cacti/',
|
|
'cacti_version' => $cacti_version,
|
|
'cacti_server_os' => 'unix',
|
|
];
|
|
$GLOBALS['__test_original_base_path'] = $cacti_root;
|
|
|
|
// thold_expand_string() include_once()s library_path/variables.php at call time.
|
|
$GLOBALS['config']['library_path'] = __DIR__ . '/Fixtures/cacti-lib';
|
|
|
|
// thold reads and writes this on every RPN evaluation.
|
|
$GLOBALS['rpn_error'] = false;
|
|
|
|
// Cacti's list of enabled plugins; thold_check_threshold() declares it global.
|
|
$GLOBALS['plugins'] = [];
|
|
|
|
// Cacti's debug flag, also declared global by thold_check_threshold().
|
|
$GLOBALS['debug'] = false;
|
|
|
|
if (!function_exists('db_execute')) {
|
|
function db_execute($sql, $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_execute', $sql);
|
|
|
|
return CactiStubs::nextReturn('db_execute', true, $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_execute_prepared')) {
|
|
function db_execute_prepared($sql, $params = [], $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_execute_prepared', $sql, $params);
|
|
|
|
return CactiStubs::nextReturn('db_execute_prepared', true, $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('register_process_start')) {
|
|
function register_process_start($tasktype, $taskname, $taskid = 0, $timeout = 300) {
|
|
CactiStubs::record('register_process_start', '', [$tasktype, $taskname, $taskid, $timeout]);
|
|
|
|
return CactiStubs::nextReturn('register_process_start', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('unregister_process')) {
|
|
function unregister_process($tasktype, $taskname, $taskid = 0, $pid = -1) {
|
|
CactiStubs::record('unregister_process', '', [$tasktype, $taskname, $taskid, $pid]);
|
|
|
|
return CactiStubs::nextReturn('unregister_process', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('heartbeat_process')) {
|
|
function heartbeat_process($tasktype, $taskname, $taskid = 0) {
|
|
CactiStubs::record('heartbeat_process', '', [$tasktype, $taskname, $taskid]);
|
|
|
|
return CactiStubs::nextReturn('heartbeat_process', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_assoc')) {
|
|
function db_fetch_assoc($sql, $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_assoc', $sql);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_assoc', [], $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_assoc_prepared')) {
|
|
function db_fetch_assoc_prepared($sql, $params = [], $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_assoc_prepared', $sql, $params);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_assoc_prepared', [], $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_row')) {
|
|
function db_fetch_row($sql, $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_row', $sql);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_row', [], $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_row_prepared')) {
|
|
function db_fetch_row_prepared($sql, $params = [], $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_row_prepared', $sql, $params);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_row_prepared', [], $sql);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cacti's cell fetchers return false, not '', when the query matches no row.
|
|
* The difference matters on PHP 8: false coerces to 0 in arithmetic while ''
|
|
* raises a TypeError, so a stub returning '' invents failures that production
|
|
* does not have.
|
|
*/
|
|
if (!function_exists('db_fetch_cell')) {
|
|
function db_fetch_cell($sql, $col_name = '', $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_cell', $sql);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_cell', false, $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_fetch_cell_prepared')) {
|
|
function db_fetch_cell_prepared($sql, $params = [], $col_name = '', $log = true, $db_conn = false) {
|
|
CactiStubs::record('db_fetch_cell_prepared', $sql, $params);
|
|
|
|
return CactiStubs::nextReturn('db_fetch_cell_prepared', false, $sql);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_qstr')) {
|
|
function db_qstr($string, $db_conn = false) {
|
|
return "'" . str_replace("'", "''", (string) $string) . "'";
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_begin_transaction')) {
|
|
function db_begin_transaction() {
|
|
CactiStubs::record('db_begin_transaction');
|
|
|
|
return CactiStubs::nextReturn('db_begin_transaction', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_commit_transaction')) {
|
|
function db_commit_transaction() {
|
|
CactiStubs::record('db_commit_transaction');
|
|
|
|
return CactiStubs::nextReturn('db_commit_transaction', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_rollback_transaction')) {
|
|
function db_rollback_transaction() {
|
|
CactiStubs::record('db_rollback_transaction');
|
|
|
|
return CactiStubs::nextReturn('db_rollback_transaction', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('html_escape')) {
|
|
function html_escape($string) {
|
|
return htmlspecialchars((string) $string, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
// Output-only Cacti UI helpers the page_head/device hook callbacks emit
|
|
// through; the tests only care that the surrounding PHP (including the CSP
|
|
// nonce/asset lines) executes, so these are recorded no-ops.
|
|
if (!function_exists('html_header')) {
|
|
function html_header(...$args) {
|
|
CactiStubs::record('html_header');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('html_start_box')) {
|
|
function html_start_box(...$args) {
|
|
CactiStubs::record('html_start_box');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('html_end_box')) {
|
|
function html_end_box(...$args) {
|
|
CactiStubs::record('html_end_box');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('form_start')) {
|
|
function form_start(...$args) {
|
|
CactiStubs::record('form_start');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('form_end')) {
|
|
function form_end(...$args) {
|
|
CactiStubs::record('form_end');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('form_alternate_row')) {
|
|
function form_alternate_row(...$args) {
|
|
CactiStubs::record('form_alternate_row');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('form_end_row')) {
|
|
function form_end_row(...$args) {
|
|
CactiStubs::record('form_end_row');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('form_dropdown')) {
|
|
function form_dropdown(...$args) {
|
|
CactiStubs::record('form_dropdown');
|
|
}
|
|
}
|
|
|
|
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'>";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mirrors Cacti 1.2 lib/functions.php. KEEP IN SYNC: if core tightens its
|
|
* checks, tests here would otherwise keep passing while production diverges.
|
|
*/
|
|
if (!function_exists('sanitize_unserialize_selected_items')) {
|
|
function sanitize_unserialize_selected_items($items) {
|
|
if (empty($items)) {
|
|
return false;
|
|
}
|
|
|
|
$data = unserialize($items, ['allowed_classes' => false]); // nosemgrep: php.lang.security.unserialize-use.unserialize-use -- test stub mirroring Cacti core; allowed_classes:false blocks object injection
|
|
|
|
if (!is_array($data)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($data as $value) {
|
|
if (!is_numeric($value)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('read_config_option')) {
|
|
function read_config_option($name, $force = false) {
|
|
return isset(CactiStubs::$configOptions[$name]) ? CactiStubs::$configOptions[$name] : '';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('set_config_option')) {
|
|
function set_config_option($name, $value) {
|
|
CactiStubs::$configOptions[$name] = $value;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('__')) {
|
|
function __($text) {
|
|
$args = array_slice(func_get_args(), 1);
|
|
|
|
// Cacti's __() accepts sprintf arguments after the format string.
|
|
return $args === [] ? $text : vsprintf($text, $args);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('__esc')) {
|
|
function __esc($text) {
|
|
return htmlspecialchars(call_user_func_array('__', func_get_args()), ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_log')) {
|
|
function cacti_log($message, $output = false, $environ = 'CMDPHP', $level = 0) {
|
|
CactiStubs::$log[] = $message;
|
|
$GLOBALS['__test_cacti_log'][] = (string) $message;
|
|
CactiStubs::record('cacti_log', '', [$message, $output, $environ, $level]);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_sizeof')) {
|
|
function cacti_sizeof($array) {
|
|
return (is_array($array) || $array instanceof Countable) ? count($array) : 0;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_count')) {
|
|
function cacti_count($array) {
|
|
return cacti_sizeof($array);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_request_var')) {
|
|
function get_request_var($name, $default = '') {
|
|
return isset(CactiStubs::$requestVars[$name]) ? CactiStubs::$requestVars[$name] : $default;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_nfilter_request_var')) {
|
|
function get_nfilter_request_var($name, $default = '') {
|
|
return get_request_var($name, $default);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_filter_request_var')) {
|
|
function get_filter_request_var($name, $filter = FILTER_VALIDATE_INT, $options = []) {
|
|
return get_request_var($name);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('isset_request_var')) {
|
|
function isset_request_var($name) {
|
|
return isset(CactiStubs::$requestVars[$name]);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_escapeshellarg')) {
|
|
function cacti_escapeshellarg($string, $quote = true) {
|
|
return escapeshellarg((string) $string);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_hook_function')) {
|
|
function api_plugin_hook_function($name, $data = '') {
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_simple_graph_perms')) {
|
|
function get_simple_graph_perms($user_id) {
|
|
return CactiStubs::nextReturn('get_simple_graph_perms', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_policies')) {
|
|
function get_policies($user_id) {
|
|
return CactiStubs::nextReturn('get_policies', []);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_policy_where')) {
|
|
function get_policy_where($graph_auth_method, $policies, $sql_where) {
|
|
CactiStubs::record('get_policy_where', $sql_where);
|
|
|
|
return CactiStubs::nextReturn('get_policy_where', $sql_where);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('expand_title')) {
|
|
function expand_title($host_id, $snmp_query_id, $snmp_index, $title) {
|
|
CactiStubs::record('expand_title', $title);
|
|
|
|
return CactiStubs::nextReturn('expand_title', $title);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('substitute_host_data')) {
|
|
function substitute_host_data($string, $l_escape_string, $r_escape_string, $host_id) {
|
|
CactiStubs::record('substitute_host_data', $string);
|
|
|
|
return CactiStubs::nextReturn('substitute_host_data', $string);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('null_out_substitutions')) {
|
|
function null_out_substitutions($string) {
|
|
return CactiStubs::nextReturn('null_out_substitutions', $string);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_graph_title')) {
|
|
function get_graph_title($local_graph_id) {
|
|
return CactiStubs::nextReturn('get_graph_title', 'Traffic - eth0');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('rrdtool_function_fetch')) {
|
|
function rrdtool_function_fetch($local_data_id, $start_time, $end_time, $resolution = 0, $show_unknown = false, $rrdtool_file = null) {
|
|
CactiStubs::record('rrdtool_function_fetch', (string) $local_data_id);
|
|
|
|
return CactiStubs::nextReturn('rrdtool_function_fetch', []);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_data_source_path')) {
|
|
function get_data_source_path($local_data_id, $expand_paths = true) {
|
|
return '/var/lib/cacti/rra/test_' . (int) $local_data_id . '.rrd';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('sql_save')) {
|
|
function sql_save($array_items, $table_name, $key_cols = 'id', $autoinc = true, $db_conn = false) {
|
|
CactiStubs::record('sql_save', $table_name, $array_items);
|
|
|
|
return CactiStubs::nextReturn('sql_save', 1);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('db_affected_rows')) {
|
|
function db_affected_rows($db_conn = false) {
|
|
return CactiStubs::nextReturn('db_affected_rows', 1);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('rrdtool_function_graph')) {
|
|
function rrdtool_function_graph($local_graph_id, $rra_id, $graph_data_array, $rrdtool_pipe = false, &$xport_meta = [], $user = 0) {
|
|
CactiStubs::record('rrdtool_function_graph', (string) $local_graph_id);
|
|
|
|
// A one-pixel PNG stands in for the rendered graph.
|
|
return base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_timespan')) {
|
|
function get_timespan(&$timespan, $time, $span, $first_weekdayid) {
|
|
$timespan['begin_now'] = $time - 86400;
|
|
$timespan['end_now'] = $time;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('read_user_setting')) {
|
|
function read_user_setting($config_name, $default = false, $force = false, $user = 0) {
|
|
return CactiStubs::nextReturn('read_user_setting', $default);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_selected_theme')) {
|
|
function get_selected_theme() {
|
|
return 'modern';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mailer')) {
|
|
function mailer($from, $to, $cc = '', $bcc = '', $replyto = '', $subject = '', $body = '', $body_text = '', $attachments = null, $headers = [], $html = true) {
|
|
CactiStubs::$mail[] = [
|
|
'to' => is_array($to) ? implode(',', $to) : (string) $to,
|
|
'bcc' => is_array($bcc) ? implode(',', $bcc) : (string) $bcc,
|
|
'subject' => (string) $subject,
|
|
];
|
|
|
|
return CactiStubs::nextReturn('mailer', '');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('cacti_snmp_send')) {
|
|
function cacti_snmp_send($hostname, $version, $community, $oid, $value, $type = 's') {
|
|
CactiStubs::record('cacti_snmp_send', (string) $oid);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('debounce_run_notification')) {
|
|
function debounce_run_notification($id, $frequency = 60, ...$args) {
|
|
CactiStubs::record('debounce_run_notification', (string) $id);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('array_rekey')) {
|
|
function array_rekey($array, $key, $key_value) {
|
|
$ret_array = [];
|
|
|
|
if (is_array($array)) {
|
|
foreach ($array as $item) {
|
|
$item_key = $item[$key];
|
|
|
|
if (is_array($key_value)) {
|
|
foreach ($key_value as $value) {
|
|
$ret_array[$item_key][$value] = $item[$value];
|
|
}
|
|
} else {
|
|
$ret_array[$item_key] = $item[$key_value];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $ret_array;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('clean_up_name')) {
|
|
function clean_up_name($string) {
|
|
$string = preg_replace('/[\s\.]+/', '_', $string);
|
|
$string = preg_replace('/[^a-zA-Z0-9_]+/', '', $string);
|
|
|
|
return preg_replace('/_{2,}/', '_', $string);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('plugin_maint_check_cacti_host')) {
|
|
function plugin_maint_check_cacti_host($host_id) {
|
|
return CactiStubs::nextReturn('plugin_maint_check_cacti_host', false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_is_enabled')) {
|
|
function api_plugin_is_enabled($plugin) {
|
|
return CactiStubs::nextReturn('api_plugin_is_enabled', false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_hook')) {
|
|
function api_plugin_hook($name, $data = '') {
|
|
CactiStubs::record('api_plugin_hook', $name);
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_register_hook')) {
|
|
function api_plugin_register_hook($name, $hook, $function, $file, $status = '') {
|
|
CactiStubs::record('api_plugin_register_hook', $hook, ['name' => $name, 'function' => $function, 'file' => $file]);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_register_realm')) {
|
|
function api_plugin_register_realm($name, $files, $title, $navigate = 0) {
|
|
CactiStubs::record('api_plugin_register_realm', $files, ['name' => $name, 'title' => $title]);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_plugin_enable_hooks')) {
|
|
function api_plugin_enable_hooks($name) {
|
|
CactiStubs::record('api_plugin_enable_hooks', $name);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_current_page')) {
|
|
function get_current_page() {
|
|
return CactiStubs::nextReturn('get_current_page', '');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('api_user_realm_auth')) {
|
|
function api_user_realm_auth($filename = '') {
|
|
return CactiStubs::nextReturn('api_user_realm_auth', true);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('raise_message')) {
|
|
function raise_message($message_id, $message = '', $level = 0) {
|
|
CactiStubs::record('raise_message', (string) $message_id);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('rrdtool_execute')) {
|
|
function rrdtool_execute($command, $log_to_stdout = false, $output_flag = 1, $rrdtool_pipe = false, $logopt = 'WEBLOG') {
|
|
CactiStubs::record('rrdtool_execute', $command);
|
|
|
|
return CactiStubs::nextReturn('rrdtool_execute', '', $command);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('rrdtool_function_interface_speed')) {
|
|
function rrdtool_function_interface_speed($data_local) {
|
|
return CactiStubs::nextReturn('rrdtool_function_interface_speed', 0);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('substitute_snmp_query_data')) {
|
|
function substitute_snmp_query_data($value, $host_id, $snmp_query_id, $snmp_index) {
|
|
CactiStubs::record('substitute_snmp_query_data', (string) $value, [$host_id, $snmp_query_id, $snmp_index]);
|
|
|
|
return CactiStubs::nextReturn('substitute_snmp_query_data', $value);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_timeinstate')) {
|
|
function get_timeinstate($host) {
|
|
return CactiStubs::nextReturn('get_timeinstate', '1 day');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('get_daysfromtime')) {
|
|
function get_daysfromtime($timestamp) {
|
|
return CactiStubs::nextReturn('get_daysfromtime', '1 day');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('number_format_i18n')) {
|
|
function number_format_i18n($number, $decimals = 0, $baseu = 1000) {
|
|
return number_format((float) $number, $decimals < 0 ? 0 : (int) $decimals);
|
|
}
|
|
}
|
|
|
|
if (!defined('FILTER_VALIDATE_IS_REGEX')) {
|
|
define('FILTER_VALIDATE_IS_REGEX', 99999);
|
|
}
|
|
|
|
// Device states, from Cacti include/global_constants.php.
|
|
foreach (['HOST_UNKNOWN' => 0, 'HOST_DOWN' => 1, 'HOST_RECOVERING' => 2, 'HOST_UP' => 3, 'HOST_ERROR' => 4] as $name => $value) {
|
|
if (!defined($name)) {
|
|
define($name, $value);
|
|
}
|
|
}
|
|
|
|
// rrdtool output modes, from Cacti include/global_constants.php.
|
|
foreach (['RRDTOOL_OUTPUT_NULL' => 0, 'RRDTOOL_OUTPUT_STDOUT' => 1, 'RRDTOOL_OUTPUT_STDERR' => 2,
|
|
'RRDTOOL_OUTPUT_GRAPH_DATA' => 3, 'RRDTOOL_OUTPUT_BOOLEAN' => 4,
|
|
'RRDTOOL_OUTPUT_RETURN_STDERR' => 5] as $name => $value) {
|
|
if (!defined($name)) {
|
|
define($name, $value);
|
|
}
|
|
}
|
|
|
|
if (!defined('CACTI_DATE_TIME_FORMAT')) {
|
|
define('CACTI_DATE_TIME_FORMAT', 'Y-m-d H:i:s');
|
|
}
|
|
|
|
if (!defined('CACTI_PATH_BASE')) {
|
|
define('CACTI_PATH_BASE', $GLOBALS['config']['base_path']);
|
|
}
|
|
|
|
if (!function_exists('plugin_test_read_source')) {
|
|
/**
|
|
* Read a plugin source file's raw contents, relative to the plugin root.
|
|
*
|
|
* @param string $relative_file Plugin file, relative to the plugin root.
|
|
*
|
|
* @return string
|
|
*/
|
|
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.
|
|
*
|
|
* Several plugin files (includes/arrays.php in particular) define their 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 thold_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a plugin source file that only assigns file-scope variables (no
|
|
* function/class declarations), publishing them to $GLOBALS the same way as
|
|
* thold_test_load().
|
|
*
|
|
* includes/arrays.php is also included with a plain include() (not
|
|
* include_once()) from several places in includes/functions.php itself (e.g.
|
|
* thold_log()), keyed off $config['base_path']. Once any test exercises one
|
|
* of those call sites, PHP's include-once registry considers the file
|
|
* already included: a later thold_test_load() (require_once) on the same
|
|
* resolved path silently no-ops and never (re)publishes $thold_types. Using
|
|
* a plain require() here sidesteps that registry entirely, so re-running it
|
|
* is always safe and cheap for a file that just assigns arrays.
|
|
*
|
|
* @param string $path Absolute path to the file.
|
|
*
|
|
* @return void
|
|
*/
|
|
function thold_test_load_always($path) {
|
|
global $config;
|
|
|
|
$__before = get_defined_vars();
|
|
|
|
require $path;
|
|
|
|
foreach (get_defined_vars() as $__name => $__value) {
|
|
if (!array_key_exists($__name, $__before) && strncmp($__name, '__', 2) !== 0) {
|
|
$GLOBALS[$__name] = $__value;
|
|
}
|
|
}
|
|
}
|