recht: Meldeverfahren v2 Phasen 5+7 — DSGVO-Strukturformular, 10-€-Checkout mit KI-Vorprüfung, Triage & Panel-Modal
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

Phase 7 (Spur D, E2 revidiert): KI-Vorprüfung VOR der Zahlung im Modal —
rot lehnt ohne Zahlung ab, gelb/grün führt zum Stripe-Checkout
(SinglePurchaseType::Change, 10 € netto), nach Zahlungseingang übernimmt
der Webhook die geprüfte Fassung sofort (applyPaid: Snapshot, Sperre,
Verlauf). Nur einmal je PM; Obergrenze legal.change_max_distance_percent
(50 %) verhindert die verdeckte Selbst-Depublizierung.

Phase 5 (Spur B): strukturiertes Schwärzungsformular mit Pflicht-Vorschau
im verifizierten Web-Bereich UND als Modal im Kunden-Panel; Admin-Editor
wird aus payload.redaction vorbefüllt. Umsetzungsmail LegalRequestExecuted
an Antragsteller, Bestätigungsmail PressReleaseChangeApplied (Spur A/D,
neuer abbestellbarer Typ press_release_corrected).

Außerdem:
- Re-Sync-Sperre + Sitemap-Invalidierung fest in depublish() verankert
  (Lücke im bezahlten Webhook-Pfad geschlossen)
- Öffentliche „Rechtliches"-Seite als zweistufige Triage: primär der
  Portal-Anmeldelink, Formular nur Fallback mit Identifizierungs-Hinweis;
  Store auf report/dsgvo/personal_rights gehärtet
- Panel-Button „Ändern/Melden" öffnet Anliegen-Modal (4 Spuren) statt
  des Web-Formulars
- Pressekontakt-Fallback für Legacy-Bestände (30k PMs ohne Verknüpfung):
  Detailseite + pressContactEmail() nutzen PM-Kontakt → Firmen-Kontakt →
  Firmendaten
- Korrektur-Gate entity-fest (Editor-Normalisierung zählt nicht als
  Änderung), toEditorHtml() in Admin-Edit + Korrektur-Panel,
  Wochenlimit-Hinweis in Text und Bremsen-Meldung

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-23 13:02:57 +00:00
parent 04b49c567d
commit e6eca9c85f
40 changed files with 2507 additions and 169 deletions

View file

