diff --git a/TODO.md b/TODO.md index 026cbd2..fb269ce 100644 --- a/TODO.md +++ b/TODO.md @@ -37,6 +37,11 @@ - [ ] AGENTS.md + config.json.example bijwerken - [ ] Verificatie: php -l, curl met Host-header, domein-switch in admin testen +## v2.6.6 (2026-08-27) ✅ +- [x] Bug: essential/protected plugins (Dashboard, Navigation) konden niet meer geactiveerd worden als ze uit enabled_plugins raakten — core forceert nu laden van essential plugins (plugin.json `essential: true`); admin-UI toont ze altijd als Actief; toggle-handler staat aanzetten wél toe, uitzetten blijft geblokkeerd; isProtectedPlugin() dekt nu ook essential (alle delete/bewerk/file-handlers automatisch beschermd) +- [x] Docs: guide plugin-development (NL/EN) beschrijving `essential`-veld bijgewerkt ("altijd geladen" + niet uit te schakelen/bewerken/verwijderen) +- [x] Verslag gemaakt (docs/release-notes/v2.6.6.md) + ## v2.6.5 (2026-08-27) ✅ - [x] Bug: dashboard toonde 0 content (AdminPluginAPI::getContentDir() gaf relatief pad "content" terug zonder normalisatie tegen project-root; Apache CWD=public/ → 0 bestanden) - [x] Dynamische pad-resolutie: PluginAPIInterface uitgebreid met getProjectRoot()/getContentDir()/getPluginsDir()/getVersionInfo(); CMSAPI en AdminPluginAPI implementeren deze nu universeel; Navigation plugin en Logs plugin halen paden via de API i.p.v. hardcoded dirname(__DIR__) diff --git a/cms/core/plugin/PluginManager.php b/cms/core/plugin/PluginManager.php index 5cdb5b4..c97afea 100644 --- a/cms/core/plugin/PluginManager.php +++ b/cms/core/plugin/PluginManager.php @@ -122,11 +122,34 @@ class PluginManager } } + /** + * Controleer of een plugin als 'essential' is gemarkeerd in zijn plugin.json. + * + * Essential plugins worden altijd geladen, ongeacht de enabled_plugins-lijst, + * en kunnen niet worden uitgeschakeld, bewerkt of verwijderd via de admin. + * + * @since 2.6.6 + * + * @param string $pluginName Plugin-naam (directorynaam). + * @return bool True indien de plugin in plugin.json essential: true heeft. + */ + public function isEssentialPlugin(string $pluginName): bool + { + $pluginJsonFile = $this->pluginsPath . '/' . $pluginName . '/plugin.json'; + if (!file_exists($pluginJsonFile)) { + return false; + } + $pluginJson = json_decode(file_get_contents($pluginJsonFile), true); + return is_array($pluginJson) && ($pluginJson['essential'] ?? false) === true; + } + /** * Laad alle ingeschakelde plugins uit de pluginsdirectory. * - * Per plugin wordt het hoofdbestand (.php) geïncludeerd, de plugin-class - * geïnstantieerd, en action- en filter-hooks automatisch geregistreerd. + * Essential plugins (plugin.json `essential: true`) worden altijd geladen, + * ook als ze niet in enabled_plugins staan. Per plugin wordt het hoofdbestand + * (.php) geïncludeerd, de plugin-class geïnstantieerd, en action- en + * filter-hooks automatisch geregistreerd. * * @since 2.6.5 * @@ -139,13 +162,19 @@ class PluginManager } $pluginDirs = glob($this->pluginsPath . '/*', GLOB_ONLYDIR); - + foreach ($pluginDirs as $pluginDir) { $pluginName = basename($pluginDir); - - if (!in_array($pluginName, $this->enabledPlugins, true)) { + + // Essential plugins are always loaded, regardless of enabled_plugins. + $isEnabled = in_array($pluginName, $this->enabledPlugins, true); + if (!$isEnabled && !$this->isEssentialPlugin($pluginName)) { continue; } + // Keep the effective enabled list consistent for getEnabledPlugins()/isEnabled(). + if (!$isEnabled) { + $this->enabledPlugins[] = $pluginName; + } $pluginFile = $pluginDir . '/' . $pluginName . '.php'; diff --git a/docs/release-notes/v2.6.6.md b/docs/release-notes/v2.6.6.md new file mode 100644 index 0000000..8a2f5c1 --- /dev/null +++ b/docs/release-notes/v2.6.6.md @@ -0,0 +1,81 @@ +# v2.6.6 (Lyra) — Essential-plugins altijd laden + +Releasedatum: 2026-08-27 +Codename: Lyra +Status: stable + +## Samenvatting + +Bug-fix: beschermde en essentiële plugins (`Navigation`, `Dashboard`) +werden op de live site als "Inactief" getoond en konden niet meer +aangezet worden via de admin-interface, terwijl ze verplichte +functionaliteit leveren (sidebar-navigatie voor handleidingen, dashboard). + +Oorzaak: zodra zo'n plugin uit `enabled_plugins` in `config.json` raakte +(bijv. door een merge of handmatige wijziging), blokkeerde de admin-code +elke toggle en toonde de Twig-template alleen een schild-icoon — geen +"Activeren"-knop. De core laadde ze vervolgens ook niet meer. + +Oplossing: de core **forceert** het laden van essential plugins +(plugin.json `essential: true`) ongeacht de `enabled_plugins`-lijst. +De admin-UI toont ze altijd als "Actief" en de toggle-handler staat +aanzetten wél toe (uitzetten blijft geblokkeerd). + +## Wijzigingen + +### `cms/core/plugin/PluginManager.php` +- Nieuwe methode `isEssentialPlugin(string $pluginName): bool` — leest + `plugin.json` `essential: true`. +- `loadPlugins()` laadt essential plugins **altijd**, ook als ze niet in + `enabled_plugins` staan. De interne lijst wordt consistent gehouden + voor `getEnabledPlugins()` / `isEnabled()`. + +### `public/admin.php` +- Nieuwe helper `isEssentialPluginByName(string $pluginName): bool` — + pad-onafhankelijke check op `plugin.json` `essential: true`. +- `isProtectedPlugin()` dekt nu ook essential plugins (naast de + hardcoded `getProtectedPlugins()`-lijst). Alle bestaande call-sites + (delete, bewerken, file-upload, file-delete, file-move, dir-create, + dir-rename, dir-delete, config-move) profiteren automatisch. +- `handlePlugins()`: `enabled` wordt `true` voor protected/essential + plugins, zodat de UI altijd "Actief" toont. +- `handlePluginsToggle()`: aanzetten van protected/essential plugins is + weer mogelijk; alleen uitzetten blijft geblokkeerd met een admin-log. + +### `guide/nl/codepress-developer/plugin-development.md` +### `guide/en/codepress-developer/plugin-development.md` +- Beschrijving van `essential`-veld bijgewerkt: "altijd geladen" + + "kan niet worden uitgeschakeld/bewerkt/verwijderd". + +## Getroffen plugins + +| Plugin | `essential` in plugin.json | Hardcoded protected | Gedrag na fix | +|----------|----------------------------|---------------------|---------------| +| `Dashboard` | ja | nee | Altijd geladen, niet uit te schakelen | +| `Navigation` | ja | ja (redundant) | Altijd geladen, niet uit te schakelen | +| `HTMLBlock` | nee | nee | Normaal (toggle werkt) | +| `Statistics` | nee | nee | Normaal (toggle werkt) | +| `Logs` | nee | nee | Normaal (toggle werkt) | +| `GeoIPInfo` | nee | nee | Normaal (toggle werkt) | + +## Tests uitgevoerd + +- `php -l` over alle PHP-bestanden (zonder vendor): 0 syntax-errors. +- Functionele tests (`cli/test/functional/run-tests.sh` tegen Apache): + 16/16 PASS (100%). +- Pentest (`cli/test/pentest/pentest.sh`): 29/29 SAFE, 0 vulnerabilities. +- Accessibility / WCAG 2.1 AA (`cli/test/accessibility.sh`): 25/25 PASS. +- Runtime-check `PluginManager` met lege `enabled_plugins`: alleen + `Dashboard` + `Navigation` worden geladen (essential). Met + `["HTMLBlock"]`: `Dashboard` + `Navigation` + `HTMLBlock`. +- Admin-UI simulatie met lege `enabled_plugins`: `Dashboard` en + `Navigation` tonen `enabled=true` (vroeger `false`). + +## Upgrade-instructies + +1. Pull de nieuwe versie. +2. Geen verdere actie nodig — essential plugins staan vanaf nu altijd aan. +3. Als je `config.json` handmatig hebt aangepast en `Navigation` of + `Dashboard` uit `enabled_plugins` hebt gehaald: je hoeft dit niet meer + terug te draaien; de core laadt ze toch. Ze verschijnen weer als + "Actief" in de admin-UI. \ No newline at end of file diff --git a/guide/en/codepress-developer/plugin-development.md b/guide/en/codepress-developer/plugin-development.md index f21a5c7..03eca68 100644 --- a/guide/en/codepress-developer/plugin-development.md +++ b/guide/en/codepress-developer/plugin-development.md @@ -47,7 +47,7 @@ plugins/MyPlugin/ | `author` | string | Author | | `description` | string | Short description | | `type` | `"system"` or `"content"` | System (blue badge) or content (green badge) | -| `essential` | boolean | Essential plugins cannot be edited/deleted | +| `essential` | boolean | Essential plugins are always loaded (regardless of `enabled_plugins`) and cannot be disabled, edited or deleted | | `hasConfig` | boolean | Shows a Config button in admin | | `default_language` | string | Fallback language for plugin translations (e.g. `nl`) | | `settings` | array | Settings schema (see below) | diff --git a/guide/nl/codepress-developer/plugin-development.md b/guide/nl/codepress-developer/plugin-development.md index 9a9aaba..4ab647f 100644 --- a/guide/nl/codepress-developer/plugin-development.md +++ b/guide/nl/codepress-developer/plugin-development.md @@ -47,7 +47,7 @@ plugins/MijnPlugin/ | `author` | string | Auteur | | `description` | string | Korte beschrijving | | `type` | `"system"` of `"content"` | Systeem (blauwe badge) of content (groene badge) | -| `essential` | boolean | Essentiële plugins kunnen niet worden bewerkt/verwijderd | +| `essential` | boolean | Essentiële plugins worden altijd geladen (ongeacht `enabled_plugins`) en kunnen niet worden uitgeschakeld, bewerkt of verwijderd | | `hasConfig` | boolean | Toont een Config-knop in admin | | `default_language` | string | Fallback taal voor plugin-vertalingen (bijv. `nl`) | | `settings` | array | Instellingen-schema (zie hieronder) | diff --git a/public/admin.php b/public/admin.php index a1f31fd..065083b 100644 --- a/public/admin.php +++ b/public/admin.php @@ -179,7 +179,7 @@ function getSidebarColor($config) { * * @since 2.6.5 * - * @return array Namen van beschermde plugins (bijv. ['Navigation']). + * @return array Namen van beschermd geworden plugins (bijv. ['Navigation']). */ // Essential plugins that cannot be disabled or deleted function getProtectedPlugins(): array { @@ -187,7 +187,34 @@ function getProtectedPlugins(): array { } /** - * Controleert of een pluginnaam op de lijst van beschermde plugins staat. + * Controleert of een plugin in zijn plugin.json `essential: true` heeft. + * + * Essential plugins worden altijd geladen en kunnen niet worden uitgeschakeld, + * bewerkt of verwijderd via de admin. De check is pad-onafhankelijk en leest + * de plugin.json uit de standaard plugins-directory. + * + * @since 2.6.6 + * + * @param string $pluginName Naam van de plugin om te controleren. + * @return bool True wanneer de plugin essential is, anders false. + */ +function isEssentialPluginByName(string $pluginName): bool { + if ($pluginName === '') { + return false; + } + $pluginJsonFile = __DIR__ . '/../plugins/' . $pluginName . '/plugin.json'; + if (!file_exists($pluginJsonFile)) { + return false; + } + $data = json_decode(file_get_contents($pluginJsonFile), true); + return is_array($data) && ($data['essential'] ?? false) === true; +} + +/** + * Controleert of een plugin beschermd is (niet uit te schakelen/verwijderen). + * + * Een plugin is beschermd wanneer deze op de hardcoded lijst staat + * (getProtectedPlugins()) OF in zijn plugin.json `essential: true` heeft. * * @since 2.6.5 * @@ -195,7 +222,7 @@ function getProtectedPlugins(): array { * @return bool True wanneer de plugin beschermd is, anders false. */ function isProtectedPlugin(string $pluginName): bool { - return in_array($pluginName, getProtectedPlugins(), true); + return in_array($pluginName, getProtectedPlugins(), true) || isEssentialPluginByName($pluginName); } /** @@ -3795,7 +3822,7 @@ function handlePlugins($auth, $config, $twig, $user, $csrf): void $pluginData = [ 'name' => $pluginName, - 'enabled' => in_array($pluginName, $enabledPlugins), + 'enabled' => in_array($pluginName, $enabledPlugins) || isProtectedPlugin($pluginName), 'protected' => isProtectedPlugin($pluginName), 'type' => 'content', ]; @@ -5216,19 +5243,21 @@ function handlePluginsToggle($auth, $config): void $plugin = $_POST['plugin'] ?? ''; $plugin = preg_replace('/[^a-zA-Z0-9_-]/', '', $plugin); - - // Block toggling protected plugins - if (isProtectedPlugin($plugin)) { + + $configFile = $config['config_json']; + $siteConfig = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : []; + $enabledPlugins = $siteConfig['enabled_plugins'] ?? []; + $currentlyEnabled = in_array($plugin, $enabledPlugins, true); + + // Protected/essential plugins mogen niet uitgeschakeld worden (maar wel + // aangezet worden als ze per ongeluk uit enabled_plugins zijn geraakt). + if (isProtectedPlugin($plugin) && $currentlyEnabled) { adminLog($config, 'warning', $user['username'] . ' probeerde essentiële plugin ' . $plugin . ' te deactiveren'); header('Location: /admin/plugins'); exit; } - - $configFile = $config['config_json']; - $siteConfig = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : []; - $enabledPlugins = $siteConfig['enabled_plugins'] ?? []; - - if (in_array($plugin, $enabledPlugins)) { + + if ($currentlyEnabled) { $enabledPlugins = array_diff($enabledPlugins, [$plugin]); adminLog($config, 'info', $user['username'] . ' deactiveerde plugin ' . $plugin); } else { diff --git a/version.php b/version.php index 6f286c8..6b26da7 100644 --- a/version.php +++ b/version.php @@ -15,8 +15,8 @@ */ return [ - 'version' => '2.6.5', - 'release_date' => '2026-08-26', + 'version' => '2.6.6', + 'release_date' => '2026-08-27', 'codename' => 'Lyra', 'status' => 'stable',