myCRM/docs/PLUGIN_MENUS_AND_PERMISSIONS.md

14 KiB

Plugin-Menüs und Permissions

Anleitung zur dynamischen Registrierung von Menü-Items und Permissions durch Plugins.

Übersicht

Das Plugin-System unterstützt jetzt:

Dynamische Menü-Items - Plugins können eigene Menüpunkte hinzufügen Automatische Permission-Synchronisation - Modul-Permissions werden automatisch in der DB angelegt Permission-basierte Sichtbarkeit - Menüpunkte erscheinen nur für berechtigte User Gruppierung - Plugin-Menüs können gruppiert werden


1. Menü-Items im Plugin definieren

In deiner Plugin-Klasse:

public function getMenuItems(): array
{
    return [
        [
            'label' => 'Rechnungen',         // Menü-Titel (Container)
            'icon' => 'pi-file-pdf',         // PrimeIcons Icon (ohne pi pi-fw prefix)
            'group' => 'Finanzen',           // Gruppierung im Menü
            // WICHTIG: Container-Items brauchen KEINE permission Property
            'items' => [
                [
                    'label' => 'Alle Rechnungen',
                    'icon' => 'pi-list',
                    'to' => '/billing/invoices',           // Vue Router path
                    'permission' => 'billing.view'         // WICHTIG: Jedes Sub-Item braucht permission
                ],
                [
                    'label' => 'Neue Rechnung',
                    'icon' => 'pi-plus',
                    'to' => '/billing/invoices/create',
                    'permission' => 'billing.create'       // Explizite Permission
                ],
                [
                    'separator' => true                     // Optional: Trennlinie
                ],
                [
                    'label' => 'Einstellungen',
                    'icon' => 'pi-cog',
                    'to' => '/billing/settings',
                    'permission' => 'billing.manage'       // Explizite Permission
                ]
            ]
        ]
    ];
}

Menü-Item Optionen:

Feld Typ Required Beschreibung
label string Anzeigename des Menüpunkts
icon string PrimeIcons Icon (ohne pi pi-fw prefix)
to string Vue Router path
url string Externe URL (alternativ zu to)
items array Sub-Menüpunkte
permission string Permission-String für Sichtbarkeits-Check
group string Gruppierung im Hauptmenü
separator bool Trennlinie (nur in Sub-Items)

2. Permissions definieren

In deiner Plugin-Klasse:

public function getPermissionModules(): array
{
    // Best Practice: Ein einziges Modul pro Plugin
    return ['billing'];
}

WICHTIG: Verwende nur ein Permission-Modul pro Plugin, nicht mehrere! Dies vereinfacht die Administration massiv.

Das System erstellt automatisch folgende Permissions für das Modul billing:

  • billing.view
  • billing.create
  • billing.edit
  • billing.delete
  • billing.export
  • billing.manage

Permission-Synchronisation durchführen:

# Alle Plugins synchronisieren
php bin/console app:plugin:sync-permissions

# Nur ein bestimmtes Plugin
php bin/console app:plugin:sync-permissions billing

# Dry-Run (zeigt nur was passieren würde)
php bin/console app:plugin:sync-permissions --dry-run

Output:

Plugin Permission-Module Synchronisation
========================================

Synchronisiere alle Plugins...
------------------------------

 Aktion         Anzahl
 Neu erstellt   2
 Aktualisiert   0
 Übersprungen   5

3. Frontend Integration

API-Endpoints:

GET /api/plugin-menu

Gibt alle Plugin-Menü-Items als flache Liste zurück.

Response:

{
    "success": true,
    "data": [
        {
            "label": "Alle Rechnungen",
            "icon": "pi-list",
            "to": "/billing/invoices",
            "permission": "billing.view",
            "source": "billing"
        }
    ],
    "count": 1
}

GET /api/plugin-menu/grouped

Gibt gruppierte Plugin-Menü-Items zurück.

Response:

{
    "success": true,
    "data": {
        "Finanzen": [
            {
                "label": "Rechnungen",
                "icon": "pi-file-pdf",
                "items": [...]
            }
        ]
    },
    "groups": ["Finanzen"]
}

AppMenu.vue

Das Menü lädt automatisch Plugin-Menüs beim Mount:

<script setup>
import { ref, onMounted } from 'vue';

const model = ref([...coreMenu]);

const loadPluginMenus = async () => {
    const response = await fetch('/api/plugin-menu/grouped');
    const data = await response.json();

    // Plugin-Menüs werden automatisch hinzugefügt
    // Permission-Checks werden automatisch durchgeführt
};

onMounted(() => {
    loadPluginMenus();
});
</script>