@ -5,16 +5,29 @@
@section('content')
{{--
Hinweistexte je Anfragetyp (R2.5, final 10.07.2026): Wortlaut folgt dem am
06.07. inhaltlich freigegebenen Meldeverfahren v0.1 (/melden bzw.
lang/de/static.php static.report.*) und dem tatsächlichen Flow
(Vorgangsnummer M-{id}, unverzügliche Eingangsbestätigung nach DSA Art. 16
Abs. 4 sofern E-Mail angegeben, kostenlose manuelle Prüfung). Die noch
ausstehende anwaltliche Freigabe des Rechtstexte-Pakets [EXTERN] umfasst
auch diese Texte.
Zweistufige Triage (Meldeverfahren v2, 23.07.2026): Stufe 1 wählt das
Anliegen, Stufe 2 zeigt den passenden Weg. Absender-Anliegen und
DSGVO/Persönlichkeitsrechte werden primär auf den passwortlosen
Anmeldelink ins Portal (pressekonto /anmeldelink) geleitet — dort werden
die PMs der Kontaktadresse im Konto zusammengeführt und alle
Selbstservice-Funktionen stehen bereit. Das Kontaktformular ist nur der
Fallback und weist auf die notwendige persönliche Identifizierung hin.
Meldungen rechtswidriger Inhalte gehen direkt ins Formular (DSA: jede
dritte Person darf melden).
Hinweistexte folgen dem am 06.07. freigegebenen Meldeverfahren v0.1;
die anwaltliche Freigabe des Rechtstexte-Pakets [EXTERN] umfasst auch
diese Texte.
--}}
<style>[x-cloak]{display:none!important}</style>
<section
x-data="{ type: @js(old('type', $type->value)), emailRequired() { return ['dsgvo', 'personal_rights'].includes(this.type); } }"
x-data="{
concern: @js(old('type', $initialConcern)),
showFallback: @js(in_array(old('type'), ['dsgvo', 'personal_rights'], true)),
pick(value) { this.concern = value; this.showFallback = false; },
needsFallbackToggle() { return ['dsgvo', 'personal_rights'].includes(this.concern); },
formVisible() { return this.concern === 'report' || (this.needsFallbackToggle() && this.showFallback); },
emailRequired() { return ['dsgvo', 'personal_rights'].includes(this.concern); }
}"
class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-10"
>
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}"
@ -28,9 +41,9 @@
Betrifft: <span class="text-ink-2 font-medium">{{ $release->title }}</span>
</p>
<p class="text-[12.5px] text-ink-3 leading-relaxed mb-6">
Datenschutz (DSGVO), Persönlichkeitsrechte und Meldungen werden von unserem
Team manuell geprüft. Bitte wählen Sie die Art Ihrer Anfrage – dazu erscheint
ein Hinweis mit den genauen Regeln.
Bitte wählen Sie zuerst Ihr Anliegen — dazu erscheint der passende Weg.
Viele Anliegen können Sie nach einer kurzen Verifizierung direkt selbst
anstoßen; alle Prüfungen sind kostenlos.
</p>
@if (session('legal-status'))
@ -39,63 +52,141 @@
</div>
@endif
<form method="POST" action="{{ route('legal-request.store', ['slug' => $release->slug]) }}" class="space-y-5">
@csrf
<div>
<label for="type" class="block text-[12px] font-semibold text-ink mb-1.5">Art der Anfrage</label>
<select id="type" name="type" x-model="type"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand">
@foreach ($types as $case)
<option value="{{ $case->value }}" @selected(old('type', $type->value) === $case->value)>
{{ $case->publicLabel() }}
</option>
@endforeach
</select>
@error('type')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
{{-- ===== Stufe 1: Anliegen wählen ===== --}}
<fieldset class="mb-6">
<legend class="block text-[12px] font-semibold text-ink mb-2">Was möchten Sie tun?</legend>
<div class="space-y-2">
@foreach ([
'owner' => ['label' => 'Korrektur, Änderung oder Depublizierung', 'desc' => 'Sie sind Absender oder hinterlegter Pressekontakt und möchten Inhalte korrigieren, ändern oder die Mitteilung offline nehmen.', 'badge' => 'Selbstservice'],
'dsgvo' => ['label' => 'Entfernung persönlicher Daten (DSGVO)', 'desc' => 'Ihr Name oder Ihre Kontaktdaten erscheinen in dieser Mitteilung und sollen entfernt werden (Art. 17 DSGVO).', 'badge' => 'kostenlos'],
'personal_rights' => ['label' => 'Verletzung von Persönlichkeitsrechten', 'desc' => 'Unwahre Tatsachenbehauptungen, Bild- oder Namensrechte — die Mitteilung betrifft Sie persönlich.', 'badge' => 'kostenlos'],
'report' => ['label' => 'Rechtswidrige Inhalte melden', 'desc' => 'Urheberrechts- oder Markenverletzung, irreführende Inhalte, Spam — Meldung als dritte Person, auch ohne eigene Betroffenheit.', 'badge' => 'kostenlos'],
] as $value => $card)
<label class="flex items-start gap-3 border border-bg-rule px-3 py-2.5 cursor-pointer hover:bg-bg-elev"
:class="concern === '{{ $value }}' ? 'border-brand bg-bg-elev' : ''">
<input type="radio" name="concern_choice" value="{{ $value }}" class="mt-0.5"
@change="pick('{{ $value }}')"
:checked="concern === '{{ $value }}'" />
<span class="text-[13px] text-ink">
<span class="font-semibold">{{ $card['label'] }}</span>
<span class="text-ink-3">· {{ $card['badge'] }}</span>
<span class="block text-[12.5px] text-ink-2">{{ $card['desc'] }}</span>
</span>
</label>
@endforeach
</div>
</fieldset>
{{-- Hinweistext je Anfragetyp (Wortlaut: siehe Kommentar am Seitenanfang). --}}
<div x-cloak x-show="type === 'report'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Meldung:</strong>
Melden Sie diese Pressemitteilung, wenn Sie sie für rechtswidrig halten
oder sie Ihre Rechte verletzt — z.&nbsp;B. unwahre Tatsachenbehauptungen,
Urheberrechts- oder Markenverletzungen, irreführende Inhalte oder Spam.
Bitte benennen Sie konkret, welche Inhalte betroffen sind und warum.
Jede Meldung wird von unserem Team manuell geprüft; die Meldung ist
kostenlos. Wenn Sie eine E-Mail-Adresse angeben, erhalten Sie unverzüglich
eine Eingangsbestätigung mit Vorgangsnummer und anschließend unsere
Entscheidung mit Begründung.
{{-- ===== Stufe 2a: Absender-Anliegen → passwortloser Portal-Login ===== --}}
<div x-cloak x-show="concern === 'owner'" class="space-y-4">
<div class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">So funktioniert es:</strong>
Fordern Sie im Portal einen passwortlosen Anmeldelink an Ihre hinterlegte
Pressekontakt- oder Firmenadresse an. Nach dem Klick sind Sie angemeldet und
Ihre Pressemitteilungen werden in Ihrem Konto zusammengeführt — dort können
Sie kleine Korrekturen kostenlos und sofort umsetzen, größere Änderungen
(10&nbsp;€) und die Depublizierung (35&nbsp;€) direkt beauftragen.
</div>
<div x-cloak x-show="type === 'dsgvo'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<a href="{{ $portalMagicLinkUrl }}"
class="inline-flex items-center px-5 py-2.5 bg-brand text-white text-[13px] font-semibold hover:opacity-90">
Anmeldelink im Portal anfordern
</a>
<p class="text-[12px] text-ink-3 leading-relaxed">
Keine erreichbare Pressekontaktadresse mehr? Dann nutzen Sie bitte das
Formular unter „Entfernung persönlicher Daten" bzw. die manuelle Prüfung
über das <a href="{{ route('melden') }}" class="underline hover:text-ink">Meldeverfahren</a> —
wir verifizieren Ihre Berechtigung anhand eines Nachweises.
</p>
</div>
{{-- ===== Stufe 2b: DSGVO ===== --}}
<div x-cloak x-show="concern === 'dsgvo'" class="space-y-4">
<div class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Datenschutz / DSGVO:</strong>
Als natürliche Person können Sie die Entfernung, Ersetzung oder
Anonymisierung Ihrer persönlichen Daten in dieser Pressemitteilung
verlangen (Art. 17 DSGVO) — zum Beispiel Name, Telefonnummer oder
E-Mail-Adresse. Bitte geben Sie an, welche Angaben Sie betreffen. Die
Prüfung und Bearbeitung ist für Sie kostenlos. Zur Prüfung Ihrer
Berechtigung benötigen wir Ihre E-Mail-Adresse; in der Regel senden wir
einen Bestätigungslink an die betroffene Adresse, andernfalls können wir
einen geeigneten Identitäts- oder Berechtigungsnachweis verlangen. Sie
erhalten unverzüglich eine Eingangsbestätigung mit Vorgangsnummer. Die
E-Mail-Adresse. Die Prüfung und Bearbeitung ist für Sie kostenlos. Die
Pressemitteilung selbst bleibt nach der Entfernung Ihrer Daten
grundsätzlich bestehen.
</div>
<div x-cloak x-show="type === 'personal_rights'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<div class="px-4 py-3 border border-bg-rule text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Empfohlener Weg — passwortloser Anmeldelink ins Portal:</strong>
Sind Sie die in der Veröffentlichung hinterlegte Kontaktperson (oder haben
Zugriff auf deren Postfach), melden Sie sich per Anmeldelink im Portal an —
Ihre Mitteilungen werden dort in Ihrem Konto zusammengeführt und Sie geben
die zu entfernenden Angaben direkt mit Vorschau an. Der schnellste Weg.
</div>
<a href="{{ $portalMagicLinkUrl }}"
class="inline-flex items-center px-5 py-2.5 bg-brand text-white text-[13px] font-semibold hover:opacity-90">
Anmeldelink im Portal anfordern
</a>
<p>
<button type="button" @click="showFallback = true" x-show="!showFallback"
class="text-[12.5px] text-ink-2 underline underline-offset-2 hover:text-ink">
Ich bin nicht der hinterlegte Pressekontakt oder habe keinen Zugriff auf das Postfach
</button>
</p>
</div>
{{-- ===== Stufe 2c: Persönlichkeitsrechte ===== --}}
<div x-cloak x-show="concern === 'personal_rights'" class="space-y-4">
<div class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Persönlichkeitsrecht:</strong>
Wenn diese Mitteilung Ihre Persönlichkeitsrechte verletzt — z.&nbsp;B.
durch unwahre Tatsachenbehauptungen oder die Verletzung von Bild- oder
Namensrechten —, schildern Sie bitte konkret, welche Inhalte betroffen
sind und warum. Für die Eingangsbestätigung mit Vorgangsnummer und
Rückfragen benötigen wir Ihre E-Mail-Adresse. Wir prüfen Ihre Anfrage
sorgfältig und zeitnah und teilen Ihnen unsere Entscheidung mit
Begründung mit; bei berechtigten Beanstandungen wird der Inhalt
angepasst, gesperrt oder entfernt. Die Prüfung ist kostenlos.
Namensrechten —, prüfen wir Ihre Anfrage sorgfältig und zeitnah und teilen
Ihnen unsere Entscheidung mit Begründung mit; bei berechtigten
Beanstandungen wird der Inhalt angepasst, gesperrt oder entfernt. Die
Prüfung ist kostenlos.
</div>
<div class="px-4 py-3 border border-bg-rule text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Schnellster Weg — passwortloser Anmeldelink ins Portal:</strong>
Sind Sie zugleich die hinterlegte Kontaktperson dieser Veröffentlichung,
melden Sie sich per Anmeldelink im Portal an und stoßen die Anpassung
direkt in Ihrem Konto an.
</div>
<a href="{{ $portalMagicLinkUrl }}"
class="inline-flex items-center px-5 py-2.5 bg-brand text-white text-[13px] font-semibold hover:opacity-90">
Anmeldelink im Portal anfordern
</a>
<p>
<button type="button" @click="showFallback = true" x-show="!showFallback"
class="text-[12.5px] text-ink-2 underline underline-offset-2 hover:text-ink">
Ich bin nicht der hinterlegte Pressekontakt — Anfrage per Formular stellen
</button>
</p>
</div>
{{-- ===== Stufe 2d: Meldung → direkt zum Formular ===== --}}
<div x-cloak x-show="concern === 'report'"
class="mb-4 px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Meldung:</strong>
Melden Sie diese Pressemitteilung, wenn Sie sie für rechtswidrig halten
oder sie Ihre Rechte verletzt — z.&nbsp;B. unwahre Tatsachenbehauptungen,
Urheberrechts- oder Markenverletzungen, irreführende Inhalte oder Spam.
Bitte benennen Sie konkret, welche Inhalte betroffen sind und warum.
Jede Meldung wird von unserem Team manuell geprüft; die Meldung ist
kostenlos. Wenn Sie eine E-Mail-Adresse angeben, erhalten Sie unverzüglich
eine Eingangsbestätigung mit Vorgangsnummer und anschließend unsere
Entscheidung mit Begründung.
</div>
{{-- ===== Formular: direkt (Meldung) bzw. Fallback (DSGVO/Persönlichkeitsrecht) ===== --}}
<form x-cloak x-show="formVisible()" method="POST"
action="{{ route('legal-request.store', ['slug' => $release->slug]) }}" class="space-y-5 mt-2">
@csrf
<input type="hidden" name="type" :value="concern" />
{{-- Identifizierungs-Hinweis nur im Fallback-Weg (DSGVO/Persönlichkeitsrecht). --}}
<div x-cloak x-show="needsFallbackToggle()"
class="px-4 py-3 border-l-2 border border-bg-rule text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Wichtig — persönliche Identifizierung erforderlich:</strong>
Da personenbezogene Daten bzw. Persönlichkeitsrechte betroffen sind, müssen
wir Ihre Berechtigung prüfen. In der Regel senden wir einen Bestätigungslink
an die betroffene Adresse; andernfalls können wir einen geeigneten
Identitäts- oder Berechtigungsnachweis verlangen. Sie erhalten unverzüglich
eine Eingangsbestätigung mit Vorgangsnummer.
</div>
<div>
@ -128,8 +219,6 @@
verwirft der Controller die Anfrage still. Die Namen ('website',
'homepage') sind bewusst „attraktiv" für Auto-Filler. Zusätzlich greift
serverseitig ein Rate-Limit pro IP/PM.
Mögliche Erweiterung später: zeitbasierte Falle (signierter Render-
Zeitstempel, Ablehnung bei Absenden < 2s) oder Captcha bei Missbrauch.
--}}
<div class="hidden" aria-hidden="true">
<label for="website">Website</label>

