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 << {$this->title} 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 .= '
'; 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 <<
A
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'); } }