Frei wählbare Startseite; Standarddateien waren lokal unerreichbar

Eine Quelle entscheidet jetzt, welche Seite unter / antwortet:
SiteSettings::homePath() und publicPath(). Das Literal home kommt im
Anwendungscode nicht mehr vor - öffentliche Routen, Canonical,
Sitemap, llms.txt, Menüauflösung, Beitragslisten und der
Formularrücksprung benutzen dieselben zwei Methoden. Die gewählte
Seite muss veröffentlicht und nicht im Papierkorb sein, ihre eigene
Adresse leitet dauerhaft auf /, und als Wurzel ist sie gegen
Umbenennen und Papierkorb gesperrt.

Zweiter echter Fehler, den nur ein HTTP-Aufruf zeigt:
/sitemap.xml, /robots.txt, /llms.txt und /feed.xml waren lokal nie
erreichbar. Der eingebaute PHP-Server antwortet für fehlende Pfade
mit Dateiendung selbst mit 404, ohne den Frontcontroller zu fragen;
alle Kernel-Tests bestanden trotzdem, weil sie die Anwendung direkt
aufrufen. bin/dev-router.php bildet die Rewrite-Regel des Hostings
nach, Start- und CI-Befehl nutzen es, und die Browserprüfung ruft die
vier Adressen jetzt über HTTP auf.

Barrierefreiheitsbefunde werden nach Ursache getrennt: ein
Kontrastfehler aus Theme oder Stylesheet bleibt ein Defekt und bricht
den Lauf ab, einer aus einer im Editor gesetzten Blockfarbe wird bei
jedem Lauf mit Seite, Farbe und Verhältnis genannt und in
reports/browser.json geschrieben, färbt aber keinen Codewechsel rot.

Geprüft: 162 PHP-Tests / 636 Assertions unter PHP 8.2.12 und 8.4.22,
15 JavaScript-Modelltests, vollständiger Editortest mit axe, wieder
grüne öffentliche Browserprüfung und Lighthouse mit je drei Läufen
mobil und Desktop, alle fünf Kategorien 100.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 14:59:55 +02:00
1 parent 6afad30956
commit 33e4ac0c54
21 files changed
+266 -49

No files matched your search

+58 -3
View File
@@ -67,9 +67,9 @@ final class CmsTest extends TestCase
foreach (['page-1' => 'home', 'page-2' => 'private'] as $id => $slug) {
$this->db->insert('page', ['id' => $id, 'slug' => $slug, 'path' => $slug, 'type' => 'page', 'locale' => 'de', 'website_id' => 'default', 'version' => 0, 'created_at' => gmdate('Y-m-d H:i:s')]);
}
$this->pages = new PageRepository($this->db, new DocumentValidator());
$this->menus = new MenuRepository($this->db);
$this->posts = new PostRepository($this->db, new MediaLibrary($this->db, $this->path.'-media'));
$this->pages = new PageRepository($this->db, new DocumentValidator(), new SiteSettings($this->db));
$this->menus = new MenuRepository($this->db, new SiteSettings($this->db));
$this->posts = new PostRepository($this->db, new MediaLibrary($this->db, $this->path.'-media'), new SiteSettings($this->db));
$this->site = new SiteSettings($this->db);
$this->redirects = new RedirectRepository($this->db);
$this->themes = new Theme($this->db);
@@ -1364,6 +1364,61 @@ final class CmsTest extends TestCase
return $id;
}
public function testAnyPublishedPageCanBecomeTheRootAndEverythingFollows(): void
{
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::pages()['home'], 0), 1);
$start = $this->publishedPage('willkommen', 'Willkommen');
self::assertNotNull($this->pages->findForEditor($start));
$this->site->save(['country' => 'DE', 'homePath' => 'willkommen', 'legalName' => 'Kant-IT', 'street' => 'Ooser Hauptstr. 51', 'city' => 'Baden-Baden']);
// The chosen page answers at the root, and its own address redirects there.
$root = $this->request('/');
self::assertSame(200, $root->getStatusCode());
self::assertStringContainsString('>Willkommen</h1>', $root->getContent());
self::assertStringContainsString('href="https://cms.test/"', $root->getContent());
$own = $this->request('/willkommen');
self::assertSame(301, $own->getStatusCode());
self::assertSame('/', $own->headers->get('Location'));
// The previous root became an ordinary page and keeps its own address.
self::assertSame(200, $this->request('/home')->getStatusCode());
// Sitemap, structured data and the menu follow the same decision.
self::assertStringContainsString('<loc>https://cms.test/</loc>', $this->request('/sitemap.xml')->getContent());
self::assertStringContainsString('<loc>https://cms.test/home</loc>', $this->request('/sitemap.xml')->getContent());
self::assertStringContainsString('"@type":"Organization"', $root->getContent(), 'Organisation data belongs on the root page.');
self::assertStringNotContainsString('schema.org', $this->request('/home')->getContent());
$this->menus->save('header', 'Hauptnavigation', [
['label' => 'Start', 'type' => 'page', 'pageId' => $start, 'anchor' => '', 'children' => []],
], 0);
self::assertStringContainsString('<a href="/" aria-current="page">Start</a>', $this->request('/')->getContent());
// The root is protected from being renamed or binned while it is the root.
foreach ([fn () => $this->pages->movePage($start, 'anders', null, $this->pages->findForEditor($start)['version'], $this->redirects),
fn () => $this->pages->trashPage($start, $this->pages->findForEditor($start)['version'])] as $attempt) {
try {
$attempt();
self::fail('Expected the root page to be protected.');
} catch (InvalidPage) {
}
}
// But the page that used to be the root can now be moved.
self::assertSame(1, $this->pages->movePage('page-1', 'alte-startseite', null, $this->pages->findForEditor('page-1')['version'], $this->redirects));
}
public function testRootMustBeAnExistingPublishedPage(): void
{
$before = $this->site->load()['homePath'];
foreach (['gibt-es-nicht', '', 'Gross'] as $candidate) {
try {
$this->site->save(['country' => 'DE', 'homePath' => $candidate]);
self::fail(sprintf('Expected "%s" to be refused as the root.', $candidate));
} catch (InvalidPage) {
}
}
self::assertSame($before, $this->site->load()['homePath']);
}
private function request(string $url, string $method = 'GET'): Response
{
$previous = [];