- App\Support\ImageCredit: Foto/Quelle-Zeile, KI-Hinweis mit Herkunft, Verantwortlich-Zeile nur mit Protokoll, sichere Links; PDF-Zeilen mit Bestätigungskennung - Artikelseite: Overlay (eigenes i-Zeichen, Alpine ohne Plugin, Bottom-Sheet auf Mobil) mit Nachweis, Lizenz, Quelle, KI-Hinweis, Verantwortlich-Zeile, „Bild melden" - Richtlinien 0.2: Prüfung als automatisiert beschrieben, Abschnitt „Bilder" (Decision-Update 3.5); Taglines, Suche, Newsrooms, Startseiten-CTA, Mails ohne „qualitätsgeprüft" - Cache-Invalidierung bei Bildwechsel an veröffentlichten Meldungen; PHPStan-Baseline für das Resource bereinigt Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
141 lines
6 KiB
PHP
141 lines
6 KiB
PHP
<?php
|
|
|
|
use App\Enums\ConfirmationChannel;
|
|
use App\Enums\Portal;
|
|
use App\Models\Category;
|
|
use App\Models\Company;
|
|
use App\Models\ImageRightsConfirmation;
|
|
use App\Models\LegalDocument;
|
|
use App\Models\PressRelease;
|
|
use App\Models\PressReleaseImage;
|
|
use App\Support\ImageCredit;
|
|
use Database\Seeders\LegalDocumentSeeder;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Bildkennzeichnung Block D: eine Auflösung für Nachweiszeile, Overlay,
|
|
* Kunden-/Adminansicht und PDF; Verantwortlich-Zeile nur mit Protokoll.
|
|
*/
|
|
function creditPublishedRelease(): array
|
|
{
|
|
$category = Category::factory()->create(['portal' => Portal::Presseecho]);
|
|
$category->translations()->create(['locale' => 'de', 'name' => 'Energie', 'slug' => 'energie']);
|
|
$company = Company::factory()->presseecho()->create(['name' => 'Beispiel AG', 'is_active' => true]);
|
|
|
|
$release = PressRelease::factory()
|
|
->published()
|
|
->forPortal(Portal::Presseecho)
|
|
->for($category)
|
|
->for($company)
|
|
->create(['slug' => 'bild-nachweis-test', 'published_at' => now()->subDay()]);
|
|
|
|
return compact('category', 'company', 'release');
|
|
}
|
|
|
|
function creditConfirmationFor(PressReleaseImage $image, string $channel = 'form'): ImageRightsConfirmation
|
|
{
|
|
return ImageRightsConfirmation::query()->create([
|
|
'subject_type' => $image->getMorphClass(),
|
|
'subject_id' => $image->getKey(),
|
|
'press_release_id' => $image->press_release_id,
|
|
'company_id' => $image->pressRelease->company_id,
|
|
'user_id' => $image->pressRelease->user_id,
|
|
'channel' => ConfirmationChannel::from($channel),
|
|
'legal_text_key' => 'img-confirm-v2',
|
|
'text_snapshot' => 'Ich bestätige …',
|
|
'agb_version' => '0.6',
|
|
'file_sha256' => $image->original_sha256,
|
|
'declaration_snapshot' => [],
|
|
'confirmed_at' => now()->setDate(2026, 9, 22)->setTime(10, 0),
|
|
'created_at' => now(),
|
|
]);
|
|
}
|
|
|
|
test('a formular v2 image renders credit line, ai hint with source and the responsible line', function () {
|
|
/** @var TestCase $this */
|
|
['release' => $release, 'company' => $company] = creditPublishedRelease();
|
|
$image = PressReleaseImage::factory()->for($release)->rulesV2()->create([
|
|
'copyright' => 'Anna Beispiel / Beispiel AG',
|
|
'ai_declaration' => 'ai_edited',
|
|
'ai_declaration_user' => 'ai_edited',
|
|
'ai_declaration_source' => 'customer',
|
|
'license_url' => 'https://example.com/lizenz',
|
|
'source_url' => 'javascript:alert(1)',
|
|
]);
|
|
creditConfirmationFor($image);
|
|
|
|
$credit = ImageCredit::for($image->fresh(), $company);
|
|
|
|
expect($credit->line())->toBe('Foto: Anna Beispiel / Beispiel AG · KI-bearbeitet')
|
|
->and($credit->responsibleLine)->toBe('Bild und Angaben: Beispiel AG, eingereicht am 22. September 2026')
|
|
->and($credit->aiSourceLabel)->toBe('Angabe des Einreichers')
|
|
->and($credit->licenseUrl)->toBe('https://example.com/lizenz')
|
|
->and($credit->sourceUrl)->toBeNull()
|
|
->and($credit->pdfLines())->toContain('Rechte bestätigt am 22.09.2026 10:00 (img-confirm-v2, AGB 0.6)');
|
|
|
|
$this->get('https://presseecho.test/de/press-release/bild-nachweis-test')
|
|
->assertSuccessful()
|
|
->assertSeeText('Foto: Anna Beispiel / Beispiel AG · KI-bearbeitet')
|
|
->assertSee('data-image-credit', false)
|
|
->assertSeeText('Dieses Bild wurde mit KI bearbeitet.')
|
|
->assertSeeText('Kennzeichnung: Angabe des Einreichers')
|
|
->assertSeeText('Bild und Angaben: Beispiel AG, eingereicht am 22. September 2026')
|
|
->assertSeeText('Bild melden')
|
|
->assertDontSee('javascript:alert', false);
|
|
});
|
|
|
|
test('a legacy image without credit falls back to the company as source and shows no responsible line', function () {
|
|
/** @var TestCase $this */
|
|
['release' => $release, 'company' => $company] = creditPublishedRelease();
|
|
$image = PressReleaseImage::factory()->for($release)->legacy()->create(['is_ai_generated' => true]);
|
|
|
|
$credit = ImageCredit::for($image, $company);
|
|
|
|
expect($credit->line())->toBe('Quelle: Beispiel AG · KI-generiert')
|
|
->and($credit->responsibleLine)->toBeNull()
|
|
->and($credit->aiSourceLabel)->toBeNull()
|
|
->and($credit->isLegacy)->toBeTrue();
|
|
|
|
$this->get('https://presseecho.test/de/press-release/bild-nachweis-test')
|
|
->assertSuccessful()
|
|
->assertSeeText('Quelle: Beispiel AG · KI-generiert')
|
|
->assertDontSeeText('Bild und Angaben');
|
|
});
|
|
|
|
test('a legacy image with author but without copyright uses the author and the old fields stay in the pdf lines', function () {
|
|
['release' => $release, 'company' => $company] = creditPublishedRelease();
|
|
$image = PressReleaseImage::factory()->for($release)->legacy()->create([
|
|
'author' => 'Max Mustermann',
|
|
'license_type' => 'own',
|
|
]);
|
|
|
|
$credit = ImageCredit::for($image, $company);
|
|
|
|
expect($credit->line())->toBe('Foto: Max Mustermann')
|
|
->and($credit->pdfLines())->toBe(['Bildnachweis: Max Mustermann']);
|
|
});
|
|
|
|
test('an image without any detail renders no line and no overlay', function () {
|
|
/** @var TestCase $this */
|
|
['release' => $release] = creditPublishedRelease();
|
|
PressReleaseImage::factory()->for($release)->legacy()->create();
|
|
$release->company()->update(['name' => '']);
|
|
|
|
$credit = ImageCredit::for($release->images()->first(), $release->company()->first());
|
|
|
|
expect($credit->line())->toBeNull()
|
|
->and($credit->hasDetails())->toBeFalse();
|
|
});
|
|
|
|
test('the guidelines page describes the automated check and the image rules under version 0.2', function () {
|
|
/** @var TestCase $this */
|
|
$this->seed(LegalDocumentSeeder::class);
|
|
|
|
expect(LegalDocument::current(LegalDocument::TYPE_RICHTLINIEN)?->version)->toBe('0.2');
|
|
|
|
$this->get('https://presseecho.test/de/richtlinien')
|
|
->assertSuccessful()
|
|
->assertSeeText('Jede Mitteilung durchläuft vor der Veröffentlichung eine automatisierte Prüfung.')
|
|
->assertSeeText('Zu jedem Bild gehört ein Bildnachweis')
|
|
->assertDontSeeText('geprüft, den Richtlinien entsprechend');
|
|
});
|