legal/web: Zustimmungsgate je Dokumentversion (aus bis AGB 0.7), OG-Variante mit eingebranntem Bildnachweis, Nachweis in Teasern (Bildkennzeichnung Blöcke G und H)

- legal_document_acceptances (insert-only), LegalAcceptanceGate, Pflicht-Schalter im Einreichungs-Modal über den gemeinsamen Trait; API: GET/POST /api/v1/legal/acceptances, submit antwortet 428 mit den geforderten Fassungen; LEGAL_ACCEPTANCE_GATE
- ImageService: og-Variante (1200×630) mit halbtransparentem Balken und Inter SemiBold, regenerateOgVariant() für Korrekturen und Bestand; Fokuspunkt/Letterbox getestet
- Uploader brennt den Nachweis beim Upload ein, ImageCorrectionService baut die OG-Variante nach einer Korrektur neu; Artikelseite nutzt og für og:image
- Teaser: Nachweis in alt/title (Feed, Hero, Kategorie, Newsroom), Unterschrift am Hero-Bild; PressRelease::cardImageCredit()

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-22 10:12:17 +00:00
parent b5b70416f2
commit 075d0d931f
27 changed files with 866 additions and 15 deletions

View file

@ -3,6 +3,8 @@
namespace App\Livewire\Concerns;
use App\Enums\AiTextDeclaration;
use App\Models\LegalDocumentAcceptance;
use App\Services\Legal\LegalAcceptanceGate;
use Illuminate\Support\Carbon;
use Illuminate\Validation\Rule;
@ -21,19 +23,51 @@ trait InteractsWithAiDeclaration
public bool $aiDeclarationConfirmed = false;
/**
* Zustimmungsgate (Bildkennzeichnung Block G): aktive Annahme der
* aktuellen AGB- und Richtlinien-Fassung, wenn das Gate an ist und eine
* Fassung noch nicht angenommen wurde.
*/
public bool $legalAcceptanceConfirmed = false;
/**
* Pflichtprüfung vor dem Einreichen — Fehler erscheinen direkt im
* Submit-Modal. Entwurf-Speichern bleibt frei von dieser Pflicht.
*/
protected function validateAiDeclaration(): void
{
$this->validate([
$rules = [
'aiTextDeclaration' => ['required', Rule::in(AiTextDeclaration::values())],
'aiDeclarationConfirmed' => ['accepted'],
], [
];
$messages = [
'aiTextDeclaration.required' => __('Bitte angeben, ob und wie generative KI beim Text eingesetzt wurde.'),
'aiDeclarationConfirmed.accepted' => __('Bitte bestätigen, dass die Angaben zum KI-Einsatz vollständig und wahrheitsgemäß sind.'),
]);
];
if (app(LegalAcceptanceGate::class)->requiredFor(auth()->user())->isNotEmpty()) {
$rules['legalAcceptanceConfirmed'] = ['accepted'];
$messages['legalAcceptanceConfirmed.accepted'] = __('Bitte nehmen Sie die aktuelle Fassung der AGB und der Veröffentlichungsrichtlinien an.');
}
$this->validate($rules, $messages);
}
/**
* Annahme protokollieren (nach validateAiDeclaration, vor dem Einreichen).
* Ohne Gate oder ohne offene Fassung passiert nichts.
*/
protected function persistLegalAcceptances(): void
{
$gate = app(LegalAcceptanceGate::class);
$required = $gate->requiredFor(auth()->user());
if ($required->isEmpty() || ! $this->legalAcceptanceConfirmed) {
return;
}
$gate->accept(auth()->user(), $required, LegalDocumentAcceptance::CONTEXT_SUBMIT, request()->ip(), request()->userAgent());
$this->legalAcceptanceConfirmed = false;
}
/**