View file

@ -4,6 +4,7 @@
@section('meta_description', __('static.pm_report.meta'))
@section('content')
<style>[x-cloak]{display:none!important}</style>
<x-web.static-page
:eyebrow="__('static.legal.eyebrow')"
:title="__('static.pm_report.concerns_title')"
@ -21,7 +22,73 @@
</div>
@endif
<form method="POST" action="{{ route('melden.pm.anliegen.store') }}" class="space-y-5">
@if (($dsgvoPreview ?? null) !== null)
{{-- Pflicht-Diff (Spur B): Vorschau der Schwärzung bestätigen, bevor
die Anfrage entsteht. Ausführung bleibt beim Admin. --}}
@php
$fieldLabels = [
'title' => __('static.pm_report.dsgvo_field_title'),
'subtitle' => __('static.pm_report.dsgvo_field_subtitle'),
'text' => __('static.pm_report.dsgvo_field_text'),
'boilerplate_override' => __('static.pm_report.dsgvo_field_boilerplate'),
'keywords' => __('static.pm_report.dsgvo_field_keywords'),
];
@endphp
<div class="mb-6 border border-bg-rule">
<div class="px-4 py-3 bg-bg-elev border-b border-bg-rule">
<p class="text-[13px] font-semibold text-ink">{{ __('static.pm_report.dsgvo_preview_title') }}</p>
<p class="text-[12.5px] text-ink-2 mt-0.5">
{{ trans_choice('static.pm_report.dsgvo_preview_hits', (int) $dsgvoPreview['total_hits'], ['hits' => (int) $dsgvoPreview['total_hits']]) }}
</p>
</div>
<div class="px-4 py-3 space-y-4 text-[13px]">
@forelse ($dsgvoPreview['fields'] as $field => $meta)
<div>
<p class="text-[12px] font-semibold text-ink mb-1">
{{ $fieldLabels[$field] ?? $field }}
<span class="font-normal text-ink-3">· {{ trans_choice('static.pm_report.dsgvo_preview_field_hits', (int) $meta['hits'], ['hits' => (int) $meta['hits']]) }}</span>
</p>
<p class="text-ink-2 leading-relaxed whitespace-pre-line border-l-2 border-bg-rule pl-3">{{ \Illuminate\Support\Str::limit(trim(strip_tags((string) $meta['after'])), 600) }}</p>
</div>
@empty
<p class="text-ink-2">{{ __('static.pm_report.dsgvo_preview_no_text_hits') }}</p>
@endforelse
@if ($dsgvoInput['detach_contacts'])
<p class="text-ink-2">✓ {{ __('static.pm_report.dsgvo_preview_contacts') }}</p>
@endif
@if ($dsgvoInput['remove_images'])
<p class="text-ink-2">✓ {{ __('static.pm_report.dsgvo_preview_images') }}</p>
@endif
</div>
<div class="px-4 py-3 border-t border-bg-rule bg-bg-elev">
<form method="POST" action="{{ route('melden.pm.anliegen.store') }}" class="flex flex-wrap items-center gap-3">
@csrf
<input type="hidden" name="concern" value="dsgvo" />
<input type="hidden" name="confirmed" value="1" />
<input type="hidden" name="redaction_terms" value="{{ $dsgvoInput['redaction_terms'] }}" />
<input type="hidden" name="detach_contacts" value="{{ $dsgvoInput['detach_contacts'] ? 1 : 0 }}" />
<input type="hidden" name="remove_images" value="{{ $dsgvoInput['remove_images'] ? 1 : 0 }}" />
<input type="hidden" name="message" value="{{ $dsgvoInput['message'] }}" />
<button type="submit"
class="inline-flex items-center px-5 py-2.5 bg-brand text-white text-[13px] font-semibold hover:opacity-90">
{{ __('static.pm_report.dsgvo_preview_confirm') }}
</button>
<span class="text-[12.5px] text-ink-3">{{ __('static.pm_report.dsgvo_preview_edit_hint') }}</span>
</form>
</div>
</div>
@endif
@php
$initialConcern = old('concern', isset($dsgvoInput) ? 'dsgvo' : '');
$prefilledTerms = old('redaction_terms', $dsgvoInput['redaction_terms'] ?? '');
$prefilledMessage = old('message', $dsgvoInput['message'] ?? '');
$prefilledDetach = old('detach_contacts', ($dsgvoInput['detach_contacts'] ?? false) ? '1' : null);
$prefilledRemoveImages = old('remove_images', ($dsgvoInput['remove_images'] ?? false) ? '1' : null);
@endphp
<form method="POST" action="{{ route('melden.pm.anliegen.store') }}" class="space-y-5"
x-data="{ concern: '{{ $initialConcern }}' }">
@csrf
<fieldset>
@ -35,7 +102,8 @@
] as $value => $track)
<label class="flex items-start gap-3 border border-bg-rule px-3 py-2.5 cursor-pointer hover:bg-bg-elev">
<input type="radio" name="concern" value="{{ $value }}" required class="mt-0.5"
@checked(old('concern') === $value) />
x-model="concern"
@checked($initialConcern === $value) />
<span class="text-[13px] text-ink">
<span class="font-semibold">{{ $track['label'] }}</span>
<span class="text-ink-3">· {{ $track['cost'] }}</span>
@ -49,11 +117,35 @@
@enderror
</fieldset>
{{-- Spur B (Phase 5): strukturierter Schwärzungsauftrag statt nur Freitext.
Wording bewusst „Schwärzung/Entfernung", nicht „Anonymisierung". --}}
<div x-cloak x-show="concern === 'dsgvo'" class="space-y-4 border border-bg-rule bg-bg-elev px-4 py-4">
<div>
<label for="redaction_terms" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.pm_report.dsgvo_terms_label') }}</label>
<textarea id="redaction_terms" name="redaction_terms" rows="4" maxlength="2000"
placeholder="{{ __('static.pm_report.dsgvo_terms_placeholder') }}"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand">{{ $prefilledTerms }}</textarea>
<p class="mt-1 text-[12px] text-ink-3">{{ __('static.pm_report.dsgvo_terms_hint') }}</p>
@error('redaction_terms')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<label class="flex items-start gap-2.5 text-[13px] text-ink cursor-pointer">
<input type="checkbox" name="detach_contacts" value="1" class="mt-0.5" @checked($prefilledDetach) />
<span>{{ __('static.pm_report.dsgvo_detach_contacts') }}</span>
</label>
<label class="flex items-start gap-2.5 text-[13px] text-ink cursor-pointer">
<input type="checkbox" name="remove_images" value="1" class="mt-0.5" @checked($prefilledRemoveImages) />
<span>{{ __('static.pm_report.dsgvo_remove_images') }}</span>
</label>
<p class="text-[12px] text-ink-3 leading-relaxed">{{ __('static.pm_report.dsgvo_note') }}</p>
</div>
<div>
<label for="message" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.pm_report.label_message') }}</label>
<textarea id="message" name="message" rows="6" required minlength="10" maxlength="5000"
placeholder="{{ __('static.pm_report.placeholder_message') }}"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand">{{ old('message') }}</textarea>
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand">{{ $prefilledMessage }}</textarea>
@error('message')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
@ -65,7 +157,8 @@
<button type="submit"
class="inline-flex items-center px-5 py-2.5 bg-brand text-white text-[13px] font-semibold hover:opacity-90">
{{ __('static.pm_report.concern_submit') }}
<span x-show="concern === 'dsgvo'" x-cloak>{{ __('static.pm_report.dsgvo_preview_button') }}</span>
<span x-show="concern !== 'dsgvo'">{{ __('static.pm_report.concern_submit') }}</span>
</button>
</form>
</div>

