fix(sidebar): render audit module nav group as details/summary section

Module-contributed sidebar.admin_nav_item slots are now rendered as
complete <details><summary> nav groups appended to the admin panel,
matching the same pattern as core groups (Organization, Roles, etc.).

Fixes undefined $moduleNavItemSlots by reading layoutNav early.
Merges mail log into the module-contributed Audit & logs group when
both exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 08:22:04 +01:00
parent 83d91789c1
commit cd4055894d
3 changed files with 52 additions and 18 deletions

View File

@@ -67,5 +67,6 @@
"Lifecycle runs (7d)": "Lifecycle-Läufe (7d)",
"Lifecycle risks (7d)": "Lifecycle-Risiken (7d)",
"Top lifecycle reason codes (7d)": "Top-Lifecycle-Fehlercodes (7d)",
"Recent lifecycle risks (7d)": "Aktuelle Lifecycle-Risiken (7d)"
"Recent lifecycle risks (7d)": "Aktuelle Lifecycle-Risiken (7d)",
"Audit & logs": "Audit & Protokolle"
}

View File

@@ -67,5 +67,6 @@
"Lifecycle runs (7d)": "Lifecycle runs (7d)",
"Lifecycle risks (7d)": "Lifecycle risks (7d)",
"Top lifecycle reason codes (7d)": "Top lifecycle reason codes (7d)",
"Recent lifecycle risks (7d)": "Recent lifecycle risks (7d)"
"Recent lifecycle risks (7d)": "Recent lifecycle risks (7d)",
"Audit & logs": "Audit & logs"
}

View File

@@ -3,6 +3,9 @@
use MintyPHP\Service\Docs\DocsCatalogService;
$layoutAuth = is_array($viewAuth['layout'] ?? null) ? $viewAuth['layout'] : [];
$moduleNavItemSlots = is_array(($layoutNav ?? [])['moduleSlots']['sidebar.admin_nav_item'] ?? null)
? $layoutNav['moduleSlots']['sidebar.admin_nav_item']
: [];
$canViewTenants = (bool) ($layoutAuth['can_view_tenants'] ?? false);
$canViewDepartments = (bool) ($layoutAuth['can_view_departments'] ?? false);
$canViewUsers = (bool) ($layoutAuth['can_view_users'] ?? false);
@@ -169,36 +172,66 @@ $adminNavGroups = [
],
];
// ── Merge module-contributed sidebar nav items into admin groups ──
// ── Append module-contributed admin nav groups (sidebar.admin_nav_item slot) ──
// Module items are grouped by their 'group' key. Each unique group becomes a
// <details><summary> section appended after the core admin nav groups.
if ($moduleNavItemSlots) {
// Index groups by key for fast lookup
$groupIndex = [];
foreach ($adminNavGroups as $idx => $group) {
$groupIndex[$group['key'] ?? ''] = $idx;
}
// Sort module items by order, then append to matching group
$moduleGroupsByKey = [];
usort($moduleNavItemSlots, static fn ($a, $b) => ($a['order'] ?? 100) <=> ($b['order'] ?? 100));
foreach ($moduleNavItemSlots as $slot) {
if (!is_array($slot)) { continue; }
$targetGroup = trim((string) ($slot['group'] ?? ''));
$groupKey = trim((string) ($slot['group'] ?? ''));
$slotPath = trim((string) ($slot['path'] ?? ''));
$slotLabel = trim((string) ($slot['label'] ?? ''));
$slotPermission = trim((string) ($slot['permission'] ?? ''));
if ($targetGroup === '' || $slotPath === '' || $slotLabel === '') { continue; }
// Check permission via layoutAuth (module permissions are merged into layout capabilities)
if ($groupKey === '' || $slotPath === '' || $slotLabel === '') { continue; }
$slotVisible = $slotPermission === '' || !empty($layoutAuth[$slotPermission]);
if (!isset($groupIndex[$targetGroup])) { continue; }
$gIdx = $groupIndex[$targetGroup];
$adminNavGroups[$gIdx]['items'][] = [
if (!isset($moduleGroupsByKey[$groupKey])) {
$moduleGroupsByKey[$groupKey] = [];
}
$moduleGroupsByKey[$groupKey][] = [
'label' => t($slotLabel),
'path' => $slotPath,
'active' => navActive($slotPath, true),
'visible' => $slotVisible,
'withTenant' => false,
];
if ($slotVisible) {
$adminNavGroups[$gIdx]['visible'] = true;
}
// Known group metadata — modules declare their group key, we map to label + icon.
$moduleGroupMeta = [
'admin-logs' => ['label' => t('Audit & logs'), 'icon' => 'bi-journal-text'],
];
foreach ($moduleGroupsByKey as $groupKey => $items) {
$hasVisible = false;
foreach ($items as $item) {
if (!empty($item['visible'])) { $hasVisible = true; break; }
}
// Also add mail log to the logs group if it exists as a core item
if ($groupKey === 'admin-logs' && $canViewMailLog) {
array_unshift($items, [
'label' => t('Mail logs'),
'path' => 'admin/mail-log',
'active' => navActive('admin/mail-log', true),
'visible' => $canViewMailLog,
'withTenant' => false,
]);
$hasVisible = true;
}
$meta = $moduleGroupMeta[$groupKey] ?? ['label' => $groupKey, 'icon' => 'bi-list'];
$adminNavGroups[] = [
'key' => $groupKey,
'label' => $meta['label'],
'icon' => $meta['icon'],
'visible' => $hasVisible,
'items' => $items,
];
}
// If modules contributed to admin-logs, remove the core-only logs group (which only had mail log)
if (isset($moduleGroupsByKey['admin-logs'])) {
$adminNavGroups = array_values(array_filter($adminNavGroups, static function ($g) {
// Remove the original core logs group (it's been merged into the module-contributed one)
return !(($g['key'] ?? '') === 'admin-logs' && count($g['items'] ?? []) === 1 && ($g['items'][0]['path'] ?? '') === 'admin/mail-log');
}));
}
}
@@ -266,7 +299,6 @@ $csrfToken = (string) ($layoutNav['csrfToken'] ?? '');
$moduleSlots = is_array($layoutNav['moduleSlots'] ?? null) ? $layoutNav['moduleSlots'] : [];
$modulePanelSlots = is_array($moduleSlots['aside.tab_panel'] ?? null) ? $moduleSlots['aside.tab_panel'] : [];
$moduleSearchSlots = is_array($moduleSlots['search.resource_item'] ?? null) ? $moduleSlots['search.resource_item'] : [];
$moduleNavItemSlots = is_array($moduleSlots['sidebar.admin_nav_item'] ?? null) ? $moduleSlots['sidebar.admin_nav_item'] : [];
?>
<aside class="app-sidebar" id="app-sidebar" data-app-component="sidebar-toggle">
<?php if ($currentTenant): ?>