Skip to content

Plugin-Anatomie

Diese Seite erklaert den Aufbau eines Plugins im Detail: welche Ordner es gibt, welche Pflicht-Methoden implementiert werden muessen und wie die Klasse mit dem Core verzahnt ist.

Ordner-Struktur

_public/extensions/core/backend/{plugin-name}/
├── bootstrap.php             # Plugin-Klasse + Registrierung (PFLICHT)
├── layout/                   # Vue-Admin-Komponenten (optional)
│   ├── index.vue             #   Listen-/Template-Ansicht
│   ├── index_detail.vue      #   Detail/Edit-Ansicht
│   └── {subview}.vue         #   Zeilen-Renderer fuer show_module_list
├── script/                   # PHP-Action-Scripts (optional)
│   ├── csvExport.php
│   └── {pfad}/{name}.php
├── api/                      # Plugin-eigene API-Models (optional)
│   ├── backend/{name}/model.php
│   └── {public}/{name}/model.php
├── widgets/                  # Pagebuilder-Widgets (optional)
│   └── {widget-name}/
│       ├── bootstrap.php
│       └── template/index.vue
├── migrations/               # Plugin-eigene SQL-Migrationen (optional)
│   └── 001_create_tables.sql
├── searchIndex/              # Elasticsearch-Index-Scripts (optional)
├── scheduledTasks/           # Cron-Script-Dateien (optional)
├── src/                      # Frontend-Assets (optional)
│   └── css/
└── config/                   # JSON-Configs (optional)

Pflicht ist nur bootstrap.php. Alle anderen Ordner sind bedarfsabhaengig.

bootstrap.php — Plugin-Klasse

Die Hauptdatei enthaelt genau eine Klasse mit dem Muster {plugin-name}_BackendPlugin und extends install_controller:

php
<?php

class menueditor_BackendPlugin extends install_controller
{
    public static function getVersion(): string
    {
        return "1.0.0";
    }

    public static function getName(): string
    {
        return "Menu Editor";
    }

    public function install()
    {
        $this->url_rewrites_backend = ["menueditor"];
        $this->content_construct    = ["template"];
        $this->content_table        = [""];
        $this->backend_widget_base  = ["core/backend/menueditor"];
        $this->content_titles       = ["Menu Editor"];
        $this->content_columns      = [""];
        $this->content_button       = ['[]'];
        $this->action_button        = ['[]'];
        $this->content_replace      = ['{}'];
        $this->header_buttons       = ['[]'];
        $this->modal_edits          = ['{}'];

        $this->icon       = "fa-light fa-bars-staggered";
        $this->menu_group = 0;

        $this->apiEndpoints = [
            '{"modelPath": "backend/menueditor/model.php", "endpoint": "backend/menueditor"}',
            '{"modelPath": "menueditor/model.php",         "endpoint": "menueditor"}'
        ];
    }

    public function uninstall()
    {
        // Leer lassen — Core-installController entfernt plugin_backend-Eintraege
    }

    public static function update()
    {
        // Nur befuellen, wenn Datenmigrationen in plugin_backend noetig sind.
        // Schema-Migrationen gehoeren in migrations/ (siehe Plugins › Migrations).
    }

    public function getDirectory()
    {
        return $this->getDirectoryClass(__DIR__);
    }
}

Beispiel aus dem echten Repo: _public/extensions/core/backend/menueditor/bootstrap.php.

Klassen-Namens-Konvention

Der Klassenname ist zwingend {root_folder}_BackendPlugin (Root-Folder = Ordnername unter _public/extensions/core/backend/). Der Plugin-Manager instanziiert die Klasse ueber new {root_folder}_BackendPlugin() — bei Abweichung kommt ein Class not found-Fatal und die Installation schlaegt fehl.

Pflicht- und empfohlene Methoden

Die Basis-Klasse install_controller gibt fuenf Pflicht-Methoden vor, die jedes Plugin ueberschreiben muss, plus eine empfohlene Methode fuer Asset-/Layout-Aufloesung.

Pflicht (5)

MethodeSignaturZweck
getName()public static function getName(): stringUI-Label im Plugin-Manager
getVersion()public static function getVersion(): stringSemver, erscheint in plugins.version
install()public function install()Properties setzen, vom Core nach new Plugin() aufgerufen
uninstall()public function uninstall()Cleanup-Hook (meist leer — Core entfernt plugin_backend-Eintraege automatisch)
update()public static function update()Daten-Migrationen bei System-Update. Schema-Aenderungen gehoeren in Migrations, typisch leer

Empfohlen (1)

MethodeSignaturZweck
getDirectory()public function getDirectory()Loest den Plugin-Ordner ueber __DIR__ auf — wichtig fuer Asset-Pfade und Layout-Resolving

Das empfohlene Muster ist ein Einzeiler:

php
public function getDirectory()
{
    return $this->getDirectoryClass(__DIR__);
}

Ohne diese Methode fallen getDirectory()-Aufrufe des Cores auf den install_controller-Default zurueck — fuer minimalistische Plugins ohne eigene Assets ist das meistens ok, fuer Plugins mit Widgets oder Custom-Layouts ist die Implementierung praktisch Pflicht.

Registrierungs-Properties

In install() werden Properties der Basis-Klasse belegt. Alle sind public und werden vom Installer gelesen:

