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 @@ -3,6 +3,7 @@
--- develop ---

* bug: Pass a boolean (not the integer 1) to api_plugin_register_hook()'s $enable argument so the hook registration stays type-correct under Cacti 1.3's strict bool type-hint (the int only coerces while the file lacks declare(strict_types=1))
* bug: Replace the audit_utilities_array() CACTI_VERSION gate with a 1.3 feature-detect (CactiTableFilter), so a develop build that reports a version comparing < 1.3.0 no longer adds the Audit Log Utilities-page entry a second time (nor runs array_merge() against an unset Technical Support section) when 1.3 already lists it via config_arrays
* bug: Skip audit logging on CLI/poller requests until the `request_status` column exists, instead of emitting "Unknown column 'request_status'" errors when the audit_log schema migration (which only runs on plugins.php/audit.php page loads) has not yet applied; likewise make the shutdown-time request finalizer (`audit_finalize_request()`), which writes `request_status`, best-effort (its writes pass `$log = false`) so it fails quietly instead of logging "Table 'audit_log' doesn't exist" when the table was dropped (e.g. plugin uninstall) after `db_table_exists()`'s request-scoped cache was primed
* dev: Enforce patch coverage of changed lines in CI and remove the inert COMPOSER_ROOT_VERSION env from the Pest step
* dev: Relocate the schema/DB provisioning helpers (`audit_setup_table()`, `audit_setup_syslog_table()`, `audit_setup_user_log_state_table()`, `audit_upgrade_event_schema()`, and the user_log index helpers) from `setup.php` into `includes/database.php` to align with the fleet schema/includes convention, and add a root `manifest.json` with upgrade-time file pruning (`audit_prune_files()`) plus a CI manifest drift check (`tests/bin/validate-manifest.php`)
Expand Down
8 changes: 6 additions & 2 deletions setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -633,10 +633,14 @@ function audit_log_valid_event(): bool {
function audit_utilities_array(): void {
global $utilities;

if (version_compare(CACTI_VERSION, '1.3.0', '<')) {
// Cacti 1.3+ adds the Audit Log link through config_arrays instead of this
// legacy Utilities hook. Detect 1.3 via a class it introduced rather than a
// CACTI_VERSION string (a develop build can report < 1.3.0) so the entry is
// not added a second time.
if (!class_exists('CactiTableFilter')) {
if (api_plugin_user_realm_auth('audit.php')) {
$utilities[__('Technical Support', 'audit')] = array_merge(
$utilities[__('Technical Support', 'audit')],
$utilities[__('Technical Support', 'audit')] ?? [],
[
__('View Audit Log', 'audit') => [
'link' => 'plugins/audit/audit.php',
Expand Down
73 changes: 73 additions & 0 deletions tests/Unit/AuditUtilitiesArrayTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
*/

/*
* Unit coverage for audit_utilities_array() in setup.php.
*
* The CI matrix runs against a Cacti build without the 1.3-only
* CactiTableFilter class, so these tests exercise the legacy Utilities
* hook body: the section-preserving merge and the realm-authorization
* gate. Realm access is driven through the same api_plugin_user_realm_auth()
* stub used by the security suite (controllable via $__audit_test_realms);
* the guard below keeps this file runnable on its own.
*/

beforeAll(function () {
require_once dirname(__DIR__, 2) . '/setup.php';
});

if (!function_exists('api_plugin_user_realm_auth')) {
function api_plugin_user_realm_auth(string $filename = ''): bool {
return !empty($GLOBALS['__audit_test_realms'][$filename]);
}
}

beforeEach(function () {
$GLOBALS['__audit_test_realms'] = [];
$GLOBALS['utilities'] = [];
});

it('adds the Audit Log entry when the Technical Support section is absent', function () {
$GLOBALS['__audit_test_realms']['audit.php'] = true;

audit_utilities_array();

$section = __('Technical Support', 'audit');

expect($GLOBALS['utilities'])->toHaveKey($section);
expect($GLOBALS['utilities'][$section])->toHaveKey(__('View Audit Log', 'audit'));
expect($GLOBALS['utilities'][$section][__('View Audit Log', 'audit')]['link'])
->toBe('plugins/audit/audit.php');
});

it('preserves existing Technical Support entries when access is granted', function () {
$GLOBALS['__audit_test_realms']['audit.php'] = true;

$section = __('Technical Support', 'audit');
$existing = __('Existing Entry', 'audit');

$GLOBALS['utilities'][$section] = [
$existing => ['link' => 'plugins/other/other.php'],
];

audit_utilities_array();

expect($GLOBALS['utilities'][$section])->toHaveKey($existing);
expect($GLOBALS['utilities'][$section][$existing]['link'])->toBe('plugins/other/other.php');
expect($GLOBALS['utilities'][$section])->toHaveKey(__('View Audit Log', 'audit'));
});

it('adds nothing when the user lacks audit.php realm access', function () {
$section = __('Technical Support', 'audit');

$GLOBALS['utilities'][$section] = [];

audit_utilities_array();

expect($GLOBALS['utilities'][$section])->toBe([]);
expect($GLOBALS['utilities'][$section])->not->toHaveKey(__('View Audit Log', 'audit'));
});
Loading