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:
| Feld | Pflicht | Zweck |
|---|---|---|
button_id | ja | Eindeutige ID innerhalb des Plugins — wird im Rechte-System referenziert ({pluginId}_{buttonId}_view/edit) |
type | ja | Siehe Liste unten — bestimmt Verhalten und Rendering |
style | nein | default, primary, success, info, danger — Bootstrap-Farbklasse |
icon | nein | Font-Awesome-Icon (ohne fa--Prefix, z. B. pencil, minus, plus, download) |
title | nein | Sichtbarer Button-Text |
support_text | nein | Hilfetext (z. B. in Delete-Dialog) |
submit_text | nein | Text des Submit-Buttons im Modal |
forStatus | nein | Nur sichtbar wenn parseInt(item.status) === parseInt(btn.forStatus) |
multilanguage | nein | "1" fuer Sprach-Tabs im Edit-Modal (erfordert base_id/language_short in DB-Tabelle) |
editable_rows | bei edit/insert | Array von Feld-Definitionen fuer das Modal |
buttons | bei verschachtelt | Verschachtelte 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.
{
"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):
{
"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.
{
"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.
{
"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".
{
"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.
{
"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.
{
"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().
link — Externer/interner Link
Kein Modal, kein API-Call — einfach ein <a href="…">:
{
"button_id": "10",
"type": "link",
"style": "default",
"icon": "external-link",
"title": "Dokumentation",
"url": "https://docs.example.com"
}Style-Farben
style | Typische Verwendung |
|---|---|
default | Standard-Aktionen (Edit) |
primary | Haupt-Action (Neu, Speichern) |
success | Positive Bestaetigung (Aktivieren, Publishen) |
info | Sekundaere Aktionen (Export, Statistik) |
danger | Destruktive Aktionen (Delete) |
Felder in editable_rows
Jedes Feld im editable_rows-Array ist ein Objekt mit:
| Feld | Pflicht | Zweck |
|---|---|---|
column | ja | DB-Spalten-Name |
placeholder | nein | Label ueber dem Input |
type | ja | Feldtyp — siehe Tabelle unten |
select_value | bei select | {table, value, title} fuer Dropdown-Optionen |
select_options | bei statischem select | Array: [{value, title}] |
data-aifeature | nein | Komma-Liste aktivierter AI-Features (z. B. "seo_title,translate") |
Feldtypen
type | UI | Beispiel |
|---|---|---|
text | Einzeiliges Input | Titel |
textarea | Mehrzeiliger Text | Kurzbeschreibung |
ckeditor | Rich-Text-Editor | Artikel-Body |
codemirror | Code-Editor (LESS/CSS/JS, GitHub-Theme, LESS-Syntax-Validierung) | Custom CSS |
select | Dropdown | Kategorie-Auswahl |
image | Media Manager-Trigger | Beitragsbild |
checkbox | Toggle | Veroeffentlicht ja/nein |
number | Numerisches Input | Preis |
date, time, datetime | Native Inputs | Publish-Datum |
password | Verdecktes Input | API-Keys (verschluesselt gespeichert) |
color | Color-Picker | Badge-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 ausfuehrbarAdmin-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:
{
"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:
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:
{
"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
- Content Constructs — wo Buttons eingehaengt werden
- Plugin-Anatomie ›
getContent()— Custom-Content-Buttons - Migrations — DB-Tabellen fuer Button-Aktionen
- API-Endpunkte — eigene API statt
script-Button, wenn mehr Kontrolle gewollt - Widgets › widget_menu-Feldtypen — gleiche Feldtypen fuer Widget-Formulare