mein-sterntours/app/Http/Controllers/NavigationTreeController.php
Kevin Adametz c39d86f80b Seiten-Sitemap mit Vorschaubild-Verwaltung im CRM ergänzen.
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 <cursoragent@cursor.com>
2026-06-18 10:46:22 +00:00

278 lines
9.6 KiB
PHP

<?php
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
{
protected $navigationService;
public function __construct(NavigationTreeService $navigationService)
{
$this->navigationService = $navigationService;
}
/**
* Zeigt die Navigationsbaum-Übersicht
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('navigation.index', [
'lfm_helper' => app(Lfm::class),
]);
}
/**
* Gibt die Navigationsbaum-Daten als JSON zurück (Frontend-Struktur)
*
* @param Request $request
* @return JsonResponse
*/
public function getData(Request $request): JsonResponse
{
try {
$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([
'success' => true,
'data' => $tree,
'meta' => [
'total_nodes' => $this->navigationService->countNodes($tree),
'include_hidden' => $includeHidden,
'structure' => 'header',
],
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Sucht im Navigationsbaum
*
* @param Request $request
* @return JsonResponse
*/
public function search(Request $request): JsonResponse
{
try {
$query = $request->get('query', '');
$flatList = $this->navigationService->getFlatNavigationList();
// Filtere nach Suchbegriff
$results = array_filter($flatList, function ($node) use ($query) {
return stripos($node['title'], $query) !== false
|| stripos($node['slug'], $query) !== false
|| stripos($node['url'], $query) !== false;
});
return response()->json([
'success' => true,
'data' => array_values($results),
'meta' => [
'query' => $query,
'total_results' => count($results)
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Exportiert den Navigationsbaum als JSON-Datei
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function export(Request $request)
{
try {
$mode = $request->get('mode', 'sitemap');
$includeHidden = $request->get('include_hidden', false);
$onlyActive = !$includeHidden;
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)
->header('Content-Type', 'application/json')
->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
} catch (\Exception $e) {
return back()->with('error', 'Export fehlgeschlagen: ' . $e->getMessage());
}
}
/**
* Löscht den Cache
*
* @return \Illuminate\Http\RedirectResponse
*/
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());
}
}
/**
* Zeigt die Statistiken
*
* @return JsonResponse
*/
public function stats(): JsonResponse
{
try {
$allTree = $this->navigationService->getNavigationTree(false);
$activeTree = $this->navigationService->getNavigationTree(true);
$flatList = $this->navigationService->getFlatNavigationList();
// Zähle verschiedene Typen
$stats = [
'total_pages' => count($flatList),
'total_nodes' => $this->navigationService->countNodes($allTree),
'active_nodes' => $this->navigationService->countNodes($activeTree),
'inactive_nodes' => $this->navigationService->countNodes($allTree) - $this->navigationService->countNodes($activeTree),
'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([
'success' => true,
'data' => $stats
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 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);
}
}
}