Forms gained steps, conditions, image choices, number fields with units and a summary step; the DEEG price finder uses all of it. Plugins are manifest plus Twig: blocks, settings, containers, routes, presets and theme packs, switched per installation. The review backlog is done: archive pages render through the normal page pipeline, forms and post lists work inside areas and sections, repeated sections get their own ids, breakpoints and colours come from the palette, backup and update work on MySQL and lock each other out. The task worker no longer shortens a process that has no time limit: set_time_limit() sets the limit instead of extending it, which turned an unlimited CLI run into eighty seconds and killed the full test suite far away from the scheduler. 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, Adressen und Designpakete in den Editor und auf die
|
||
Website. 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
|
||
```
|
||
|
||
Die mitgelieferten Plugins sind die Vorlagen zum Abschreiben: `plugins/vergleich` (Bausteine,
|
||
Container, Einstellungen, Adresse, Seitenvorlage) und `plugins/nachtblau` (nur ein Designpaket).
|
||
|
||
## 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" }
|
||
},
|
||
"theme": { "colors": [ ], "fonts": [ ], "base": { }, "buttons": { } }
|
||
}
|
||
```
|
||
|
||
- `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, eine Vorlage oder ein
|
||
Designpaket. 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`.
|
||
|
||
### Designpaket (`theme`)
|
||
|
||
`colors`, `fonts`, `base` und optional `buttons` im Format des Designbereichs (siehe die
|
||
Aufbauskripte unter `scripts/` oder `plugins/nachtblau`). „Designpaket übernehmen“ ersetzt die
|
||
Palette der Website – dieselbe Prüfung wie beim Speichern unter Design, die Inhalte bleiben.
|
||
|
||
## 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.
|