Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

--- develop ---

* issue: Load Thold's relocated function library from `includes/functions.php` (with a legacy `thold_functions.php` fallback) and skip the threshold poller when Thold's API is unavailable, so intropage no longer fatals after Thold moved its functions out of the plugin root
* dev: Rename the plugin's stylesheet directory from `themes/` to `css/`; the old directory is tombstoned in `manifest.json` and removed on upgrade
* dev: Move the plugin's library directory from `include/` to `includes/` and re-register the affected hooks on upgrade (a `plugin_hooks` file repoint in `intropage_upgrade_database()`) so existing installations load them from the new path
* dev: Measure CI coverage with xdebug instead of pcov so the plugin's own sources are instrumented (pcov auto-scopes to the Composer root and skipped cacti/plugins/, leaving the patch-coverage gate with nothing to measure)
Expand Down
42 changes: 38 additions & 4 deletions panellib/thold.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@
+-------------------------------------------------------------------------+
*/

/**
* Loads the Thold plugin's function library. Thold relocated its helpers
* from the legacy plugins/thold/thold_functions.php to
* plugins/thold/includes/functions.php, so prefer the new path and fall
* back to the old one for older Thold installs.
*
* @return bool True when a Thold function library was found and loaded.
*
* @global array $config Cacti global configuration array; used to resolve
* the plugin base path.
*/
function intropage_load_thold_functions(): bool {
global $config;

$candidates = [
$config['base_path'] . '/plugins/thold/includes/functions.php',
$config['base_path'] . '/plugins/thold/thold_functions.php',
];

foreach ($candidates as $file) {
if (file_exists($file)) {
include_once($file);

return true;
}
}

return false;
}

/**
* Registers the 'thold' panel category and its 'Last Threshold Events'
* and 'Threshold' (trend graph) panels with the panel library. Called
Expand Down Expand Up @@ -239,7 +269,7 @@ function graph_thold($panel, $user_id, $timespan = 0): void {

$panel['alarm'] = 'green';

include_once($config['base_path'] . '/plugins/thold/thold_functions.php');
intropage_load_thold_functions();

$graph = [
'line' => [
Expand Down Expand Up @@ -374,7 +404,7 @@ function graph_thold($panel, $user_id, $timespan = 0): void {
function graph_thold_detail(): array {
global $config, $sql_where;

include_once($config['base_path'] . '/plugins/thold/thold_functions.php');
intropage_load_thold_functions();

$panel = [
'name' => __('Threshold Details', 'intropage'),
Expand Down Expand Up @@ -498,7 +528,7 @@ function graph_thold_detail(): array {
function thold_event_detail(): array {
global $config;

include_once($config['base_path'] . '/plugins/thold/thold_functions.php');
intropage_load_thold_functions();

$panel = [
'name' => __('Last Threshold Events', 'intropage'),
Expand Down Expand Up @@ -592,7 +622,11 @@ function thold_collect(): void {
// update in poller
$users = get_user_list();

include_once($config['base_path'] . '/plugins/thold/thold_functions.php');
// Skip quietly when Thold is absent or does not expose its threshold API,
// rather than fataling the intropage poller.
if (!intropage_load_thold_functions() || !function_exists('get_allowed_thresholds')) {
return;
}

foreach ($users as $user) {
$t_brea = 0;
Expand Down
103 changes: 103 additions & 0 deletions tests/Unit/TholdFunctionLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
*/

/*
* Unit coverage for intropage_load_thold_functions() and the Thold panel
* renderers' behaviour when the Thold plugin is absent, in panellib/thold.php.
*/

require_once __DIR__ . '/../../includes/functions.php';
require_once __DIR__ . '/../../panellib/thold.php';

if (!function_exists('api_plugin_is_enabled')) {
function api_plugin_is_enabled($plugin) {
return false;
}
}

if (!function_exists('api_plugin_user_realm_auth')) {
function api_plugin_user_realm_auth($file) {
return false;
}
}

beforeEach(function () {
$GLOBALS['__intropage_thold_base_restore'] = $GLOBALS['config']['base_path'];
$GLOBALS['__test_db_calls'] = array();
$GLOBALS['config']['is_web'] = false;
});

afterEach(function () {
$GLOBALS['config']['base_path'] = $GLOBALS['__intropage_thold_base_restore'];
});

function intropage_thold_sandbox(string $suffix): string {
$base = sys_get_temp_dir() . '/intropage-thold-' . $suffix . '-' . uniqid();
$GLOBALS['config']['base_path'] = $base;

return $base;
}

it('loads Thold functions from the new includes/functions.php path', function () {
$base = intropage_thold_sandbox('new');
mkdir($base . '/plugins/thold/includes', 0777, true);
file_put_contents($base . '/plugins/thold/includes/functions.php', "<?php\n");

expect(intropage_load_thold_functions())->toBeTrue();
});

it('falls back to the legacy thold_functions.php path', function () {
$base = intropage_thold_sandbox('legacy');
mkdir($base . '/plugins/thold', 0777, true);
file_put_contents($base . '/plugins/thold/thold_functions.php', "<?php\n");

expect(intropage_load_thold_functions())->toBeTrue();
});

it('returns false when no Thold library is present', function () {
$base = intropage_thold_sandbox('none');
mkdir($base . '/plugins/thold', 0777, true);

expect(intropage_load_thold_functions())->toBeFalse();
});

it('renders the thold graph panel as grey when Thold is not installed', function () {
intropage_thold_sandbox('graph');

$panel = array('id' => 1, 'refresh' => 300, 'alarm' => 'green');
graph_thold($panel, 1);

$updates = array_values(array_filter($GLOBALS['__test_db_calls'], function ($call) {
return $call['fn'] === 'db_execute_prepared'
&& stripos($call['sql'], 'UPDATE plugin_intropage_panel_data') !== false;
}));

expect($updates)->toHaveCount(1);
expect($updates[0]['params'][1])->toBe('grey');
});

it('renders the thold detail panels when Thold is not installed', function () {
intropage_thold_sandbox('detail');

$detail = graph_thold_detail();
$events = thold_event_detail();

expect($detail['alarm'])->toBe('grey');
expect($events['alarm'])->toBe('yellow');
});

it('skips thold_collect when the Thold threshold API is unavailable', function () {
intropage_thold_sandbox('collect');

thold_collect();

$trend_writes = array_filter($GLOBALS['__test_db_calls'], function ($call) {
return isset($call['sql']) && stripos($call['sql'], 'plugin_intropage_trends') !== false;
});

expect($trend_writes)->toBeEmpty();
});
Loading