A plugin can no longer replace the site palette. "Designpaket übernehmen" wrote the plugin's colours, fonts and buttons into the website for good: no confirmation, no way back, right next to a switch that is safe to flip. Colours belong to the design screen, where they have a preview and one-step undo, so the feature is gone rather than guarded (ADR 105). Removed: PluginRegistry::themePackage() and applyTheme(), "theme" in the manifest and in the plugin structure, the admin_plugin_theme route and the Theme dependency of PluginAdminController, the button and the design-package line on the "Plugins" screen, and plugins/nachtblau. A manifest now needs at least one block, one route or one preset. The test says the opposite of what it used to: switching a plugin on and off leaves both the design and the content alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
8.8 KiB
Markdown
184 lines
8.8 KiB
Markdown
# Plugins schreiben
|
||
|
||
Ein Plugin bringt eigene Bausteine, Vorlagen und Adressen in den Editor und auf die
|
||
Website. Farben, Schriften und Buttons fasst es nie an – das Design gehört der Website (ADR 105). Es ist ein Ordner unter `plugins/` mit einer `plugin.json`, Twig-Vorlagen und optional
|
||
Stylesheets und Skripten. Eingeschaltet und eingestellt wird es in der Verwaltung unter **Plugins**
|
||
(Administrator). Hintergrund und Grenzen: ADR 102 und ADR 103 in `docs/ARCHITECTURE.md`.
|
||
|
||
**Ein Plugin bringt keinen PHP-Code mit.** Alles ist Manifest plus Twig. Deshalb gibt es keine
|
||
Hooks in Speichern oder Veröffentlichen und keine eigenen Tabellen – und deshalb kann ein Update
|
||
des Kerns ein Plugin nicht brechen, solange die Übergaben unten stehen.
|
||
|
||
## Der Ordner
|
||
|
||
```
|
||
plugins/
|
||
vergleich/ ← Name des Plugins: Kleinbuchstaben und Ziffern, mit Buchstabe beginnend
|
||
plugin.json ← das Manifest
|
||
blocks/bildvergleich.html.twig
|
||
blocks/galerie.html.twig
|
||
routes/daten.json.twig
|
||
assets/bildvergleich.css ← nur unter assets/ liegt, was die Website ausliefert
|
||
assets/bildvergleich.js
|
||
```
|
||
|
||
Das mitgelieferte Plugin ist die Vorlage zum Abschreiben: `plugins/vergleich` (Bausteine,
|
||
Container, Einstellungen, Adresse, Seitenvorlage).
|
||
|
||
## Das Manifest
|
||
|
||
```json
|
||
{
|
||
"name": "vergleich",
|
||
"title": "Bildvergleich",
|
||
"description": "Vorher und nachher in einem Bild.",
|
||
"version": "1.1.0",
|
||
"requires": "1.17.0",
|
||
"settings": {
|
||
"labelBefore": { "kind": "text", "label": "Standardbeschriftung vorher", "default": "Vorher" }
|
||
},
|
||
"blocks": {
|
||
"bildvergleich": {
|
||
"label": "Bildvergleich",
|
||
"icon": "⇔",
|
||
"template": "blocks/bildvergleich.html.twig",
|
||
"css": "assets/bildvergleich.css",
|
||
"script": "assets/bildvergleich.js",
|
||
"props": {
|
||
"before": { "kind": "media", "label": "Bild vorher" },
|
||
"position": { "kind": "number", "label": "Startposition (%)", "min": 5, "max": 95, "default": 50 }
|
||
}
|
||
},
|
||
"galerie": {
|
||
"label": "Vergleichsgalerie",
|
||
"template": "blocks/galerie.html.twig",
|
||
"container": true,
|
||
"children": ["vergleich.bildvergleich"],
|
||
"props": { "columns": { "kind": "select", "label": "Spalten", "choices": { "2": "Zwei", "3": "Drei" } } }
|
||
}
|
||
},
|
||
"presets": {
|
||
"abschnitt": { "label": "Vorher-Nachher-Abschnitt", "blocks": [ { "id": "s", "type": "section", "children": [ ] } ] }
|
||
},
|
||
"routes": {
|
||
"daten": { "path": "/vergleich/daten", "template": "routes/daten.json.twig", "type": "json" }
|
||
}
|
||
}
|
||
```
|
||
|
||
- `name` muss dem Ordnernamen entsprechen. Der Typ eines Bausteins ist `name.baustein`, hier
|
||
`vergleich.bildvergleich`; so heißt er im Dokument und in der Ausgabe.
|
||
- `version` in der Form `1.2.3`. Sie hängt an den Adressen der Dateien (`?v=1.1.0`), damit der
|
||
Browser nach einem Update die neue Fassung holt.
|
||
- `requires` (optional): die kleinste KantSites-Fassung. Passt sie nicht, erscheint das Plugin in
|
||
der Verwaltung mit dem Grund und lässt sich nicht einschalten.
|
||
- Ein Manifest braucht mindestens einen Baustein, eine Adresse oder eine Vorlage. Fehlt eine
|
||
genannte Datei, ist das Manifest ungültig.
|
||
|
||
### Eigenschaften und Einstellungen (Schema)
|
||
|
||
Eigenschaften (`props` eines Bausteins) und Einstellungen (`settings` des Plugins) haben dasselbe
|
||
Schema: eine Art (`kind`), eine Beschriftung (`label`), optional `help` und `default`.
|
||
|
||
| Art | Feld | Prüfung | Weitere Schlüssel |
|
||
|---|---|---|---|
|
||
| `text` | einzeilig | Text bis `maxLength` (Standard 200) | `maxLength` |
|
||
| `textarea` | mehrzeilig | Text bis `maxLength` (Standard 2000) | `maxLength` |
|
||
| `number` | Zahl | zwischen `min` und `max` | `min`, `max` |
|
||
| `boolean` | Kästchen | an oder aus | – |
|
||
| `select` | Liste | einer der Werte | `choices`: `{"wert": "Beschriftung"}` |
|
||
| `url` | einzeilig | lokaler Pfad, `#sprungmarke`, `https://`, `mailto:`, `tel:` | `maxLength` |
|
||
| `media` | Bild aus der Mediathek | Kennung eines Bildes oder leer | – |
|
||
|
||
Höchstens 30 Eigenschaften je Baustein. Unbekannte Eigenschaften in einem gespeicherten Dokument
|
||
werden ignoriert (so kann ein Plugin eine Eigenschaft streichen), bekannte müssen passen, sonst
|
||
lässt sich die Seite nicht speichern. Einstellungen bearbeitet der Administrator auf dem Bildschirm
|
||
„Plugins“; sie werden genauso geprüft.
|
||
|
||
### Container
|
||
|
||
`"container": true` macht einen Baustein zum Behälter. `"children"` (optional) zählt auf, was
|
||
hinein darf – Kernbausteine wie `text` oder Bausteine des Plugins; ohne die Liste darf alles hinein.
|
||
Der Editor zeigt die Regel im Werkzeugkasten, der Server prüft sie beim Speichern.
|
||
|
||
### Seitenvorlagen (`presets`)
|
||
|
||
Fertige Bausteine, die der Editor als Ganzes einfügt – hinter den eingebauten Vorlagen mit demselben
|
||
Knopf. `blocks` ist eine Liste von Bausteinen wie in einem Dokument (höchstens 64 KiB); Kennungen
|
||
vergibt der Editor bei jedem Einfügen neu, du kannst also feste schreiben.
|
||
|
||
### Eigene Adressen (`routes`)
|
||
|
||
`path` hat die Form einer Seite (`/name` oder `/name/unter/seite`, Slugs) und darf nichts belegen,
|
||
was der Kern beantwortet (`/admin`, `/media`, `/assets`, `/formular`, `/beitraege` …). `type` ist
|
||
`html`, `json`, `text` oder `xml`. Eine veröffentlichte Seite mit derselben Adresse geht vor.
|
||
Ausgeschaltet antwortet die Adresse mit 404; alles außer `html` trägt `noindex`.
|
||
|
||
### Was ein Plugin nicht kann: das Design
|
||
|
||
Es gibt kein `theme` im Manifest. Ein Plugin gestaltet seine eigenen Bausteine über deren
|
||
Stylesheet und die Design-Tokens des Kerns; Palette, Schriften und Buttons der Website ändert
|
||
nur der Betreiber unter **Design**. So färbt kein Schalter eine Website um, und nichts, was dort
|
||
eingestellt wurde, geht durch ein Plugin verloren (ADR 105).
|
||
|
||
## Die Vorlagen
|
||
|
||
### Bausteine
|
||
|
||
Die Vorlage eines Bausteins bekommt genau sechs Dinge:
|
||
|
||
- `props` – die geprüften Eigenschaften, jede vorhanden, fehlende mit ihrem Vorgabewert
|
||
- `settings` – die Einstellungen des Plugins, jede vorhanden
|
||
- `children` – bei einem Container die fertig gerenderten Kinder (leer sonst); ausgeben mit
|
||
`{{ children }}`
|
||
- `block` – der Baustein selbst; `block.id` gehört als `id` an das Wurzelelement, sonst greifen
|
||
eigenes CSS und die Editorauswahl nicht
|
||
- `designClasses` – Klassen aus Sichtbarkeit, Design-Tokens und Einblendungen, an die `class`
|
||
des Wurzelelements anhängen
|
||
- `editing` – `true` in der Editorvorschau, für Hinweise wie „bitte ein Bild wählen“
|
||
|
||
Dazu eine Funktion: `kant_plugin_media(kennung)` liefert ein Bild der Mediathek als
|
||
`{id, title, alt, variants: [{src, width, height}, …]}` (kleinste Fassung zuerst) oder `null`.
|
||
|
||
```twig
|
||
{% set bild = props.before ? kant_plugin_media(props.before) : null %}
|
||
<div id="{{ block.id }}" class="block-bildvergleich{{ designClasses }}" data-position="{{ props.position }}">
|
||
{% if bild %}<img src="{{ (bild.variants|last).src }}" alt="{{ props.alt ?: settings.labelBefore }}">{% endif %}
|
||
{{ children }}
|
||
</div>
|
||
```
|
||
|
||
### Adressen
|
||
|
||
Die Vorlage einer Adresse bekommt `settings`, `query` (die Abfrageparameter mit sauberen Namen,
|
||
gekürzt) und `route`. Für `json` und `xml` schreibt sie die Antwort selbst, etwa mit `json_encode`.
|
||
|
||
Was keine Vorlage bekommt: Datenbank, Einstellungen der Website, Anfrage. Sie läuft mit den
|
||
Rechten der Anwendung – ein Sandkasten ist das nicht, und so wird es auch nicht genannt.
|
||
|
||
## Stylesheet und Skript
|
||
|
||
Beide werden nur auf Seiten geladen, auf denen der Baustein vorkommt, über
|
||
`/plugin-assets/<name>/assets/...`. Die Content-Security-Policy erlaubt Skripte vom eigenen
|
||
Ursprung, sobald die Seite eines braucht; Inline-Stile und Inline-Skripte bleiben verboten. Ein
|
||
Skript setzt Werte deshalb über das CSSOM (`element.style.setProperty(...)`), nie über ein
|
||
`style`-Attribut in der Vorlage. In der Editorvorschau wird das Stylesheet geladen, das Skript
|
||
nicht – die Vorlage muss auch ohne Skript ein brauchbares Bild abgeben.
|
||
|
||
## Was der Kern übernimmt
|
||
|
||
- Speichern, Veröffentlichen, Fassungen, Undo/Redo – der Baustein ist ein gewöhnlicher Block.
|
||
- Eigenes CSS je Block, Design-Tokens (Abstände, Ausrichtung, Ecken), Sichtbarkeit je Gerät,
|
||
Einblendungen – alles über `designClasses` und die `id`.
|
||
- Der Editor: Eintrag im Werkzeugkasten unter „Erweiterungen“, Eigenschaftenpanel aus dem
|
||
Schema, Container-Regeln, Vorlagen, Vorschau über den Server.
|
||
- Der Erhaltungsvertrag: ist das Plugin aus oder fehlt es, bleibt der Baustein im Dokument, der
|
||
Editor zeigt einen Platzhalter, die Website einen HTML-Kommentar. Nichts geht verloren.
|
||
|
||
## Updates
|
||
|
||
Mitgelieferte Plugins liegen im Releasearchiv und werden beim Archiv-Update Datei für Datei
|
||
aktualisiert. Ein selbst installiertes Plugin bleibt dabei stehen, weil sein Ordner im Archiv
|
||
nicht vorkommt. Beim Git-Update ist ein fremder Ordner unter `plugins/` nicht von Git verwaltet;
|
||
`git checkout` lässt ihn in Ruhe. Die Einstellungen liegen in der Datenbank und überleben beides.
|