PropertyTypZweck
$url_rewrites_backendarray<string>URL-Slug pro Content-Construct → /admin/{slug}
$content_constructarray<string>table, template oder formonly — siehe Content Constructs
$content_tablearray<string>DB-Tabelle bei table-Construct
$content_titlesarray<string>UI-Label
$content_columnsarray<string>Sichtbare Spalten in Liste (comma-separated)
$backend_widget_basearray<string>Pfad zum Widget-Einstiegspunkt pro Construct (z. B. core/backend/{plugin})
$content_buttonarray<string>Zeilen-Buttons (JSON-String)
$action_buttonarray<string>Header-Action-Button (JSON-String)
$header_buttonsarray<string>Weitere Header-Buttons
$content_replacearray<string>Spalten-Umbenennungen
$modal_editsarray<string>Modal-Feld-Konfig
$iconstringFont-Awesome-Klasse fuer Navigation
$menu_groupintGruppierung in der Admin-Sidebar
$apiEndpointsarray<string>JSON-Strings, je ein Endpoint — siehe API-Endpunkte
$scheduledTasksarray<array>Cron-Jobs — siehe Scheduled Tasks
$webhookEventsarray<string>Manuelle Events — siehe Webhook-Events
$pagebuilder_widgetsarray<string>Pagebuilder-Widgets des Plugins

Arrays, nicht Skalare

Alle content_*-Properties sind Arrays — ein Eintrag pro content_construct. Ein Plugin kann mehrere Einstiegspunkte haben (z. B. eine Tabellen-Ansicht und eine Template-Ansicht). Auch bei nur einem Eintrag muss das Array-Format eingehalten werden, sonst zerfaellt die for-Iteration in installBackend() und es entstehen leere Eintraege in plugin_backend.

Der Installer-Fluss

Nach install() ruft der Core nacheinander diese Installer auf (alle in install_controller.php):

installPlugin($root_folder, $location)
  └─ install()                      # Dein Code, setzt Properties
  └─ installBackend($pluginID)      # plugin_backend-Eintraege
  └─ installWidgets($pluginID)      # pagebuilder_widgets + page_widgets
  └─ installDatabase()              # Plugin-eigene SQL via MigrationRunner
  └─ installInjections($pluginID)   # Script-Hooks (PreInjection/MidInjection/PostInjection/CacheInjection) registrieren
  └─ installSources($pluginID, …)   # CSS/JS-Assets
  └─ installAPI($pluginID)          # pluginAPI-Tabelle
  └─ installScheduledTasks($pluginID)
  └─ installWebhookEvents($pluginID)

Dein Plugin schreibt also keine Inserts von Hand — die Core-Installer uebernehmen das. Dein Job ist nur, die Properties richtig zu setzen.

onLoad() — Template-Plugins

Bei content_construct=template kann das Plugin eine onLoad()-Methode implementieren, die echtes JSON zurueckgibt. Der Rueckgabewert wird als content in der Response des Admin-Page-Endpoints ausgeliefert und steht im Frontend-Template zur Verfuegung:

php
public function onLoad()
{
    parent::onLoad();

    $menus = [];
    $q = query("SELECT * FROM menueditor_menus ORDER BY id ASC");
    while ($row = fetch_assoc($q)) {
        $menus[] = $row;
    }

    if ($GLOBALS['isApiCall']) {
        return json_encode(['menus' => $menus]);
    }
    return "[]";
}

Das Original im menueditor-Plugin laedt zusaetzlich die Sprachen-Liste und belegt im else-Pfad $GLOBALS['smarty'] fuer das Legacy-Smarty-Rendering. Das obige Snippet ist fuer die Nuxt-Admin-API gekuerzt — nur der $GLOBALS['isApiCall']-Pfad ist relevant, wenn keine Smarty-Kompatibilitaet benoetigt wird.

onLoad() muss gueltiges JSON liefern

Leere Rueckgabe "" oder String "[]" sind okay, aber niemals PHP-Arrays oder Objekte direkt zurueckgeben — der Core echot den Wert ungefiltert, das Frontend erwartet parsebares JSON.

getContent() — Custom-Content-Buttons

Fuer Buttons mit type="show_custom_content" liefert die Methode getContent() Daten fuer einen beliebig gestalteten Detail-Bereich:

php
public function getContent(): array
{
    return [
        'stats'       => $this->loadStats(),
        'recent_logs' => $this->loadRecentLogs(),
    ];
}

Details dazu in Buttons.

Datenbank-Helper

Plugins nutzen die Helper aus include/mysql.phpniemals mysqli_* oder PDO direkt:

php
query("SELECT ...");              // laeuft gegen aktive Connection
fetch_assoc($q);                  // ein Record
fetch_all($q);                    // alle Records
num_rows($q);                     // Zeilen zaehlen
insert_id();                      // ID nach INSERT — NICHT last_insert_id()!
real_escape_string($value);       // Escape fuer Strings

insert_id() statt last_insert_id()

last_insert_id() existiert im CMS nicht. Bei INSERT immer insert_id() nutzen.

Haeufige Fehler

Klassenname passt nicht zum Ordner

Ordner shop → Klasse shop_BackendPlugin. Bei Abweichung (z. B. Shop_BackendPlugin oder shopPlugin) schlaegt installPlugin() fehl — das Plugin ist im Manager sichtbar, laesst sich aber nicht installieren.

content_*-Properties nicht als Array

Auch bei nur einem Eintrag muessen content_construct, content_table, url_rewrites_backend etc. Arrays sein. Skalare Werte fuehren zu leeren Eintraegen in plugin_backend.

onLoad() gibt PHP-Array zurueck

Core echot die Rueckgabe unveraendert. PHP-Objekte/Arrays werden als Array-String ausgeliefert → Frontend-JSON-Parse-Error. Immer json_encode() verwenden.

Siehe auch