Skip to content

Buttons

Buttons sind die primaere Art, Aktionen ins Admin-UI zu integrieren — sowohl in Listen-Zeilen ($content_button) als auch im Header ($action_button, $header_buttons). Diese Seite zeigt alle Button-Typen mit JSON-Struktur und Copy-Paste-Beispielen.

Gemeinsame Felder

Jeder Button ist ein JSON-Objekt mit folgenden Feldern:

FeldPflichtZweck
button_idjaEindeutige ID innerhalb des Plugins — wird im Rechte-System referenziert ({pluginId}_{buttonId}_view/edit)
typejaSiehe Liste unten — bestimmt Verhalten und Rendering
styleneindefault, primary, success, info, danger — Bootstrap-Farbklasse
iconneinFont-Awesome-Icon (ohne fa--Prefix, z. B. pencil, minus, plus, download)
titleneinSichtbarer Button-Text
support_textneinHilfetext (z. B. in Delete-Dialog)
submit_textneinText des Submit-Buttons im Modal
forStatusneinNur sichtbar wenn parseInt(item.status) === parseInt(btn.forStatus)
multilanguagenein"1" fuer Sprach-Tabs im Edit-Modal (erfordert base_id/language_short in DB-Tabelle)
editable_rowsbei edit/insertArray von Feld-Definitionen fuer das Modal
buttonsbei verschachteltVerschachtelte Sub-Buttons (z. B. in show_module_list)

Button-Typen

edit — Edit-Modal

Oeffnet ein Bearbeiten-Modal mit den in editable_rows definierten Feldern. Der Datensatz wird beim Oeffnen ueber /api/backend/item?table=…&id=…&language=… geladen.

json
{
  "button_id": "2",
  "type": "edit",
  "style": "default",
  "icon": "pencil",
  "title": "Bearbeiten",
  "submit_text": "Speichern",
  "multilanguage": "1",
  "editable_rows": [
    {"placeholder": "Titel", "column": "title", "type": "text"},
    {"placeholder": "Inhalt", "column": "body",  "type": "ckeditor"},
    {"placeholder": "Bild",  "column": "image", "type": "image"}
  ]
}

Beim Submit geht ein PATCH /api/backend/item mit dem Form-Payload raus. Bei multilanguage: "1" wird pro Sprache ein separater Satz gespeichert (base_id/language_short-Pattern).

insert — Neu-Anlegen-Modal

Identisch zu edit, aber ohne Vorladen eines Datensatzes. Landet im $action_button-Property (Header-Action):

json
{
  "button_id": "3",
  "type": "insert",
  "style": "primary",
  "icon": "plus",
  "title": "Neuer Eintrag",
  "submit_text": "Speichern",
  "editable_rows": [
    {"placeholder": "Titel", "column": "title", "type": "text"},
    {"placeholder": "Kategorie", "column": "category", "type": "select",
     "select_value": {"table": "news_category", "value": "id", "title": "category"}}
  ]
}

Submit triggert POST /api/backend/item.

delete — Delete-Dialog

Zeigt einen Bestaetigungs-Dialog, triggert bei OK DELETE /api/backend/item.

json
{
  "button_id": "1",
  "type": "delete",
  "style": "danger",
  "icon": "minus",
  "title": "Loeschen",
  "submit_text": "Loeschen",
  "support_text": "Eintrag wirklich loeschen?"
}

delete braucht weder column-Payload noch editable_rows — die Zeilen-ID liefert die Tabelle, die Core-API loescht direkt.

script — PHP-Action-Script ausfuehren

Fuehrt ein PHP-Script aus dem Plugin-script/-Ordner aus, ohne eigene API registrieren zu muessen. Script bekommt id, url_rewrite und optional ein data-Payload uebergeben.

json
{
  "button_id": "4",
  "type": "script",
  "style": "info",
  "icon": "download",
  "title": "Exportieren",
  "script": "export/runExport.php",
  "submit_text": "Starten"
}

Das Script liegt unter _public/extensions/core/backend/{plugin}/script/export/runExport.php. Der script-Wert ist der Pfad relativ zu script/ — mit oder ohne Unterordner. Liegt das Script direkt unter script/runExport.php, reicht "script": "runExport.php". Der backend/actions/script-Endpoint laedt die Datei dynamisch und gibt die Response als JSON zurueck.