4. Permission-Checks im Frontend

Mit Composition API:

<script setup>
import { useAuthStore } from '@/stores/auth';

const authStore = useAuthStore();

// Permission prüfen
const canViewInvoices = authStore.hasPermission('billing.view');
</script>

<template>
    <Button v-if="canViewInvoices" label="Rechnung anzeigen" />
</template>

Im Template:

<template>
    <div v-if="authStore.hasPermission('billing.create')">
        <Button label="Neue Rechnung" @click="createInvoice" />
    </div>
</template>

5. Beispiel: Vollständiges Plugin

BillingModulePlugin.php

<?php

namespace MyCRM\BillingModule;

use App\Plugin\LicenseValidatorInterface;
use App\Plugin\ModulePluginInterface;

class BillingModulePlugin implements ModulePluginInterface
{
    public function __construct(
        private readonly LicenseValidatorInterface $licenseValidator
    ) {}

    public function getIdentifier(): string
    {
        return 'billing';
    }

    public function getDisplayName(): string
    {
        return 'Rechnungsmodul';
    }

    public function getVersion(): string
    {
        return '1.0.0';
    }

    public function getDescription(): string
    {
        return 'Verwaltung von Rechnungen, Angeboten und Zahlungen';
    }

    public function isLicensed(): bool
    {
        return $this->licenseValidator->validate('billing')['valid'];
    }

    public function getLicenseInfo(): array
    {
        return $this->licenseValidator->validate('billing');
    }

    public function boot(): void
    {
        if (!$this->isLicensed()) {
            throw new \RuntimeException('Billing Module nicht lizenziert');
        }

        // Services, Routes, etc. registrieren
    }

    public function getPermissionModules(): array
    {
        // Best Practice: Ein einziges Modul für das gesamte Plugin
        return ['billing'];
    }

    public function getMenuItems(): array
    {
        return [
            [
                'label' => 'Rechnungen',
                'icon' => 'pi-file-pdf',
                'group' => 'Finanzen',
                // Wichtig: Container-Item hat KEINE permission Property
                'items' => [
                    [
                        'label' => 'Dashboard',
                        'icon' => 'pi-chart-line',
                        'to' => '/billing/dashboard',
                        'permission' => 'billing.view'  // Explizite Permission
                    ],
                    [
                        'label' => 'Alle Rechnungen',
                        'icon' => 'pi-list',
                        'to' => '/billing/invoices',
                        'permission' => 'billing.view'
                    ],
                    [
                        'label' => 'Neue Rechnung',
                        'icon' => 'pi-plus',
                        'to' => '/billing/invoices/create',
                        'permission' => 'billing.create'
                    ],
                    [
                        'separator' => true
                    ],
                    [
                        'label' => 'Zahlungen',
                        'icon' => 'pi-money-bill',
                        'to' => '/billing/payments',
                        'permission' => 'billing.view'  // Einheitliches Modul!
                    ],
                    [
                        'label' => 'Einstellungen',
                        'icon' => 'pi-cog',
                        'to' => '/billing/settings',
                        'permission' => 'billing.manage'
                    ]
                ]
            ]
        ];
    }

    public function canInstall(): array
    {
        $errors = [];

        if (PHP_VERSION_ID < 80200) {
            $errors[] = 'PHP 8.2+ erforderlich';
        }

        return [
            'success' => empty($errors),
            'errors' => $errors
        ];
    }
}

6. Workflow: Neues Plugin mit Menüs

Schritt 1: Plugin erstellen

cd ../mycrm-billing-module
composer init
# Name: mycrm/billing-module

Schritt 2: Plugin-Klasse implementieren

Siehe Beispiel oben - implementiere getMenuItems() und getPermissionModules().

Schritt 3: In myCRM installieren

cd ../myCRM
composer config repositories.mycrm-billing-module path ../mycrm-billing-module
composer require mycrm/billing-module:@dev

Schritt 4: Permissions synchronisieren

php bin/console app:plugin:sync-permissions billing

Output:

Neu erstellt: 3 (billing, invoicing, payments)

Schritt 5: Cache leeren

php bin/console cache:clear

Schritt 6: Lizenz aktivieren

php bin/console app:module:license billing YOUR_GITEA_TOKEN

Schritt 7: Testen

  1. Frontend öffnen
  2. Plugin-Menü erscheint unter "Finanzen"
  3. Menüpunkte sind nur sichtbar mit Berechtigung

7. Debugging

Menü-Items überprüfen:

curl http://localhost:8000/api/plugin-menu | jq

Permissions in DB überprüfen:

SELECT * FROM modules WHERE code IN ('billing', 'invoicing', 'payments');

