title = $attributes['title'] ?? 'Admin Panel';
$this->logoUrl = $attributes['logo-url'] ?? '/admin';
$this->currentPath = $attributes['current-path'] ?? $attributes['currentPath'] ?? '/admin';
// Parse navigation menu from attributes
$this->navigationMenu = $this->parseNavigationMenu($attributes);
}
public function getRootNode(): Node
{
$nav = new ElementNode('nav');
$nav->setAttribute('class', 'admin-sidebar');
$nav->setAttribute('role', 'navigation');
$nav->setAttribute('aria-label', 'Admin navigation');
// Use TextNode for HTML content (will be parsed by HtmlRenderer)
$content = new TextNode($this->buildSidebarContent());
$nav->appendChild($content);
return $nav;
}
private function buildSidebarContent(): string
{
$header = $this->buildHeader();
$navigation = $this->buildNavigation();
$footer = $this->buildFooter();
$resizeHandle = '
';
return $header . $navigation . $footer . $resizeHandle;
}
private function buildHeader(): string
{
return <<
HTML;
}
private function buildNavigation(): string
{
if ($this->navigationMenu !== null) {
return $this->buildDynamicNavigation();
}
return $this->buildFallbackNavigation();
}
private function buildDynamicNavigation(): string
{
$html = '';
return $html;
}
private function renderSection(NavigationSection $section): string
{
$sectionId = $this->generateSectionId($section->name);
// Use name attribute for exclusive accordion behavior
$html = 'value() . '="' . $this->escape($sectionId) . '">';
if ($section->name !== '') {
$chevronIcon = '';
$html .= '';
$html .= '' . htmlspecialchars($section->name) . '
';
$html .= $chevronIcon;
$html .= '
';
}
$html .= '';
foreach ($section->items as $item) {
$html .= $this->renderItem($item);
}
$html .= '
';
return $html;
}
/**
* Generate a section ID from section name
*/
private function generateSectionId(string $name): string
{
return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', trim($name)));
}
private function renderItem(NavigationItem $item): string
{
$activeState = $item->isActive($this->currentPath)
? 'aria-current="page"'
: '';
$iconHtml = $this->renderIcon($item->icon);
return <<
{$iconHtml}
{$this->escape($item->name)}
HTML;
}
private function renderIcon(?string $icon): string
{
if ($icon === null || $icon === '') {
return '';
}
// Try to match icon name to SVG or use emoji fallback
$svgIcon = $this->getSvgIcon($icon);
if ($svgIcon !== null) {
return '';
}
// Fallback to emoji or text
$emojiIcon = $this->getEmojiIcon($icon);
return '' . $emojiIcon . '';
}
private function getSvgIcon(string $iconName): ?string
{
// Common icon mappings
$icons = [
'dashboard' => '',
'server' => '',
'database' => '',
'photo' => '',
'chart-bar' => '',
'code' => '',
'bell' => '',
'brain' => '',
'file' => '',
];
return $icons[strtolower($iconName)] ?? null;
}
private function getEmojiIcon(string $iconName): string
{
$emoji = [
'dashboard' => '📊',
'server' => '🖥️',
'database' => '💾',
'photo' => '🖼️',
'chart-bar' => '📈',
'code' => '💻',
'bell' => '🔔',
'brain' => '🧠',
'file' => '📄',
];
return $emoji[strtolower($iconName)] ?? '📄';
}
private function buildFallbackNavigation(): string
{
// Fallback to old hardcoded navigation for backward compatibility
$sectionId = $this->generateSectionId('Dashboard');
$chevronIcon = '';
return <<
Dashboard
{$chevronIcon}
HTML;
}
private function buildFooter(): string
{
return <<
HTML;
}
private function getActiveState(string $path): string
{
return $path === $this->currentPath ? 'aria-current="page"' : '';
}
/**
* Parse navigation menu from attributes
*/
private function parseNavigationMenu(array $attributes): ?NavigationMenu
{
// Try different attribute names
$menuData = $attributes['navigation-menu']
?? $attributes['navigation_menu']
?? $attributes['navigationMenu']
?? null;
if ($menuData === null) {
return null;
}
// Handle array directly
if (is_array($menuData)) {
try {
return NavigationMenu::fromArray($menuData);
} catch (\Exception $e) {
error_log("AdminSidebar: Failed to parse navigation menu from array: " . $e->getMessage());
return null;
}
}
// Handle JSON string
if (is_string($menuData)) {
// Decode HTML entities first (template system escapes values in HTML attributes)
$decodedJson = html_entity_decode($menuData, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$decoded = json_decode($decodedJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("AdminSidebar: JSON decode error: " . json_last_error_msg() . " - Data: " . substr($decodedJson, 0, 200));
return null;
}
if (!is_array($decoded)) {
error_log("AdminSidebar: Decoded data is not an array: " . gettype($decoded));
return null;
}
try {
return NavigationMenu::fromArray($decoded);
} catch (\Exception $e) {
error_log("AdminSidebar: Failed to parse navigation menu from decoded JSON: " . $e->getMessage() . " - Data: " . json_encode($decoded));
return null;
}
}
return null;
}
/**
* Escape HTML special characters
*/
private function escape(string $string): string
{
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
}