resolveSlugs($slug); $page = null; try { $page = LegalPage::query() ->whereIn('slug', $slugCandidates) ->where('is_published', true) ->orderByDesc('version') ->first(); } catch (QueryException $exception) { // Table does not exist or query failed; fallback to filesystem documents $page = null; } $locale = $request->route('locale', app()->getLocale()); if (! $page) { $fallback = $this->loadFallbackDocument($slugCandidates, $locale); if (! $fallback) { abort(404); } return Inertia::render('legal/Show', [ 'seoTitle' => $fallback['title'].' - '.config('app.name', 'Fotospiel'), 'title' => $fallback['title'], 'content' => $this->convertMarkdownToHtml($fallback['markdown']), 'effectiveFrom' => null, 'effectiveFromLabel' => null, 'versionLabel' => null, 'slug' => $slugCandidates[0], ]); } $title = $page->title[$locale] ?? $page->title[$page->locale_fallback] ?? $page->title['de'] ?? $page->title['en'] ?? Str::title($slugCandidates[0]); $title = Str::ucfirst($title); $bodyMarkdown = $page->body_markdown[$locale] ?? $page->body_markdown[$page->locale_fallback] ?? reset($page->body_markdown) ?? ''; $effectiveFrom = optional($page->effective_from); return Inertia::render('legal/Show', [ 'seoTitle' => $title.' - '.config('app.name', 'Fotospiel'), 'title' => $title, 'content' => $this->convertMarkdownToHtml($bodyMarkdown), 'effectiveFrom' => $effectiveFrom ? $effectiveFrom->toDateString() : null, 'effectiveFromLabel' => $effectiveFrom ? __('legal.effective_from', ['date' => $effectiveFrom->translatedFormat('d. F Y')]) : null, 'versionLabel' => __('legal.version', ['version' => $page->version]), 'slug' => $slugCandidates[0], ]); } private function resolveSlugs(?string $slug): array { $slug = strtolower($slug ?? ''); $aliases = [ 'imprint' => 'impressum', 'privacy' => 'datenschutz', 'terms' => 'agb', 'withdrawal' => 'widerrufsbelehrung', 'cancellation' => 'widerrufsbelehrung', 'cancellation-policy' => 'widerrufsbelehrung', 'widerruf' => 'widerrufsbelehrung', ]; $canonical = $aliases[$slug] ?? $slug ?: 'impressum'; // Support both slugs for withdrawal in case DB uses a shorter slug $fallbacks = $canonical === 'widerrufsbelehrung' ? ['widerrufsbelehrung', 'widerruf'] : [$canonical]; return array_values(array_unique($fallbacks)); } private function convertMarkdownToHtml(string $markdown): string { $environment = new Environment([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); $environment->addExtension(new CommonMarkCoreExtension); $environment->addExtension(new TableExtension); $environment->addExtension(new AutolinkExtension); $environment->addExtension(new StrikethroughExtension); $environment->addExtension(new TaskListExtension); $converter = new MarkdownConverter($environment); return trim((string) $converter->convert($markdown)); } private function loadFallbackDocument(array $slugCandidates, string $locale): ?array { $candidates = array_unique([ strtolower($locale), strtolower(config('app.fallback_locale', 'de')), 'de', 'en', ]); foreach ($slugCandidates as $slug) { foreach ($candidates as $candidateLocale) { $path = base_path("docs/legal/{$slug}-{$candidateLocale}.md"); if (! is_file($path)) { continue; } $markdown = (string) file_get_contents($path); $title = $this->extractTitleFromMarkdown($markdown) ?? Str::title($slug); return [ 'markdown' => $markdown, 'title' => $title, ]; } } return null; } private function extractTitleFromMarkdown(string $markdown): ?string { foreach (preg_split('/\r?\n/', $markdown) as $line) { $trimmed = trim($line); if ($trimmed === '') { continue; } if (str_starts_with($trimmed, '# ')) { return trim(substr($trimmed, 2)); } if (str_starts_with($trimmed, '## ')) { return trim(substr($trimmed, 3)); } // First non-empty line can act as fallback title return $trimmed; } return null; } }