Files
tsv08kulmbach-website/app/pages/stadionzeitung-ausgabe.php
fs 8f864368f0 Stadionzeitung: Ausgabe 19.09., Gegnerseiten ohne Fest, Gegner-Kurzname
Gegnerseiten lassen sich jetzt ueber `gegner_teams` auch ohne Veranstaltung
fuer weitere Mannschaften anlegen (Fenster zwei Tage vor bis drei Tage nach
dem Ausgabedatum, auch auswaerts), und `opponent_short` liefert den
gedruckten Anzeigenamen, waehrend `opponent` die BFV-Schreibweise fuer den
Namensvergleich behaelt.

Dazu die Ausgabe zum Heimspiel gegen Wirsberg/Kupferberg als Entwurf samt
Seitenbildern. Die gemeinsamen QR-Codes sind neu erzeugt (die vendorte
Bibliothek arbeitet nicht reproduzierbar, das Maskenmuster wechselt).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv9SGDu3m3diiPrW9Fmj3v
2026-09-19 10:53:56 +02:00

141 lines
6.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* Einzelne Stadionzeitung-Ausgabe: dynamische Route stadionzeitung/<slug>,
* siehe public/index.php (Prefix-Match, da app/routes.php nur exakte Slugs
* kennt). Ermittelt den Ausgaben-Slug selbst wieder aus der URL, Daten aus
* data/stadionzeitung.json (maschinenverwaltet von bin/stadionzeitung-add.php).
*
* Bewusst OHNE das übliche hero-Component: der Swipe-Viewer soll so viel
* Bildschirmhöhe wie möglich bekommen (gerade am Handy) — stattdessen ein
* schmaler Kopfbereich direkt über dem Viewer.
*/
$urlSlug = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
$ausgabeSlug = substr($urlSlug, strlen('stadionzeitung/'));
/*
* Reservierter Slug „aktuell": leitet auf die neueste veröffentlichte Ausgabe
* weiter. Damit zeigt ein GEDRUCKTER QR-Code (Plakat, Aushang) dauerhaft auf die
* jeweils aktuelle Ausgabe, ohne dass zu jedem Heimspiel ein neuer Code
* entstehen muss. Kollision ausgeschlossen: Ausgaben-Slugs beginnen immer mit
* dem Datum (2026-08-01-…).
*
* Drei Dinge sind hier load-bearing:
* 1. **302, nie 301.** Ein 301 wird von Browsern und Crawlern dauerhaft
* gemerkt — das Plakat zeigte dann für immer auf die erste Ausgabe, und
* zwar unreparierbar auf allen Geräten, die ihn einmal gesehen haben.
* 2. `no-store`, damit ein Handy, das im letzten Monat gescannt hat, nicht
* das alte Ziel wiederverwendet.
* 3. Ohne veröffentlichte Ausgabe geht es aufs Archiv statt in einen 404 —
* ein gedruckter Code darf nie ins Leere führen.
*/
if ($ausgabeSlug === 'aktuell') {
$latest = stadionzeitung_latest();
header('Cache-Control: no-store');
header(
'Location: ' . ($latest !== null ? url('stadionzeitung/' . $latest['slug']) : url('stadionzeitung')),
true,
302
);
// exit, nicht return: nach einem `return` liefe in public/index.php noch
// layout.php und hängte einen kompletten HTML-Body an die Weiterleitung.
// Der Ausgabepuffer aus render_page() ist hier noch leer, es geht also
// nichts verloren.
exit;
}
$issue = null;
foreach (json_load('stadionzeitung')['issues'] ?? [] as $entry) {
if (($entry['slug'] ?? '') === $ausgabeSlug) {
$issue = $entry;
break;
}
}
if ($issue === null) {
// http_response_code(404) ist globaler Engine-State (kein Scope-Problem wie
// $meta/$current) — public/index.php liest ihn nach render_page() wieder aus,
// um $current auf '404' zu setzen (noindex + kein Self-Canonical in meta.php).
http_response_code(404);
require APP_PATH . '/pages/404.php';
return;
}
// Entwurf vs. veröffentlicht (Interview-Beschluss 29.07.2026): Entwürfe sind
// „unsichtbar, aber offen" — sie fehlen im Archiv und in der Sitemap und
// tragen noindex, die direkte URL funktioniert aber (zum Gegenlesen ohne
// Login-System). Die Druckfassung (?druck=1) montiert dieselben Seiten in
// Broschüren-Reihenfolge fürs gefaltete A5-Heft.
$isDraft = !stadionzeitung_is_published($issue);
$isDruck = isset($_GET['druck']);
// Erste Seite ist seit dem automatischen Cover (Phase 3) eine Live-Seite ohne
// eigenes Bild — og_image gibt es nur, wenn die Ausgabe (noch/wieder) mit
// einem Bild beginnt, sonst greift das globale Default-OG-Bild (meta.php).
$firstImage = $issue['images'][0] ?? null;
$cover = ($firstImage !== null && ($firstImage['type'] ?? 'image') === 'image') ? $firstImage : null;
$meta = [
'title' => 'Stadionzeitung gegen ' . stadionzeitung_opponent_display($issue) . ($isDruck ? ' (Druckfassung)' : ''),
'description' => 'Stadionzeitung zum Heimspiel gegen ' . stadionzeitung_opponent_display($issue) . ' am ' . german_date($issue['date']) . ' – digital zum Durchblättern.',
'scripts' => [$isDruck ? 'stadionzeitung-druck.js' : 'stadionzeitung-viewer.js'],
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Fußball', 'slug' => 'fussball'],
['name' => 'Stadionzeitung', 'slug' => 'stadionzeitung'],
['name' => 'Gegen ' . stadionzeitung_opponent_display($issue), 'slug' => 'stadionzeitung/' . $issue['slug']],
]),
];
if ($cover !== null) {
$meta['og_image'] = $cover['base'] . '-' . max($cover['widths']) . '.jpg';
}
// Entwürfe und die Druckfassung (parametrisierte Zweitansicht derselben
// Inhalte) gehören nicht in den Index.
if ($isDraft || $isDruck) {
$meta['noindex'] = true;
}
?>
<?php
// Kopfleiste: im Viewer Titel + Datum, in der Druckfassung stattdessen die
// Blatt-Bilanz und ein Drucken-Knopf griffbereit oben (der ausführliche
// Hinweis + Button am Seitenende bleibt).
$druckSeiten = count($issue['images'] ?? []);
$druckBlaetter = (int) ceil($druckSeiten / 4);
?>
<?php /* Kopfleiste in drei Zonen und zwei Reihen: oben Rückweg links, Titel mittig,
Aktion rechts — darunter eine ruhige Statuszeile. Die Badges standen
früher neben dem Titel und machten die Mitte unruhig; Zustand („Entwurf",
„Druckfassung") und Bilanz sind Information, keine Überschrift. */ ?>
<header class="stadionzeitung-issue-head<?= $isDruck ? ' stadionzeitung-issue-head--druck' : '' ?>">
<div class="container stadionzeitung-issue-head__inner">
<a class="stadionzeitung-issue-head__back" href="<?= e($isDruck ? url('stadionzeitung/' . $issue['slug']) : url('stadionzeitung')) ?>">
<?= icon('chevron-left') ?> <?= $isDruck ? 'Ausgabe' : 'Archiv' ?>
</a>
<p class="stadionzeitung-issue-head__title">Gegen <?= e(stadionzeitung_opponent_display($issue)) ?></p>
<?php if ($isDruck): ?>
<p class="stadionzeitung-issue-head__actions">
<button class="btn btn--sm stadionzeitung-issue-head__print" type="button" data-druck-print hidden>
<?= icon('printer') ?> Drucken
</button>
</p>
<?php else: ?>
<p class="stadionzeitung-issue-head__date">
<time datetime="<?= e($issue['date']) ?>"><?= e(german_date($issue['date'])) ?></time>
</p>
<?php endif; ?>
<?php if ($isDraft || $isDruck): ?>
<p class="stadionzeitung-issue-head__state">
<?php if ($isDraft): ?>
<span class="stadionzeitung-issue-head__badge stadionzeitung-issue-head__badge--entwurf">Entwurf</span>
<?php endif; ?>
<?php if ($isDruck): ?>
<span>Druckfassung · <?= $druckBlaetter ?> Blätter · <?= $druckSeiten ?> Seiten</span>
<?php endif; ?>
</p>
<?php endif; ?>
</div>
</header>
<?php component($isDruck ? 'stadionzeitung-druck' : 'stadionzeitung-viewer', ['issue' => $issue]); ?>