where('token_hash', hash('sha256', $token)) ->where('purpose', 'press_release_access') ->first(); if (! $magicLink) { return $this->renderError(__('Der Vorschau-Link ist ungültig.'), 404); } if ($magicLink->expires_at->isPast()) { return $this->renderError(__('Der Vorschau-Link ist abgelaufen.'), 410); } $pressReleaseId = (int) ($magicLink->payload['press_release_id'] ?? 0); $pressRelease = $pressReleaseId ? PressRelease::withoutGlobalScopes() ->with([ 'company', 'user:id,agency_name', 'category.translations', 'category.parent.translations', 'images' => fn ($query) => $query->orderByDesc('is_preview')->orderBy('sort_order'), 'contacts', ]) ->find($pressReleaseId) : null; if (! $pressRelease) { return $this->renderError(__('Die Pressemitteilung wurde nicht gefunden.'), 404); } $domainKey = $this->resolveTargetDomainKey($pressRelease); $this->applyTargetPortalPresentation($pressRelease, $domainKey); return response() ->view('web.release-detail', [ 'release' => $pressRelease, 'companyReleases' => collect(), 'relatedReleases' => collect(), 'isPreview' => true, 'previewExpiresAt' => $magicLink->expires_at, 'previewPortalOptions' => $this->previewPortalOptions($pressRelease, $domainKey), ]) ->header('X-Robots-Tag', 'noindex, nofollow'); } /** * Zielportal der Vorschau. Bei PMs für beide Portale wählt der optionale * Query-Parameter `portal` die Ansicht (Default: presseecho); bei * Einzel-Portal-PMs ist das Portal fix. */ private function resolveTargetDomainKey(PressRelease $pressRelease): string { if ($pressRelease->portal === Portal::Both) { $requested = request()->query('portal'); return in_array($requested, [Portal::Presseecho->value, Portal::Businessportal24->value], true) ? $requested : Portal::Presseecho->value; } return $pressRelease->portal === Portal::Businessportal24 ? Portal::Businessportal24->value : Portal::Presseecho->value; } /** * Umschalt-Optionen für den Vorschau-Banner — nur bei PMs, die auf beiden * Portalen erscheinen. * * @return list|null */ private function previewPortalOptions(PressRelease $pressRelease, string $activeDomainKey): ?array { if ($pressRelease->portal !== Portal::Both) { return null; } return collect([Portal::Presseecho->value, Portal::Businessportal24->value]) ->map(fn (string $domainKey): array => [ 'label' => $domainKey, 'url' => request()->url().'?portal='.$domainKey, 'active' => $domainKey === $activeDomainKey, ]) ->all(); } /** * Rendert die Vorschau im Look des Zielportals: Theme-/Asset-Konfiguration * der Web-Domain anwenden, Links auf das Zielportal zeigen lassen und die * Ausgabe-Sprache der PM übernehmen — damit die Vorschau 1:1 der späteren * öffentlichen Detailseite entspricht. */ private function applyTargetPortalPresentation(PressRelease $pressRelease, string $domainKey): void { $domainConfig = DomainAssetContext::applyWebDomainConfig($domainKey); URL::forceRootUrl($domainConfig['url'] ?? config('app.url')); $language = in_array($pressRelease->language, SetEdition::EDITIONS, true) ? $pressRelease->language : SetEdition::DEFAULT_EDITION; app()->setLocale($language); URL::defaults(['edition' => $language]); } private function renderError(string $message, int $status): Response { return response()->view('press-release-preview-error', [ 'message' => $message, ], $status); } }