pressReleaseId = $pressReleaseId; } public function openUploadForm(): void { $this->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 * „Lizenzdetails / Begründung" von „Sonstiges". Für KI-Bilder wird der * öffentliche Bildnachweis vorgeschlagen (AI-Act-Kennzeichnung). */ public function updatedNewLicenseType(): void { $this->newLicenseDetail = ''; $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, __('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; } // Ohne „Foto:"-Präfix — die Veröffentlichungsseite stellt es dem // Bildnachweis bereits selbst voran (sonst stünde dort „Foto: Foto: …"). $this->autoCopyright = filled($this->newAuthor) ? trim($this->newAuthor) : ''; $this->newCopyright = $this->autoCopyright; } /** * Tool-Angabe in den Bildnachweis-Vorschlag übernehmen, solange der * Nutzer den Nachweis nicht selbst überschrieben hat. */ public function updatedNewLicenseDetail(): void { $this->suggestAiCopyright(); } private function suggestAiCopyright(): void { if ($this->newLicenseType !== ImageLicenseType::AiGenerated->value) { return; } if (filled($this->newCopyright) && !str_starts_with($this->newCopyright, __('KI-generiert'))) { return; } $this->newCopyright = filled($this->newLicenseDetail) ? __('KI-generiert (:tool)', ['tool' => trim($this->newLicenseDetail)]) : __('KI-generiert'); } public function closeUploadForm(): void { $this->resetUploadForm(); $this->resetErrorBag(); $this->isUploadFormOpen = false; } public function saveImage(ImageService $imageService): void { $pressRelease = $this->getPressRelease(); $this->authorize('update', $pressRelease); if (!$this->canChangeImages($pressRelease)) { $this->addError('newImage', __('Das Titelbild kann nur bei Entwürfen oder abgelehnten PMs geändert werden.')); return; } if ($this->titleImageFor($pressRelease) !== null) { $this->addError('newImage', __('Bitte löschen Sie zuerst das vorhandene Titelbild.')); return; } $licenseType = ImageLicenseType::tryFrom($this->newLicenseType); $requiresLicenseUrl = $licenseType?->requiresLicenseUrl() ?? false; $requiresLicenseDetail = $licenseType?->requiresLicenseDetail() ?? false; $isAiGenerated = $licenseType?->isAiGenerated() ?? false; $this->validate( [ 'newImage' => ['required', 'image', 'mimes:jpeg,jpg,png,webp', 'max:' . (int) (ImageService::MAX_PRESS_RELEASE_IMAGE_BYTES / 1024)], 'newTitle' => ['nullable', 'string', 'max:120'], 'newCopyright' => ['required', 'string', 'max:255'], 'newAuthor' => ['required', 'string', 'max:255'], 'newLicenseType' => ['required', Rule::enum(ImageLicenseType::class)], 'newLicenseDetail' => [$requiresLicenseDetail ? 'required' : 'nullable', 'string', 'max:120'], 'newLicenseUrl' => [$requiresLicenseUrl ? 'required' : 'nullable', 'url', 'max:2048'], 'newSourceUrl' => ['nullable', 'url', 'max:2048'], 'newPeopleRightsStatus' => ['required', Rule::in(array_keys($this->peopleRightsOptions()))], 'newPropertyRightsStatus' => ['required', Rule::in(array_keys($this->propertyRightsOptions()))], '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. Max Mustermann / Beispiel GmbH.'), 'newAuthor.required' => $isAiGenerated ? __('Bitte angeben, wer für die Erstellung verantwortlich ist (Person oder Firma).') : __('Bitte Urheber, Fotograf oder Rechteinhaber angeben.'), 'newLicenseType.required' => __('Bitte einen Lizenztyp wählen.'), 'newLicenseDetail.required' => $isAiGenerated ? __('Bitte das verwendete KI-Tool angeben, z. B. Midjourney, Gemini, ChatGPT, DALL·E, Adobe Firefly.') : __('Bitte die Lizenz genauer angeben.'), 'newLicenseUrl.required' => __('Für diesen Lizenztyp ist eine Nachweis-URL erforderlich.'), 'newPeopleRightsStatus.required' => __('Bitte angeben, ob erkennbare Personen abgebildet sind.'), '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.'), ], ); $stored = $imageService->storePressReleaseImage($this->newImage, $pressRelease->id); $pressRelease->images()->update(['is_preview' => false]); $pressRelease->images()->create([ 'disk' => $stored['disk'], 'path' => $stored['path'], 'variants' => $stored['variants'], 'title' => $this->newTitle ?: null, 'copyright' => $this->newCopyright ?: null, 'author' => $this->newAuthor, 'license_type' => $this->newLicenseType, 'license_detail' => $this->newLicenseDetail ?: null, 'license_url' => $this->newLicenseUrl ?: null, 'source_url' => $this->newSourceUrl ?: null, 'persons_consent' => $this->newPeopleRightsStatus === 'consent', 'people_rights_status' => $this->newPeopleRightsStatus, 'property_rights_status' => $this->newPropertyRightsStatus, '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'], 'height' => $stored['height'], 'mime' => $stored['mime'], ]); $this->resetUploadForm(); $this->isUploadFormOpen = false; $this->dispatch('title-image-changed'); Flux::toast(text: __('Titelbild hochgeladen.'), variant: 'success'); } public function removeNewImage(): void { $this->reset('newImage'); $this->resetErrorBag('newImage'); } public function newImagePreviewUrl(): ?string { if (!is_object($this->newImage) || !method_exists($this->newImage, 'temporaryUrl')) { return null; } try { return $this->newImage->temporaryUrl(); } catch (\Throwable) { return null; } } public function remove(int $imageId, ImageService $imageService): void { $pressRelease = $this->getPressRelease(); $this->authorize('update', $pressRelease); if (!$this->canChangeImages($pressRelease)) { return; } $image = $pressRelease->images()->whereKey($imageId)->first(); if (!$image) { return; } $imageService->deletePressReleaseImage($image->disk, $image->path, $image->variants); $image->delete(); $this->dispatch('title-image-changed'); Flux::toast(text: __('Titelbild entfernt.'), variant: 'success'); } public function with(): array { $pressRelease = $this->getPressRelease(); return [ 'titleImage' => $this->titleImageFor($pressRelease), 'canEdit' => auth()->user()?->can('update', $pressRelease) === true && $this->canChangeImages($pressRelease), 'licenseTypeOptions' => ImageLicenseType::options(), 'ccLicenseOptions' => $this->ccLicenseOptions(), 'peopleRightsOptions' => $this->peopleRightsOptions(), 'propertyRightsOptions' => $this->propertyRightsOptions(), 'licenseUrlRequired' => ImageLicenseType::tryFrom($this->newLicenseType)?->requiresLicenseUrl() ?? false, 'licenseDetailRequired' => ImageLicenseType::tryFrom($this->newLicenseType)?->requiresLicenseDetail() ?? false, 'showsCcWarning' => $this->newLicenseType === ImageLicenseType::CreativeCommons->value, 'showsAiSection' => $this->newLicenseType === ImageLicenseType::AiGenerated->value, 'showsRightsWarning' => $this->shouldShowRightsWarning(), ]; } private function getPressRelease(): PressRelease { return PressRelease::withoutGlobalScope(PortalScope::class)->findOrFail($this->pressReleaseId); } private function canChangeImages(PressRelease $pressRelease): bool { if (auth()->user()?->canAccessAdmin()) { return !in_array($pressRelease->status, [PressReleaseStatus::Archived], true); } return in_array($pressRelease->status, [PressReleaseStatus::Draft, PressReleaseStatus::Rejected], true); } private function titleImageFor(PressRelease $pressRelease): ?PressReleaseImage { return $pressRelease->images()->orderByDesc('is_preview')->orderBy('sort_order')->orderBy('id')->first(); } private function resetUploadForm(): void { $this->reset(['newImage', 'newTitle', 'newCopyright', 'newAuthor', 'newLicenseType', 'newLicenseDetail', 'newLicenseUrl', 'newSourceUrl', 'newPeopleRightsStatus', 'newPropertyRightsStatus', 'newRightsNotes', 'newRightsConfirmed', 'newAiTermsConfirmed', 'newAiDeclaration', 'autoCopyright']); } /** * @return array */ private function ccLicenseOptions(): array { return [ 'cc0' => 'CC0', 'cc_by' => 'CC BY', 'cc_by_sa' => 'CC BY-SA', 'cc_by_nd' => 'CC BY-ND', 'cc_by_nc' => 'CC BY-NC', 'cc_by_nc_sa' => 'CC BY-NC-SA', 'cc_by_nc_nd' => 'CC BY-NC-ND', ]; } /** * @return array */ private function peopleRightsOptions(): array { return [ 'none' => __('Nein, keine erkennbaren Personen'), 'consent' => __('Ja, Einwilligung liegt vor'), 'public_event' => __('Ja, öffentliche Veranstaltung / redaktioneller Kontext'), ]; } /** * @return array */ private function propertyRightsOptions(): array { return [ 'none' => __('Nein'), 'cleared' => __('Ja, Rechte / Nutzung sind geklärt'), ]; } private function shouldShowRightsWarning(): bool { $restrictedCcLicense = str_contains($this->newLicenseDetail, '_nc') || str_contains($this->newLicenseDetail, '_nd'); return $this->newLicenseType === ImageLicenseType::Other->value || $restrictedCcLicense; } }; ?>
{{ __('Titelbild') }} {{ $titleImage ? __('gesetzt') : __('Platzhalter aktiv') }}
@if ($titleImage)
@php $titleImageUrl = $titleImage->variantUrl('cover') ?? ($titleImage->variantUrl('large') ?? ($titleImage->variantUrl('medium') ?? $titleImage->url())); @endphp @if ($titleImageUrl) {{ $titleImage->title ?? __('Titelbild') }} @endif
{{ $titleImage->title ?: __('Eigenes Titelbild') }}
@if ($titleImage->author)

© {{ $titleImage->author }}

@endif @if ($titleImage->copyright)

{{ __('Bildnachweis: :copyright', ['copyright' => $titleImage->copyright]) }}

@endif
@if ($titleImage->is_ai_generated) {{ __('KI-generiert') }} @endif @if ($titleImage->license_type) {{ $titleImage->license_type->label() }} @endif @if ($titleImage->width && $titleImage->height) {{ $titleImage->width }}×{{ $titleImage->height }} @endif
@if ($canEdit) {{ __('Titelbild löschen') }} @endif
@elseif($canEdit)
{{ __('Hier fehlt ein Titelbild') }} {{ __('Der Platzhalter bleibt aktiv, bis ein eigenes Titelbild hochgeladen wurde.') }}
{{ __('Eigenes Titelbild hochladen') }}
@if ($isUploadFormOpen)
{{ __('Titelbild hochladen') }} {{ __('Bild auswählen, Herkunft und Bildrechte angeben — fertig.') }}
{{-- ===== Schritt 1 · Bild auswählen ===== --}}
1 {{ __('Bild auswählen') }}
@if (!$newImage)
{{ __('Bitte laden Sie nur Bilder hoch, für die Sie die erforderlichen Nutzungsrechte besitzen. Bilder aus Google, Social Media, Messenger-Gruppen oder fremden Websites dürfen nicht ohne ausdrückliche Erlaubnis verwendet werden.') }}

{{ __('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. --}}
@if ($this->newImagePreviewUrl()) {{ __('Vorschau des gewählten Titelbilds') }} @else
@endif
{{ $newImage->getClientOriginalName() }} · {{ number_format($newImage->getSize() / 1048576, 2, ',', '.') }} MB
{{ __('Anderes Bild wählen') }}
@endif
{{-- Die weiteren Schritte erscheinen erst, wenn ein Bild gewählt wurde — so bleibt der Einstieg übersichtlich. --}} @if ($newImage) {{-- ===== Schritt 2 · Herkunft & Bildnachweis ===== --}}
2 {{ __('Herkunft & Bildnachweis') }}
{{ __('Bitte wählen…') }} @foreach ($licenseTypeOptions as $value => $label) {{ $label }} @endforeach
@if ($showsAiSection)
{{ __('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.') }}
@elseif ($newLicenseType === \App\Enums\ImageLicenseType::CreativeCommons->value) {{ __('Bitte wählen…') }} @foreach ($ccLicenseOptions as $value => $label) {{ $label }} @endforeach
{{ __('Creative-Commons-Lizenzen können Einschränkungen enthalten. Bitte prüfen Sie, ob kommerzielle Nutzung, Bearbeitung und Veröffentlichung als Titelbild erlaubt sind.') }}
@elseif($licenseDetailRequired) @endif @if ($licenseUrlRequired) @endif {{-- KI-Kennzeichnung je Bild (Art. 50 KI-VO): Pflichtauswahl. Beim Lizenztyp „KI-generiert" wird sie automatisch gesetzt und deshalb hier nicht mehr abgefragt. --}} @if (!$showsAiSection) @foreach (\App\Enums\AiImageDeclaration::cases() as $aiImageOption) @endforeach @endif
{{-- ===== Schritt 3 · Personen & Rechte Dritter ===== --}}
3 {{ __('Personen & Rechte Dritter') }}
@foreach ($peopleRightsOptions as $value => $label) @endforeach @foreach ($propertyRightsOptions as $value => $label) @endforeach
@if (in_array($newPeopleRightsStatus, ['consent', 'public_event'], true))
{{ __('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.') }}
@endif
{{-- ===== Optionale Angaben (eingeklappt) ===== --}}
{{ __('Optionale Angaben') }} — {{ __('Alt-Text, Quellen-URLs, interne Hinweise') }}
@unless ($licenseUrlRequired) @endunless
{{-- ===== Schritt 4 · Bestätigung ===== --}}
4 {{ __('Bestätigung') }}
@endif
{{ __('Abbrechen') }} @if ($newImage) {{ __('Hochladen & einsetzen') }} @endif
@endif
@else
{{ __('Noch kein eigenes Titelbild hinterlegt. Der Platzhalter bleibt aktiv.') }}
@endif