Files
Cactiplugin_thold/tests/bootstrap-unit.php
T
TheWitnessandCopilot 9b217e0e3a INFO: set compat to 1.2.29 (#838)
* 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>
2026-10-01 01:40:00 -05:00

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