$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 "";
}
}
if (!function_exists('get_md5_include_css')) {
function get_md5_include_css($path) {
return "";
}
}
/*
* 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('isempty_request_var')) {
function isempty_request_var($name) {
return !isset(CactiStubs::$requestVars[$name]) || CactiStubs::$requestVars[$name] === '';
}
}
if (!function_exists('set_request_var')) {
function set_request_var($name, $value) {
CactiStubs::$requestVars[$name] = $value;
}
}
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 (!function_exists('html_split_string')) {
function html_split_string($string, $length = 76, $forgiving = 10) {
return $string;
}
}
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;
}
}
}