+@endif
diff --git a/resources/views/components/press-release-submit-modal.blade.php b/resources/views/components/press-release-submit-modal.blade.php
index 0d0b4250..2eb8d590 100644
--- a/resources/views/components/press-release-submit-modal.blade.php
+++ b/resources/views/components/press-release-submit-modal.blade.php
@@ -5,6 +5,7 @@
'confirmLabel' => null,
'quotaTotal' => null,
'quotaRemaining' => null,
+ 'hasTitleImage' => true,
])
{{--
@@ -24,6 +25,10 @@
speichert und dann zur Buchungsseite leitet. ZWINGEND in Erstellen/Bearbeiten
setzen — sonst ginge der erfasste Inhalt beim Sprung verloren. Fehlt sie
(z. B. Detailansicht, dort ist bereits gespeichert), wird direkt verlinkt.
+
+ `has-title-image`: Ohne eigenes Titelbild (nur Platzhalter) entfällt die
+ Bildrechte-Bestätigung — es gibt nichts zu bestätigen. Default true, damit
+ die Checkbox im Zweifel lieber einmal zu viel erscheint.
--}}
@php($bookingRequired = ! (auth()->user()?->hasActiveBooking() ?? true))
@@ -62,7 +67,9 @@
@else
-
+ {{-- Ohne eigenes Titelbild startet `images` auf true: die Checkbox ist
+ ausgeblendet und darf den Bestätigen-Button nicht blockieren. --}}
+
@@ -147,13 +154,15 @@
:label="__('Der Inhalt entspricht den AGB und gesetzlichen Vorgaben.')"
/>
-
-
-
+ @if ($hasTitleImage)
+
+
+
+ @endif
isUploadFormOpen = true;
}
+ /**
+ * Wird das Modal clientseitig geschlossen (X-Button), soll der
+ * Formularstand nicht in die nächste Sitzung „kleben".
+ */
+ public function updatedIsUploadFormOpen(bool $isOpen): void
+ {
+ if (!$isOpen) {
+ $this->resetUploadForm();
+ $this->resetErrorBag();
+ }
+ }
+
/**
* Beim Wechsel des Lizenztyps das Detail-Feld leeren — sonst klebt
* z. B. der zuvor gewählte CC-Wert (cc_by) im Freitextfeld
@@ -73,7 +100,43 @@ new class extends Component {
$this->newAiTermsConfirmed = false;
$this->resetErrorBag('newLicenseDetail');
+ // Automatisch erzeugte Vorschläge weichen dem Vorschlag des neuen Typs.
+ if ($this->newCopyright === $this->autoCopyright || str_starts_with($this->newCopyright, __('Bild: KI-generiert'))) {
+ $this->newCopyright = '';
+ $this->autoCopyright = '';
+ }
+
+ // Lizenztyp „KI-generiert" legt die KI-Kennzeichnung direkt fest.
+ if ($this->newLicenseType === ImageLicenseType::AiGenerated->value) {
+ $this->newAiDeclaration = AiImageDeclaration::AiGenerated->value;
+ $this->resetErrorBag('newAiDeclaration');
+ }
+
$this->suggestAiCopyright();
+ $this->suggestCopyrightFromAuthor();
+ }
+
+ /**
+ * Urheber-Angabe in den Bildnachweis-Vorschlag übernehmen, solange der
+ * Nutzer den Nachweis nicht selbst überschrieben hat.
+ */
+ public function updatedNewAuthor(): void
+ {
+ $this->suggestCopyrightFromAuthor();
+ }
+
+ private function suggestCopyrightFromAuthor(): void
+ {
+ if ($this->newLicenseType === ImageLicenseType::AiGenerated->value) {
+ return;
+ }
+
+ if (filled($this->newCopyright) && $this->newCopyright !== $this->autoCopyright) {
+ return;
+ }
+
+ $this->autoCopyright = filled($this->newAuthor) ? __('Foto: :author', ['author' => trim($this->newAuthor)]) : '';
+ $this->newCopyright = $this->autoCopyright;
}
/**
@@ -143,6 +206,12 @@ new class extends Component {
'newRightsNotes' => ['nullable', 'string', 'max:1000'],
'newRightsConfirmed' => ['accepted'],
'newAiTermsConfirmed' => [$isAiGenerated ? 'accepted' : 'nullable'],
+ 'newAiDeclaration' => array_filter([
+ 'required',
+ Rule::enum(AiImageDeclaration::class),
+ // Lizenztyp „KI-generiert" schließt „Originalfoto" aus.
+ $isAiGenerated ? Rule::in([AiImageDeclaration::AiGenerated->value, AiImageDeclaration::AiEdited->value]) : null,
+ ]),
],
[
'newCopyright.required' => __('Bitte einen öffentlichen Bildnachweis angeben, z. B. Foto: Max Mustermann / Beispiel GmbH.'),
@@ -154,6 +223,8 @@ new class extends Component {
'newPropertyRightsStatus.required' => __('Bitte angeben, ob Marken, Kunstwerke oder private Orte sichtbar sind.'),
'newRightsConfirmed.accepted' => __('Bitte bestätigen, dass die Bildrechte geklärt sind.'),
'newAiTermsConfirmed.accepted' => __('Bitte bestätigen, dass die Anbieter-Bedingungen die kommerzielle Nutzung erlauben.'),
+ 'newAiDeclaration.required' => __('Bitte die KI-Kennzeichnung des Bildes wählen: Originalfoto, KI-generiert oder KI-bearbeitet.'),
+ 'newAiDeclaration.in' => __('Bei Lizenztyp „KI-generiert" muss die Kennzeichnung KI-generiert oder KI-bearbeitet sein.'),
],
);
@@ -178,6 +249,7 @@ new class extends Component {
'rights_notes' => $this->newRightsNotes ?: null,
'rights_confirmed_at' => now(),
'is_ai_generated' => $isAiGenerated,
+ 'ai_declaration' => $this->newAiDeclaration,
'is_preview' => true,
'sort_order' => ((int) $pressRelease->images()->max('sort_order')) + 1,
'width' => $stored['width'],
@@ -275,7 +347,7 @@ new class extends Component {
private function resetUploadForm(): void
{
- $this->reset(['newImage', 'newTitle', 'newCopyright', 'newAuthor', 'newLicenseType', 'newLicenseDetail', 'newLicenseUrl', 'newSourceUrl', 'newPeopleRightsStatus', 'newPropertyRightsStatus', 'newRightsNotes', 'newRightsConfirmed', 'newAiTermsConfirmed']);
+ $this->reset(['newImage', 'newTitle', 'newCopyright', 'newAuthor', 'newLicenseType', 'newLicenseDetail', 'newLicenseUrl', 'newSourceUrl', 'newPeopleRightsStatus', 'newPropertyRightsStatus', 'newRightsNotes', 'newRightsConfirmed', 'newAiTermsConfirmed', 'newAiDeclaration', 'autoCopyright']);
}
/**
@@ -388,24 +460,29 @@ new class extends Component {
@elseif($canEdit)
- @if (!$isUploadFormOpen)
-
-
- {{ __('Hier fehlt ein Titelbild') }}
-
- {{ __('Der Platzhalter bleibt aktiv, bis ein eigenes Titelbild hochgeladen wurde.') }}
-
-
-
-
- {{ __('Eigenes Titelbild hochladen') }}
-
+
+
+ {{ __('Hier fehlt ein Titelbild') }}
+
+ {{ __('Der Platzhalter bleibt aktiv, bis ein eigenes Titelbild hochgeladen wurde.') }}
+
- @else
-
+
+
+ @if ($isUploadFormOpen)
+
+
+
+ {{ __('Nach der Bildauswahl folgen drei kurze Schritte zu Herkunft und Bildrechten.') }}
+
@else
{{-- Große Vorschau im Titelbild-Format, damit das Motiv
vor dem Hochladen wirklich beurteilbar ist. --}}
@@ -460,151 +541,185 @@ new class extends Component {
- {{-- ===== Schritt 2 · Öffentliche Bildinfos ===== --}}
-
-
+ {{ __('KI-generierte Bilder haben keinen menschlichen Urheber — maßgeblich sind die Nutzungsbedingungen des KI-Anbieters. Das Bild wird öffentlich als KI-generiert gekennzeichnet (Transparenzpflicht, EU AI Act). Achten Sie darauf, dass keine realen Personen, Marken oder geschützten Werke erkennbar nachgebildet werden.') }}
+
- {{ __('KI-generierte Bilder haben keinen menschlichen Urheber — maßgeblich sind die Nutzungsbedingungen des KI-Anbieters. Das Bild wird öffentlich als KI-generiert gekennzeichnet (Transparenzpflicht, EU AI Act). Achten Sie darauf, dass keine realen Personen, Marken oder geschützten Werke erkennbar nachgebildet werden.') }}
+
+ @foreach ($propertyRightsOptions as $value => $label)
+
+ @endforeach
+
+ {{ __('Bei erkennbaren Personen können zusätzlich Persönlichkeits- oder Datenschutzrechte betroffen sein. Bitte stellen Sie sicher, dass die Veröffentlichung zulässig ist.') }}
+
+ @endif
-
- {{ __('Creative-Commons-Lizenzen können Einschränkungen enthalten. Bitte prüfen Sie, ob kommerzielle Nutzung, Bearbeitung und Veröffentlichung als Titelbild erlaubt sind.') }}
+ @if ($showsRightsWarning)
+
+ {{ __('Diese Auswahl kann Einschränkungen enthalten. Bitte laden Sie das Bild nur hoch, wenn die Nutzung, Bearbeitung und Veröffentlichung als Titelbild erlaubt ist.') }}
+
- {{ __('Bei erkennbaren Personen können zusätzlich Persönlichkeits- oder Datenschutzrechte betroffen sein. Bitte stellen Sie sicher, dass die Veröffentlichung zulässig ist.') }}
-
- @endif
-
- @if ($showsRightsWarning)
-
- {{ __('Diese Auswahl kann Einschränkungen enthalten. Bitte laden Sie das Bild nur hoch, wenn die Nutzung, Bearbeitung und Veröffentlichung als Titelbild erlaubt ist.') }}
-
diff --git a/resources/views/livewire/customer/press-releases/create.blade.php b/resources/views/livewire/customer/press-releases/create.blade.php
index 978638c6..17bdcf85 100644
--- a/resources/views/livewire/customer/press-releases/create.blade.php
+++ b/resources/views/livewire/customer/press-releases/create.blade.php
@@ -24,6 +24,7 @@ use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class extends Component
{
+ use \App\Livewire\Concerns\InteractsWithAiDeclaration;
use \App\Livewire\Concerns\InteractsWithPressReleaseContacts;
use \App\Livewire\Concerns\InteractsWithPressReleaseScheduling;
use \App\Livewire\Concerns\InteractsWithPressReleaseTags;
@@ -297,6 +298,12 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
public function save(string $submitStatus = 'draft'): void
{
+ // KI-Kennzeichnung (Art. 50 KI-VO): Pflicht nur beim Einreichen —
+ // zuerst prüfen, damit die Fehler direkt im Submit-Modal erscheinen.
+ if ($submitStatus === 'review') {
+ $this->validateAiDeclaration();
+ }
+
$pr = $this->persistDraft();
if (! $pr) {
@@ -374,6 +381,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
? $this->resolveScheduledAt()
: null,
'embargo_at' => null,
+ ...$this->aiDeclarationAttributes(),
];
}
@@ -485,7 +493,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- 1) FIRMA-SELEKTOR --}}
-
+
{{ __('Für Firma') }}
@@ -534,8 +542,31 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
- {{-- 2) TITEL --}}
-
+ {{-- 2) KATEGORIE (Pflichtfeld — bewusst prominent vor der Headline) --}}
+
+
@@ -636,7 +667,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
- {{-- 5) TITELBILD --}}
+ {{-- 6) TITELBILD --}}
@@ -692,7 +723,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- Titelbild-Platzhalter-Auswahl --}}
- {{-- 6) ANHÄNGE — TEMPORÄR DEAKTIVIERT
+ {{-- 7) ANHÄNGE — TEMPORÄR DEAKTIVIERT
Datei-Uploads erfordern eine vollständige Sicherheitsprüfung
(Virus-/Malware-Scan, MIME-Validierung, Storage-Quoten).
Wird in einer späteren Phase aktiviert.
@@ -713,8 +744,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
--}}
- {{-- 7) BOILERPLATE --}}
-
+ {{-- 8) BOILERPLATE --}}
+
@@ -775,6 +806,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{ __('Entwurf') }}
+
+
@@ -806,28 +839,6 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
- {{-- Kategorie (Pflichtfeld - prominent direkt unter Status) --}}
-
-
-
- {{ __('Kategorie') }}
- *
-
-
-
-
-
-
- @foreach ($categories as $cat)
-
- @endforeach
-
-
- {{ __('Pflichtfeld — steuert, in welcher Rubrik die PM erscheint.') }}
-
-
-
-
{{-- Portal (Read-only, Badge in Portal-Farbe) --}}
@php
$portalPillClass = 'portal-pill';
@@ -857,7 +868,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- Pressekontakt --}}
-
+
{{ __('Pressekontakt') }}
@@ -966,7 +977,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- Veröffentlichung --}}
-
+
{{ __('Veröffentlichung') }}
@@ -1026,7 +1037,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- Weitere Felder --}}
-
+
{{ __('Weitere Felder') }}
@@ -1066,12 +1077,15 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
{{-- Einreichungs-Modal (öffnet über „Zur Prüfung senden") --}}
+ {{-- Im Erstellen-Formular gibt es noch keinen Bild-Upload (der folgt im
+ Editor nach dem Entwurf-Sichern), daher entfällt die Bildrechte-Frage. --}}
+ :quota-remaining="$quotaRemaining"
+ :has-title-image="false" />
@endif
diff --git a/resources/views/livewire/customer/press-releases/edit.blade.php b/resources/views/livewire/customer/press-releases/edit.blade.php
index eff129d2..98018527 100644
--- a/resources/views/livewire/customer/press-releases/edit.blade.php
+++ b/resources/views/livewire/customer/press-releases/edit.blade.php
@@ -8,6 +8,7 @@ use App\Models\Company;
use App\Models\Contact;
use App\Models\PressRelease;
use App\Services\Customer\CustomerCompanyContext;
+use App\Services\PressRelease\AiDeclarationRequiredException;
use App\Services\PressRelease\BlacklistViolationException;
use App\Services\PressRelease\BookingRequiredException;
use App\Services\PressRelease\PressReleaseCoverImage;
@@ -24,6 +25,7 @@ use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] class extends Component
{
+ use \App\Livewire\Concerns\InteractsWithAiDeclaration;
use \App\Livewire\Concerns\InteractsWithPressReleaseContacts;
use \App\Livewire\Concerns\InteractsWithPressReleaseScheduling;
use \App\Livewire\Concerns\InteractsWithPressReleaseTags;
@@ -122,6 +124,10 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
}
$this->placeholderVariant = PressReleasePlaceholder::fromValueOrDefault($pr->placeholder_variant?->value)->value;
+
+ // KI-Grad vorbelegen (bereits deklariert); die verbindliche
+ // Bestätigung muss bei jedem Submit erneut gesetzt werden.
+ $this->aiTextDeclaration = $pr->ai_text_declaration?->value;
}
#[On('placeholder-selected')]
@@ -248,6 +254,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
? $this->resolveScheduledAt()
: null,
'embargo_at' => null,
+ ...$this->aiDeclarationAttributes(),
]);
$contentChanged = $pr->wasChanged(['title', 'text']);
@@ -287,6 +294,12 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
public function save(bool $submitAfterSave = false): void
{
+ // KI-Kennzeichnung (Art. 50 KI-VO): Pflicht nur beim Einreichen —
+ // zuerst prüfen, damit die Fehler direkt im Submit-Modal erscheinen.
+ if ($submitAfterSave) {
+ $this->validateAiDeclaration();
+ }
+
$result = $this->persistEdit();
if (! $result) {
@@ -311,7 +324,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
$this->redirect(route('me.press-releases.show', $pr->id), navigate: true);
return;
- } catch (BookingRequiredException|QuotaExceededException $e) {
+ } catch (AiDeclarationRequiredException|BookingRequiredException|QuotaExceededException $e) {
Flux::toast(
heading: __('Gespeichert, aber nicht eingereicht'),
text: $e->getMessage(),
@@ -487,7 +500,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
{{-- 1) FIRMA-SELEKTOR --}}
-
+
{{ __('Für Firma') }}
@@ -534,8 +547,31 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
- {{-- 2) TITEL --}}
-
+ {{-- 2) KATEGORIE (Pflichtfeld — bewusst prominent vor der Headline) --}}
+
+