presseportale/app/Mail/PressReleaseChangeApplied.php
Kevin Adametz 9afbedddeb recht: Meldeverfahren v2 Phasen 5+7 — DSGVO-Strukturformular, 10-€-Checkout mit KI-Vorprüfung, Triage & Panel-Modal
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>
2026-07-23 13:02:57 +00:00

83 lines
2.5 KiB
PHP

<?php
namespace App\Mail;
use App\Enums\EmailNotificationType;
use App\Enums\LegalRequestType;
use App\Mail\Concerns\Unsubscribable;
use App\Models\LegalRequest;
use App\Models\PressRelease;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
/**
* Bestätigung an den Auftraggeber, dass eine Korrektur (Spur A, kostenlos)
* oder eine bezahlte Änderung (Spur D) an seiner veröffentlichten PM
* übernommen wurde und sofort öffentlich sichtbar ist.
*/
class PressReleaseChangeApplied extends Mailable
{
use Queueable, SerializesModels, Unsubscribable;
public string $showUrl;
public bool $isPaidChange;
/** @var list<string> Deutsche Labels der geänderten Felder. */
public array $changedFieldLabels;
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
public readonly LegalRequest $legalRequest,
) {
$this->showUrl = $user->canAccessAdmin()
? route('admin.press-releases.show', $pressRelease->id)
: route('me.press-releases.show', $pressRelease->id);
$this->isPaidChange = $legalRequest->type === LegalRequestType::Change;
$this->changedFieldLabels = $this->resolveChangedFieldLabels($legalRequest);
$this->configureUnsubscribe($user, EmailNotificationType::PressReleaseCorrected);
}
public function envelope(): Envelope
{
$prefix = $this->isPaidChange
? 'Ihre Änderung ist live'
: 'Ihre Korrektur ist live';
return new Envelope(
subject: $prefix.': '.$this->pressRelease->title,
);
}
public function content(): Content
{
return new Content(view: 'emails.press-release.change-applied');
}
/**
* @return list<string>
*/
private function resolveChangedFieldLabels(LegalRequest $legalRequest): array
{
$labels = [
'title' => 'Titel',
'subtitle' => 'Untertitel',
'text' => 'Text',
'keywords' => 'Keywords',
];
return collect((array) ($legalRequest->payload['fields'] ?? []))
->filter(fn (array $meta): bool => (bool) ($meta['changed'] ?? false))
->keys()
->map(fn (string $field): string => $labels[$field] ?? $field)
->values()
->all();
}
}