Skip to content

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

ConstructUIWann?
tablePrimeVue DataTable (Liste + Edit-Modal)Standard-CRUD fuer eine DB-Tabelle — Listen, Sortierung, Suche, Edit/Delete kostenlos
templateEigene Vue-Komponente (layout/index.vue)Custom-UI ohne standardisierte Tabelle — Dashboards, Editoren, Integrations-Panels
formonlyEinzelnes Edit-Modal ohne ListeEin-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.

PropertyZweck
$content_construct"table", "template" oder "formonly"
$content_tableDB-Tabelle (nur table) — bei anderen: ""
$content_titlesUI-Label (z. B. "Blog-Artikel")
$content_columnsComma-Liste der sichtbaren Spalten in der Liste (nur table)
$content_buttonZeilen-Buttons (JSON-Array, siehe Buttons)
$action_buttonHeader-Action-Button, z. B. "Neu" (JSON-Objekt)
$header_buttonsWeitere Header-Buttons (JSON-Array)
$content_replaceSpalten-Umbenennung fuer die Anzeige (JSON-Objekt)
$url_rewrites_backendURL-Slug → /admin/{slug}
$modal_editsModal-Feld-Config (JSON-Objekt)
$backend_widget_basePfad 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

php
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 von install()): 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

typeRendert alsBeispiel-Zusatzfelder
textEinzeiliges Input
textareaMehrzeiliger Text
ckeditorRich-Text-Editor
selectDropdown aus DB-Tabelleselect_value: {table, value, title}
imageMedia Manager-Button
checkboxToggle
number, date, timeNative 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

php
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:

Echte Beispiele im Repo: menueditor, design, taskmanager, updatemanager, plugin_manager, dashboard.

Daten holen

Ein Template-Plugin hat drei Wege, Daten zu laden:

  1. onLoad() liefert JSON direkt in die Page-Response — einmalig beim Seitenaufruf.
  2. Plugin-eigener API-Endpoint (backend/my-plugin) — fuer interaktive Aktionen. Siehe API-Endpunkte.
  3. Generische backend/item-API — wenn DB-Tabellen geschrieben werden muessen, ohne dass ein table-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.

php
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:

php
$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