diff --git a/CHANGELOG.md b/CHANGELOG.md index 002deb3..f161939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ --- develop --- +* issue: Fix a fatal `Cannot redeclare intropage_get_allowed_devices()` that disabled the plugin on installs upgraded from the old `include/` layout; `intropage_config_arrays()` now repoints any stale `plugin_hooks` row and removes the leftover `include/` directory on the earliest per-page hook, before a stale hook can load a second copy of the library * 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 diff --git a/setup.php b/setup.php index 706108d..c02fc38 100644 --- a/setup.php +++ b/setup.php @@ -111,6 +111,49 @@ function plugin_intropage_uninstall(): void { intropage_drop_database(); } +/** + * Repoints any stale plugin_hooks row still bound to the old include/ + * library path to includes/ and removes the leftover include/ directory + * from disk. The plugin's library directory moved from include/ to + * includes/; on an upgraded install the old directory (and the hook rows + * that reference it) can linger, so Cacti loads include/functions.php via + * a stale hook and then includes/functions.php via the plugin's own code, + * fatally redeclaring every function the two files share. This runs from + * intropage_config_arrays() - the earliest per-page hook, always loaded + * from setup.php - so it executes before any stale graph_buttons/ + * console_after hook can load the old library. + * + * @return void + * + * @global array $config Cacti global configuration array; used to resolve + * the plugin directory. + */ +function intropage_cleanup_legacy_include(): void { + global $config; + + // Repoint stale hook rows first - unconditionally, before any filesystem + // check - because the rows can outlive the include/ directory (e.g. a prior + // partial cleanup removed the files but never committed the repoint), and + // while they remain Cacti keeps failing its file-inclusion check for the + // missing path on every request. The LIKE filter makes this a no-op on + // healthy installs. + db_execute("UPDATE plugin_hooks SET file = REPLACE(file, 'include/', 'includes/') WHERE name = 'intropage' AND file LIKE 'include/%'"); + + $legacy_dir = $config['base_path'] . '/plugins/intropage/include'; + + // Nothing left to remove once the stale directory is gone. + if (!is_dir($legacy_dir)) { + return; + } + + // Physically remove the stale directory so no stale hook row cached for + // the current request can load a second copy of the library and redeclare + // its functions. + if (!plugin_intropage_rmtree($legacy_dir)) { + cacti_log('WARNING: intropage could not remove the legacy include/ directory (check file/directory permissions)', false, 'INTROPAGE'); + } +} + /** * Config_arrays hook: augments Cacti's role system to grant the * Intropage realms to the Normal User/System Administration roles, and @@ -136,6 +179,10 @@ function plugin_intropage_uninstall(): void { function intropage_config_arrays(): void { global $intropage_intervals, $trend_timespans, $panel_lines; + // Runs before any stale include/ hook can load the old library and + // redeclare functions already defined by includes/. + intropage_cleanup_legacy_include(); + // Core builds $user_auth_roles with __('Normal User') in the core domain // (include/global_arrays.php), and auth_augment_roles() indexes by that // exact string. Adding the intropage domain here would key a new role on diff --git a/tests/Unit/IntropageLegacyIncludeCleanupTest.php b/tests/Unit/IntropageLegacyIncludeCleanupTest.php new file mode 100644 index 0000000..a06a642 --- /dev/null +++ b/tests/Unit/IntropageLegacyIncludeCleanupTest.php @@ -0,0 +1,87 @@ +toContain("REPLACE(file, 'include/', 'includes/')"); + expect($GLOBALS['__test_cacti_log'])->toBe([]); +}); + +it('repoints stale hooks and removes the legacy include/ directory', function () { + $base = sys_get_temp_dir() . '/intropage-legacy-' . uniqid(); + $plugin = $base . '/plugins/intropage'; + mkdir($plugin . '/include', 0777, true); + file_put_contents($plugin . '/include/functions.php', "toBeFalse(); + + $sql = implode("\n", array_column($GLOBALS['__test_db_calls'], 'sql')); + expect($sql)->toContain("REPLACE(file, 'include/', 'includes/')"); + expect($GLOBALS['__test_cacti_log'])->toBe([]); +}); + +it('warns when the legacy include/ directory cannot be removed', function () { + $base = sys_get_temp_dir() . '/intropage-legacy-fail-' . uniqid(); + $plugin = $base . '/plugins/intropage'; + mkdir($plugin, 0777, true); + + $target = $base . '/realtarget'; + mkdir($target, 0777, true); + file_put_contents($target . '/functions.php', "toContain('legacy include/ directory'); +});