diff --git a/app/components/stadionzeitung-cards.php b/app/components/stadionzeitung-cards.php
index 215fa17..fb0fcee 100644
--- a/app/components/stadionzeitung-cards.php
+++ b/app/components/stadionzeitung-cards.php
@@ -31,7 +31,7 @@ $issues = $issues ?? [];
-
Stadionzeitung zum Heimspiel gegen = e($issue['opponent']) ?>
+
Stadionzeitung zum Heimspiel gegen = e(stadionzeitung_opponent_display($issue)) ?>
· = (int) $issue['pages'] ?> Seiten
diff --git a/app/components/stadionzeitung-cover.php b/app/components/stadionzeitung-cover.php
index ffd2c14..1318ec8 100644
--- a/app/components/stadionzeitung-cover.php
+++ b/app/components/stadionzeitung-cover.php
@@ -32,7 +32,7 @@ declare(strict_types=1);
* der Gegner läuft über den Initialen-Fallback der crest-Komponente.
*/
$issue = $issue ?? [];
-$opponent = (string) ($issue['opponent'] ?? '');
+$opponent = stadionzeitung_opponent_display($issue);
$date = (string) ($issue['date'] ?? '');
$teamKey = (string) ($issue['team_key'] ?? '');
$playerImage = $issue['player_image'] ?? null;
diff --git a/app/components/stadionzeitung-viewer.php b/app/components/stadionzeitung-viewer.php
index df4a7b1..814621a 100644
--- a/app/components/stadionzeitung-viewer.php
+++ b/app/components/stadionzeitung-viewer.php
@@ -26,7 +26,7 @@ $total = count($images);
if ($images === []) {
return;
}
-$opponent = (string) ($issue['opponent'] ?? '');
+$opponent = stadionzeitung_opponent_display($issue);
// Live-Daten nur laden, wenn die Ausgabe tatsächlich eine Live-Seite enthält.
$hasLive = false;
diff --git a/app/helpers.php b/app/helpers.php
index d404e07..6b0f6ed 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -992,6 +992,22 @@ function stadionzeitung_teaser(array $issue): string
return trim((string) ($issue['interview']['teaser'] ?? ''));
}
+/**
+ * Der Name des Gegners, wie er GEDRUCKT wird — Cover, Archivzeile, Kopf, Meta.
+ *
+ * `opponent` muss die BFV-Schreibweise tragen, weil die Gegnerseite damit ihre
+ * Tabellenzeile und ihre Begegnung in matchcenter.json findet (exakter
+ * Namensvergleich). Bei Spielgemeinschaften ist das eine Zeile wie
+ * „(SG1) TSV Wirsberg I/ FC Kupferberg I", die auf einem Cover nichts zu
+ * suchen hat. `opponent_short` ist deshalb der im Chat gepflegte Anzeigename
+ * („Wirsberg/Kupferberg"); fehlt er, bleibt es beim vollen Namen.
+ */
+function stadionzeitung_opponent_display(array $issue): string
+{
+ $short = trim((string) ($issue['opponent_short'] ?? ''));
+ return $short !== '' ? $short : trim((string) ($issue['opponent'] ?? ''));
+}
+
/**
* Welche Mannschaften bekommen in einer Ausgabe eine Gegnerseite — die EINE
* Logik dafür, benutzt von bin/stadionzeitung-add.php (Seitenplanung) UND
@@ -999,10 +1015,19 @@ function stadionzeitung_teaser(array $issue): string
* oder Daten ohne Seite entstehen.
*
* Immer: die tragende Mannschaft (Gegner = `opponent`, Tag = `date`).
- * Zusätzlich, wenn die Ausgabe eine `veranstaltung` (Slug) mit Zeitraum trägt
- * (Sportfest-Ausgabe): jede weitere Mannschaft aus matchcenter.json mit einem
- * HEIMspiel in diesem Zeitraum — drei Heimspiele am Fest, drei Gegnerseiten.
- * Der Gegner ist dann der andere Verein der Begegnung. Reihenfolge wie in
+ * Zusätzlich auf zwei Wegen, die sich nicht in die Quere kommen:
+ *
+ * 1. `veranstaltung` (Slug) mit Zeitraum — die Sportfest-Ausgabe: jede weitere
+ * Mannschaft aus matchcenter.json mit einem HEIMspiel in diesem Zeitraum.
+ * Am Fest spielen alle zuhause, das ist der Aufhänger.
+ * 2. `gegner_teams` (Liste von Team-Keys, im Chat gepflegt) — für ein normales
+ * Spielwochenende, an dem auch die anderen Mannschaften antreten. Genommen
+ * wird ihr nächstes Spiel im Fenster von zwei Tagen vor bis drei Tage nach
+ * dem Ausgabedatum, AUCH auswärts: der Gegner der Frauen ist auch dann eine
+ * Seite wert, wenn wir zu ihm fahren. Ohne Spiel in diesem Fenster entsteht
+ * keine Seite; bin/stadionzeitung-add.php meldet das.
+ *
+ * Der Gegner ist jeweils der andere Verein der Begegnung. Reihenfolge wie in
* matchcenter.json (= config 'matchcenter.teams').
*
* Rückgabe: [team_key => ['opponent' => string, 'date' => 'YYYY-MM-DD']]
@@ -1013,14 +1038,19 @@ function stadionzeitung_gegner_fixtures(array $issue): array
$issueTeam = (string) ($issue['team_key'] ?? '');
$issueOpponent = trim((string) ($issue['opponent'] ?? ''));
- $von = '';
- $bis = '';
+ $eventVon = '';
+ $eventBis = '';
$veranstaltung = veranstaltung_find((string) ($issue['veranstaltung'] ?? ''));
if ($veranstaltung !== null) {
- $von = (string) ($veranstaltung['date_start'] ?? '');
- $bis = (string) ($veranstaltung['date_end'] ?? $von);
+ $eventVon = (string) ($veranstaltung['date_start'] ?? '');
+ $eventBis = (string) ($veranstaltung['date_end'] ?? $eventVon);
}
+ $extraTeams = array_values(array_filter(array_map(
+ static fn ($k): string => trim((string) $k),
+ is_array($issue['gegner_teams'] ?? null) ? $issue['gegner_teams'] : []
+ )));
+
$fixtures = [];
foreach (json_load('matchcenter')['teams'] ?? [] as $team) {
$key = (string) ($team['key'] ?? '');
@@ -1033,18 +1063,32 @@ function stadionzeitung_gegner_fixtures(array $issue): array
}
continue;
}
+
+ // Weg 2: ausdrücklich benannte Mannschaft, Fenster ums Ausgabedatum,
+ // Heim wie auswärts. Steht sie in `gegner_teams`, gilt dieses Fenster
+ // und nicht der Veranstaltungszeitraum.
+ if (in_array($key, $extraTeams, true) && $date !== '') {
+ $von = date('Y-m-d', strtotime($date . ' -2 days'));
+ $bis = date('Y-m-d', strtotime($date . ' +3 days'));
+ $nurHeim = false;
+ } else {
+ $von = $eventVon;
+ $bis = $eventBis;
+ $nurHeim = true;
+ }
if ($von === '') {
continue;
}
foreach ($team['next_matches'] ?? [] as $match) {
- if (empty($match['is_home'])) {
+ $istHeim = !empty($match['is_home']);
+ if ($nurHeim && !$istHeim) {
continue;
}
$matchDate = substr((string) ($match['kickoff'] ?? ''), 0, 10);
if ($matchDate < $von || $matchDate > $bis) {
continue;
}
- $opponent = trim((string) ($match['away'] ?? ''));
+ $opponent = trim((string) ($istHeim ? ($match['away'] ?? '') : ($match['home'] ?? '')));
if ($opponent === '' || $opponent === 'SPIELFREI') {
continue;
}
@@ -1140,14 +1184,53 @@ function stadionzeitung_build_snapshot(array $issue): array
}
}
+ // `opponent` behält die BFV-Schreibweise (der Namensvergleich oben
+ // hängt daran), gedruckt wird `opponent_display`: bei der tragenden
+ // Mannschaft der Kurzname der Ausgabe, sonst der volle Name.
+ $gegnerDisplay = strcasecmp($gegnerName, trim((string) ($issue['opponent'] ?? ''))) === 0
+ ? stadionzeitung_opponent_display($issue)
+ : $gegnerName;
+
+ // Tabellenausschnitt statt zweier loser Platz-Ziffern im Kopf: ein
+ // Fenster um beide Mannschaften herum (mit einer Zeile Luft davor
+ // und dahinter). Liegen sie weit auseinander, werden es zwei
+ // Blöcke — gerendert wird beides mit der geteilten
+ // league-table-Komponente, nie mit einer zweiten Tabelle.
+ $ausschnitt = [];
+ if ($ownRow !== null && $opponentRow !== null && $table !== []) {
+ $ranks = [(int) ($ownRow['rank'] ?? 0), (int) ($opponentRow['rank'] ?? 0)];
+ sort($ranks);
+ $fenster = static function (int $von, int $bis) use ($table): array {
+ return array_values(array_filter($table, static function (array $row) use ($von, $bis): bool {
+ $rank = (int) ($row['rank'] ?? 0);
+ return $rank >= $von && $rank <= $bis;
+ }));
+ };
+ if ($ranks[1] - $ranks[0] <= 5) {
+ $block = $fenster($ranks[0], $ranks[1]);
+ if ($block !== []) {
+ $ausschnitt[] = $block;
+ }
+ } else {
+ foreach ($ranks as $rank) {
+ $block = $fenster($rank, $rank + 1);
+ if ($block !== []) {
+ $ausschnitt[] = $block;
+ }
+ }
+ }
+ }
+
$gegner[$key] = [
'team_name' => (string) ($team['name'] ?? ''),
'opponent' => $gegnerName,
+ 'opponent_display' => $gegnerDisplay,
'league' => (string) ($team['league'] ?? ''),
'season' => (string) ($team['season'] ?? ''),
'fixture' => $fixture,
'own' => $ownRow,
'opponent_stats' => $opponentRow,
+ 'table_excerpt' => $ausschnitt,
];
}
diff --git a/app/pages/stadionzeitung-ausgabe.php b/app/pages/stadionzeitung-ausgabe.php
index 23837c8..bea6f52 100644
--- a/app/pages/stadionzeitung-ausgabe.php
+++ b/app/pages/stadionzeitung-ausgabe.php
@@ -78,14 +78,14 @@ $firstImage = $issue['images'][0] ?? null;
$cover = ($firstImage !== null && ($firstImage['type'] ?? 'image') === 'image') ? $firstImage : null;
$meta = [
- 'title' => 'Stadionzeitung gegen ' . $issue['opponent'] . ($isDruck ? ' (Druckfassung)' : ''),
- 'description' => 'Stadionzeitung zum Heimspiel gegen ' . $issue['opponent'] . ' am ' . german_date($issue['date']) . ' – digital zum Durchblättern.',
+ '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 ' . $issue['opponent'], 'slug' => 'stadionzeitung/' . $issue['slug']],
+ ['name' => 'Gegen ' . stadionzeitung_opponent_display($issue), 'slug' => 'stadionzeitung/' . $issue['slug']],
]),
];
if ($cover !== null) {
@@ -113,7 +113,7 @@ $druckBlaetter = (int) ceil($druckSeiten / 4);
= icon('chevron-left') ?> = $isDruck ? 'Ausgabe' : 'Archiv' ?>
-
Gegen = e($issue['opponent']) ?>
+
Gegen = e(stadionzeitung_opponent_display($issue)) ?>