- archive_entries (append-only) + archive_access_log; Disk archive außerhalb des Web-Roots; eigener ARCHIVE_KEY (archive:key), AES-256-CBC, SHA-256 am Eintrag - EvidenceArchive: record/open/manifest/export (signiertes ZIP), setLegalHold/releaseLegalHold (Frist läuft neu), purgeDue, exportTombstones, reconcileAfterRestore - PressReleaseArchiver: image_removed (Original, Snapshot, Bestätigungen), pm_depublished (letzter veröffentlichter Stand), gdpr_change (Anonymisierung, Korrekturen), label_corrected - Hooks in Remover, depublish/archive/changeStatusFromAdmin/deleteFromAdmin, AnonymizationService, CorrectionService, ImageCorrectionService - Scheduler archive:purge 03:20 und archive:export-tombstones 03:30; archive:reconcile-after-restore im Runbook - Admin ▸ Management ▸ Beweisarchiv (Liste, Legal Hold mit Pflichtvermerk, Öffnen/Export nur mit Zweck) - Scope-Wächter: neue Befehle nutzen withoutGlobalScope(PortalScope::class) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 lines
3.3 KiB
PHP
90 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Enums\PressReleaseStatus;
|
|
use App\Models\PressReleaseImage;
|
|
use App\Scopes\PortalScope;
|
|
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->withoutGlobalScope(PortalScope::class)->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;
|
|
}
|
|
}
|