presseportale/app/Services/Image/ProvenanceResult.php
Kevin Adametz e9bdff3ad3 images: Datenmodell, Textarchiv, Herkunftsprüfung und gemeinsamer Uploader für Formular v2 (Bildkennzeichnung Block A)
- press_release_images: Kundenangabe/Portalkennzeichnung getrennt, Herkunftsbefund, Beweisoriginal, Snapshot-Pfade, published_ever_at, rules_version, Fokus/Letterbox; Bestand unangetastet
- legal_texts (insert-only) mit img-confirm-v1 (alter Wortlaut), v2 (AGB 0.6), v3 (AGB 0.7, noch ohne Datum); image-rights:backfill-confirmations
- image_rights_confirmations polymorph, insert-only, IP als versionierter HMAC; Löschung nur für nie veröffentlichte Uploads
- ImageProvenanceInspector (Imagick/exif): IPTC, XMP, EXIF; algorithmicallyEnhanced nur Vermerk; unlesbare Datei = error
- PressReleaseImageUploader: Original vor Derivaten, Hash-Bindung, Titelbild-Regel unter Sperre, Dateien bei Fehlern zurückgerollt; Admin-Upload ohne Bestätigung
- ImageService: kein Rückfall auf die Quelldatei, og-Variante, Fokuspunkt und no_crop

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

70 lines
2.3 KiB
PHP

<?php
namespace App\Services\Image;
use App\Enums\AiImageDeclaration;
use App\Enums\ImageProvenanceStatus;
/**
* Normalisiertes Ergebnis der Herkunftsprüfung (Bildkennzeichnung A.5).
* Wird als json am Bild und im Bestätigungs-Snapshot gespeichert.
*
* @phpstan-type IptcData array{digital_source_type: string|null, creator: list<string>, credit: string|null, byline: string|null, copyright_notice: string|null}
* @phpstan-type ExifData array{artist: string|null, copyright: string|null}
* @phpstan-type DerivedData array{ai_declaration_suggestion: string|null, credit_suggestion: string|null, credit_suggestion_source: string|null, credit_truncated: bool, enhancement_hint: bool}
*/
final readonly class ProvenanceResult
{
/**
* @param IptcData $iptc
* @param ExifData $exif
* @param DerivedData $derived
* @param array{class: string, message: string}|null $error
*/
public function __construct(
public ImageProvenanceStatus $status,
public string $fileSha256,
public array $iptc,
public array $exif,
public array $derived,
public string $toolVersion,
public int $durationMs,
public ?array $error = null,
) {}
public function suggestedAiDeclaration(): ?AiImageDeclaration
{
return AiImageDeclaration::tryFrom((string) $this->derived['ai_declaration_suggestion']);
}
public function creditSuggestion(): ?string
{
return $this->derived['credit_suggestion'];
}
/**
* Nur ein vertrauenswürdig geprüfter Befund darf die Kennzeichnung
* erzwingen (Decision-Update E9). In Phase 1 immer null.
*/
public function enforcedAiDeclaration(): ?AiImageDeclaration
{
return $this->status->allowsLock() ? $this->suggestedAiDeclaration() : null;
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return array_filter([
'status' => $this->status->value,
'file_sha256' => $this->fileSha256,
'tool' => $this->toolVersion,
'duration_ms' => $this->durationMs,
'iptc' => $this->iptc,
'exif' => $this->exif,
'derived' => $this->derived,
'error' => $this->error,
], fn ($value) => $value !== null);
}
}