presseportale/app/Services/PressRelease/Images/PublishedImageSnapshot.php
Kevin Adametz 46b89e1d23 images: Lebenszyklus — Veröffentlichungsgate, published_ever_at, Snapshot der veröffentlichten Fassung, Sofortlöschung, Legacy-Bereinigung, Beweiszugriff, Korrekturweg (Bildkennzeichnung Block E)
- publish()/changeStatusFromAdmin(): kein Bild aus Formular v2 ohne gültige Bestätigung (ImageRightsConfirmationRequiredException, Admin-Seite meldet es); published_ever_at an PM und Bildern; PublishedImageSnapshot (Cover + JSON mit Nachweis/Kennzeichnung)
- deleteFromAdmin() und press-releases:purge-drafts löschen Bilder nie veröffentlichter Meldungen samt Original und Bestätigung sofort (E4, O1)
- press-releases:backfill-published-ever (Status-Log), press-release-images:purge-inactive-legacy (E-B11, Trockenlauf Standard)
- Admin-Route für Beweisoriginal und veröffentlichte Fassung nur mit Zweck, ActivityType EvidenceAccess; Architekturtest: original_path nur in vier Dateien
- ImageCorrectionService + image_declaration_corrections (insert-only), withheld_at blendet das Bild öffentlich aus (publiclyVisible-Scope), Korrekturpanel auf der Admin-Seite
- Systemstatus: fehlgeschlagene Herkunftsprüfungen der letzten 7 Tage

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 09:53:49 +00:00

85 lines
3.3 KiB
PHP

<?php
namespace App\Services\PressRelease\Images;
use App\Models\PressRelease;
use App\Models\PressReleaseImage;
use App\Support\ImageCredit;
use Illuminate\Support\Facades\Storage;
/**
* Snapshot der veröffentlichten Bildfassung (Bildkennzeichnung E.2,
* Decision-Update E2/S1): beim Veröffentlichen wird die ausgelieferte
* Cover-Variante zusammen mit dem gerenderten Nachweis und der Kennzeichnung
* auf den privaten Snapshot-Pfad kopiert. So lässt sich später belegen, was
* mit welchem Nachweis öffentlich war — auch nach Korrektur oder Entfernung.
*/
class PublishedImageSnapshot
{
public function captureForPressRelease(PressRelease $pressRelease): void
{
$pressRelease->loadMissing('company');
foreach ($pressRelease->images()->whereNotNull('rules_version')->get() as $image) {
$this->capture($image, $pressRelease);
}
}
public function capture(PressReleaseImage $image, ?PressRelease $pressRelease = null): void
{
$pressRelease ??= $image->pressRelease;
if ($pressRelease === null || $image->isLegacyRules() || $image->original_sha256 === null) {
return;
}
$sourceDisk = Storage::disk($image->disk);
$coverPath = $image->pathForVariant('cover') ?? $image->path;
if (! $sourceDisk->exists($coverPath)) {
return;
}
$bytes = (string) $sourceDisk->get($coverPath);
$sha = hash('sha256', $bytes);
// Unverändert seit dem letzten Snapshot: nichts zu tun.
if ($image->published_snapshot_sha256 !== null && hash_equals($image->published_snapshot_sha256, $sha) && $image->published_snapshot_path !== null) {
return;
}
$credit = ImageCredit::for($image, $pressRelease->company);
$extension = pathinfo($coverPath, PATHINFO_EXTENSION) ?: 'jpg';
$base = sprintf(
'%s/%d/%d-%s',
config('images.published_snapshot_directory'),
$pressRelease->getKey(),
$image->getKey(),
substr($sha, 0, 12),
);
$targetDisk = Storage::disk($image->original_disk ?? (string) config('images.originals_disk'));
$targetDisk->put($base.'.'.$extension, $bytes);
$targetDisk->put($base.'.json', (string) json_encode([
'captured_at' => now()->toIso8601String(),
'press_release_id' => $pressRelease->getKey(),
'image_id' => $image->getKey(),
'variant' => 'cover',
'variant_sha256' => $sha,
'original_sha256' => $image->original_sha256,
'credit_line' => $credit->line(),
'credit' => $credit->credit,
'ai_declaration' => $image->ai_declaration?->value,
'ai_declaration_source' => $image->ai_declaration_source?->value,
'responsible_line' => $credit->responsibleLine,
'confirmation_key' => $credit->confirmation?->legal_text_key,
'confirmation_at' => $credit->confirmation?->confirmed_at?->toIso8601String(),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$image->forceFill([
'published_snapshot_path' => $base.'.'.$extension,
'published_snapshot_sha256' => $sha,
'published_ever_at' => $image->published_ever_at ?? now(),
])->save();
}
}