From 917c64eaa94e38e51004e565ba312e7d05deb503 Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Wed, 24 Jun 2026 23:35:07 +0200 Subject: [PATCH 1/3] fix(plugins): show a settings button for ANY plugin with a settings page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin Plugins list only rendered a settings button for a few hardcoded plugins (open-library, api-book-scraper, goodlib, discogs). Any other plugin with a working settings page — notably Mobile API, whose 'Abilita accesso app mobile' gate (mobile_api.enabled) lives there — was unreachable from the UI, so the gate could never be turned on and the app got 'mobile access disabled'. PluginController::index now flags which active plugins expose a settings page (hasSettingsPage + getSettingsViewPath), and the list shows a generic 'Impostazioni' link to /admin/plugins/{id}/settings for them. The custom modals of open-library / api-book-scraper / goodlib are preserved; discogs now flows through the generic path (same link it already used). No new i18n key. Verified live: the Mobile API card now shows 'Impostazioni' linking to its settings page with the app-access toggle; existing plugins unaffected. PHPStan L5 clean. --- app/Controllers/PluginController.php | 20 ++++++++++++++++++++ app/Views/admin/plugins.php | 12 +++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/Controllers/PluginController.php b/app/Controllers/PluginController.php index 39ec429f..84954632 100644 --- a/app/Controllers/PluginController.php +++ b/app/Controllers/PluginController.php @@ -40,9 +40,29 @@ public function index(Request $request, Response $response): Response $plugins = $this->pluginManager->getAllPlugins(); $pluginSettings = []; + // Which plugins expose a settings page (so the list can show a generic + // "Impostazioni" button for ANY such plugin, not just a hardcoded few — + // otherwise e.g. the Mobile API gate is unreachable from the UI). + $pluginHasSettings = []; foreach ($plugins as $plugin) { $settings = $this->pluginManager->getSettings((int) $plugin['id']); + // Settings-page detection: only meaningful for active plugins (the + // instance must be loaded). Mirror the settings-route guard. + $hasSettingsPage = false; + if (!empty($plugin['is_active'])) { + try { + $instance = $this->pluginManager->getPluginInstance((int) $plugin['id']); + $hasSettingsPage = $instance !== null + && is_callable([$instance, 'hasSettingsPage']) + && $instance->hasSettingsPage() + && is_callable([$instance, 'getSettingsViewPath']); + } catch (\Throwable $e) { + $hasSettingsPage = false; + } + } + $pluginHasSettings[$plugin['id']] = $hasSettingsPage; + // Handle Google Books API key if (array_key_exists('google_books_api_key', $settings)) { $settings['google_books_api_key_exists'] = $settings['google_books_api_key'] !== ''; diff --git a/app/Views/admin/plugins.php b/app/Views/admin/plugins.php index 1ef8d3b3..4ddc9ac8 100644 --- a/app/Views/admin/plugins.php +++ b/app/Views/admin/plugins.php @@ -5,6 +5,8 @@ $pageTitle = __('Gestione Plugin'); $pluginSettings = $pluginSettings ?? []; +/** @var array $pluginHasSettings */ +$pluginHasSettings = $pluginHasSettings ?? []; ?>
@@ -296,7 +298,15 @@ class="px-4 py-2 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 tr - + + From af6883d5a7aeedef3a518c5d6b13b1358081bced Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Wed, 24 Jun 2026 23:56:07 +0200 Subject: [PATCH 2/3] fix(plugins): persist settings for self-rendering plugin pages (Mobile API toggle) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The POST /admin/plugins/{id}/settings route always called updateSettings(), whose hardcoded if/elseif chain bailed with 'plugin non supporta impostazioni' for mobile-api — so the 'Abilita accesso app mobile' toggle (and push config) could never be saved from the UI, and the app kept seeing app_access_enabled=false. The Mobile API settings page is a normal (non-AJAX) form that handles its own POST inside the view (CSRF + plugin->saveSettings() + success message). The legacy AJAX handlers require a nested 'settings' payload; when it's absent the request is such a self-rendering form, so delegate to settingsPage() and let the view's own POST logic run. Generic — fixes mobile-api and any future self-rendering settings page; AJAX plugins (which always send 'settings') are unaffected. Verified end-to-end: toggling 'Abilita accesso app mobile' now flips system_settings mobile_api.enabled 0->1. PHPStan L5 clean. --- app/Controllers/PluginController.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Controllers/PluginController.php b/app/Controllers/PluginController.php index 84954632..5d416052 100644 --- a/app/Controllers/PluginController.php +++ b/app/Controllers/PluginController.php @@ -320,6 +320,16 @@ public function updateSettings(Request $request, Response $response, array $args return $response->withHeader('Content-Type', 'application/json')->withStatus(404); } + // Self-rendering settings pages (e.g. Mobile API) post flat form fields and + // handle their OWN POST inside the view — CSRF, persistence via the plugin's + // saveSettings(), success message, re-render. The legacy AJAX handlers below + // require a nested `settings` payload; when it's absent, this is such a + // self-handling form, so render the settings page (which runs that logic) + // instead of falling through to "questo plugin non supporta impostazioni". + if (!is_array($body) || !array_key_exists('settings', $body)) { + return $this->settingsPage($request, $response, $args); + } + error_log('[PluginController] Plugin name: ' . $plugin['name']); $settings = $body['settings'] ?? []; From f6e44116f012b30c1efa53c41f6574c06312e51e Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Wed, 24 Jun 2026 23:59:43 +0200 Subject: [PATCH 3/3] fix(plugins): only show settings button when the view file actually exists Align the settings-page detection with settingsPage()'s guard (is_string + is_file on getSettingsViewPath), so a plugin declaring a settings page with a missing view file doesn't surface an 'Impostazioni' button that 404s on click. Addresses CodeRabbit review on #191. --- app/Controllers/PluginController.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/Controllers/PluginController.php b/app/Controllers/PluginController.php index 5d416052..d2f6d8aa 100644 --- a/app/Controllers/PluginController.php +++ b/app/Controllers/PluginController.php @@ -53,10 +53,17 @@ public function index(Request $request, Response $response): Response if (!empty($plugin['is_active'])) { try { $instance = $this->pluginManager->getPluginInstance((int) $plugin['id']); - $hasSettingsPage = $instance !== null + if ($instance !== null && is_callable([$instance, 'hasSettingsPage']) && $instance->hasSettingsPage() - && is_callable([$instance, 'getSettingsViewPath']); + && is_callable([$instance, 'getSettingsViewPath']) + ) { + // Mirror settingsPage()'s guard exactly: a declared view + // path that doesn't exist on disk must NOT surface a button + // (the click would 404). + $viewPath = $instance->getSettingsViewPath(); + $hasSettingsPage = is_string($viewPath) && is_file($viewPath); + } } catch (\Throwable $e) { $hasSettingsPage = false; }