Vorschaubild beim Teilen, robots.txt als Bot-Steuerung
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Das Feld "Vorschaubild beim Teilen" gab es seit dem SEO-Paket,
ausgegeben wurde es nie: keine mit KantSites gebaute Seite hatte ein
og:image. Es wird jetzt aufgeloest - erst das gesetzte Vorschaubild,
sonst das Beitragsbild - als absolute Adresse bis 1600 px, dazu
og:site_name, og:locale, og:image:alt und twitter:card (klein ohne
Bild, gross mit). Dasselbe Bild steht im strukturierten Datensatz
eines Beitrags; Archive haben jetzt ebenfalls Open-Graph-Angaben.

Eigene robots.txt-Zeilen: 200 statt 30, und Leerzeilen bleiben als
Trenner erhalten - Regeln je Bot (welche KI-Crawler lesen duerfen)
brauchen Bloecke, nicht einen Klumpen.

Behoben: die Symbolerzeugung suchte feste Breiten statt der Varianten,
die das Bild wirklich hat - ein 192 px grosses Zeichen, also der
haeufigste Fall, scheiterte damit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 14:01:16 +02:00
1 parent f2ef2a4901
commit 35d8270afd
10 files changed
+137 -10

No files matched your search

+52 -1
View File
@@ -877,6 +877,45 @@ final class CmsTest extends TestCase
self::assertStringContainsString('Nächstes Projekt', $rand);
}
public function testThePreviewPictureForSharingActuallyReachesTheHead(): void
{
// Das Feld „Vorschaubild beim Teilen" gab es seit dem SEO-Paket - ausgegeben wurde es
// nie. Eine Einstellung, die speichert und nichts tut, ist schlimmer als keine.
$media = new MediaLibrary($this->db, $this->path.'-media');
$file = tempnam(sys_get_temp_dir(), 'kant-teilen-');
$image = imagecreatetruecolor(1800, 1000);
imagepng($image, $file);
imagedestroy($image);
$item = $media->upload(new UploadedFile($file, 'vorschau.png', test: true));
@unlink($file);
$media->update($item['id'], ['title' => 'Vorschau', 'alt' => 'Ein Blick auf die Website', 'archived' => false, 'version' => 1]);
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::starter('Startseite'), 0), 1);
// Ohne Bild: die kleine Karte, kein Verweis auf ein Bild, das es nicht gibt.
$ohne = $this->request('/')->getContent();
self::assertStringNotContainsString('og:image', $ohne);
self::assertStringContainsString('<meta name="twitter:card" content="summary">', $ohne);
self::assertStringContainsString('<meta property="og:locale" content="de_DE">', $ohne);
$this->db->update('page', ['og_media_id' => $item['id']], ['id' => 'page-1']);
$html = $this->request('/')->getContent();
// Absolut, weil die Adresse in fremden Diensten landet, und höchstens 1600 px breit.
self::assertStringContainsString('<meta property="og:image" content="https://cms.test/media/'.$item['id'].'/1600">', $html);
self::assertStringContainsString('<meta property="og:image:alt" content="Ein Blick auf die Website">', $html);
self::assertStringContainsString('<meta name="twitter:card" content="summary_large_image">', $html);
// Ein Beitrag ohne eigenes Vorschaubild nimmt sein Beitragsbild.
$id = $this->publishedPost('mit-bild', 'Mit Bild', '2026-02-01', ['Projekte'], []);
$this->posts->saveMetadata($id, ['featuredMediaId' => $item['id'], 'publishedAt' => '2026-02-01'], ['Projekte'], [], $this->pages->findForEditor($id)['version']);
$page = $this->pages->findForEditor($id);
$this->pages->publish($id, $page['draft_revision_id'], $page['version']);
$beitrag = $this->request('/mit-bild')->getContent();
self::assertStringContainsString('<meta property="og:image" content="https://cms.test/media/'.$item['id'].'/1600">', $beitrag);
// Und dasselbe Bild steht im strukturierten Datensatz des Beitrags.
self::assertStringContainsString('"image":"https://cms.test/media/'.$item['id'].'/1600"', $beitrag);
}
public function testTheSiteIconIsMadeFromAPictureOfTheLibrary(): void
{
// Umut: „dann brauchen wir Favicon-Möglichkeit zum Festlegen“. Aus einem Bild der
@@ -909,13 +948,25 @@ final class CmsTest extends TestCase
self::assertSame([32, 32], [$size[0], $size[1]], 'Quadratisch, nicht gestaucht.');
self::assertSame(200, $this->request('/apple-touch-icon.png')->getStatusCode());
// Ein winziges quadratisches Zeichen hat keine einzige der vier Regelbreiten - genau so
// sieht ein echtes Favicon aus, und genau daran ist die erste Fassung gescheitert.
$klein = tempnam(sys_get_temp_dir(), 'kant-zeichen-');
$zeichen = imagecreatetruecolor(192, 192);
imagepng($zeichen, $klein);
imagedestroy($zeichen);
$kleines = $media->upload(new UploadedFile($klein, 'zeichen.png', test: true));
@unlink($klein);
self::assertSame([192], array_column($kleines['variants'], 'width'));
$this->site->save(['country' => 'DE', 'homePath' => 'home', 'faviconMediaId' => $kleines['id']]);
self::assertSame(200, $this->request('/favicon.png')->getStatusCode());
// Ein Bild, das es nicht gibt, wird nicht gespeichert - sonst zeigte der Kopf ins Leere.
try {
$this->site->save(['country' => 'DE', 'homePath' => 'home', 'faviconMediaId' => str_repeat('a', 32)]);
self::fail('Eine unbekannte Bildkennung muss abgelehnt werden.');
} catch (InvalidPage) {
}
self::assertSame($item['id'], $this->site->load()['faviconMediaId']);
self::assertSame($kleines['id'], $this->site->load()['faviconMediaId']);
}
public function testUnderlinedLinksAreAChoiceAndOnlyApplyToRunningText(): void