$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; } } }