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>
This commit is contained in:
Kevin Adametz 2026-09-22 09:53:49 +00:00
parent 9f44546082
commit da167b713b
24 changed files with 1202 additions and 12 deletions

View file

@ -0,0 +1,89 @@
<?php
namespace App\Console\Commands;
use App\Enums\PressReleaseStatus;
use App\Models\PressReleaseImage;
use App\Services\Image\ImageService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
/**
* Legacy-Bereinigung (Bildkennzeichnung E.5, Entscheidung E-B11): Bilder
* aus dem Alt-Import an Meldungen, die nicht veröffentlicht sind (offline
* genommen, archiviert, Entwurf), werden samt Dateien gelöscht. Sie haben
* weder Nachweis noch Protokoll; eine erneute Einreichung läuft ohne Bild.
* Auf der Dev-Kopie: 10.768 Bilder (Stand 22.09.2026).
*/
class PurgeInactiveLegacyPressReleaseImages extends Command
{
protected $signature = 'press-release-images:purge-inactive-legacy
{--execute : Wirklich löschen (Standard ist ein Trockenlauf)}
{--chunk=500 : Zeilen je Durchlauf}';
protected $description = 'Löscht Legacy-Bilder an nicht veröffentlichten Meldungen samt Dateien (E-B11)';
public function __construct(private readonly ImageService $imageService)
{
parent::__construct();
}
public function handle(): int
{
$execute = (bool) $this->option('execute');
$inactive = [PressReleaseStatus::Rejected->value, PressReleaseStatus::Archived->value, PressReleaseStatus::Draft->value];
$query = PressReleaseImage::query()
->whereNotNull('press_release_images.legacy_id')
->whereNull('press_release_images.rules_version')
->whereHas('pressRelease', fn ($q) => $q->withoutGlobalScopes()->whereIn('press_releases.status', $inactive));
$byStatus = (clone $query)
->join('press_releases', 'press_releases.id', '=', 'press_release_images.press_release_id')
->selectRaw('press_releases.status as status, count(*) as c')
->groupBy('press_releases.status')
->pluck('c', 'status')
->all();
$total = array_sum($byStatus);
$this->info(($execute ? 'Lösche' : 'Trockenlauf:').' Legacy-Bilder an inaktiven Meldungen: '.$total);
foreach ($byStatus as $status => $count) {
$this->line(sprintf(' %-10s %d', $status, $count));
}
if (! $execute || $total === 0) {
return self::SUCCESS;
}
$deleted = 0;
$bytes = 0;
$query->with('pressRelease')->chunkById((int) $this->option('chunk'), function ($images) use (&$deleted, &$bytes): void {
foreach ($images as $image) {
$bytes += $this->sizeOf($image);
$this->imageService->deletePressReleaseImage($image->disk, $image->path, $image->variants);
$image->forceDelete();
$deleted++;
}
});
$this->info(sprintf('Gelöscht: %d Bilder, etwa %s MB freigegeben.', $deleted, number_format($bytes / 1048576, 1, ',', '.')));
return self::SUCCESS;
}
private function sizeOf(PressReleaseImage $image): int
{
$disk = Storage::disk($image->disk);
$size = 0;
foreach (array_filter([$image->path, ...array_values($image->variants ?? [])]) as $path) {
if (is_string($path) && $disk->exists($path)) {
$size += (int) $disk->size($path);
}
}
return $size;
}
}