Script-Pfad-Validierung

Der Pfad wird durch eine Regex-Whitelist (/^[a-zA-Z0-9_\/\-]+\.php$/) und str_contains($script, '..') gefiltert. Pfade mit Punkten (..) oder ungueltigen Zeichen schlagen fehl — niemals User-Input direkt als Script-Pfad einsetzen.

show_module_list — Sub-Liste mit eigener Zeilen-Komponente

Oeffnet ein Modal mit einer eigenen Liste, die ueber eine Vue-Komponente gerendert wird (pro Zeile). Nuetzlich fuer 1:n-Beziehungen — z. B. "Artikel bearbeiten → Artikel-Bilder" oder "Kurs bearbeiten → Episoden".

json
{
  "button_id": "5",
  "type": "show_module_list",
  "style": "default",
  "icon": "list",
  "title": "Episoden",
  "values": {
    "table": "elearning_episodes",
    "template": "episodes.vue"
  },
  "buttons": [
    {"button_id": "6", "type": "edit", "icon": "pencil", "title": "Episode bearbeiten",
     "editable_rows": [...]},
    {"button_id": "7", "type": "delete", "icon": "minus", "title": "Episode loeschen"}
  ]
}

Die Zeilen-Komponente episodes.vue liegt unter layout/episodes.vue im Plugin-Ordner und rendert pro Zeile ein <tr>. Sie bekommt item, config, urlRewrite und buttons als Props.

Details zu Zeilen-Komponenten: Plugin-Anatomie › Ordner-Struktur.

show_checklist — Checkbox-Gruppe

Oeffnet ein Modal mit einer Checklist, die gegen eine Gruppen-Tabelle gemappt wird. Typischer Anwendungsfall: User-Rechte (user_groups.rights) oder Feature-Flags pro Entity.

json
{
  "button_id": "8",
  "type": "show_checklist",
  "style": "info",
  "icon": "check-square",
  "title": "Rechte vergeben",
  "values": {
    "table": "user_groups",
    "column": "rights"
  }
}

Submit ruft /api/backend/actions/checklist auf, das den Spalten-Wert (JSON-Array) atomar updatet.

show_custom_content — Custom-UI-Bereich

Laedt eine beliebige PHP-Klasse und ruft getContent() auf. Die Rueckgabe wird als Data-Source fuer ein Vue-Template verwendet. Fuer komplexe Detail-Ansichten (z. B. Statistiken, Report-Dashboards), die nicht in ein Standard-Edit-Modal passen.

json
{
  "button_id": "9",
  "type": "show_custom_content",
  "style": "default",
  "icon": "chart-bar",
  "title": "Statistik",
  "values": {
    "class": "StatsController",
    "template": "stats.vue"
  }
}

Details in Plugin-Anatomie › getContent().

Kein Modal, kein API-Call — einfach ein <a href="…">:

json
{
  "button_id": "10",
  "type": "link",
  "style": "default",
  "icon": "external-link",
  "title": "Dokumentation",
  "url": "https://docs.example.com"
}

Style-Farben

styleTypische Verwendung
defaultStandard-Aktionen (Edit)
primaryHaupt-Action (Neu, Speichern)
successPositive Bestaetigung (Aktivieren, Publishen)
infoSekundaere Aktionen (Export, Statistik)
dangerDestruktive Aktionen (Delete)

Felder in editable_rows

Jedes Feld im editable_rows-Array ist ein Objekt mit:

FeldPflichtZweck
columnjaDB-Spalten-Name
placeholderneinLabel ueber dem Input
typejaFeldtyp — siehe Tabelle unten
select_valuebei select{table, value, title} fuer Dropdown-Optionen
select_optionsbei statischem selectArray: [{value, title}]
data-aifeatureneinKomma-Liste aktivierter AI-Features (z. B. "seo_title,translate")

Feldtypen

typeUIBeispiel
textEinzeiliges InputTitel
textareaMehrzeiliger TextKurzbeschreibung
ckeditorRich-Text-EditorArtikel-Body
codemirrorCode-Editor (LESS/CSS/JS, GitHub-Theme, LESS-Syntax-Validierung)Custom CSS
selectDropdownKategorie-Auswahl
imageMedia Manager-TriggerBeitragsbild
checkboxToggleVeroeffentlicht ja/nein
numberNumerisches InputPreis
date, time, datetimeNative InputsPublish-Datum
passwordVerdecktes InputAPI-Keys (verschluesselt gespeichert)
colorColor-PickerBadge-Farbe

