A plugin can now bring a plugin.php that registers hooks, scheduled tasks, its own tables, public pages and admin screens. Failures stay contained to the plugin; a form on a plugin page can be submitted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
5.6 KiB
PHP
118 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Ops\Scheduler;
|
|
use App\Plugin\PluginRegistry;
|
|
use App\Plugin\PluginRuntime;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
|
|
/**
|
|
* Der Bildschirm „Plugins“: was im Ordner liegt, was davon eingeschaltet ist, der Schalter, die
|
|
* Einstellungen eines Plugins.
|
|
*/
|
|
final class PluginAdminController extends AbstractController
|
|
{
|
|
public function __construct(private PluginRegistry $plugins, private PluginRuntime $runtime, private Scheduler $scheduler)
|
|
{
|
|
}
|
|
|
|
#[Route('/%kant.admin_path%/plugins', name: 'admin_plugins', methods: ['GET'])]
|
|
public function index(): Response
|
|
{
|
|
// Ein Plugin bringt Code auf die Website - das schaltet nur ein Administrator.
|
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
|
|
|
$admin = [];
|
|
foreach ($this->plugins->all() as $name => $plugin) {
|
|
$admin[$name] = $this->runtime->admin($name);
|
|
}
|
|
|
|
return $this->render('admin/plugins.html.twig', ['plugins' => $this->plugins->all(), 'directory' => $this->plugins->directory(), 'admin' => $admin, 'runtimeErrors' => $this->runtime->errors()]);
|
|
}
|
|
|
|
#[Route('/%kant.admin_path%/plugins/{name}/{state}', name: 'admin_plugin_toggle', requirements: ['name' => '[a-z][a-z0-9]{1,30}', 'state' => 'an|aus'], methods: ['POST'])]
|
|
public function toggle(Request $request, string $name, string $state): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
|
if (!$this->isCsrfTokenValid('plugins', $request->request->getString('_csrf_token'))) {
|
|
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
|
|
}
|
|
try {
|
|
if ('an' === $state) {
|
|
$this->runtime->activate($name);
|
|
$this->scheduler->pluginsChanged();
|
|
|
|
return $this->back(sprintf('„%s“ ist eingeschaltet. Seine Bausteine stehen jetzt im Editor unter „Erweiterungen“.', $this->plugins->all()[$name]['title']), 'success');
|
|
}
|
|
$this->runtime->deactivate($name);
|
|
$this->scheduler->pluginsChanged();
|
|
|
|
return $this->back(sprintf('„%s“ ist ausgeschaltet. Seine Bausteine bleiben in den Seiten gespeichert, werden aber nicht mehr dargestellt.', $this->plugins->all()[$name]['title']), 'success');
|
|
} catch (\InvalidArgumentException $e) {
|
|
return $this->back($e->getMessage());
|
|
}
|
|
}
|
|
|
|
#[Route('/%kant.admin_path%/plugins/{name}/einstellungen', name: 'admin_plugin_settings', requirements: ['name' => '[a-z][a-z0-9]{1,30}'], methods: ['POST'])]
|
|
public function settings(Request $request, string $name): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
|
if (!$this->isCsrfTokenValid('plugins', $request->request->getString('_csrf_token'))) {
|
|
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
|
|
}
|
|
try {
|
|
$this->plugins->saveSettings($name, $request->request->all('settings'));
|
|
} catch (\InvalidArgumentException $e) {
|
|
return $this->back($e->getMessage());
|
|
}
|
|
|
|
return $this->back(sprintf('Die Einstellungen von „%s“ wurden gespeichert.', $this->plugins->all()[$name]['title']), 'success');
|
|
}
|
|
|
|
#[Route('/%kant.admin_path%/plugins/{name}/seite/{key}', name: 'admin_plugin_page', requirements: ['name' => '[a-z][a-z0-9]{1,30}', 'key' => '[a-z][a-z0-9-]{0,40}'], methods: ['GET'])]
|
|
public function page(string $name, string $key): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
|
$admin = $this->runtime->admin($name);
|
|
$page = $admin['pages'][$key] ?? throw $this->createNotFoundException();
|
|
|
|
return $this->render('admin/plugin-page.html.twig', ['plugin' => $this->plugins->all()[$name], 'page' => $page, 'actions' => $admin['actions'], 'data' => ($page['callback'])()]);
|
|
}
|
|
|
|
#[Route('/%kant.admin_path%/plugins/{name}/aktion/{key}', name: 'admin_plugin_action', requirements: ['name' => '[a-z][a-z0-9]{1,30}', 'key' => '[a-z][a-z0-9-]{0,40}'], methods: ['POST'])]
|
|
public function action(Request $request, string $name, string $key): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
|
if (!$this->isCsrfTokenValid('plugins', $request->request->getString('_csrf_token'))) {
|
|
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
|
|
}
|
|
$admin = $this->runtime->admin($name);
|
|
$action = $admin['actions'][$key] ?? throw $this->createNotFoundException();
|
|
try {
|
|
if (is_string($action['handler'])) {
|
|
$result = $this->scheduler->runNow(PluginRuntime::taskName($name, $action['handler']));
|
|
[$type, $message] = null === $result ? ['error', 'Die Aufgabe läuft gerade schon.'] : ['ok' === $result['status'] ? 'success' : 'error', $result['message']];
|
|
} else {
|
|
[$type, $message] = ['success', (string) ($action['handler'])()];
|
|
}
|
|
} catch (\Throwable $e) {
|
|
[$type, $message] = ['error', $e->getMessage()];
|
|
}
|
|
$this->addFlash($type, $message);
|
|
$back = $request->request->getString('zurueck');
|
|
|
|
return isset($admin['pages'][$back]) ? $this->redirectToRoute('admin_plugin_page', ['name' => $name, 'key' => $back]) : $this->redirectToRoute('admin_plugins');
|
|
}
|
|
|
|
private function back(string $message, string $type = 'error'): Response
|
|
{
|
|
$this->addFlash($type, $message);
|
|
|
|
return $this->redirectToRoute('admin_plugins');
|
|
}
|
|
}
|