presseportale/tests/Fixtures/images/generate-fixtures.php
Kevin Adametz 214aea8910 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

85 lines
3 KiB
PHP

<?php
/**
* Erzeugt die Fixture-Dateien für die Herkunftsprüfung (Bildkennzeichnung
* A.5) einmalig mit Imagick. Aufruf: php tests/Fixtures/images/generate-fixtures.php
*
* Die Dateien werden eingecheckt; das Skript dient nur der Nachvollziehbarkeit.
*/
declare(strict_types=1);
$dir = __DIR__;
function iptcBlock(array $datasets): string
{
// IPTC-IIM: 0x1C, Record 2, Dataset, Länge (16 Bit), Daten.
$block = '';
foreach ($datasets as $dataset => $values) {
foreach ((array) $values as $value) {
$block .= "\x1C\x02".chr($dataset).pack('n', strlen($value)).$value;
}
}
return $block;
}
function xmp(array $description): string
{
return '<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'
.'<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'
.'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/">'
.implode('', $description)
.'</rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="w"?>';
}
function make(string $file, string $format, ?string $iptc = null, ?string $xmp = null): void
{
$image = new Imagick;
$image->newImage(64, 48, new ImagickPixel('#4a6fa5'), $format);
$image->setImageFormat($format);
if ($iptc !== null) {
$image->setImageProfile('iptc', $iptc);
}
if ($xmp !== null) {
$image->setImageProfile('xmp', $xmp);
}
$image->writeImage($file);
$image->clear();
echo "written: {$file}\n";
}
// 1. Kamera-JPEG mit klassischem IPTC (Credit, By-line, Copyright).
make("{$dir}/camera-iptc.jpg", 'jpeg', iptcBlock([
110 => 'Musterfirma GmbH / Anna Beispiel',
80 => 'Anna Beispiel',
116 => '© 2026 Musterfirma GmbH',
]));
// 2. JPEG mit XMP: KI-generiert (volle IPTC-URI) und Credit als Element.
make("{$dir}/xmp-ai-generated.jpg", 'jpeg', null, xmp([
'<Iptc4xmpExt:DigitalSourceType>http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia</Iptc4xmpExt:DigitalSourceType>',
'<photoshop:Credit>KI-Bild / Musterfirma</photoshop:Credit>',
]));
// 3. JPEG mit XMP algorithmicallyEnhanced (Kurzform) und dc:creator-Liste.
make("{$dir}/xmp-enhanced.jpg", 'jpeg', null, xmp([
'<Iptc4xmpExt:DigitalSourceType>algorithmicallyEnhanced</Iptc4xmpExt:DigitalSourceType>',
'<dc:creator><rdf:Seq><rdf:li>Max Mustermann</rdf:li></rdf:Seq></dc:creator>',
]));
// 4. PNG ohne Metadaten.
make("{$dir}/plain.png", 'png');
// 5. WebP mit XMP als Attribute (digitalCapture, dc:rights).
make("{$dir}/webp-capture.webp", 'webp', null, xmp([
'<Iptc4xmpExt:DigitalSourceType>digitalCapture</Iptc4xmpExt:DigitalSourceType>',
'<dc:rights><rdf:Alt><rdf:li xml:lang="x-default">© Beispiel AG</rdf:li></rdf:Alt></dc:rights>',
]));
// 6. Beschädigte Datei: JPEG-Header, dann Müll.
file_put_contents("{$dir}/broken.jpg", "\xFF\xD8\xFF\xE0".random_bytes(200));
echo "written: {$dir}/broken.jpg\n";