web/admin/pdf: gemeinsame Bildnachweis-Auflösung, Detailfenster am Artikelbild, Bestätigungsstand in Kunden- und Adminansicht, Wortwahl „automatisierte Prüfung" (Bildkennzeichnung Block D)
- App\Support\ImageCredit: Foto/Quelle-Zeile, KI-Hinweis mit Herkunft, Verantwortlich-Zeile nur mit Protokoll, sichere Links; PDF-Zeilen mit Bestätigungskennung - Artikelseite: Overlay (eigenes i-Zeichen, Alpine ohne Plugin, Bottom-Sheet auf Mobil) mit Nachweis, Lizenz, Quelle, KI-Hinweis, Verantwortlich-Zeile, „Bild melden" - Richtlinien 0.2: Prüfung als automatisiert beschrieben, Abschnitt „Bilder" (Decision-Update 3.5); Taglines, Suche, Newsrooms, Startseiten-CTA, Mails ohne „qualitätsgeprüft" - Cache-Invalidierung bei Bildwechsel an veröffentlichten Meldungen; PHPStan-Baseline für das Resource bereinigt Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
24184f1dec
commit
9f44546082
26 changed files with 578 additions and 199 deletions
|
|
@ -108,7 +108,7 @@
|
|||
<a href="{{ $presseechoUrl }}" style="font-weight: bold; text-decoration: none;"><span style="color: #1B2417;">presse</span><span style="color: #345636;">echo</span></a>
|
||||
und
|
||||
<a href="{{ $businessportalUrl }}" style="font-weight: bold; text-decoration: none;"><span style="color: #232A33;">businessportal</span><span style="color: #C84A1E;">24</span></a>
|
||||
— ein Konto, qualitätsgeprüft, auf beiden Portalen veröffentlichen.
|
||||
— ein Konto, auf beiden Portalen veröffentlichen.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
'name' => 'businessportal',
|
||||
'accent' => '24',
|
||||
'tagline_short' => 'Pressemitteilungen · DACH',
|
||||
'tagline_long' => 'Veröffentlichungs-Portal für qualitätsgeprüfte Pressemitteilungen aus Deutschland, Österreich und der Schweiz.',
|
||||
'tagline_long' => 'Veröffentlichungs-Portal für Pressemitteilungen aus Deutschland, Österreich und der Schweiz.',
|
||||
'footer_legal' => '© :year businessportal24 · Alle Rechte vorbehalten',
|
||||
'about_label' => 'Über businessportal24',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<x-emails.layout title="Ihre Pressemitteilung wurde veröffentlicht" preheader="Ihre Pressemitteilung wurde geprüft und veröffentlicht.">
|
||||
<x-emails.layout title="Ihre Pressemitteilung wurde veröffentlicht" preheader="Ihre Pressemitteilung hat die automatisierte Prüfung bestanden und ist veröffentlicht.">
|
||||
<p>{{ filled($recipientName) ? 'Hallo '.$recipientName.',' : 'Guten Tag,' }}</p>
|
||||
|
||||
<p>Ihre Pressemitteilung wurde geprüft und erfolgreich veröffentlicht.</p>
|
||||
<p>Ihre Pressemitteilung hat unsere automatisierte Prüfung bestanden und ist jetzt veröffentlicht.</p>
|
||||
|
||||
<p class="title">{{ $pressRelease->title }}</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -213,14 +213,25 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
@php
|
||||
$adminTitleImage = $pr->images->sortByDesc('is_preview')->first();
|
||||
@endphp
|
||||
@if ($adminTitleImage && ($adminTitleImage->copyright || $adminTitleImage->is_ai_generated || $adminTitleImage->ai_declaration?->publicLabel()))
|
||||
{{-- Bildnachweis + KI-Kennzeichnung (Art. 50 EU AI Act) --}}
|
||||
@if ($adminTitleImage && ($adminCreditInfo = \App\Support\ImageCredit::for($adminTitleImage, $pr->company))->hasDetails())
|
||||
{{-- Bildnachweis, KI-Kennzeichnung, Herkunft und Bestätigungsstand
|
||||
(Bildkennzeichnung D.1/D.3) --}}
|
||||
<div class="flex flex-wrap items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
@if ($aiImageBadge = $adminTitleImage->ai_declaration?->publicLabel() ?? ($adminTitleImage->is_ai_generated ? __('KI-generiert') : null))
|
||||
<span class="badge hub">{{ $aiImageBadge }}</span>
|
||||
@if ($adminCreditInfo->aiLabel)
|
||||
<span class="badge hub">{{ $adminCreditInfo->aiLabel }}</span>
|
||||
@if ($adminCreditInfo->aiSourceLabel)
|
||||
<span class="text-[color:var(--color-ink-4)]">({{ $adminCreditInfo->aiSourceLabel }})</span>
|
||||
@endif
|
||||
@endif
|
||||
@if ($adminTitleImage->copyright)
|
||||
<span>{{ $adminTitleImage->copyright }}</span>
|
||||
@if ($adminCreditInfo->credit)
|
||||
<span>{{ $adminCreditInfo->creditLabel }}: {{ $adminCreditInfo->credit }}</span>
|
||||
@endif
|
||||
@if ($adminCreditInfo->confirmation)
|
||||
<span class="text-[color:var(--color-ink-4)]">· {{ __('Rechte bestätigt am :date (:key, AGB :agb, :channel)', ['date' => $adminCreditInfo->confirmation->confirmed_at->format('d.m.Y H:i'), 'key' => $adminCreditInfo->confirmation->legal_text_key, 'agb' => $adminCreditInfo->confirmation->agb_version ?? '–', 'channel' => $adminCreditInfo->confirmation->channel->value]) }}</span>
|
||||
@elseif ($adminTitleImage->awaitsCustomerConfirmation())
|
||||
<span class="badge">{{ __('wartet auf Kundenbestätigung') }}</span>
|
||||
@elseif ($adminCreditInfo->isLegacy)
|
||||
<span class="text-[color:var(--color-ink-4)]">· {{ __('Bestand ohne Bestätigungsprotokoll') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1748,15 +1748,20 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<flux:icon.photo variant="micro" class="size-3.5" />
|
||||
<span>{{ __('Platzhalter-Titelbild — laden Sie im Editor ein eigenes Bild hoch.') }}</span>
|
||||
</div>
|
||||
@elseif ($titleImage && ($titleImage->copyright || $titleImage->is_ai_generated || $titleImage->ai_declaration?->publicLabel()))
|
||||
{{-- Bildnachweis + KI-Kennzeichnung (Art. 50 EU AI Act): kurzes
|
||||
Badge („KI-generiert" / „KI-bearbeitet"), kein Satz-Wortlaut. --}}
|
||||
@elseif ($titleImage && ($imageCreditInfo = \App\Support\ImageCredit::for($titleImage, $pr->company))->hasDetails())
|
||||
{{-- Bildnachweis, KI-Kennzeichnung und Bestätigungsstand aus der
|
||||
gemeinsamen Auflösung (Bildkennzeichnung D.1/D.3). --}}
|
||||
<div class="flex flex-wrap items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
@if ($aiImageBadge = $titleImage->ai_declaration?->publicLabel() ?? ($titleImage->is_ai_generated ? __('KI-generiert') : null))
|
||||
<span class="badge hub">{{ $aiImageBadge }}</span>
|
||||
@if ($imageCreditInfo->aiLabel)
|
||||
<span class="badge hub">{{ $imageCreditInfo->aiLabel }}</span>
|
||||
@endif
|
||||
@if ($titleImage->copyright)
|
||||
<span>{{ $titleImage->copyright }}</span>
|
||||
@if ($imageCreditInfo->credit)
|
||||
<span>{{ $imageCreditInfo->creditLabel }}: {{ $imageCreditInfo->credit }}</span>
|
||||
@endif
|
||||
@if ($imageCreditInfo->confirmation)
|
||||
<span class="text-[color:var(--color-ink-4)]">· {{ __('Rechte bestätigt am :date (:key, AGB :agb, :channel)', ['date' => $imageCreditInfo->confirmation->confirmed_at->format('d.m.Y H:i'), 'key' => $imageCreditInfo->confirmation->legal_text_key, 'agb' => $imageCreditInfo->confirmation->agb_version ?? '–', 'channel' => $imageCreditInfo->confirmation->channel->value]) }}</span>
|
||||
@elseif ($titleImage->awaitsCustomerConfirmation())
|
||||
<span class="badge">{{ __('wartet auf Ihre Bestätigung') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
? ($brand['meta_title'] ?? 'Aktuelle Pressemitteilungen aus Wirtschaft & Industrie – presseecho')
|
||||
: __('web.meta.home_title', ['brand' => ($brand['name'] ?? 'presseecho').($brand['accent'] ?? '')]))
|
||||
@section('meta_description', app()->getLocale() === 'de'
|
||||
? ($brand['meta_description'] ?? 'Qualitätsgeprüfte Pressemitteilungen aus Wirtschaft und Industrie im DACH-Raum.')
|
||||
? ($brand['meta_description'] ?? 'Pressemitteilungen aus Wirtschaft und Industrie im DACH-Raum — automatisiert geprüft, strukturiert distribuiert.')
|
||||
: __('web.meta.home_description'))
|
||||
|
||||
@section('hreflang')
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
@php
|
||||
use App\Enums\Portal;
|
||||
use App\Services\PressRelease\CanonicalAssigner;
|
||||
use App\Support\ImageCredit;
|
||||
use App\Support\StructuredData;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
|
@ -47,10 +48,13 @@
|
|||
$signMedia = $isPreview ?? false;
|
||||
$imageUrl = $image?->variantUrl('detail', $signMedia) ?? $image?->variantUrl('card', $signMedia) ?? $image?->url($signMedia);
|
||||
$imageCaption = $image?->description ?: $image?->title;
|
||||
$imageCredit = $image?->copyright ?: $image?->author;
|
||||
// KI-Bildkennzeichnung (Art. 50 KI-VO): Badge direkt am Bild
|
||||
// („KI-generiert" / „KI-bearbeitet"), null bei Originalfotos.
|
||||
$imageAiBadge = $image?->ai_declaration?->publicLabel();
|
||||
// Bildnachweis, KI-Kennzeichnung und Verantwortlich-Zeile aus einer
|
||||
// Auflösung (Bildkennzeichnung D.1, Decision-Update 3.4). Die sichtbare
|
||||
// Zeile unter dem Bild ist die Nennung, das Overlay die Erklärung.
|
||||
$imageCreditInfo = $image ? ImageCredit::for($image, $company) : null;
|
||||
$imageCredit = $imageCreditInfo?->credit;
|
||||
$imageAiBadge = $imageCreditInfo?->aiLabel;
|
||||
$imageReportUrl = route('melden', ['edition' => app()->getLocale()]);
|
||||
|
||||
// Ohne Pressefoto: die im User-Panel gewählte Platzhalter-Variante.
|
||||
// Null bei Altbeständen ohne Variante — dann generischer Hero-Fallback.
|
||||
|
|
@ -417,10 +421,65 @@
|
|||
{{ $imageAiBadge }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if ($imageCreditInfo?->hasDetails())
|
||||
{{-- Detailfenster zum Bildnachweis (Bildkennzeichnung D.2, E-B13):
|
||||
eigenes „i"-Zeichen, kein „cr"-Logo. Alpine ohne Plugin: Fokus
|
||||
wandert in den Dialog und beim Schließen zurück; Escape schließt.
|
||||
Auf Mobil ein Bottom-Sheet, ab sm ein Popover am Icon. --}}
|
||||
<div x-data="{ open: false, show() { this.open = true; this.$nextTick(() => this.$refs.close.focus()) }, hide() { this.open = false; this.$nextTick(() => this.$refs.trigger.focus()) } }"
|
||||
@keydown.escape.window="if (open) hide()"
|
||||
class="absolute bottom-3 left-3 z-10" data-image-credit>
|
||||
<button type="button" x-ref="trigger" @click="open ? hide() : show()" :aria-expanded="open ? 'true' : 'false'"
|
||||
aria-haspopup="dialog" aria-controls="image-credit-dialog"
|
||||
class="flex size-7 items-center justify-center rounded-full bg-black/60 text-white ring-1 ring-white/50 backdrop-blur-[2px] hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
||||
title="{{ __('web.detail.image_credit_open') }}">
|
||||
<span class="sr-only">{{ __('web.detail.image_credit_open') }}</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9.5" stroke="currentColor" stroke-width="1.6" />
|
||||
<path d="M12 10.5v6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
||||
<circle cx="12" cy="7.6" r="1.15" fill="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open" x-cloak @click.outside="hide()" id="image-credit-dialog" role="dialog" aria-modal="true"
|
||||
aria-labelledby="image-credit-title"
|
||||
class="fixed inset-x-0 bottom-0 z-40 max-h-[80vh] overflow-y-auto border-t border-bg-rule-strong bg-bg p-5 text-[13px] text-ink shadow-2xl sm:absolute sm:inset-auto sm:bottom-10 sm:left-0 sm:w-[22rem] sm:border sm:shadow-lg">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<h2 id="image-credit-title" class="m-0 text-[11px] font-semibold uppercase tracking-[0.14em] text-ink-3">{{ __('web.detail.image_credit_title') }}</h2>
|
||||
<button type="button" x-ref="close" @click="hide()" class="text-[12px] text-ink-3 underline-offset-2 hover:text-ink hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ink">
|
||||
{{ __('web.detail.image_credit_close') }}
|
||||
</button>
|
||||
</div>
|
||||
<ul class="m-0 mt-3 list-none space-y-2 p-0 leading-[1.5]">
|
||||
@if ($imageCreditInfo->credit)
|
||||
<li>{{ $imageCreditInfo->creditLabel }}: <span class="font-medium">{{ $imageCreditInfo->credit }}</span></li>
|
||||
@endif
|
||||
@if ($imageCreditInfo->licenseUrl)
|
||||
<li>{{ __('web.detail.image_license') }}: <a href="{{ $imageCreditInfo->licenseUrl }}" rel="nofollow noopener" target="_blank" class="underline underline-offset-2">{{ \Illuminate\Support\Str::limit($imageCreditInfo->licenseUrl, 60) }}</a></li>
|
||||
@endif
|
||||
@if ($imageCreditInfo->sourceUrl)
|
||||
<li>{{ __('web.detail.image_source_link') }}: <a href="{{ $imageCreditInfo->sourceUrl }}" rel="nofollow noopener" target="_blank" class="underline underline-offset-2">{{ \Illuminate\Support\Str::limit($imageCreditInfo->sourceUrl, 60) }}</a></li>
|
||||
@endif
|
||||
@if ($imageCreditInfo->aiLabel)
|
||||
<li>
|
||||
{{ $image->ai_declaration?->value === 'ai_edited' ? __('web.detail.image_ai_hint_edited') : __('web.detail.image_ai_hint_generated') }}
|
||||
@if ($imageCreditInfo->aiSourceLabel)
|
||||
<span class="block text-ink-3">{{ __('web.detail.image_ai_source', ['source' => $imageCreditInfo->aiSourceLabel]) }}</span>
|
||||
@endif
|
||||
</li>
|
||||
@endif
|
||||
@if ($imageCreditInfo->responsibleLine)
|
||||
<li class="text-ink-3">{{ $imageCreditInfo->responsibleLine }}</li>
|
||||
@endif
|
||||
</ul>
|
||||
<a href="{{ $imageReportUrl }}" class="mt-4 inline-block text-[12px] text-ink-3 underline underline-offset-2 hover:text-ink">{{ __('web.detail.image_report') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if ($imageCaption || $imageCredit)
|
||||
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]">
|
||||
{{ $imageCaption }}@if ($imageCaption && $imageCredit) · @endif@if ($imageCredit)<span class="not-italic">{{ __('web.detail.photo') }}: {{ $imageCredit }}</span>@endif
|
||||
@if ($imageCaption || $imageCreditInfo?->line())
|
||||
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]" data-image-credit-line>
|
||||
{{ $imageCaption }}@if ($imageCaption && $imageCreditInfo?->line()) · @endif@if ($imageCreditInfo?->line())<span class="not-italic">{{ $imageCreditInfo->line() }}</span>@endif
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@
|
|||
<p>
|
||||
Diese Richtlinien gelten für alle Pressemitteilungen auf presseecho.de und
|
||||
businessportal24.com. Sie sind Bestandteil unserer <a href="{{ route('agb') }}">AGB</a>.
|
||||
Unser Ziel ist einfach: Leser sollen sich darauf verlassen können, dass hier echte
|
||||
Unternehmensnachrichten stehen — geprüft, den Richtlinien entsprechend und klar
|
||||
gekennzeichnet.
|
||||
Jede Mitteilung durchläuft vor der Veröffentlichung eine automatisierte Prüfung. Sie
|
||||
sortiert Spam, reine Werbung und Beiträge aus, die keine Pressemitteilung sind, ordnet
|
||||
die Kategorie zu und bewertet die formale Qualität des Textes. Eine Prüfung, ob Inhalte
|
||||
richtig sind oder ob Sie die Rechte an Texten und Bildern besitzen, findet nicht statt.
|
||||
Dafür sind Sie verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2>So funktioniert die Prüfung</h2>
|
||||
|
|
@ -64,6 +66,19 @@
|
|||
<li><strong>Passende Kategorie:</strong> Wählen Sie die Rubrik, die inhaltlich passt — nicht die mit der meisten Aufmerksamkeit.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Bilder</h2>
|
||||
<p>
|
||||
Zu jedem Bild gehört ein Bildnachweis; er erscheint öffentlich unter dem Bild. Laden Sie
|
||||
nur Bilder hoch, für die Sie die Rechte haben, und achten Sie darauf, dass abgebildete
|
||||
Personen mit der Veröffentlichung einverstanden sind. Bilder, die mit KI erzeugt oder
|
||||
bearbeitet wurden, kennzeichnen Sie beim Hochladen; enthält die Datei signierte
|
||||
Herkunftsdaten dazu, prüfen wir sie und zeigen die Kennzeichnung am Bild. Halten Sie eine
|
||||
von uns gesetzte Kennzeichnung für falsch, schreiben Sie uns an
|
||||
<a href="mailto:{{ $contactEmail }}">{{ $contactEmail }}</a>; wir prüfen das und
|
||||
korrigieren Fehler. Bilder ohne geklärte Rechte entfernen wir; das
|
||||
<a href="{{ route('melden') }}">Meldeverfahren</a> gilt auch für Bilder.
|
||||
</p>
|
||||
|
||||
<h2>Ablehnungskategorien</h2>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue