From c39d86f80b4ad1007e60c53690ce9b4917639697 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Thu, 18 Jun 2026 10:46:22 +0000 Subject: [PATCH] =?UTF-8?q?Seiten-Sitemap=20mit=20Vorschaubild-Verwaltung?= =?UTF-8?q?=20im=20CRM=20erg=C3=A4nzen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Nested-Set-Baum zeigt box_image_url, box_body und Reiseprogramm-Bilder inklusive Bearbeitung per Modal und Mediathek, damit fehlende Kachelbilder schneller gefunden und korrigiert werden können. Co-authored-by: Cursor --- .../Controllers/NavigationTreeController.php | 129 ++- app/Services/NavigationTreeService.php | 465 +++++++++- config/app.php | 2 +- .../layouts/includes/layout-sidenav.blade.php | 4 +- resources/views/navigation/index.blade.php | 839 +++++++++++++++--- routes/web.php | 2 + 6 files changed, 1305 insertions(+), 136 deletions(-) diff --git a/app/Http/Controllers/NavigationTreeController.php b/app/Http/Controllers/NavigationTreeController.php index 9c9c542..f8b21fb 100644 --- a/app/Http/Controllers/NavigationTreeController.php +++ b/app/Http/Controllers/NavigationTreeController.php @@ -2,9 +2,11 @@ namespace App\Http\Controllers; +use App\Models\Page; use App\Services\NavigationTreeService; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; +use IqContent\LaravelFilemanager\Lfm; class NavigationTreeController extends Controller { @@ -22,7 +24,9 @@ class NavigationTreeController extends Controller */ public function index() { - return view('navigation.index'); + return view('navigation.index', [ + 'lfm_helper' => app(Lfm::class), + ]); } /** @@ -34,7 +38,25 @@ class NavigationTreeController extends Controller public function getData(Request $request): JsonResponse { try { - $includeHidden = $request->get('include_hidden', true); + $mode = $request->get('mode', 'sitemap'); + $includeHidden = $request->get('include_hidden', false); + $onlyActive = !$includeHidden; + + if ($mode === 'sitemap') { + $tree = $this->navigationService->getSitemapTree($onlyActive); + + return response()->json([ + 'success' => true, + 'data' => $tree, + 'meta' => [ + 'total_nodes' => $this->navigationService->countNodes($tree), + 'missing_preview_images' => $this->navigationService->countMissingPreviewImages($tree), + 'include_hidden' => $includeHidden, + 'structure' => 'sitemap', + ], + ]); + } + $tree = $this->navigationService->getFrontendNavigationTree($includeHidden); return response()->json([ @@ -43,8 +65,8 @@ class NavigationTreeController extends Controller 'meta' => [ 'total_nodes' => $this->navigationService->countNodes($tree), 'include_hidden' => $includeHidden, - 'structure' => 'frontend' - ] + 'structure' => 'header', + ], ]); } catch (\Exception $e) { return response()->json([ @@ -98,10 +120,17 @@ class NavigationTreeController extends Controller public function export(Request $request) { try { - $includeHidden = $request->get('include_hidden', true); - $tree = $this->navigationService->getFrontendNavigationTree($includeHidden); + $mode = $request->get('mode', 'sitemap'); + $includeHidden = $request->get('include_hidden', false); + $onlyActive = !$includeHidden; - $filename = 'navigation-tree-frontend-' . date('Y-m-d-His') . '.json'; + if ($mode === 'sitemap') { + $tree = $this->navigationService->getSitemapTree($onlyActive); + $filename = 'sitemap-tree-' . date('Y-m-d-His') . '.json'; + } else { + $tree = $this->navigationService->getFrontendNavigationTree($includeHidden); + $filename = 'navigation-tree-frontend-' . date('Y-m-d-His') . '.json'; + } $json = json_encode($tree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); return response($json) @@ -117,12 +146,21 @@ class NavigationTreeController extends Controller * * @return \Illuminate\Http\RedirectResponse */ - public function clearCache() + public function clearCache(Request $request) { try { $this->navigationService->clearCache(); + + if ($request->expectsJson() || $request->ajax()) { + return response()->json(['success' => true]); + } + return back()->with('success', 'Navigation-Cache erfolgreich gelöscht'); } catch (\Exception $e) { + if ($request->expectsJson() || $request->ajax()) { + return response()->json(['success' => false, 'error' => $e->getMessage()], 500); + } + return back()->with('error', 'Cache-Löschung fehlgeschlagen: ' . $e->getMessage()); } } @@ -149,6 +187,9 @@ class NavigationTreeController extends Controller 'travel_programs' => count(array_filter($flatList, fn($n) => $n['is_travel_program'])), 'fewo_lodgings' => count(array_filter($flatList, fn($n) => $n['is_fewo_lodging'])), 'country_pages' => count(array_filter($flatList, fn($n) => $n['is_country_page'])), + 'missing_preview_images' => $this->navigationService->countMissingPreviewImages( + $this->navigationService->getSitemapTree(false) + ), ]; return response()->json([ @@ -162,4 +203,76 @@ class NavigationTreeController extends Controller ], 500); } } + + /** + * Setzt oder löscht box_image_url einer Seite. + */ + public function updateBoxImage(Request $request, int $id): JsonResponse + { + try { + $page = Page::findOrFail($id); + $action = $request->input('action'); + + if ($action === 'clear') { + if (empty($page->box_image_url)) { + return response()->json([ + 'success' => false, + 'error' => 'Für diese Seite ist kein verknüpftes Seitenbild (box_image_url) hinterlegt.', + ], 422); + } + + if (!$this->navigationService->pageCanClearBoxImage($page)) { + return response()->json([ + 'success' => false, + 'error' => 'Das Seitenbild kann hier nicht entfernt werden, weil kein Reiseprogramm-Bild als Ersatz vorhanden ist.', + ], 422); + } + + $page->box_image_url = null; + $page->save(); + } elseif ($action === 'clear_box_body') { + if (!$this->navigationService->pageHasBoxBodyPreview($page)) { + return response()->json([ + 'success' => false, + 'error' => 'Für diese Seite ist kein Legacy-Bild (box_body) hinterlegt.', + ], 422); + } + + $page->box_body = null; + $page->save(); + } elseif ($action === 'set') { + $url = trim((string) $request->input('box_image_url', '')); + if ($url === '') { + return response()->json([ + 'success' => false, + 'error' => 'Bitte wählen Sie zuerst ein Bild aus der Mediathek.', + ], 422); + } + + $page->box_image_url = $url; + $page->save(); + } else { + return response()->json([ + 'success' => false, + 'error' => 'Ungültige Aktion.', + ], 422); + } + + $this->navigationService->clearCache(); + + return response()->json([ + 'success' => true, + 'message' => match ($action) { + 'clear' => 'Das verknüpfte Seitenbild wurde entfernt.', + 'clear_box_body' => 'Das Legacy-Bild (box_body) wurde entfernt.', + default => 'Das neue Seitenbild wurde gespeichert.', + }, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'error' => $e->getMessage(), + ], 500); + } + } } diff --git a/app/Services/NavigationTreeService.php b/app/Services/NavigationTreeService.php index abe5c32..06b8afb 100644 --- a/app/Services/NavigationTreeService.php +++ b/app/Services/NavigationTreeService.php @@ -5,6 +5,7 @@ namespace App\Services; use App\Models\Page; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; class NavigationTreeService { @@ -20,6 +21,14 @@ class NavigationTreeService */ protected $cacheEnabled = true; + /** @var array */ + protected $travelProgramImageMap = []; + + /** @var array */ + protected $fewoImageMap = []; + + protected $previewImagesPreloaded = false; + /** * Aktiviert oder deaktiviert Caching * @@ -55,6 +64,10 @@ class NavigationTreeService Cache::forget('navigation_tree_active'); Cache::forget('navigation_flat_full'); Cache::forget('navigation_flat_active'); + Cache::forget('navigation_tree_sitemap_full'); + Cache::forget('navigation_tree_sitemap_active'); + Cache::forget('navigation_tree_frontend_with_hidden'); + Cache::forget('navigation_tree_frontend_visible'); // Lösche auch alle Subtree-Caches $keys = Cache::get('navigation_subtree_keys', []); @@ -102,6 +115,421 @@ class NavigationTreeService return $this->buildFrontendNavigationTree($includeHidden); } + /** + * Vollständiger Seiten-Sitemap-Baum (Nested Set), von Root bis Reiseprogramm. + * + * @param bool $onlyActive + * @return array + */ + public function getSitemapTree(bool $onlyActive = false): array + { + $cacheKey = $onlyActive ? 'navigation_tree_sitemap_active' : 'navigation_tree_sitemap_full'; + + if ($this->cacheEnabled) { + return Cache::remember($cacheKey, $this->cacheTime, function () use ($onlyActive) { + return $this->buildSitemapTree($onlyActive); + }); + } + + return $this->buildSitemapTree($onlyActive); + } + + /** + * @param bool $onlyActive + * @return array + */ + protected function buildSitemapTree(bool $onlyActive = false): array + { + $this->preloadPreviewImageMaps(); + $roots = $this->getNestedSetRootPages($onlyActive); + + $children = []; + foreach ($roots as $rootPage) { + $children[] = $this->buildSitemapNode($rootPage, $onlyActive); + } + + return [ + [ + 'id' => 'homepage', + 'title' => 'Startseite', + 'slug' => null, + 'url' => '/', + 'frontend_url' => $this->buildFrontendUrl('/'), + 'status' => 1, + 'show_in_navi' => 1, + 'node_type' => 'homepage', + 'node_type_label' => 'Startseite', + 'is_virtual' => true, + 'has_preview_image' => null, + 'preview_image_source' => null, + 'children' => $children, + 'has_children' => count($children) > 0, + ], + ]; + } + + /** + * @param bool $onlyActive + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function getNestedSetRootPages(bool $onlyActive = false) + { + $query = Page::where('lvl', 0) + ->orderBy('lft') + ->orderBy('order') + ->orderBy('title'); + + if ($onlyActive) { + $query->where('status', 1); + } + + return $query->get(); + } + + /** + * @param Page $page + * @param bool $onlyActive + * @return array + */ + protected function buildSitemapNode(Page $page, bool $onlyActive = false): array + { + $node = $this->buildNodeData($page, true); + $node['node_type'] = $this->resolveSitemapNodeType($page); + $node['node_type_label'] = $this->resolveSitemapNodeTypeLabel($node['node_type']); + $node['is_virtual'] = false; + + if ($this->nodeNeedsPreviewImage($node['node_type'])) { + if (empty($node['preview_image_url'])) { + $node['preview_image_url'] = $this->getNoPreviewImageUrl(); + } + } else { + $node['has_preview_image'] = null; + } + + $children = $this->getNestedSetChildren($page, $onlyActive); + $childNodes = []; + foreach ($children as $child) { + $childNodes[] = $this->buildSitemapNode($child, $onlyActive); + } + + $node['children'] = $childNodes; + $node['has_children'] = count($childNodes) > 0; + + return $node; + } + + /** + * Direkte Kinder per Nested-Set (wie im Symfony-Frontend). + * + * @param Page $parent + * @param bool $onlyActive + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function getNestedSetChildren(Page $parent, bool $onlyActive = false) + { + if ($parent->lft === null || $parent->rgt === null || $parent->lvl === null) { + return Page::where('parent_id', $parent->id) + ->orderBy('order') + ->orderBy('title') + ->when($onlyActive, function ($query) { + $query->where('status', 1); + }) + ->get(); + } + + $query = Page::where('lvl', $parent->lvl + 1) + ->where('lft', '>', $parent->lft) + ->where('rgt', '<', $parent->rgt) + ->when($parent->tree_root !== null, function ($query) use ($parent) { + $query->where('tree_root', $parent->tree_root); + }) + ->orderBy('lft') + ->orderBy('order') + ->orderBy('title'); + + if ($onlyActive) { + $query->where('status', 1); + } + + return $query->get(); + } + + protected function resolveSitemapNodeType(Page $page): string + { + if (!empty($page->travel_program)) { + return 'travel_program'; + } + + if (!empty($page->fewo_lodging)) { + return 'fewo_lodging'; + } + + if (!empty($page->travel_guide_content_id)) { + return 'travel_guide'; + } + + if (!empty($page->country_id) && $page->template === 'overview' && (int) $page->lvl === 0) { + return 'country_root'; + } + + if ($page->template === 'overview') { + return 'category'; + } + + if (in_array($page->template, ['travelProgram', 'travelProgramOverview', 'nilecruise'], true)) { + return 'program_overview'; + } + + return 'content'; + } + + protected function resolveSitemapNodeTypeLabel(string $nodeType): string + { + $labels = [ + 'homepage' => 'Startseite', + 'country_root' => 'Land / Reisekategorie', + 'category' => 'Unterkategorie', + 'program_overview' => 'Programm-Übersicht', + 'travel_program' => 'Einzelreise', + 'fewo_lodging' => 'Ferienwohnung', + 'travel_guide' => 'Reiseführer', + 'content' => 'Inhaltsseite', + ]; + + return $labels[$nodeType] ?? 'Seite'; + } + + protected function nodeNeedsPreviewImage(string $nodeType): bool + { + return in_array($nodeType, [ + 'country_root', + 'category', + 'program_overview', + 'travel_program', + 'fewo_lodging', + ], true); + } + + /** + * @param Page $page + * @return array{has_preview_image: bool, preview_image_source: string|null, preview_image_url: string|null} + */ + protected function resolvePreviewImage(Page $page): array + { + $this->preloadPreviewImageMaps(); + + $boxBodyThumb = $this->getBoxBodyThumbUrl($page->box_body); + if ($boxBodyThumb !== null) { + return $this->previewImageResult(true, 'box_body', $boxBodyThumb); + } + + if (!empty($page->box_image_url)) { + return $this->previewImageResult(true, 'box_image_url', $page->box_image_url); + } + + if (!empty($page->travel_program)) { + $image = $this->travelProgramImageMap[$page->travel_program] ?? null; + if ($image !== null) { + $source = (int) $image->type === 2 + ? 'travel_program_image' + : 'travel_program_image_fallback'; + + return [ + 'has_preview_image' => true, + 'preview_image_source' => $source, + 'preview_image_url' => $this->buildTravelProgramImageUrl($image, false), + ]; + } + } + + if (!empty($page->fewo_lodging)) { + $image = $this->fewoImageMap[$page->fewo_lodging] ?? null; + if ($image !== null && !empty($image->full_file_name)) { + return $this->previewImageResult( + true, + 'fewo_lodging_image', + '_uploads/images/' . $image->full_file_name + ); + } + } + + return $this->previewImageResult(false, null, null); + } + + /** + * @param bool $hasPreview + * @param string|null $source + * @param string|null $path + * @return array{has_preview_image: bool, preview_image_source: string|null, preview_image_url: string|null} + */ + protected function previewImageResult(bool $hasPreview, ?string $source, ?string $path): array + { + return [ + 'has_preview_image' => $hasPreview, + 'preview_image_source' => $source, + 'preview_image_url' => $hasPreview ? $this->buildFrontendAssetUrl($path) : null, + ]; + } + + protected function resolveTravelProgramPreviewUrl(Page $page, bool $useThumb = true): ?string + { + if (empty($page->travel_program)) { + return null; + } + + $this->preloadPreviewImageMaps(); + + $image = $this->travelProgramImageMap[$page->travel_program] ?? null; + if ($image === null) { + return null; + } + + return $this->buildTravelProgramImageUrl($image, $useThumb); + } + + public function pageCanClearBoxImage(Page $page): bool + { + if (empty($page->box_image_url) || empty($page->travel_program)) { + return false; + } + + return $this->resolveTravelProgramPreviewUrl($page, false) !== null; + } + + public function pageHasBoxBodyPreview(Page $page): bool + { + return $this->getBoxBodyThumbUrl($page->box_body) !== null; + } + + public function pageCanClearBoxBody(Page $page): bool + { + return $this->pageHasBoxBodyPreview($page); + } + + /** + * Reiseprogramm-Bilder liegen auf v2 (nicht sterntours.de). + * + * @param object $image + * @param bool $useThumb + * @return string + */ + protected function buildTravelProgramImageUrl(object $image, bool $useThumb = true): string + { + $fileName = $image->file_name; + if ($useThumb) { + $fileName .= '_thumb'; + } + + $path = 'uploads/travel_program/' . $fileName . $image->extension; + + return rtrim((string) config('app.url_v2'), '/') . '/' . ltrim($path, '/'); + } + + protected function preloadPreviewImageMaps(): void + { + if ($this->previewImagesPreloaded) { + return; + } + + $travelImages = DB::connection('mysql_stern') + ->table('travel_program_image') + ->orderBy('id') + ->get(); + + foreach ($travelImages->groupBy('program_id') as $programId => $images) { + $preferred = $images->first(function ($image) { + return (int) $image->type === 2; + }); + $this->travelProgramImageMap[$programId] = $preferred ?? $images->first(); + } + + $fewoImages = DB::connection('mysql_stern') + ->table('fewo_lodging_image') + ->orderBy('pos') + ->orderBy('id') + ->get(); + + foreach ($fewoImages->groupBy('lodging_id') as $lodgingId => $images) { + $this->fewoImageMap[$lodgingId] = $images->first(); + } + + $this->previewImagesPreloaded = true; + } + + protected function getBoxBodyThumbUrl(?string $boxBody): ?string + { + if (empty($boxBody)) { + return null; + } + + $decoded = json_decode($boxBody, true); + + if (!is_array($decoded) || empty($decoded['thumb_url'])) { + return null; + } + + return $decoded['thumb_url']; + } + + protected function getNoPreviewImageUrl(): string + { + return $this->buildFrontendAssetUrl('bundles/app/images/no-picture.png'); + } + + /** + * @param string|null $path + * @return string|null + */ + protected function buildFrontendAssetUrl(?string $path): ?string + { + if ($path === null || $path === '') { + return null; + } + + if (preg_match('/^https?:\/\//i', $path)) { + return $path; + } + + $base = rtrim((string) config('app.url_stern'), '/'); + + return $base . (str_starts_with($path, '/') ? $path : '/' . $path); + } + + protected function hasBoxBodyPreviewImage(?string $boxBody): bool + { + return $this->getBoxBodyThumbUrl($boxBody) !== null; + } + + /** + * Zählt Knoten ohne Vorschaubild im Sitemap-Baum. + * + * @param array $tree + * @return int + */ + public function countMissingPreviewImages(array $tree): int + { + $count = 0; + + foreach ($tree as $node) { + if (!empty($node['is_virtual'])) { + if (!empty($node['children'])) { + $count += $this->countMissingPreviewImages($node['children']); + } + continue; + } + + if (isset($node['has_preview_image']) && $node['has_preview_image'] === false) { + $count++; + } + + if (!empty($node['children'])) { + $count += $this->countMissingPreviewImages($node['children']); + } + } + + return $count; + } + /** * Baut den Frontend-Navigationsbaum auf * @@ -110,6 +538,7 @@ class NavigationTreeService */ protected function buildFrontendNavigationTree(bool $includeHidden = false): array { + $this->preloadPreviewImageMaps(); $tree = []; // 1. Länderseiten (Hauptnavigation) @@ -492,6 +921,8 @@ class NavigationTreeService */ protected function buildNodeData(Page $page, bool $includeRelations = true): array { + $path = $page->real_url_path ?: $this->buildUrlPath($page); + $data = [ 'id' => $page->id, 'title' => $page->title, @@ -499,7 +930,8 @@ class NavigationTreeService 'before_title' => $page->before_title, 'slug' => $page->slug, 'real_url_path' => $page->real_url_path, - 'url' => $page->real_url_path ?: $this->buildUrlPath($page), + 'url' => $path, + 'frontend_url' => $this->buildFrontendUrl($path), 'status' => $page->status, 'show_in_navi' => $page->show_in_navi, 'order' => $page->order, @@ -521,6 +953,9 @@ class NavigationTreeService // Box-Informationen (für Karten/Boxen in der Navigation) 'box_body' => $page->box_body, 'box_image_url' => $page->box_image_url, + 'box_image_frontend_url' => !empty($page->box_image_url) + ? $this->buildFrontendAssetUrl($page->box_image_url) + : null, 'box_star' => $page->box_star, 'box_discount' => $page->box_discount, @@ -583,9 +1018,37 @@ class NavigationTreeService } } + $preview = $this->resolvePreviewImage($page); + $data['has_preview_image'] = $preview['has_preview_image']; + $data['preview_image_source'] = $preview['preview_image_source']; + $data['preview_image_url'] = $preview['preview_image_url']; + $data['travel_program_preview_url'] = $this->resolveTravelProgramPreviewUrl($page, true); + $data['travel_program_preview_url_full'] = $this->resolveTravelProgramPreviewUrl($page, false); + $data['can_clear_box_image'] = $this->pageCanClearBoxImage($page); + $data['has_box_body_preview'] = $this->pageHasBoxBodyPreview($page); + $data['box_body_preview_url'] = $this->getBoxBodyThumbUrl($page->box_body); + $data['can_clear_box_body'] = $this->pageCanClearBoxBody($page); + return $data; } + /** + * Baut die vollständige Frontend-URL für eine Seite. + * + * @param string|null $path + * @return string|null + */ + protected function buildFrontendUrl(?string $path): ?string + { + if ($path === null || $path === '') { + return null; + } + + $base = rtrim((string) config('app.url_stern'), '/'); + + return $base . ($path === '/' ? '/' : (str_starts_with($path, '/') ? $path : '/' . $path)); + } + /** * Baut den URL-Pfad für eine Page rekursiv auf (Fallback wenn real_url_path nicht gesetzt) * diff --git a/config/app.php b/config/app.php index 4cf57f2..d36bf34 100755 --- a/config/app.php +++ b/config/app.php @@ -59,7 +59,7 @@ return [ 'url_v2' => env('APP_URL_V2', 'https://v2.stern-tours.de'), - 'url_stern' => env('APP_URL_STERN', 'https://www.sterntours.de'), + 'url_stern' => env('APP_URL_STERN', 'https://sterntours.de'), 'domain_tld' => env('APP_DOMAIN_TLD', 'test'), diff --git a/resources/views/layouts/includes/layout-sidenav.blade.php b/resources/views/layouts/includes/layout-sidenav.blade.php index 83a4ac2..38c7844 100755 --- a/resources/views/layouts/includes/layout-sidenav.blade.php +++ b/resources/views/layouts/includes/layout-sidenav.blade.php @@ -434,10 +434,10 @@ @endif @if (Auth::user()->isPermission('crm-nav-api')) -
  • +
  • -
    Navigation API
    +
    Seiten-Sitemap
  • @endif diff --git a/resources/views/navigation/index.blade.php b/resources/views/navigation/index.blade.php index a4c88a6..153b43d 100644 --- a/resources/views/navigation/index.blade.php +++ b/resources/views/navigation/index.blade.php @@ -2,24 +2,31 @@ @section('content')

    - Navigation API - Navigationsbaum (Frontend-Struktur) + Seiten-Sitemap & Navigation

    -
    - Dieser Baum zeigt die Navigation genau so, wie sie im Frontend (header.html.twig) - angezeigt wird. -
    - Bereiche: -
      -
    • Länder-Navigation: Länderseiten mit Children, gruppiert nach "Haupt" und "Infos"
    • -
    • USEDOM Ferienwohnungen: Ferienwohnungs-Übersicht mit einzelnen FeWos
    • -
    • Weitere Seiten: Seiten aus dem "Mehr"-Menü (Über uns, Reiseversicherung, Reiseführer, etc.) -
    • -
    + + + +
    + Vollständiger Seitenbaum vom Nested-Set (lft/rgt/lvl) + — von der Startseite über Reisekategorien bis zu einzelnen Reisen. + Fehlende Vorschaubilder werden markiert (box_image_url, box_body-JSON oder Reiseprogramm-Bild). +
    +
    -
    +
    -
    @@ -27,7 +34,7 @@
    -
    +
    -
    @@ -35,7 +42,7 @@
    -
    +
    -
    @@ -43,7 +50,7 @@
    -
    +
    -
    @@ -51,6 +58,14 @@
    +
    +
    +
    +
    -
    + Ohne Vorschaubild +
    +
    +
    @@ -68,18 +83,11 @@
    - - +
    - -
    -
    -
    @@ -113,6 +121,110 @@
    + + diff --git a/routes/web.php b/routes/web.php index 74c6281..08200a3 100755 --- a/routes/web.php +++ b/routes/web.php @@ -429,6 +429,8 @@ Route::group(['middleware' => ['admin', '2fa']], function () { Route::get('/navigation-api/export', 'NavigationTreeController@export')->name('navigation_api_export'); Route::post('/navigation-api/clear-cache', 'NavigationTreeController@clearCache')->name('navigation_api_clear_cache'); Route::get('/navigation-api/stats', 'NavigationTreeController@stats')->name('navigation_api_stats'); + Route::post('/navigation-api/page/{id}/box-image', 'NavigationTreeController@updateBoxImage')->name('navigation_api_page_box_image'); + Route::get('/navigation-api/iq-assets/modal', 'IQ\ContentAssetController@modal')->name('navigation_api_iq_assets_modal'); }); });