Completes the generic module platform that enables modules to contribute
UI elements, runtime JS components, and search resources without any
core hardcoding.
New generic UI slot types:
- topbar.right_item: module-contributed topbar buttons
- layout.body_end_template: module-contributed dialog/overlay templates
- layout.head_style: module-contributed global CSS
- runtime.component: declarative JS component registration with phase ordering
New infrastructure:
- ModuleAutoloader: PSR-4 autoloading for module-local PHP classes
- ModuleRuntimePageBuilder: symlinks module pages into runtime directory
- ModuleRuntimeAssetPublisher: publishes module CSS/JS to web/modules/
- ModulePermissionSynchronizer: syncs module permissions to DB
- CLI scripts: module-runtime-sync, module-build, module-migrate,
module-permissions-sync, module-assets-sync
- {{userId}} placeholder in SearchDataService for user-scoped search queries
- Component runtime with phased initialization (early/default/late)
- AppContainer.protectExistingBindings() to prevent module→core overwrites
- Architecture tests: ModuleStructureContractTest, CoreTemplateIsolationTest,
FrontendComponentRuntimeContractTest, AppContainerIsolationContractTest
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
75 lines
3.9 KiB
PHTML
75 lines
3.9 KiB
PHTML
<?php
|
|
$accountUrl = accountUrl();
|
|
$accountName = currentUserDisplayName();
|
|
$accountTooltip = $accountName !== '' ? $accountName : t('Account');
|
|
$layoutAuth = is_array($viewAuth['layout'] ?? null) ? $viewAuth['layout'] : [];
|
|
$hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
|
?>
|
|
<aside class="aside-icon-bar" data-app-component="aside-panels" data-aside-storage="app-sidebar-panel">
|
|
<nav>
|
|
<ul class="aside-icon-group" role="tablist" aria-orientation="vertical">
|
|
<li>
|
|
<button type="button" id="aside-tab-explorer" data-aside-target="explorer" role="tab"
|
|
aria-selected="true" aria-controls="aside-panel-explorer" aria-label="<?php e(t('Explorer')); ?>"
|
|
data-tooltip="<?php e(t('Explorer')); ?>" data-tooltip-pos="right">
|
|
<i class="bi bi-house"></i>
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" id="aside-tab-search" data-aside-target="search" role="tab" aria-selected="false"
|
|
aria-controls="aside-panel-search" aria-label="<?php e(t('Search')); ?>"
|
|
data-tooltip="<?php e(t('Search')); ?>" data-tooltip-pos="right">
|
|
<i class="bi bi-search"></i>
|
|
</button>
|
|
</li>
|
|
<?php
|
|
// ── Module-contributed aside tabs (aside.tab_panel slot) ──
|
|
$moduleSlots = is_array($layoutNav['moduleSlots'] ?? null) ? $layoutNav['moduleSlots'] : [];
|
|
$moduleTabSlots = is_array($moduleSlots['aside.tab_panel'] ?? null) ? $moduleSlots['aside.tab_panel'] : [];
|
|
foreach ($moduleTabSlots as $slot):
|
|
if (!is_array($slot)) { continue; }
|
|
$slotKey = $slot['key'] ?? '';
|
|
$slotLabel = $slot['label'] ?? '';
|
|
$slotIcon = $slot['icon'] ?? 'bi-puzzle';
|
|
$slotHref = $slot['href'] ?? '';
|
|
$slotPermission = $slot['permission'] ?? '';
|
|
if ($slotPermission !== '' && empty($layoutAuth[$slotPermission])) { continue; }
|
|
// Resolve relative route keys via lurl()
|
|
if ($slotHref !== '' && !str_starts_with($slotHref, '/') && !str_starts_with($slotHref, 'http')) {
|
|
$slotHref = lurl($slotHref);
|
|
}
|
|
?>
|
|
<li>
|
|
<button type="button" id="aside-tab-<?php e($slotKey); ?>"
|
|
data-aside-target="<?php e($slotKey); ?>"
|
|
<?php if ($slotHref !== ''): ?>data-aside-href="<?php e($slotHref); ?>"<?php endif; ?>
|
|
role="tab" aria-selected="false"
|
|
aria-controls="aside-panel-<?php e($slotKey); ?>"
|
|
aria-label="<?php e(t($slotLabel)); ?>"
|
|
data-tooltip="<?php e(t($slotLabel)); ?>" data-tooltip-pos="right">
|
|
<i class="bi <?php e($slotIcon); ?>"></i>
|
|
</button>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
<?php if ($hasAdminPanel): ?>
|
|
<li>
|
|
<button type="button" id="aside-tab-admin" data-aside-target="admin" role="tab" aria-selected="false"
|
|
aria-controls="aside-panel-admin" aria-label="<?php e(t('Admin')); ?>"
|
|
data-tooltip="<?php e(t('Admin')); ?>" data-tooltip-pos="right">
|
|
<i class="bi bi-shield-lock"></i>
|
|
</button>
|
|
</li>
|
|
<?php endif; ?>
|
|
</ul>
|
|
<ul role="list">
|
|
<li>
|
|
<a href="<?php e($accountUrl); ?>" aria-label="<?php e(t('Account')); ?>"
|
|
data-tooltip="<?php e($accountTooltip); ?>" data-tooltip-pos="right" data-aside-shortcut="account"><i
|
|
class="bi bi-person-circle"></i></a>
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
</nav>
|
|
</aside>
|