Plugin-Status:

php bin/console app:module:list

Logs:

tail -f var/log/dev.log | grep -E "(Plugin|Menu|Permission)"

8. Best Practices

DO's:

  • Gruppiere verwandte Menüs - Nutze group für bessere Organisation
  • Permission-Checks - Definiere permission für alle sensiblen Menüpunkte
  • Icons verwenden - Macht das Menü übersichtlicher
  • Synchronisiere nach Installation - app:plugin:sync-permissions nach jedem Plugin-Update

DON'Ts:

  • Keine Core-Gruppen überschreiben - "Home", "CRM", "Administration" sind reserviert
  • Keine tiefen Verschachtelungen - Max. 2 Ebenen (Gruppe → Items)
  • Keine externen URLs ohne Warnung - User erwarten interne Navigation
  • Nicht mehrere Permission-Module für ein Plugin - Erschwert die Administration unnötig

🎯 WICHTIG: Permission-Format und Sub-Items (Stand: 2025-12-28)

Ein Permission-Modul pro Plugin:

// ✅ RICHTIG: Ein einziges Modul
public function getPermissionModules(): array
{
    return ['billing'];
}

// ❌ FALSCH: Unnötig aufgeteilt
public function getPermissionModules(): array
{
    return ['billing', 'invoices', 'payments'];
}

Explizite Permissions für ALLE Sub-Items:

// ✅ RICHTIG: Jedes anklickbare Item hat permission
public function getMenuItems(): array
{
    return [
        [
            'label' => 'Rechnungen',      // Container - KEINE Permission nötig
            'icon' => 'pi-file-pdf',
            'group' => 'Finanzen',
            'items' => [
                [
                    'label' => 'Alle Rechnungen',
                    'to' => '/billing/invoices',
                    'permission' => 'billing.view'    // ✅ Explizite Permission
                ],
                [
                    'label' => 'Neue Rechnung',
                    'to' => '/billing/invoices/create',
                    'permission' => 'billing.create'  // ✅ Explizite Permission
                ]
            ]
        ]
    ];
}

// ❌ FALSCH: Sub-Items ohne Permission
public function getMenuItems(): array
{
    return [
        [
            'label' => 'Rechnungen',
            'permission' => 'billing.view',  // ❌ Container braucht keine Permission
            'items' => [
                [
                    'label' => 'Alle Rechnungen',
                    'to' => '/billing/invoices'
                    // ❌ FEHLT: permission Property!
                ]
            ]
        ]
    ];
}

Warum ist das wichtig?

Das Frontend (AppMenu.vue) verwendet eine rekursive convertMenuItem() Funktion, die:

  1. Alle Sub-Items durchläuft und deren permission Properties in visible Funktionen umwandelt
  2. Items mit source Property automatisch zu ${source}.view Permission konvertiert
  3. Nur Items mit erfüllter Permission anzeigt

Container-Items (mit items Array) brauchen KEINE eigene Permission, da sie nur dann angezeigt werden, wenn mindestens ein Sub-Item sichtbar ist.


9. Troubleshooting

Problem: Menü erscheint nicht

Lösung:

# Cache leeren
php bin/console cache:clear

# Plugin-Status prüfen
php bin/console app:module:list

# API testen
curl http://localhost:8000/api/plugin-menu/grouped

# Browser-Console überprüfen

Problem: Permissions fehlen

Lösung:

# Permissions synchronisieren
php bin/console app:plugin:sync-permissions

# DB überprüfen
SELECT * FROM modules WHERE code = 'billing';

# Rolle zuweisen
# In der Rollenverwaltung das neue Modul auswählen

Problem: Permission-Checks funktionieren nicht

Lösung:

  • Prüfe ob User die Rolle hat
  • Prüfe ob Rolle die Permission hat
  • Prüfe authStore.hasPermission() Implementierung

10. Migration bestehender Plugins

Wenn du bereits Plugins hast, füge einfach getMenuItems() hinzu:

// Altes Plugin
class MyPlugin implements ModulePluginInterface {
    // ... bestehende Methoden ...
}

// Neu: Füge hinzu
class MyPlugin implements ModulePluginInterface {
    // ... bestehende Methoden ...

    public function getMenuItems(): array
    {
        return [
            // Deine Menüs hier
        ];
    }
}

Dann:

composer dump-autoload
php bin/console cache:clear

Support

Bei Fragen siehe:

  • PLUGIN_SYSTEM.md - Allgemeine Plugin-Architektur
  • GITEA_LICENSE_SYSTEM.md - Lizenzierung
  • docs/PERMISSIONS.md - Permission-System