diff --git a/formwork/schemes/plugins/plugin.yaml b/formwork/schemes/plugins/plugin.yaml new file mode 100644 index 000000000..855a7ccf6 --- /dev/null +++ b/formwork/schemes/plugins/plugin.yaml @@ -0,0 +1,11 @@ +title: Plugin + +fields: + enabled: + label: Enabled + type: togglegroup + options: + 1: Enabled + 0: Disabled + default: 0 + visible: false diff --git a/formwork/src/Panel/Controllers/PluginsController.php b/formwork/src/Panel/Controllers/PluginsController.php index 9eb7bfe3e..2ce68a2fc 100644 --- a/formwork/src/Panel/Controllers/PluginsController.php +++ b/formwork/src/Panel/Controllers/PluginsController.php @@ -2,11 +2,11 @@ namespace Formwork\Panel\Controllers; -use Formwork\Fields\FieldCollection; use Formwork\Http\JsonResponse; use Formwork\Http\Response; use Formwork\Http\ResponseStatus; use Formwork\Parsers\Yaml; +use Formwork\Plugins\Exceptions\PluginInitializationException; use Formwork\Plugins\Plugin; use Formwork\Plugins\Plugins; use Formwork\Router\RouteParams; @@ -55,19 +55,7 @@ public function plugin(RouteParams $routeParams, Plugins $plugins): Response return $this->forward(ErrorsController::class, 'notFound'); } - $scheme = $this->getPluginScheme($plugin); - - // If no scheme, just show plugin info - if ($scheme === null) { - return new Response($this->view('@panel.plugins.plugin', [ - 'title' => $plugin->manifest()->title() ?? $plugin->name(), - 'plugin' => $plugin, - 'fields' => new FieldCollection(), - ...$this->getPreviousAndNextPlugin($plugin), - ])); - } - - $fields = $scheme->fields(); + $fields = $this->getPluginScheme($plugin)->fields(); $fields->setValues($this->config->getArray("plugins.{$name}", [])); @@ -87,7 +75,7 @@ public function plugin(RouteParams $routeParams, Plugins $plugins): Response return new Response($this->view('@panel.plugins.plugin', [ 'title' => $plugin->manifest()->title() ?? $plugin->name(), 'plugin' => $plugin, - 'fields' => $form->fields(), + 'fields' => $form->fields()->reject(fn($field) => $field->name() === 'enabled'), ...$this->getPreviousAndNextPlugin($plugin), ]), $form->getResponseStatus()); } @@ -109,6 +97,14 @@ public function enable(RouteParams $routeParams, Plugins $plugins): Response $this->togglePluginStatus($plugin, true); + try { + $plugins->initialize($name); + } catch (PluginInitializationException) { + $this->togglePluginStatus($plugin, false); + $this->panel->notify($this->translate('panel.plugins.plugin.cannotEnable.initializationError'), 'error'); + return JsonResponse::error($this->translate('panel.plugins.plugin.cannotEnable.initializationError'), ResponseStatus::InternalServerError); + } + $this->panel->notify($this->translate('panel.plugins.plugin.enabled'), 'success'); return JsonResponse::success($this->translate('panel.plugins.plugin.enabled')); } @@ -159,25 +155,32 @@ private function updatePluginsOptions(Plugin $plugin, array $options): void /** * Get scheme for a given plugin */ - private function getPluginScheme(Plugin $plugin): ?Scheme + private function getPluginScheme(Plugin $plugin): Scheme { $id = $plugin->id(); $schemes = $this->app->schemes(); if ($schemes->has("plugins.{$id}")) { - return $schemes->get("plugins.{$id}"); + $scheme = $schemes->get("plugins.{$id}"); + } else { + // Try to load scheme from plugin path + $path = FileSystem::joinPaths($plugin->path(), "schemes/plugins/{$id}.yaml"); + + if (FileSystem::exists($path)) { + $schemes->load("plugins.{$id}", $path); + $scheme = $schemes->get("plugins.{$id}"); + } } - // Try to load scheme from plugin path - $path = FileSystem::joinPaths($plugin->path(), "schemes/plugins/{$id}.yaml"); - - if (FileSystem::exists($path)) { - $schemes->load("plugins.{$id}", $path); - return $schemes->get("plugins.{$id}"); + // Require that the scheme extends the base plugin scheme, + // so that the `enabled` field is always present + if (isset($scheme) && !$scheme->extendsScheme('plugins.plugin')) { + // @phpstan-ignore argument.type + $scheme->extendWith($schemes->get('plugins.plugin')->toArray()); } - return null; + return $scheme ?? $schemes->get('plugins.plugin'); } /** diff --git a/formwork/src/Plugins/Exceptions/PluginInitializationException.php b/formwork/src/Plugins/Exceptions/PluginInitializationException.php new file mode 100644 index 000000000..316633f1c --- /dev/null +++ b/formwork/src/Plugins/Exceptions/PluginInitializationException.php @@ -0,0 +1,7 @@ +id = basename($this->path); + if ($this->id === 'plugin') { + throw new InvalidArgumentException('Invalid plugin id "plugin". The plugin id "plugin" is reserved.'); + } + if (!preg_match('/^[a-z0-9-]+$/', $this->id)) { throw new InvalidArgumentException(sprintf('Invalid plugin id "%s". Plugin ids can only contain lowercase letters, numbers and hyphens.', $this->id)); } @@ -127,6 +131,8 @@ final public function isInitialized(): bool /** * Get the plugin autoloader + * + * @internal This method is called during plugin initialization. Since it can have side effects, it should not be called directly */ public function autoload(): ?ClassLoader { diff --git a/formwork/src/Plugins/Plugins.php b/formwork/src/Plugins/Plugins.php index f1c1ef6b4..315e208a5 100644 --- a/formwork/src/Plugins/Plugins.php +++ b/formwork/src/Plugins/Plugins.php @@ -5,9 +5,11 @@ use Formwork\Config\Config; use Formwork\Events\EventDispatcher; use Formwork\Plugins\Events\PluginsInitializedEvent; +use Formwork\Plugins\Exceptions\PluginInitializationException; use Formwork\Utils\FileSystem; use Formwork\Utils\Str; use InvalidArgumentException; +use Throwable; use UnexpectedValueException; /** @@ -44,7 +46,8 @@ public function loadFromPath(string $path): void /** * Initialize a plugin from id * - * @throws InvalidArgumentException If the plugin id is invalid + * @throws InvalidArgumentException If the plugin id is invalid + * @throws PluginInitializationException If the plugin autoload or initialization fails */ public function initialize(string $name): void { @@ -52,13 +55,26 @@ public function initialize(string $name): void throw new InvalidArgumentException(sprintf('Invalid plugin "%s"', $name)); } - $plugin->autoload()?->register(); + try { + if ($autoload = $plugin->autoload()) { + // Requiring vendor/autoload.php always prepends the autoloader to the stack, + // so we need to unregister and re-register without prepending + $autoload->unregister(); + $autoload->register(prepend: false); + } + } catch (Throwable $e) { + throw new PluginInitializationException(sprintf('Failed autoload for plugin "%s"', $name), $e->getCode(), previous: $e); + } + + try { + $plugin->initialize(); + } catch (Throwable $e) { + throw new PluginInitializationException(sprintf('Failed initialization for plugin "%s"', $name), $e->getCode(), previous: $e); + } foreach ($plugin->getEventListeners() as $eventName => $eventListener) { $this->eventDispatcher->on($eventName, $plugin->{$eventListener}(...)); } - - $plugin->initialize(); } /** diff --git a/formwork/src/Schemes/Scheme.php b/formwork/src/Schemes/Scheme.php index 00441987f..998483713 100644 --- a/formwork/src/Schemes/Scheme.php +++ b/formwork/src/Schemes/Scheme.php @@ -201,6 +201,37 @@ public function getExtendedScheme(): ?Scheme return isset($this->data['extend']) ? $this->schemes->get($this->data['extend']) : null; } + /** + * Check if the scheme extends another scheme + * + * @throws RecursionException If there is recursion in scheme extension + */ + public function extendsScheme(Scheme|string $scheme): bool + { + $id = $scheme instanceof Scheme ? $scheme->id : $scheme; + + $extended = $this->getExtendedScheme(); + + $visited = [$this->id => true]; + + while ($extended instanceof Scheme) { + $extendedId = $extended->id; + + if (isset($visited[$extendedId])) { + throw new RecursionException(sprintf('Recursion in the extension of the scheme "%s". Extension chain: "%s"', $this->id, implode('" > "', array_keys($visited)))); + } + + if ($extendedId === $id) { + return true; + } + + $visited[$extendedId] = true; + $extended = $extended->getExtendedScheme(); + } + + return false; + } + /** * Translate a value */ diff --git a/panel/src/ts/components/views/plugins.ts b/panel/src/ts/components/views/plugins.ts index 9a9e45ef5..8debf6417 100644 --- a/panel/src/ts/components/views/plugins.ts +++ b/panel/src/ts/components/views/plugins.ts @@ -3,11 +3,12 @@ import { app } from "../../app"; import { Notification } from "../notification"; import { Request } from "../../utils/request"; import { throttle } from "../../utils/events"; +import { TogglegroupInput } from "../inputs/togglegroup-input"; export class Plugins { constructor() { $$(".plugin-status-toggle").forEach((toggle) => { - const fieldset = toggle.closest(".form-togglegroup") as HTMLFieldSetElement; + const togglegroup = new TogglegroupInput(toggle.closest(".form-togglegroup") as HTMLFieldSetElement); const action = toggle.dataset.action; toggle.addEventListener("change", () => { @@ -15,7 +16,7 @@ export class Plugins { return; } - fieldset.disabled = true; + togglegroup.element.disabled = true; throttle(() => { new Request( @@ -25,12 +26,15 @@ export class Plugins { data: { "csrf-token": app.config.csrfToken as string }, }, (response) => { - if (response.status === "success" && !app.forms["plugin-form"]?.hasChanged()) { + if (!app.forms["plugin-form"]?.hasChanged()) { window.location.reload(); } else { const notification = new Notification(response.message, response.status); notification.show(); - fieldset.disabled = false; + if (response.status === "error") { + togglegroup.value = toggle.value === "1" ? "0" : "1"; + } + togglegroup.element.disabled = false; } }, ); diff --git a/panel/translations/de.yaml b/panel/translations/de.yaml index 4e14c863d..927b666e9 100644 --- a/panel/translations/de.yaml +++ b/panel/translations/de.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Seite anzeigen panel.panel: Administrationspanel panel.plugins.info: Info panel.plugins.nextPlugin: Nächstes Plugin +panel.plugins.plugin.cannotEnable.initializationError: Plugin kann nicht aktiviert werden. Beim Initialisieren des Plugins ist ein Fehler aufgetreten. panel.plugins.plugin.cannotSave.invalidFields: Plugin-Optionen können nicht gespeichert werden, einige Felder sind ungültig panel.plugins.plugin.disabled: Plugin deaktiviert panel.plugins.plugin.enabled: Plugin aktiviert diff --git a/panel/translations/el.yaml b/panel/translations/el.yaml index da9a452fa..a1e2f8e8b 100644 --- a/panel/translations/el.yaml +++ b/panel/translations/el.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Προβολή σελίδας panel.panel: Πίνακας διαχείρισης panel.plugins.info: Πληροφορίες panel.plugins.nextPlugin: Επόμενο πρόσθετο +panel.plugins.plugin.cannotEnable.initializationError: Δεν είναι δυνατή η ενεργοποίηση του πρόσθετου. Παρουσιάστηκε σφάλμα κατά την αρχικοποίηση του πρόσθετου. panel.plugins.plugin.cannotSave.invalidFields: Δεν είναι δυνατή η αποθήκευση των επιλογών του πρόσθετου, ορισμένα πεδία δεν είναι έγκυρα panel.plugins.plugin.disabled: Το πρόσθετο απενεργοποιήθηκε panel.plugins.plugin.enabled: Το πρόσθετο ενεργοποιήθηκε diff --git a/panel/translations/en.yaml b/panel/translations/en.yaml index 1b3f32be2..42f59d9c9 100644 --- a/panel/translations/en.yaml +++ b/panel/translations/en.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: View page panel.panel: Administration panel panel.plugins.info: Info panel.plugins.nextPlugin: Next plugin +panel.plugins.plugin.cannotEnable.initializationError: Cannot enable plugin. An error occurred while initializing the plugin. panel.plugins.plugin.cannotSave.invalidFields: Cannot save plugin options, some fields are invalid panel.plugins.plugin.disabled: Plugin disabled panel.plugins.plugin.enabled: Plugin enabled diff --git a/panel/translations/es.yaml b/panel/translations/es.yaml index 0e74d96d0..e8f8abb95 100644 --- a/panel/translations/es.yaml +++ b/panel/translations/es.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Ver página panel.panel: Panel de administración panel.plugins.info: Info panel.plugins.nextPlugin: Plugin siguiente +panel.plugins.plugin.cannotEnable.initializationError: No se puede activar el plugin. Se produjo un error al inicializar el plugin. panel.plugins.plugin.cannotSave.invalidFields: No se pueden guardar las opciones del plugin, algunos campos no son válidos panel.plugins.plugin.disabled: Plugin deshabilitado panel.plugins.plugin.enabled: Plugin habilitado diff --git a/panel/translations/fr.yaml b/panel/translations/fr.yaml index 2fedf19e6..0726f3acd 100644 --- a/panel/translations/fr.yaml +++ b/panel/translations/fr.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Voir la page panel.panel: Panneau d’administration panel.plugins.info: Info panel.plugins.nextPlugin: Plugin suivant +panel.plugins.plugin.cannotEnable.initializationError: Impossible d’activer le plugin. Une erreur s’est produite lors de l’initialisation du plugin. panel.plugins.plugin.cannotSave.invalidFields: Impossible d’enregistrer les options du plugin, certains champs sont invalides panel.plugins.plugin.disabled: Plugin désactivé panel.plugins.plugin.enabled: Plugin activé diff --git a/panel/translations/hu.yaml b/panel/translations/hu.yaml index 0735e92d3..9bae88068 100644 --- a/panel/translations/hu.yaml +++ b/panel/translations/hu.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Oldal megtekintése panel.panel: Adminisztrációs felület panel.plugins.info: Információ panel.plugins.nextPlugin: Következő bővítmény +panel.plugins.plugin.cannotEnable.initializationError: Nem lehet engedélyezni a bővítményt. Hiba történt a bővítmény inicializálása során. panel.plugins.plugin.cannotSave.invalidFields: Nem lehet menteni a bővítmény beállításait, egyes mezők érvénytelenek panel.plugins.plugin.disabled: Bővítmény letiltva panel.plugins.plugin.enabled: Bővítmény engedélyezve diff --git a/panel/translations/it.yaml b/panel/translations/it.yaml index 11dfdf061..aa87aee49 100644 --- a/panel/translations/it.yaml +++ b/panel/translations/it.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Visualizza pagina panel.panel: Pannello di amministrazione panel.plugins.info: Info panel.plugins.nextPlugin: Plugin successivo +panel.plugins.plugin.cannotEnable.initializationError: Impossibile abilitare il plugin. Si è verificato un errore durante l’inizializzazione del plugin. panel.plugins.plugin.cannotSave.invalidFields: Impossibile salvare le impostazioni del plugin, alcuni campi non sono validi panel.plugins.plugin.disabled: Plugin disabilitato panel.plugins.plugin.enabled: Plugin abilitato diff --git a/panel/translations/nl.yaml b/panel/translations/nl.yaml index 6cbb87444..42c7c32a7 100644 --- a/panel/translations/nl.yaml +++ b/panel/translations/nl.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Bekijk pagina panel.panel: Administratiepaneel panel.plugins.info: Info panel.plugins.nextPlugin: Volgende plugin +panel.plugins.plugin.cannotEnable.initializationError: Kan plugin niet inschakelen. Er is een fout opgetreden bij het initialiseren van de plugin. panel.plugins.plugin.cannotSave.invalidFields: Pluginopties kunnen niet worden opgeslagen, sommige velden zijn ongeldig panel.plugins.plugin.disabled: Plugin uitgeschakeld panel.plugins.plugin.enabled: Plugin ingeschakeld diff --git a/panel/translations/pl.yaml b/panel/translations/pl.yaml index 80a57bad7..1bbce0a09 100644 --- a/panel/translations/pl.yaml +++ b/panel/translations/pl.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Wyświetl stronę panel.panel: Panel administracyjny panel.plugins.info: Info panel.plugins.nextPlugin: Następna wtyczka +panel.plugins.plugin.cannotEnable.initializationError: Nie można włączyć wtyczki. Wystąpił błąd podczas inicjalizacji wtyczki. panel.plugins.plugin.cannotSave.invalidFields: Nie można zapisać opcji wtyczki, niektóre pola są nieprawidłowe panel.plugins.plugin.disabled: Wtyczka wyłączona panel.plugins.plugin.enabled: Wtyczka włączona diff --git a/panel/translations/pt.yaml b/panel/translations/pt.yaml index b67efc561..53879339c 100644 --- a/panel/translations/pt.yaml +++ b/panel/translations/pt.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Ver página panel.panel: Painel de administração panel.plugins.info: Info panel.plugins.nextPlugin: Plugin seguinte +panel.plugins.plugin.cannotEnable.initializationError: Não é possível ativar o plugin. Ocorreu um erro ao inicializar o plugin. panel.plugins.plugin.cannotSave.invalidFields: Não é possível guardar as opções do plugin, alguns campos são inválidos panel.plugins.plugin.disabled: Plugin desativado panel.plugins.plugin.enabled: Plugin ativado diff --git a/panel/translations/ro.yaml b/panel/translations/ro.yaml index 6793fe8ff..f3409f499 100644 --- a/panel/translations/ro.yaml +++ b/panel/translations/ro.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Vezi pagina panel.panel: Panoul de administrare panel.plugins.info: Info panel.plugins.nextPlugin: Plugin următor +panel.plugins.plugin.cannotEnable.initializationError: Pluginul nu poate fi activat. A apărut o eroare la inițializarea pluginului. panel.plugins.plugin.cannotSave.invalidFields: Nu se pot salva opțiunile pluginului, unele câmpuri sunt nevalide panel.plugins.plugin.disabled: Plugin dezactivat panel.plugins.plugin.enabled: Plugin activat diff --git a/panel/translations/ru.yaml b/panel/translations/ru.yaml index 062cb4898..e1725a07b 100644 --- a/panel/translations/ru.yaml +++ b/panel/translations/ru.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Просмотреть страницу panel.panel: Панель администрирования panel.plugins.info: Информация panel.plugins.nextPlugin: Следующий плагин +panel.plugins.plugin.cannotEnable.initializationError: Невозможно включить плагин. Произошла ошибка при инициализации плагина. panel.plugins.plugin.cannotSave.invalidFields: Не удалось сохранить настройки плагина, некоторые поля недействительны panel.plugins.plugin.disabled: Плагин отключён panel.plugins.plugin.enabled: Плагин включён diff --git a/panel/translations/sv.yaml b/panel/translations/sv.yaml index 83edd68b3..81f836e90 100644 --- a/panel/translations/sv.yaml +++ b/panel/translations/sv.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Visa sida panel.panel: Administrationspanel panel.plugins.info: Information panel.plugins.nextPlugin: Nästa plugin +panel.plugins.plugin.cannotEnable.initializationError: Kan inte aktivera pluginet. Ett fel uppstod när pluginet initierades. panel.plugins.plugin.cannotSave.invalidFields: Kan inte spara plugin-alternativ, vissa fält är ogiltiga panel.plugins.plugin.disabled: Plugin inaktiverad panel.plugins.plugin.enabled: Plugin aktiverad diff --git a/panel/translations/tr.yaml b/panel/translations/tr.yaml index 72ee976ff..8dd97e2d2 100644 --- a/panel/translations/tr.yaml +++ b/panel/translations/tr.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Sayfayı görüntüle panel.panel: Yönetim paneli panel.plugins.info: Bilgi panel.plugins.nextPlugin: Sonraki eklenti +panel.plugins.plugin.cannotEnable.initializationError: Eklenti etkinleştirilemiyor. Eklenti başlatılırken bir hata oluştu. panel.plugins.plugin.cannotSave.invalidFields: Eklenti seçenekleri kaydedilemiyor, bazı alanlar geçersiz panel.plugins.plugin.disabled: Eklenti devre dışı panel.plugins.plugin.enabled: Eklenti etkin diff --git a/panel/translations/uk.yaml b/panel/translations/uk.yaml index 53bd7675c..ac4c611d4 100644 --- a/panel/translations/uk.yaml +++ b/panel/translations/uk.yaml @@ -277,6 +277,7 @@ panel.pages.viewPage: Переглянути сторінку panel.panel: Панель адміністратора panel.plugins.info: Інформація panel.plugins.nextPlugin: Наступний плагін +panel.plugins.plugin.cannotEnable.initializationError: Неможливо ввімкнути плагін. Під час ініціалізації плагіна сталася помилка. panel.plugins.plugin.cannotSave.invalidFields: Не вдалося зберегти налаштування плагіна, деякі поля недійсні panel.plugins.plugin.disabled: Плагін вимкнено panel.plugins.plugin.enabled: Плагін увімкнено