cacheEnabled = $enabled; return $this; } /** * Setzt die Cache-Zeit * * @param int $minutes * @return $this */ public function setCacheTime(int $minutes): self { $this->cacheTime = $minutes; return $this; } /** * Löscht den Navigation-Cache * * @return void */ public function clearCache(): void { Cache::forget('navigation_tree_full'); Cache::forget('navigation_tree_active'); Cache::forget('navigation_flat_full'); Cache::forget('navigation_flat_active'); // Lösche auch alle Subtree-Caches $keys = Cache::get('navigation_subtree_keys', []); foreach ($keys as $key) { Cache::forget($key); } Cache::forget('navigation_subtree_keys'); } /** * Gibt den kompletten Navigationsbaum zurück * * @param bool $onlyActive Nur aktive und sichtbare Seiten zurückgeben * @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen * @return array */ public function getNavigationTree(bool $onlyActive = false, bool $onlyShowInNavi = false): array { $cacheKey = $onlyActive ? 'navigation_tree_active' : 'navigation_tree_full'; if ($this->cacheEnabled) { return Cache::remember($cacheKey, $this->cacheTime, function () use ($onlyActive, $onlyShowInNavi) { return $this->buildNavigationTree($onlyActive, $onlyShowInNavi); }); } return $this->buildNavigationTree($onlyActive, $onlyShowInNavi); } /** * Gibt den Navigationsbaum wie im Frontend zurück (nur Länderseiten mit Children) * * @param bool $includeHidden Auch ausgeblendete Pages anzeigen * @return array */ public function getFrontendNavigationTree(bool $includeHidden = false): array { $cacheKey = 'navigation_tree_frontend_' . ($includeHidden ? 'with_hidden' : 'visible'); if ($this->cacheEnabled) { return Cache::remember($cacheKey, $this->cacheTime, function () use ($includeHidden) { return $this->buildFrontendNavigationTree($includeHidden); }); } return $this->buildFrontendNavigationTree($includeHidden); } /** * Baut den Frontend-Navigationsbaum auf * * @param bool $includeHidden * @return array */ protected function buildFrontendNavigationTree(bool $includeHidden = false): array { $tree = []; // 1. Länderseiten (Hauptnavigation) $countryPages = $this->getCountryPages($includeHidden); foreach ($countryPages as $page) { $node = $this->buildFrontendNode($page, $includeHidden); if ($node) { $node['section'] = 'Länder-Navigation'; $tree[] = $node; } } // 2. Ferienwohnungen (USEDOM) $fewoPages = $this->getFewoPages($includeHidden); if (!empty($fewoPages)) { $tree[] = [ 'is_section_separator' => true, 'title' => 'USEDOM Ferienwohnungen', 'icon' => 'isv-fewo', 'section' => 'Ferienwohnungen' ]; foreach ($fewoPages as $page) { $node = $this->buildFrontendNode($page, $includeHidden); if ($node) { $node['section'] = 'Ferienwohnungen'; $tree[] = $node; } } } // 3. Weitere wichtige Seiten (Mehr-Menü) $morePages = $this->getMoreMenuPages($includeHidden); if (!empty($morePages)) { $tree[] = [ 'is_section_separator' => true, 'title' => 'Weitere Seiten (Mehr-Menü)', 'icon' => 'fa fa-ellipsis-v', 'section' => 'Mehr' ]; foreach ($morePages as $page) { $node = $this->buildFrontendNode($page, $includeHidden); if ($node) { $node['section'] = 'Mehr'; $tree[] = $node; } } } return $tree; } /** * Hole alle Länderseiten * * @param bool $includeHidden * @return \Illuminate\Database\Eloquent\Collection */ protected function getCountryPages(bool $includeHidden = false) { $query = Page::whereNull('parent_id') ->whereNotNull('country_id') ->orderBy('order') ->orderBy('title'); if (!$includeHidden) { $query->where('show_in_navi', 1); $query->where('status', 1); } return $query->get(); } /** * Hole Ferienwohnungs-Übersichtsseite mit Children * * @param bool $includeHidden * @return array */ protected function getFewoPages(bool $includeHidden = false) { // Suche die Hauptseite "Ferienwohnungen" $query = Page::where('slug', 'ferienwohnungen') ->orWhere('real_url_path', '/ferienwohnungen'); if (!$includeHidden) { $query->where('status', 1); } $fewoMainPage = $query->first(); if (!$fewoMainPage) { return []; } // Nur die Hauptseite zurückgeben, Children werden über buildFrontendNode geladen return [$fewoMainPage]; } /** * Hole Seiten für "Mehr"-Menü * * @param bool $includeHidden * @return array */ protected function getMoreMenuPages(bool $includeHidden = false) { // Typische Slugs/Pfade aus dem Mehr-Menü mit ihren möglichen Children $morePages = [ 'ueber-uns' => true, // Könnte Children haben 'reiseversicherung' => false, // Keine Children 'reisefuehrer' => true, // Könnte Children haben 'reisemagazin' => true, // Könnte Children haben 'reisenews' => true // Könnte Children haben ]; $pages = []; foreach ($morePages as $slug => $hasChildren) { $query = Page::where('slug', $slug) ->orWhere('real_url_path', '/' . $slug); if (!$includeHidden) { $query->where('status', 1); } $page = $query->first(); if ($page) { $pages[] = $page; } } return $pages; } /** * Baut einen Frontend-Node auf (mit Children gruppiert nach beforeTitle) * * @param Page $page * @param bool $includeHidden * @param bool $loadChildren Soll Children geladen werden? * @return array|null */ protected function buildFrontendNode(Page $page, bool $includeHidden = false, bool $loadChildren = true): ?array { $node = $this->buildNodeData($page, true); if (!$loadChildren) { $node['children'] = []; $node['has_children'] = false; return $node; } // Hole Children $query = Page::where('parent_id', $page->id) ->orderBy('order') ->orderBy('title'); if (!$includeHidden) { $query->where('show_in_navi', 1); $query->where('status', 1); } $children = $query->get(); // Gruppiere Children nach beforeTitle $groupedChildren = [ 'main' => [], 'infos' => [] ]; foreach ($children as $child) { $childNode = $this->buildNodeData($child, true); if ($child->before_title === 'Infos') { $groupedChildren['infos'][] = $childNode; } else { $groupedChildren['main'][] = $childNode; } } // Baue finale Children-Liste mit Gruppierung $finalChildren = []; // Erst Haupt-Children foreach ($groupedChildren['main'] as $childNode) { $finalChildren[] = $childNode; } // Dann Info-Children mit Separator if (!empty($groupedChildren['infos'])) { $finalChildren[] = [ 'is_separator' => true, 'title' => 'Infos', 'icon' => 'fa fa-info-circle' ]; foreach ($groupedChildren['infos'] as $childNode) { $finalChildren[] = $childNode; } } $node['children'] = $finalChildren; $node['has_children'] = count($finalChildren) > 0; return $node; } /** * Baut den Navigationsbaum auf (ohne Caching) * * @param bool $onlyActive * @param bool $onlyShowInNavi * @return array */ protected function buildNavigationTree(bool $onlyActive = false, bool $onlyShowInNavi = false): array { // Hole alle Root-Seiten (ohne Parent) $query = Page::whereNull('parent_id') ->orderBy('order') ->orderBy('title'); if ($onlyActive) { $query->where('status', 1); } if ($onlyShowInNavi) { $query->where('show_in_navi', 1); } $rootPages = $query->get(); $tree = []; foreach ($rootPages as $page) { $tree[] = $this->buildNode($page, $onlyActive, $onlyShowInNavi); } return $tree; } /** * Gibt einen Teilbaum zurück, beginnend mit einer bestimmten Page * * @param int $rootId Die ID der Root-Page * @param bool $onlyActive Nur aktive Seiten zurückgeben * @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen * @return array|null */ public function getNavigationSubTree(int $rootId, bool $onlyActive = false, bool $onlyShowInNavi = false): ?array { $cacheKey = "navigation_subtree_{$rootId}_" . ($onlyActive ? 'active' : 'full'); if ($this->cacheEnabled) { // Speichere Cache-Key für späteres Löschen $keys = Cache::get('navigation_subtree_keys', []); if (!in_array($cacheKey, $keys)) { $keys[] = $cacheKey; Cache::put('navigation_subtree_keys', $keys, $this->cacheTime); } return Cache::remember($cacheKey, $this->cacheTime, function () use ($rootId, $onlyActive, $onlyShowInNavi) { return $this->buildSubTree($rootId, $onlyActive, $onlyShowInNavi); }); } return $this->buildSubTree($rootId, $onlyActive, $onlyShowInNavi); } /** * Baut einen Teilbaum auf (ohne Caching) * * @param int $rootId * @param bool $onlyActive * @param bool $onlyShowInNavi * @return array|null */ protected function buildSubTree(int $rootId, bool $onlyActive = false, bool $onlyShowInNavi = false): ?array { $page = Page::find($rootId); if (!$page) { return null; } return $this->buildNode($page, $onlyActive, $onlyShowInNavi); } /** * Gibt eine flache Liste aller Navigationspunkte zurück * * @param bool $onlyActive Nur aktive Seiten zurückgeben * @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen * @return array */ public function getFlatNavigationList(bool $onlyActive = false, bool $onlyShowInNavi = false): array { $cacheKey = $onlyActive ? 'navigation_flat_active' : 'navigation_flat_full'; if ($this->cacheEnabled) { return Cache::remember($cacheKey, $this->cacheTime, function () use ($onlyActive, $onlyShowInNavi) { return $this->buildFlatList($onlyActive, $onlyShowInNavi); }); } return $this->buildFlatList($onlyActive, $onlyShowInNavi); } /** * Baut eine flache Liste auf (ohne Caching) * * @param bool $onlyActive * @param bool $onlyShowInNavi * @return array */ protected function buildFlatList(bool $onlyActive = false, bool $onlyShowInNavi = false): array { $query = Page::orderBy('order') ->orderBy('title'); if ($onlyActive) { $query->where('status', 1); } if ($onlyShowInNavi) { $query->where('show_in_navi', 1); } $pages = $query->get(); $list = []; foreach ($pages as $page) { $list[] = $this->buildNodeData($page, false); } return $list; } /** * Baut einen einzelnen Knoten mit allen Kindern rekursiv auf * * @param Page $page * @param bool $onlyActive * @param bool $onlyShowInNavi * @return array */ protected function buildNode(Page $page, bool $onlyActive = false, bool $onlyShowInNavi = false): array { $node = $this->buildNodeData($page, true); // Hole alle Child-Seiten $query = Page::where('parent_id', $page->id) ->orderBy('order') ->orderBy('title'); if ($onlyActive) { $query->where('status', 1); } if ($onlyShowInNavi) { $query->where('show_in_navi', 1); } $children = $query->get(); $childNodes = []; foreach ($children as $child) { $childNodes[] = $this->buildNode($child, $onlyActive, $onlyShowInNavi); } $node['children'] = $childNodes; $node['has_children'] = count($childNodes) > 0; return $node; } /** * Erstellt die Datenstruktur für einen einzelnen Navigationspunkt * * @param Page $page * @param bool $includeRelations Beziehungen laden (TravelProgram, etc.) * @return array */ protected function buildNodeData(Page $page, bool $includeRelations = true): array { $data = [ 'id' => $page->id, 'title' => $page->title, 'title_short' => $page->title_short, 'before_title' => $page->before_title, 'slug' => $page->slug, 'real_url_path' => $page->real_url_path, 'url' => $page->real_url_path ?: $this->buildUrlPath($page), 'status' => $page->status, 'show_in_navi' => $page->show_in_navi, 'order' => $page->order, 'template' => $page->template, 'lvl' => $page->lvl, 'parent_id' => $page->parent_id, // SEO-Informationen 'pagetitle' => $page->pagetitle, 'description' => $page->description, 'keywords' => $page->keywords, 'canonical_url' => $page->canonical_url, // Tree-Informationen 'lft' => $page->lft, 'rgt' => $page->rgt, 'tree_root' => $page->tree_root, // Box-Informationen (für Karten/Boxen in der Navigation) 'box_body' => $page->box_body, 'box_image_url' => $page->box_image_url, 'box_star' => $page->box_star, 'box_discount' => $page->box_discount, // Zusätzliche Flags 'is_travel_program' => !is_null($page->travel_program), 'is_fewo_lodging' => !is_null($page->fewo_lodging), 'is_country_page' => !is_null($page->country_id), ]; if ($includeRelations) { // Lade Beziehungen wenn benötigt if ($page->travel_program) { $travelProgram = $page->travel_program_content; if ($travelProgram) { $data['travel_program'] = [ 'id' => $travelProgram->id, 'title' => $travelProgram->title, 'subtitle' => $travelProgram->subtitle, 'status' => $travelProgram->status, 'position' => $travelProgram->position, 'duration' => $travelProgram->duration, 'price_from' => $travelProgram->price_from, ]; } } if ($page->fewo_lodging) { $fewoLodging = $page->fewo_lodging(); if ($fewoLodging) { $fewo = $fewoLodging->first(); if ($fewo) { $data['fewo_lodging'] = [ 'id' => $fewo->id, 'name' => $fewo->name, 'status' => $fewo->status, ]; } } } if ($page->country_id) { $country = $page->travel_country; if ($country) { $data['country'] = [ 'id' => $country->id, 'name' => $country->name, 'title' => $country->title, 'slug' => $country->slug, ]; } } } // CMS Settings (JSON) if ($page->cms_settings) { try { $data['cms_settings'] = json_decode($page->cms_settings, true); } catch (\Exception $e) { $data['cms_settings'] = null; } } return $data; } /** * Baut den URL-Pfad für eine Page rekursiv auf (Fallback wenn real_url_path nicht gesetzt) * * @param Page $page * @return string */ protected function buildUrlPath(Page $page): string { $slugs = []; $current = $page; // Sammle alle Slugs von unten nach oben while ($current) { if ($current->slug) { array_unshift($slugs, $current->slug); } $current = $current->parent_page; } return '/' . implode('/', $slugs); } /** * Zählt die Anzahl der Knoten im Baum * * @param array $tree * @return int */ public function countNodes(array $tree): int { $count = 0; foreach ($tree as $node) { $count++; // Zähle den aktuellen Knoten if (isset($node['children']) && is_array($node['children'])) { $count += $this->countNodes($node['children']); } } return $count; } /** * Findet einen Knoten anhand seiner ID im Baum * * @param array $tree * @param int $nodeId * @return array|null */ public function findNodeById(array $tree, int $nodeId): ?array { foreach ($tree as $node) { if ($node['id'] === $nodeId) { return $node; } if (isset($node['children']) && is_array($node['children'])) { $found = $this->findNodeById($node['children'], $nodeId); if ($found) { return $found; } } } return null; } /** * Gibt den Breadcrumb-Pfad für eine bestimmte Page zurück * * @param int $pageId * @return array */ public function getBreadcrumb(int $pageId): array { $page = Page::find($pageId); if (!$page) { return []; } $breadcrumb = []; $current = $page; while ($current) { array_unshift($breadcrumb, [ 'id' => $current->id, 'title' => $current->title, 'title_short' => $current->title_short, 'url' => $current->real_url_path ?: $this->buildUrlPath($current), 'slug' => $current->slug, ]); $current = $current->parent_page; } return $breadcrumb; } }