web/seo: Änderungsdatum unabhängig von Aufrufen, EN-URLs in Sitemap und Legacy-API, signierte Bounce-Referenz, Merge/Canonical konsistent (Umsetzungsaudit 29.09.: R-02 bis R-11, N17, N18, OPT-1, OPT-2; 02.10.: O01)

- R-02/K-03: Aufrufaggregation und Merge ändern updated_at nicht mehr; neue
  Spalte press_releases.content_changed_at (INSTANT), contentModifiedAt()
  für Sitemap-lastmod, dateModified und article:modified_time
- R-03: Sitemap und Legacy-view_url mit /en/ für englische Meldungen
- R-04/O01: X-Pk-Ref je Empfänger per HMAC signiert; auf eine Sperre zählt —
  hart wie weich — nur ein gültig signierter Rückläufer, unsignierte
  standardmäßig nur zur Sichtung (BOUNCE_UNSIGNED_KIND=policy)
- R-05/R-06: Unmerge setzt nur unveränderte Füllwerte zurück
  (company_merge_moves.merged_value), Canonical-Neubewertung nach Merge und
  Unmerge, assignAll setzt veraltete Verweise zurück
- R-07 bis R-11: Crawler zählen nicht als Aufruf, Mail-Klick gedrosselt und
  verzögert geschrieben, Claim-Zähler erst nach Versand, email_bounces nach
  395 Tagen gelöscht, Canonical-Portal über Kategorie-ID, llms-Cache geleert,
  Feed-Teaser ohne Doppel-Escaping, Merge-Ketten aufgelöst
- N17/N18: kein falsches „gelistet seit", Rollen im Indexbericht wie Sitemap
- OPT-1/OPT-2: topFeeds in SQL, purge-events löscht nach Grenz-ID

Enthält aus PressRelease.php auch die Card-Bild-Helfer (B-01) und
wasEverPublished()/neverPublished() (A-05), weil die Datei ungeteilt
committet ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-02 13:09:00 +00:00
parent 1ccb4a8479
commit d6e49c11c3
48 changed files with 1676 additions and 134 deletions

View file

@ -234,3 +234,26 @@ test('the release summary reports the sitemap role: the source is listed, a copy
config()->set('canonical.enabled', false);
expect($stats->releaseSummary($copy)['sitemap_role'])->toBe('source');
});
test('the listing date is not claimed when the release has a counterpart on the other portal', function () {
/** @var TestCase $this */
[$customer, $company] = claimant();
config()->set('canonical.enabled', true);
// Fassung auf dem anderen Portal, inzwischen depubliziert: Die Kopie ist
// erst seitdem Erstquelle — wann genau, ist nicht gespeichert (N17-Rest).
$former = PressRelease::factory()->forPortal(Portal::Presseecho)->create(['company_id' => $company->id, 'title' => 'Gleich', 'text' => 'Gleicher Text', 'status' => 'archived', 'published_at' => now()->subDays(20)]);
$release = PressRelease::factory()->published()->forPortal(Portal::Businessportal24)->create(['company_id' => $company->id, 'user_id' => $customer->id, 'title' => 'Gleich', 'text' => 'Gleicher Text', 'published_at' => now()->subDays(10)]);
expect($former->content_hash)->toBe($release->content_hash);
$summary = app(VisibilityStats::class)->releaseSummary($release);
expect($summary['sitemap_role'])->toBe('source')
->and($summary['in_sitemap_since'])->toBeNull()
->and($summary['sitemap_portals'])->toBe([Portal::Businessportal24->label()]);
LivewireVolt::test('customer.press-releases.show', ['id' => $release->id])
->assertSeeText('In der Sitemap gelistet (Businessportal24)')
->assertDontSeeText('Gelistet seit')
->assertDontSeeText('Derzeit nicht gelistet');
});