' . "\n"; echo '' . "\n"; foreach ($routes as $routeSlug => $route) { if (empty($route['sitemap'])) { continue; } $lastmod = date('Y-m-d', (int) filemtime(APP_PATH . '/pages/' . $route['file'])); echo " " . e(abs_url($routeSlug)) . "{$lastmod}\n"; } // Stadionzeitung-Ausgaben sind keine routes.php-Einträge (dynamische Route, // siehe unten) — hier separat aus data/stadionzeitung.json ergänzen. foreach (json_load('stadionzeitung')['issues'] ?? [] as $issue) { // Entwürfe tauchen nicht in der Sitemap auf (status-Feld, noindex). if (empty($issue['slug']) || !stadionzeitung_is_published($issue)) { continue; } $loc = e(abs_url('stadionzeitung/' . $issue['slug'])); $lastmod = e((string) ($issue['date'] ?? date('Y-m-d'))); echo " {$loc}{$lastmod}\n"; } // Veranstaltungen: zweite dynamische Route, gleiches Muster. // veranstaltungen_load() liefert bereits nur die veröffentlichten — // Entwürfe tragen noindex und gehören nicht in die Sitemap. // // lastmod kommt hier NICHT aus dem Termin (anders als bei der // Stadionzeitung, deren Ausgabe nach dem Veröffentlichen fixiert ist): eine // Fest-Seite ändert sich gerade NACH ihrem Datum noch, wenn Rückblick und // Bilder dazukommen. filemtime der Datenquelle ist damit die einzige // Angabe, die nie zu alt ist. $veranstaltungenLastmod = date('Y-m-d', (int) filemtime(DATA_PATH . '/veranstaltungen.json')); foreach (veranstaltungen_load() as $veranstaltung) { if (empty($veranstaltung['slug'])) { continue; } $loc = e(abs_url('veranstaltungen/' . $veranstaltung['slug'])); echo " {$loc}{$veranstaltungenLastmod}\n"; } // News-Artikel: dritte dynamische Route. news_load() liefert nur die // veröffentlichten, und ohne artikel[] existiert gar keine Seite. // // lastmod kommt hier aus dem Datum der Meldung (Stadionzeitungs-Muster, // nicht Veranstaltungs-Muster): ein Artikel ist mit dem Erscheinen fertig, // er wächst nicht wie eine Fest-Seite nach ihrem Datum weiter. filemtime // der Datenquelle hätte hier sogar einen Nachteil — jede neue Meldung würde // das lastmod ALLER Artikel zurücksetzen und Crawler auf unveränderte // Seiten schicken. foreach (news_load() as $item) { if (!news_has_article($item)) { continue; } $loc = e(abs_url('news/' . $item['slug'])); $lastmod = e((string) ($item['date'] ?? date('Y-m-d'))); echo " {$loc}{$lastmod}\n"; } echo ''; exit; } // Die drei dynamischen Routen der Seite (Stadionzeitungs-Ausgaben, // Veranstaltungen, News-Artikel). routes.php kennt nur exakte Slugs, deshalb // hier gezielte Prefix-Matches VOR dem normalen Lookup — die Page-Datei // ermittelt den Detail-Slug selbst wieder aus der URL und schlägt ihn in ihrer // JSON nach. Bewusst drei benannte Präfixe, KEINE generelle Wildcard-Fähigkeit. // // 'news/' ist kein neues Muster, sondern das dritte Vorkommen desselben: // Detailseite zu einem Eintrag in einer data/*.json. Die Alternative wäre ein // exakter routes.php-Eintrag pro Meldung — damit stünden redaktionelle Inhalte // im Routen-Register und die Single-Source-Regel wäre gebrochen. // Der exakte Slug 'news' (Übersicht) beginnt nicht mit 'news/' und bleibt // davon unberührt; '/news/' wird oben auf '/news' 301-t. $prefixRoutes = [ 'stadionzeitung/' => 'stadionzeitung-ausgabe.php', 'veranstaltungen/' => 'veranstaltung.php', 'news/' => 'news-artikel.php', ]; foreach ($prefixRoutes as $prefix => $file) { if (!str_starts_with($slug, $prefix)) { continue; } [$meta, $content] = render_page(APP_PATH . '/pages/' . $file); // http_response_code() ohne Argument liest den Code zurück, den die Page-Datei // ggf. gesetzt hat (globaler Engine-State, kein $meta-artiges Scope-Problem) — // so bekommt meta.php für einen unbekannten Slug dieselbe 404-Behandlung wie // die echte 404-Route (noindex, kein Self-Canonical). $current = http_response_code() === 404 ? '404' : $slug; require APP_PATH . '/layout.php'; exit; } if (isset($routes[$slug]) && $slug !== '404') { $current = $slug; } else { http_response_code(404); $current = '404'; } [$meta, $content] = render_page(APP_PATH . '/pages/' . $routes[$current]['file']); require APP_PATH . '/layout.php';