- 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>
63 lines
2.6 KiB
PHP
63 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Enums\PressReleaseStatus;
|
|
use App\Models\PressRelease;
|
|
use App\Models\PressReleaseStatusLog;
|
|
use App\Scopes\PortalScope;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Bildkennzeichnung E.1: `published_ever_at` für den Bestand — jede
|
|
* Meldung, die heute veröffentlicht ist, ein `published_at` trägt oder
|
|
* laut Status-Log jemals auf `published` stand, war öffentlich. Ihre Bilder
|
|
* erben den Zeitpunkt. Idempotent, nur NULL-Zeilen werden gesetzt.
|
|
*/
|
|
class BackfillPublishedEver extends Command
|
|
{
|
|
protected $signature = 'press-releases:backfill-published-ever {--dry-run : Nur zählen}';
|
|
|
|
protected $description = 'Setzt published_ever_at für Meldungen und Bilder, die jemals öffentlich waren';
|
|
|
|
public function handle(): int
|
|
{
|
|
$candidates = PressRelease::withoutGlobalScope(PortalScope::class)
|
|
->whereNull('published_ever_at')
|
|
->where(function ($query): void {
|
|
$query->whereNotNull('published_at')
|
|
->orWhere('status', PressReleaseStatus::Published->value)
|
|
->orWhereIn('id', PressReleaseStatusLog::query()->where('to_status', PressReleaseStatus::Published->value)->select('press_release_id'));
|
|
});
|
|
|
|
$count = $candidates->count();
|
|
$this->info("Meldungen ohne published_ever_at, die öffentlich waren: {$count}");
|
|
|
|
if ($this->option('dry-run') || $count === 0) {
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$updated = 0;
|
|
|
|
$candidates->select(['id', 'published_at', 'created_at'])->chunkById(1000, function ($releases) use (&$updated): void {
|
|
foreach ($releases as $release) {
|
|
$firstLog = PressReleaseStatusLog::query()
|
|
->where('press_release_id', $release->id)
|
|
->where('to_status', PressReleaseStatus::Published->value)
|
|
->min('created_at');
|
|
|
|
$at = collect([$release->published_at, $firstLog, $release->created_at])->filter()->min();
|
|
|
|
DB::table('press_releases')->where('id', $release->id)->update(['published_ever_at' => $at]);
|
|
DB::table('press_release_images')->where('press_release_id', $release->id)->whereNull('published_ever_at')
|
|
->update(['published_ever_at' => DB::raw('CASE WHEN created_at > '.DB::getPdo()->quote((string) $at).' THEN created_at ELSE '.DB::getPdo()->quote((string) $at).' END')]);
|
|
$updated++;
|
|
}
|
|
});
|
|
|
|
$this->info("Gesetzt: {$updated} Meldungen samt Bildern.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|