From 9b55e111595a8bb56e2f1bde3ac9c2b7de9dc92b Mon Sep 17 00:00:00 2001
From: Kevin Adametz
Date: Tue, 7 Jul 2026 15:00:40 +0000
Subject: [PATCH] =?UTF-8?q?PM-Detailseite:=20Direktlinks=20zur=20=C3=B6ffe?=
=?UTF-8?q?ntlichen=20Seite=20je=20Portal?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Veröffentlichte PMs zeigten im Backend nur den Status-Badge, ohne Weg
zur öffentlichen Seite. Im Seitenkopf stehen jetzt Direktlinks — bei
Portal::Both einer je Portal (QA-Befund 07.07.).
Die URL-Logik ist als PressRelease::publicPortalLinks() zentralisiert
(vorher doppelt in der Published-Mail und fehlerhaft im Nachweis-PDF:
dort fehlte das /de/press-release/-Segment, die dokumentierte URL lief
ins Leere; bei Both nennt der Nachweis jetzt beide URLs). Edition folgt
der PM-Sprache statt hart „de".
Co-Authored-By: Claude Fable 5
---
app/Mail/PressReleasePublished.php | 10 +++++
app/Models/PressRelease.php | 39 +++++++++++++++++
.../PressRelease/ProofPdfRenderer.php | 20 ++++-----
.../customer/press-releases/show.blade.php | 15 ++++++-
tests/Feature/PressReleaseShowPhase8aTest.php | 43 +++++++++++++++++++
tests/Feature/ProofPdfAndExtraPmTest.php | 14 ++++++
6 files changed, 128 insertions(+), 13 deletions(-)
diff --git a/app/Mail/PressReleasePublished.php b/app/Mail/PressReleasePublished.php
index d11bc51e..39cb935f 100644
--- a/app/Mail/PressReleasePublished.php
+++ b/app/Mail/PressReleasePublished.php
@@ -16,6 +16,14 @@ class PressReleasePublished extends Mailable
public string $showUrl;
+ /**
+ * Öffentliche Detailseiten je Portal, auf dem die PM erschienen ist
+ * (bei Portal::Both beide Spokes).
+ *
+ * @var list
+ */
+ public array $portalLinks;
+
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
@@ -23,6 +31,8 @@ class PressReleasePublished extends Mailable
$this->showUrl = $user->canAccessAdmin()
? route('admin.press-releases.show', $pressRelease->id)
: route('me.press-releases.show', $pressRelease->id);
+
+ $this->portalLinks = $pressRelease->publicPortalLinks();
}
public function envelope(): Envelope
diff --git a/app/Models/PressRelease.php b/app/Models/PressRelease.php
index 7d5fe8c2..109059b0 100644
--- a/app/Models/PressRelease.php
+++ b/app/Models/PressRelease.php
@@ -7,6 +7,7 @@ use App\Enums\PressReleaseClassification;
use App\Enums\PressReleaseContentTier;
use App\Enums\PressReleasePlaceholder;
use App\Enums\PressReleaseStatus;
+use App\Http\Middleware\SetEdition;
use App\Models\Concerns\HasUniqueSlug;
use App\Scopes\PortalScope;
use App\Services\PressRelease\PressReleaseHtmlSanitizer;
@@ -165,6 +166,44 @@ class PressRelease extends Model
return $this->hasMany(PressReleaseStatusLog::class)->orderByDesc('created_at');
}
+ /**
+ * Öffentliche Detailseiten je Portal, auf dem die PM erscheint
+ * (bei Portal::Both beide Spokes). Zentrale Quelle für Mail,
+ * Nachweis-PDF und Detailansicht.
+ *
+ * @return list
+ */
+ public function publicPortalLinks(): array
+ {
+ if (! $this->slug) {
+ return [];
+ }
+
+ $portals = match ($this->portal) {
+ Portal::Both => [Portal::Presseecho, Portal::Businessportal24],
+ null => [],
+ default => [$this->portal],
+ };
+
+ $edition = in_array($this->language, SetEdition::EDITIONS, true)
+ ? $this->language
+ : 'de';
+
+ return array_map(fn (Portal $portal): array => [
+ 'portal' => $portal,
+ 'label' => $portal->label(),
+ 'url' => sprintf(
+ '%s/%s/press-release/%s',
+ rtrim((string) match ($portal) {
+ Portal::Presseecho => config('domains.domain_presseecho_url'),
+ default => config('domains.domain_businessportal_url'),
+ }, '/'),
+ $edition,
+ $this->slug,
+ ),
+ ], $portals);
+ }
+
public function kiAudits(): HasMany
{
return $this->hasMany(KiAudit::class)->orderByDesc('created_at');
diff --git a/app/Services/PressRelease/ProofPdfRenderer.php b/app/Services/PressRelease/ProofPdfRenderer.php
index e6cc7223..d8fae064 100644
--- a/app/Services/PressRelease/ProofPdfRenderer.php
+++ b/app/Services/PressRelease/ProofPdfRenderer.php
@@ -90,25 +90,21 @@ class ProofPdfRenderer
*/
private function metaRows(PressRelease $pressRelease, string $portalLabel, string $dateLabel): array
{
+ // Bei Portal::Both dokumentiert der Nachweis beide öffentlichen URLs.
+ $links = $pressRelease->publicPortalLinks();
+ $urlRows = array_map(static fn (array $link): array => [
+ count($links) > 1 ? 'URL '.$link['label'] : 'URL',
+ $link['url'],
+ ], $links);
+
return array_values(array_filter([
['Portal', $portalLabel],
['Veröffentlicht', $dateLabel],
- ['URL', $this->publicUrl($pressRelease)],
+ ...$urlRows,
$pressRelease->category?->name ? ['Kategorie', (string) $pressRelease->category->name] : null,
]));
}
- private function publicUrl(PressRelease $pressRelease): string
- {
- $base = match ($pressRelease->portal) {
- Portal::Presseecho => config('domains.domain_presseecho_url'),
- Portal::Businessportal24 => config('domains.domain_businessportal_url'),
- default => config('app.url'),
- };
-
- return rtrim((string) $base, '/').'/'.ltrim((string) $pressRelease->slug, '/');
- }
-
private function previewText(PressRelease $pressRelease): string
{
$plain = trim(html_entity_decode(strip_tags((string) $pressRelease->text)));
diff --git a/resources/views/livewire/customer/press-releases/show.blade.php b/resources/views/livewire/customer/press-releases/show.blade.php
index 824eab9b..0c020e4b 100644
--- a/resources/views/livewire/customer/press-releases/show.blade.php
+++ b/resources/views/livewire/customer/press-releases/show.blade.php
@@ -209,6 +209,9 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
return [
'pr' => $pr,
+ 'publicLinks' => $pr->status === PressReleaseStatus::Published
+ ? $pr->publicPortalLinks()
+ : [],
'creditBalance' => $user->creditBalance(),
'canBoost' => $boostService->canBoost($pr),
'isBoosted' => $boostService->isBoosted($pr),
@@ -353,7 +356,17 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
-
+
+ {{-- Direktlinks zur öffentlichen Seite (QA-Befund 07.07.):
+ bei Portal::Both je Portal ein eigener Button. --}}
+ @foreach ($publicLinks as $link)
+
+ {{ count($publicLinks) > 1
+ ? __('Auf :portal ansehen', ['portal' => $link['label']])
+ : __('Öffentlich ansehen') }}
+
+ @endforeach
@if ($canEdit)
{{ __('Bearbeiten') }}
diff --git a/tests/Feature/PressReleaseShowPhase8aTest.php b/tests/Feature/PressReleaseShowPhase8aTest.php
index c0136457..c33df0b9 100644
--- a/tests/Feature/PressReleaseShowPhase8aTest.php
+++ b/tests/Feature/PressReleaseShowPhase8aTest.php
@@ -1,5 +1,6 @@
assertDontSee('Eigener Abbinder (Boilerplate)');
});
+test('customer show links to the public page of a published release', function () {
+ /** @var TestCase $this */
+ ['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
+ 'status' => PressReleaseStatus::Published,
+ 'published_at' => now()->subHour(),
+ 'slug' => 'oeffentliche-pm',
+ 'language' => 'de',
+ ]);
+ $this->actingAs($customer);
+
+ LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
+ ->assertSee('Öffentlich ansehen')
+ ->assertSee(config('domains.domain_presseecho_url').'/de/press-release/oeffentliche-pm', escape: false);
+});
+
+test('customer show links to both portals when the release is published on both', function () {
+ /** @var TestCase $this */
+ ['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
+ 'status' => PressReleaseStatus::Published,
+ 'published_at' => now()->subHour(),
+ 'portal' => Portal::Both,
+ 'slug' => 'beide-portale-pm',
+ 'language' => 'de',
+ ]);
+ $this->actingAs($customer);
+
+ LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
+ ->assertSee('Auf Presseecho ansehen')
+ ->assertSee('Auf Businessportal24 ansehen')
+ ->assertSee(config('domains.domain_presseecho_url').'/de/press-release/beide-portale-pm', escape: false)
+ ->assertSee(config('domains.domain_businessportal_url').'/de/press-release/beide-portale-pm', escape: false);
+});
+
+test('customer show shows no public link for drafts', function () {
+ /** @var TestCase $this */
+ ['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a();
+ $this->actingAs($customer);
+
+ LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
+ ->assertDontSee('Öffentlich ansehen');
+});
+
test('admin show zeigt den Untertitel direkt unter dem Titel', function () {
/** @var TestCase $this */
$admin = makeAdminForShowPhase8a();
diff --git a/tests/Feature/ProofPdfAndExtraPmTest.php b/tests/Feature/ProofPdfAndExtraPmTest.php
index 80b2bd57..5eb995d0 100644
--- a/tests/Feature/ProofPdfAndExtraPmTest.php
+++ b/tests/Feature/ProofPdfAndExtraPmTest.php
@@ -1,5 +1,6 @@
toBeGreaterThan(800);
});
+test('the rendered proof documents the real public url of the release', function () {
+ $pr = PressRelease::factory()->published()->create([
+ 'portal' => Portal::Presseecho,
+ 'slug' => 'nachweis-pm',
+ 'language' => 'de',
+ ]);
+
+ $pdf = app(ProofPdfRenderer::class)->render($pr);
+
+ // Vorher fehlte das /de/press-release/-Segment — die URL im Nachweis lief ins Leere.
+ expect($pdf)->toContain(config('domains.domain_presseecho_url').'/de/press-release/nachweis-pm');
+});
+
test('the proof download requires a prior purchase', function () {
$user = customerWithRole();
$pr = PressRelease::factory()->published()->create(['user_id' => $user->id]);