feat: implement recursive menu item conversion with permission checks
This commit is contained in:
parent
b4974b93ef
commit
ed2199096d
@ -79,6 +79,44 @@ const adminMenu = {
|
|||||||
// Dynamisches Menü (wird geladen)
|
// Dynamisches Menü (wird geladen)
|
||||||
const model = ref([...coreMenu]);
|
const model = ref([...coreMenu]);
|
||||||
|
|
||||||
|
// Hilfsfunktion: Konvertiert ein Menü-Item und alle Sub-Items
|
||||||
|
const convertMenuItem = (item) => {
|
||||||
|
// Erstelle Permission-Prüfung
|
||||||
|
let visibleFn;
|
||||||
|
|
||||||
|
if (item.permission) {
|
||||||
|
// Explizite Permission aus Plugin (z.B. "billing.view")
|
||||||
|
visibleFn = () => authStore.hasPermission(item.permission);
|
||||||
|
} else if (item.module) {
|
||||||
|
// Fallback: Modulname + ".view" (z.B. "billing" -> "billing.view")
|
||||||
|
visibleFn = () => authStore.hasPermission(`${item.module}.view`);
|
||||||
|
} else if (item.source) {
|
||||||
|
// Fallback: Plugin-Identifier + ".view" (z.B. "billing" -> "billing.view")
|
||||||
|
visibleFn = () => authStore.hasPermission(`${item.source}.view`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const converted = {
|
||||||
|
label: item.label,
|
||||||
|
icon: item.icon ? `pi pi-fw ${item.icon}` : 'pi pi-fw pi-circle',
|
||||||
|
to: item.to,
|
||||||
|
...(visibleFn && { visible: visibleFn })
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rekursiv Sub-Items verarbeiten
|
||||||
|
if (item.items && Array.isArray(item.items)) {
|
||||||
|
converted.items = item.items
|
||||||
|
.filter(subItem => !subItem.separator) // Separators filtern
|
||||||
|
.map(subItem => convertMenuItem(subItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separators beibehalten
|
||||||
|
if (item.separator) {
|
||||||
|
return { separator: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
};
|
||||||
|
|
||||||
// Plugin-Menüs laden
|
// Plugin-Menüs laden
|
||||||
const loadPluginMenus = async () => {
|
const loadPluginMenus = async () => {
|
||||||
try {
|
try {
|
||||||
@ -91,29 +129,7 @@ const loadPluginMenus = async () => {
|
|||||||
// Konvertiere zu PrimeVue Menü-Format
|
// Konvertiere zu PrimeVue Menü-Format
|
||||||
const menuGroup = {
|
const menuGroup = {
|
||||||
label: groupLabel,
|
label: groupLabel,
|
||||||
items: items.map(item => {
|
items: items.map(item => convertMenuItem(item))
|
||||||
// Erstelle Permission-Prüfung
|
|
||||||
let visibleFn;
|
|
||||||
|
|
||||||
if (item.permission) {
|
|
||||||
// Explizite Permission aus Plugin
|
|
||||||
visibleFn = () => authStore.hasPermission(item.permission);
|
|
||||||
} else if (item.module) {
|
|
||||||
// Fallback: Leite von Modulname ab (z.B. 'billing')
|
|
||||||
visibleFn = () => permissionStore.canView(item.module);
|
|
||||||
} else if (item.source) {
|
|
||||||
// Fallback: Nutze Plugin-Identifier als Modulname
|
|
||||||
visibleFn = () => permissionStore.canView(item.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: item.label,
|
|
||||||
icon: item.icon ? `pi pi-fw ${item.icon}` : 'pi pi-fw pi-circle',
|
|
||||||
to: item.to,
|
|
||||||
...(item.items && { items: item.items }),
|
|
||||||
...(visibleFn && { visible: visibleFn })
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
model.value.push(menuGroup);
|
model.value.push(menuGroup);
|
||||||
|
|||||||
8
composer.lock
generated
8
composer.lock
generated
@ -2052,12 +2052,12 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.osdata-home.de/mycrm/mycrm-billing-module",
|
"url": "https://git.osdata-home.de/mycrm/mycrm-billing-module",
|
||||||
"reference": "a70043acd597e4ff5064a51e639a5f28227e5cd4"
|
"reference": "8c2544569381333f45ae013b6d0a4ac61d4eb0e0"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"doctrine/orm": "^3.0",
|
"doctrine/orm": "^3.0",
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"symfony/framework-bundle": "^7.1"
|
"symfony/framework-bundle": "^7.3"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"api-platform/symfony": "Required for API Platform integration (>=4.0)"
|
"api-platform/symfony": "Required for API Platform integration (>=4.0)"
|
||||||
@ -2066,7 +2066,7 @@
|
|||||||
"type": "symfony-bundle",
|
"type": "symfony-bundle",
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"require": "7.1.*"
|
"require": "7.3.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@ -2078,7 +2078,7 @@
|
|||||||
"proprietary"
|
"proprietary"
|
||||||
],
|
],
|
||||||
"description": "Ausgangsrechnungsverwaltung für myCRM",
|
"description": "Ausgangsrechnungsverwaltung für myCRM",
|
||||||
"time": "2025-12-22T08:27:54+00:00"
|
"time": "2025-12-28T09:14:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nelmio/cors-bundle",
|
"name": "nelmio/cors-bundle",
|
||||||
|
|||||||
@ -22,21 +22,22 @@ public function getMenuItems(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
'label' => 'Rechnungen', // Menü-Titel
|
'label' => 'Rechnungen', // Menü-Titel (Container)
|
||||||
'icon' => 'pi-file-pdf', // PrimeIcons Icon (ohne pi pi-fw prefix)
|
'icon' => 'pi-file-pdf', // PrimeIcons Icon (ohne pi pi-fw prefix)
|
||||||
'group' => 'Finanzen', // Gruppierung im Menü
|
'group' => 'Finanzen', // Gruppierung im Menü
|
||||||
|
// WICHTIG: Container-Items brauchen KEINE permission Property
|
||||||
'items' => [
|
'items' => [
|
||||||
[
|
[
|
||||||
'label' => 'Alle Rechnungen',
|
'label' => 'Alle Rechnungen',
|
||||||
'icon' => 'pi-list',
|
'icon' => 'pi-list',
|
||||||
'to' => '/billing/invoices', // Vue Router path
|
'to' => '/billing/invoices', // Vue Router path
|
||||||
'permission' => 'billing.view' // Optional: Permission-Check
|
'permission' => 'billing.view' // WICHTIG: Jedes Sub-Item braucht permission
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Neue Rechnung',
|
'label' => 'Neue Rechnung',
|
||||||
'icon' => 'pi-plus',
|
'icon' => 'pi-plus',
|
||||||
'to' => '/billing/invoices/create',
|
'to' => '/billing/invoices/create',
|
||||||
'permission' => 'billing.create'
|
'permission' => 'billing.create' // Explizite Permission
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'separator' => true // Optional: Trennlinie
|
'separator' => true // Optional: Trennlinie
|
||||||
@ -45,7 +46,7 @@ public function getMenuItems(): array
|
|||||||
'label' => 'Einstellungen',
|
'label' => 'Einstellungen',
|
||||||
'icon' => 'pi-cog',
|
'icon' => 'pi-cog',
|
||||||
'to' => '/billing/settings',
|
'to' => '/billing/settings',
|
||||||
'permission' => 'billing.manage'
|
'permission' => 'billing.manage' // Explizite Permission
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -75,11 +76,14 @@ public function getMenuItems(): array
|
|||||||
```php
|
```php
|
||||||
public function getPermissionModules(): array
|
public function getPermissionModules(): array
|
||||||
{
|
{
|
||||||
return ['billing', 'invoicing'];
|
// Best Practice: Ein einziges Modul pro Plugin
|
||||||
|
return ['billing'];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Das System erstellt automatisch folgende Permissions:
|
**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.view`
|
||||||
- `billing.create`
|
- `billing.create`
|
||||||
- `billing.edit`
|
- `billing.edit`
|
||||||
@ -276,7 +280,8 @@ class BillingModulePlugin implements ModulePluginInterface
|
|||||||
|
|
||||||
public function getPermissionModules(): array
|
public function getPermissionModules(): array
|
||||||
{
|
{
|
||||||
return ['billing', 'invoicing', 'payments'];
|
// Best Practice: Ein einziges Modul für das gesamte Plugin
|
||||||
|
return ['billing'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMenuItems(): array
|
public function getMenuItems(): array
|
||||||
@ -286,12 +291,13 @@ class BillingModulePlugin implements ModulePluginInterface
|
|||||||
'label' => 'Rechnungen',
|
'label' => 'Rechnungen',
|
||||||
'icon' => 'pi-file-pdf',
|
'icon' => 'pi-file-pdf',
|
||||||
'group' => 'Finanzen',
|
'group' => 'Finanzen',
|
||||||
|
// Wichtig: Container-Item hat KEINE permission Property
|
||||||
'items' => [
|
'items' => [
|
||||||
[
|
[
|
||||||
'label' => 'Dashboard',
|
'label' => 'Dashboard',
|
||||||
'icon' => 'pi-chart-line',
|
'icon' => 'pi-chart-line',
|
||||||
'to' => '/billing/dashboard',
|
'to' => '/billing/dashboard',
|
||||||
'permission' => 'billing.view'
|
'permission' => 'billing.view' // Explizite Permission
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Alle Rechnungen',
|
'label' => 'Alle Rechnungen',
|
||||||
@ -312,7 +318,7 @@ class BillingModulePlugin implements ModulePluginInterface
|
|||||||
'label' => 'Zahlungen',
|
'label' => 'Zahlungen',
|
||||||
'icon' => 'pi-money-bill',
|
'icon' => 'pi-money-bill',
|
||||||
'to' => '/billing/payments',
|
'to' => '/billing/payments',
|
||||||
'permission' => 'payments.view'
|
'permission' => 'billing.view' // Einheitliches Modul!
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Einstellungen',
|
'label' => 'Einstellungen',
|
||||||
@ -438,6 +444,78 @@ tail -f var/log/dev.log | grep -E "(Plugin|Menu|Permission)"
|
|||||||
- **Keine Core-Gruppen überschreiben** - "Home", "CRM", "Administration" sind reserviert
|
- **Keine Core-Gruppen überschreiben** - "Home", "CRM", "Administration" sind reserviert
|
||||||
- **Keine tiefen Verschachtelungen** - Max. 2 Ebenen (Gruppe → Items)
|
- **Keine tiefen Verschachtelungen** - Max. 2 Ebenen (Gruppe → Items)
|
||||||
- **Keine externen URLs ohne Warnung** - User erwarten interne Navigation
|
- **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:**
|
||||||
|
```php
|
||||||
|
// ✅ 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:**
|
||||||
|
```php
|
||||||
|
// ✅ 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -74,7 +74,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mycrm/billing-module": {
|
"mycrm/billing-module": {
|
||||||
"version": "v1.0.0"
|
"version": "dev-main"
|
||||||
},
|
},
|
||||||
"nelmio/cors-bundle": {
|
"nelmio/cors-bundle": {
|
||||||
"version": "2.6",
|
"version": "2.6",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user