View file

@ -65,7 +65,7 @@
<div>
<label for="reported_url" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.report_form.label_url') }}</label>
<input id="reported_url" type="url" name="reported_url" value="{{ old('reported_url') }}" required
<input id="reported_url" type="url" name="reported_url" value="{{ old('reported_url', request('reported_url')) }}" required
placeholder="https://…"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand" />
@error('reported_url')

View file

@ -70,6 +70,10 @@
$contact = $release->contacts->first();
$contactName = $contact ? trim(implode(' ', array_filter([$contact->title, $contact->first_name, $contact->last_name]))) : null;
// Legacy-Bestände ohne Kontakt-Datensatz: der Block erscheint trotzdem,
// solange die Firma erreichbare Angaben hat (wie im Altsystem).
$contactEmail = $contact?->email ?: $company?->email;
$hasContactBlock = $contact || filled($contactEmail) || filled($company?->address) || filled($company?->website);
// Attributions-Zeile wie im Legacy-System: Urheber (Firma der PM) und
// Übermittler (Profilfeld agency_name des einliefernden Accounts, z. B.
@ -440,8 +444,8 @@
<x-web.ad-slot variant="inline" />
@endunless
{{-- Pressekontakt --}}
@if ($contact)
{{-- Pressekontakt (Fallback ohne Kontakt-Datensatz: Firmendaten) --}}
@if ($hasContactBlock)
<div class="mt-8 px-5 sm:px-7 py-6 bg-white border border-bg-rule-strong max-w-[680px]">
<div class="eyebrow mb-3 text-[10.5px]">{{ __('web.detail.press_contact') }}</div>
@ -456,9 +460,9 @@
</span>
@endif
</div>
@if ($contact->responsibility || $companyName)
@if ($contact?->responsibility || $companyName)
<div class="text-[12.5px] text-ink-3 mb-3.5 leading-[1.5]">
@if ($contact->responsibility){{ $contact->responsibility }}<br />@endif
@if ($contact?->responsibility){{ $contact->responsibility }}<br />@endif
{{ $companyName }}
</div>
@endif
@ -470,16 +474,16 @@
<span class="text-ink-2">{{ $company->address }}</span>
</span>
@endif
@if ($contact->phone)
@if ($contact?->phone)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M2 3l3-1 1.5 3-1.5 1c.5 2 2 3.5 4 4l1-1.5L13 10l-1 3c-5 0-10-5-10-10z" fill="currentColor" /></svg>
<a href="tel:{{ preg_replace('/[^0-9+]/', '', $contact->phone) }}" class="text-ink-2 hover:text-ink transition-colors">{{ $contact->phone }}</a>
</span>
@endif
@if ($contact->email)
@if ($contactEmail)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M1 3h12v8H1zM1 3l6 4 6-4" stroke="currentColor" stroke-width="1.2" fill="none" /></svg>
<a href="mailto:{{ $contact->email }}" class="text-ink underline underline-offset-2 hover:text-brand transition-colors">{{ $contact->email }}</a>
<a href="mailto:{{ $contactEmail }}" class="text-ink underline underline-offset-2 hover:text-brand transition-colors">{{ $contactEmail }}</a>
</span>
@endif
@if ($company?->website)