Skip to content
Merged
11 changes: 11 additions & 0 deletions formwork/schemes/plugins/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
title: Plugin

fields:
enabled:
label: Enabled
type: togglegroup
options:
1: Enabled
0: Disabled
default: 0
visible: false
51 changes: 27 additions & 24 deletions formwork/src/Panel/Controllers/PluginsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}", []));

Expand All @@ -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());
}
Expand All @@ -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'));
}
Expand Down Expand Up @@ -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');
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Formwork\Plugins\Exceptions;

use RuntimeException;

class PluginInitializationException extends RuntimeException {}
6 changes: 6 additions & 0 deletions formwork/src/Plugins/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ final public function __construct(
) {
$this->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));
}
Expand Down Expand Up @@ -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
{
Expand Down
24 changes: 20 additions & 4 deletions formwork/src/Plugins/Plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -44,21 +46,35 @@ 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
{
if (($plugin = $this->get($name)) === null) {
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);
Comment thread
giuscris marked this conversation as resolved.
}

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();
}

/**
Expand Down
31 changes: 31 additions & 0 deletions formwork/src/Schemes/Scheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
12 changes: 8 additions & 4 deletions panel/src/ts/components/views/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ 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() {
$$<HTMLInputElement>(".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", () => {
if (!action) {
return;
}

fieldset.disabled = true;
togglegroup.element.disabled = true;

throttle(() => {
new Request(
Expand All @@ -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();
Comment thread
giuscris marked this conversation as resolved.
fieldset.disabled = false;
if (response.status === "error") {
togglegroup.value = toggle.value === "1" ? "0" : "1";
}
togglegroup.element.disabled = false;
}
},
);
Expand Down
1 change: 1 addition & 0 deletions panel/translations/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/el.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: Το πρόσθετο ενεργοποιήθηκε
Expand Down
1 change: 1 addition & 0 deletions panel/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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é
Expand Down
1 change: 1 addition & 0 deletions panel/translations/hu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/nl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/pl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/pt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/ro.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: Плагин включён
Expand Down
1 change: 1 addition & 0 deletions panel/translations/sv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/tr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions panel/translations/uk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: Плагін увімкнено
Expand Down