Rechte pro Button

Fuer jeden Button kann der Admin pro Gruppe view und edit setzen. Die Rechte-Keys folgen dem Schema {pluginId}_{buttonId}_view und {pluginId}_{buttonId}_edit — automatisch generiert, keine manuelle Pflege noetig.

24_2_view   # Plugin 24, Button 2 (Edit) sichtbar
24_2_edit   # Plugin 24, Button 2 ausfuehrbar
24_1_view   # Button 1 (Delete) sichtbar
24_1_edit   # Button 1 ausfuehrbar

Admin-User (rights === "0") umgehen alle Checks. Plugin-Rechte werden unter /admin/user-management vergeben.

forStatus — bedingte Sichtbarkeit

Einen Button nur anzeigen, wenn der Datensatz einen bestimmten status hat:

json
{
  "button_id": "11",
  "type": "script",
  "icon": "check",
  "title": "Freigeben",
  "forStatus": 0,
  "script": "publish.php"
}

Der Button ist nur sichtbar, wenn parseInt(item.status) === 0. Sinnvoll fuer Workflow-Stati (Entwurf → Freigegeben → Archiviert).

Sentinel-Wert -1: Setzt man forStatus: -1, wird der Check komplett uebersprungen — der Button ist immer sichtbar. Das ist der konventionelle Weg, einen bereits vorhandenen forStatus-Eintrag zu deaktivieren, ohne ihn zu entfernen. Der reale Check in NsTable.vue sieht so aus:

js
if (btn.forStatus !== undefined && btn.forStatus !== -1) {
    if (parseInt(item.status) !== parseInt(btn.forStatus)) return false
}

Verschachtelte Buttons

show_module_list- und show_custom_content-Buttons koennen eigene buttons enthalten, die im Sub-Modal gerendert werden:

json
{
  "button_id": "5",
  "type": "show_module_list",
  "title": "Episoden",
  "values": {"table": "elearning_episodes", "template": "episodes.vue"},
  "buttons": [
    {"button_id": "6", "type": "edit",   "editable_rows": [...]},
    {"button_id": "7", "type": "delete"},
    {"button_id": "8", "type": "script", "script": "reorder.php", "title": "Sortieren"}
  ]
}

Die button_ids muessen innerhalb des Plugins eindeutig sein — auch fuer verschachtelte Buttons. Andernfalls kollidieren die Rechte-Keys. (Dass in den Snippets oben mehrfach dieselben IDs auftauchen, ist reine Doku-Vereinfachung — im echten Plugin werden IDs nicht wiederverwendet.)

Haeufige Fehler

JSON-Escaping in PHP-Heredoc

$content_button ist ein PHP-String mit JSON-Content. Am einfachsten ueber Single-Quotes: '[{"button_id":"1",...}]'. Bei Double-Quotes muss jeder " mit \ escaped werden — fehleranfaellig. Wenn viele Nested-Escapes: Heredoc mit <<<JSON nutzen.

button_id als String, nicht als Zahl

Alle button_id-Werte sind Strings ("2", nicht 2). Das Rechte-System arbeitet mit String-Keys — 2 vs. "2" fuehrt zu nicht matchenden Rechten.

editable_rows ohne gueltige DB-Spalte

Ein Feld mit "column": "nicht_vorhandene_spalte" wirft beim Submit einen SQL-Fehler. Die Core-API macht keine Vorab-Validierung der Spalten.

forStatus vergleicht per parseInt

forStatus: 0 matcht item.status === 0 und item.status === "0" (parseInt). Aber forStatus: "draft" matcht nichts Sinnvolles — parseInt("draft") === NaN. Nur numerische Stati verwenden.

Submit-Response-Behandlung bei script

PHP-Scripts muessen selbst json_encode() und exit aufrufen, damit die Core-Action sauber returnt. Die Response wird 1:1 zurueckgegeben, kein automatisches Wrappen in {success:true, ...}.

Siehe auch