Files
KantSites/src/Twig/PluginExtension.php
T
kantarciandClaude Opus 5 711af312af Pick images, pages, forms, sections and terms instead of typing ids and addresses
The share image and featured image of a page become image fields with the library dialog,
as do image settings of plugins. The editor offers forms, sections, categories and tags as
lists, a page search next to every link target and in text links, the redirect target has a
page search, and the form editor picks the reply field, the condition and the option images.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 09:35:05 +02:00

76 lines
2.8 KiB
PHP

<?php
namespace App\Twig;
use App\Content\MediaLibrary;
use App\Domain\InvalidDocument;
use App\Plugin\PluginRegistry;
use Twig\Extension\AbstractExtension;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;
/**
* Was eine Plugin-Vorlage vom Kern bekommt: die Definition ihres Bausteins, die geprüften
* Eigenschaften und Bilder aus der Mediathek. Mehr nicht - keine Datenbank, keine Einstellungen.
*/
final class PluginExtension extends AbstractExtension
{
public function __construct(private PluginRegistry $plugins, private MediaLibrary $media, FilesystemLoader $loader)
{
// Der Namensraum @plugins zeigt auf den Pluginordner - aber nur, wenn es ihn gibt. Ein
// fester Pfad in twig.yaml liesse eine Installation ohne plugins/ mit einem Ladefehler
// stehen, bevor sie eine einzige Seite ausgeliefert hat.
$directory = $plugins->directory();
if (is_dir($directory) && !in_array($directory, $loader->getPaths('plugins'), true)) {
$loader->addPath($directory, 'plugins');
}
}
public function getFunctions(): array
{
return [
new TwigFunction('kant_plugin_block', $this->plugins->block(...)),
new TwigFunction('kant_plugin_props', $this->props(...)),
new TwigFunction('kant_plugin_media', $this->mediaItem(...)),
new TwigFunction('kant_plugin_settings', $this->plugins->settingsOf(...)),
new TwigFunction('kant_media', $this->adminMedia(...)),
];
}
/**
* Ein Bild fuer ein Bildfeld der Verwaltung - auch ein archiviertes: es ist noch gewaehlt und
* wird noch ausgeliefert, also soll es auch zu sehen sein.
*/
public function adminMedia(string $id): ?array
{
return 1 === preg_match('/^[a-f0-9]{32}$/D', $id) ? $this->media->find($id) : null;
}
/** Die geprüften Eigenschaften eines Bausteins - oder null, wenn sie nicht ins Schema passen. */
public function props(array $block): ?array
{
try {
return $this->plugins->validateProps((string) ($block['type'] ?? ''), is_array($block['props'] ?? null) ? $block['props'] : []);
} catch (InvalidDocument) {
return null;
}
}
/**
* Ein Bild der Mediathek für eine Vorlage: Kennung, Titel, Alternativtext und die Fassungen
* (src, width, height), die kleinste zuerst.
*/
public function mediaItem(string $id): ?array
{
if (1 !== preg_match('/^[a-f0-9]{32}$/D', $id)) {
return null;
}
$item = $this->media->find($id);
if (null === $item || ($item['archived'] ?? false)) {
return null;
}
return ['id' => $item['id'], 'title' => (string) ($item['title'] ?? ''), 'alt' => (string) ($item['alt'] ?? ''), 'variants' => $item['variants']];
}
}