Content Constructs
Jedes Plugin-Einstiegspunkt braucht einen Content-Construct — er bestimmt, wie das Backend-UI fuer den Eintrag gerendert wird. Es gibt drei Varianten: table, template und formonly. Diese Seite erklaert wann welche, und welche Properties in bootstrap.php dafuer zu setzen sind.
Uebersicht
| Construct | UI | Wann? |
|---|---|---|
table | PrimeVue DataTable (Liste + Edit-Modal) | Standard-CRUD fuer eine DB-Tabelle — Listen, Sortierung, Suche, Edit/Delete kostenlos |
template | Eigene Vue-Komponente (layout/index.vue) | Custom-UI ohne standardisierte Tabelle — Dashboards, Editoren, Integrations-Panels |
formonly | Einzelnes Edit-Modal ohne Liste | Ein-Record-Konfiguration (Site-Settings, Stammdaten) |
Mehrere Constructs in einem Plugin sind erlaubt. Das blog-Plugin z. B. registriert drei table-Eintraege nebeneinander (Posts, Kategorien, Standard-Seite).
Die 10 Kern-Properties
Alle Properties sind Arrays mit einem Eintrag pro Construct. In install() des Plugins werden sie parallel zueinander befuellt — Index [0] des einen Arrays gehoert zu Index [0] aller anderen.
| Property | Zweck |
|---|---|
$content_construct | "table", "template" oder "formonly" |
$content_table | DB-Tabelle (nur table) — bei anderen: "" |
$content_titles | UI-Label (z. B. "Blog-Artikel") |
$content_columns | Comma-Liste der sichtbaren Spalten in der Liste (nur table) |
$content_button | Zeilen-Buttons (JSON-Array, siehe Buttons) |
$action_button | Header-Action-Button, z. B. "Neu" (JSON-Objekt) |
$header_buttons | Weitere Header-Buttons (JSON-Array) |
$content_replace | Spalten-Umbenennung fuer die Anzeige (JSON-Objekt) |
$url_rewrites_backend | URL-Slug → /admin/{slug} |
$modal_edits | Modal-Feld-Config (JSON-Objekt) |
$backend_widget_base | Pfad zum Widget-Einstieg (typisch core/backend/{plugin}) |
table — CRUD aus der Box
Das meistgenutzte Construct. Ein Plugin registriert eine DB-Tabelle und bekommt dafuer eine komplette Admin-UI: DataTable mit Pagination, Suche, Sortierung, Edit-Modal und Delete-Dialog.
Minimal-Beispiel
public function install()
{
$this->url_rewrites_backend = ["my-articles"];
$this->content_construct = ["table"];
$this->content_table = ["my_articles"];
$this->content_titles = ["Meine Artikel"];
$this->content_columns = ["title,category,created_at"];
// Edit-Button (Stift) + Delete-Button (Muelleimer)
$this->content_button = ['[
{"button_id":"2","type":"edit","style":"default","icon":"pencil","title":"Bearbeiten",
"submit_text":"Speichern",
"editable_rows":[
{"placeholder":"Titel","column":"title","type":"text"},
{"placeholder":"Text","column":"body","type":"ckeditor"}
],
"multilanguage":"1"},
{"button_id":"1","type":"delete","style":"danger","icon":"minus","title":"Loeschen",
"submit_text":"Loeschen",
"support_text":"Eintrag wirklich loeschen?"}
]'];
// "Neuer Artikel"-Button im Header
$this->action_button = ['{"button_id":"3","type":"insert","style":"primary",
"icon":"plus","title":"Neuer Artikel","submit_text":"Speichern",
"editable_rows":[
{"placeholder":"Titel","column":"title","type":"text"},
{"placeholder":"Text","column":"body","type":"ckeditor"}
]}'];
$this->backend_widget_base = ["core/backend/my-plugin"];
$this->content_replace = ['{}'];
$this->header_buttons = ['[]'];
$this->modal_edits = ['{}'];
}Das genuegt. Ohne weitere Arbeit hat dein Plugin:
- eine Liste unter
/admin/my-articles(Pagination, Suche, Sortierung per Spalte), - einen "Bearbeiten"-Button pro Zeile mit Modal-Formular,
- einen "Loeschen"-Button mit Bestaetigungs-Dialog,
- einen "Neuer Artikel"-Button im Header,
- komplette REST-API unter
/api/backend/item?table=my_articles&id=...(GET/POST/PATCH/DELETE).
Multi-Language
Setze "multilanguage":"1" im Button-JSON, damit das Edit-Modal Sprach-Tabs rendert. Voraussetzung: die DB-Tabelle hat base_id und language_short als Spalten. Die Core-Endpoints kuemmern sich automatisch um INSERT pro Sprache und Merge-Logik beim Abruf.
Details: Migrations.
content_columns vs. editable_rows
Zwei unabhaengige Konzepte:
content_columns(als Property voninstall()): Liste der Spalten, die in der DataTable sichtbar sind. Format:"col1,col2,col3"— einfacher Komma-String.editable_rows(im Button-JSON): Welche Spalten bearbeitbar sind und welcher Input-Typ gerendert wird. Feldtypen:text,textarea,ckeditor,select,image,checkbox, …
Ein Plugin kann also Spalten anzeigen, die nicht editierbar sind (z. B. Timestamp created_at sichtbar in der Liste, aber im Edit-Modal ausgeblendet).
Felder-Typen fuer editable_rows
type | Rendert als | Beispiel-Zusatzfelder |
|---|---|---|
text | Einzeiliges Input | — |
textarea | Mehrzeiliger Text | — |
ckeditor | Rich-Text-Editor | — |
select | Dropdown aus DB-Tabelle | select_value: {table, value, title} |
image | Media Manager-Button | — |
checkbox | Toggle | — |
number, date, time | Native Inputs | — |
Vollstaendige Uebersicht der Feldtypen: Widgets › widget_menu-Feldtypen — die Feldtypen sind identisch zwischen Plugin-Modals und Widget-Formularen.
template — Custom-UI
Fuer alles, was nicht in eine Standard-Tabelle passt: Dashboards, visuelle Editoren, Integrations-Konfiguration. Das Plugin liefert eine eigene layout/index.vue, in der die UI frei gebaut wird.
Minimal-Beispiel
public function install()
{
$this->url_rewrites_backend = ["my-dashboard"];
$this->content_construct = ["template"];
$this->content_table = [""]; // leer bei template
$this->content_titles = ["Mein Dashboard"];
$this->content_columns = [""];
$this->content_button = ['[]'];
$this->action_button = ['[]'];
$this->content_replace = ['{}'];
$this->header_buttons = ['[]'];
$this->modal_edits = ['{}'];
$this->backend_widget_base = ["core/backend/my-plugin"];
}Daneben braucht das Plugin:
layout/index.vue— die eigene UI- optional
onLoad()— liefert initialdaten als JSON, siehe Plugin-Anatomie ›onLoad()
Echte Beispiele im Repo: menueditor, design, taskmanager, updatemanager, plugin_manager, dashboard.
Daten holen
Ein Template-Plugin hat drei Wege, Daten zu laden:
onLoad()liefert JSON direkt in die Page-Response — einmalig beim Seitenaufruf.- Plugin-eigener API-Endpoint (
backend/my-plugin) — fuer interaktive Aktionen. Siehe API-Endpunkte. - Generische
backend/item-API — wenn DB-Tabellen geschrieben werden muessen, ohne dass eintable-Construct aktiv ist.
formonly — Einzel-Formular
Fuer Konfigurationen, von denen es nur einen Datensatz gibt: Site-Einstellungen, Stammdaten, Branding-Config. Keine Liste, kein Neu-Button — nur ein Modal, das den einzigen Record bearbeitet.
public function install()
{
$this->url_rewrites_backend = ["branding"];
$this->content_construct = ["formonly"];
$this->content_table = ["branding_settings"];
$this->content_titles = ["Branding"];
$this->content_columns = [""];
$this->content_button = ['[]'];
$this->action_button = ['{"button_id":"1","type":"edit","style":"primary",
"icon":"pencil","title":"Branding bearbeiten","submit_text":"Speichern",
"editable_rows":[
{"placeholder":"Logo","column":"logo","type":"image"},
{"placeholder":"Primaerfarbe","column":"primary_color","type":"text"}
]}'];
$this->content_replace = ['{}'];
$this->header_buttons = ['[]'];
$this->modal_edits = ['{}'];
$this->backend_widget_base = ["core/backend/branding"];
}Nutze besser template
In der Praxis wird fuer Einzel-Configs oft ein template-Construct mit eigener index.vue bevorzugt — mehr UI-Freiheit, CodeMirror-Editoren, Previews usw. formonly ist die schnellste Variante, aber vom Freiheitsgrad eingeschraenkt.
Combined: mehrere Constructs
Ein Plugin kann mehrere Einstiegspunkte haben. Das blog-Plugin registriert drei table-Constructs nebeneinander — jeder mit eigener Tabelle, eigenen Buttons und eigenem URL-Slug:
$this->url_rewrites_backend = ["news", "news-categories", "news-standard-page"];
$this->content_construct = ["table", "table", "table"];
$this->content_table = ["blog", "news_category", "news_page"];
$this->backend_widget_base = ["core/backend/blog", "core/backend/blog", "core/backend/blog"];
$this->content_titles = ['Blog posts', "Blog categories", "Main page for blog entries"];
$this->content_columns = ['title', "category", "content_file"];
$this->content_button = [
'[...]', // Buttons fuer blog-Tabelle
'[...]', // Buttons fuer news_category-Tabelle
'[...]', // Buttons fuer news_page-Tabelle
];
// etc.Die Admin-Sidebar zeigt dann drei getrennte Menue-Eintraege — jeder laedt seine eigene Liste unter /admin/news, /admin/news-categories, /admin/news-standard-page.
Echte Quelle: _public/extensions/core/backend/blog/bootstrap.php.
Haeufige Fehler
Ungleich lange Arrays
Alle content_*-Arrays und url_rewrites_backend muessen dieselbe Laenge haben. Fehlt z. B. fuer den dritten Construct ein Eintrag in content_button, bricht installBackend() mitten im Loop ab — nur zwei plugin_backend-Zeilen werden angelegt.
content_table bei template nicht weglassen
Auch bei "template" muss content_table einen Eintrag haben — "" reicht, aber nicht fehlen. installBackend() liest $this->content_table[$i] indexbasiert; fehlende Indizes liefern null und produzieren leere plugin_backend-Spalten plus PHP-Notice im Log.
JSON-Strings, keine PHP-Arrays
$content_button, $action_button, $header_buttons, $content_replace und $modal_edits sind JSON-Strings — $this->content_button = [['button_id' => 2, ...]] funktioniert nicht, das wird als Array serialisiert. Zwei Ebenen im Kopf behalten: das aeussere PHP-Array enthaelt pro Construct einen einzelnen String; der String ist dann selbst ein JSON-Array von Button-Objekten.
Siehe auch
- Plugin-Anatomie — wo in
install()die Properties gesetzt werden - Buttons — JSON-Struktur fuer
content_button/action_button/header_buttons - Migrations — DB-Tabelle fuer
table-Construct anlegen - API-Endpunkte — eigene API fuer
template-Konstruktionen - Widgets › widget_menu-Feldtypen — Feldtypen (identisch mit
editable_rows)