Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cron-Endpoint — die ZWEITE benannte Ausnahme zu Regel 3 (siehe CLAUDE.md).
|
|
|
|
|
*
|
Takt über public/cron.php: KAS-Cronjobs können nur URLs
Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.
Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.
Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.
Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 17:16:12 +02:00
|
|
|
* Warum es ihn gibt (Stand 31.07.2026, geprüft im KAS): der Tarif hat inzwischen
|
|
|
|
|
* SSH und Cronjobs, aber die KAS-Cronjobs können ausschließlich **URLs** aufrufen,
|
|
|
|
|
* keine Shell-Befehle. Ein `php bin/…` als Cronjob ist dort nicht eintragbar. Die
|
|
|
|
|
* Skripte müssen aber getaktet laufen, also ruft der KAS-Cron diese Datei auf und
|
|
|
|
|
* sie startet das Skript serverseitig.
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
*
|
Takt über public/cron.php: KAS-Cronjobs können nur URLs
Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.
Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.
Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.
Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 17:16:12 +02:00
|
|
|
* Das ist besser als der ursprünglich geplante Weg mit einem externen Cron-Dienst:
|
|
|
|
|
* der Aufrufer ist jetzt der Hoster selbst, der Schlüssel verlässt den Webspace nie
|
|
|
|
|
* und es ist überhaupt kein Dritter beteiligt.
|
|
|
|
|
*
|
|
|
|
|
* Warum nicht GitHub Actions mit FTP-Upload: dort müsste ein Dritter Schreibzugang
|
|
|
|
|
* zum ganzen Webspace kennen. Hier kennt der Aufrufer einen Schlüssel, der
|
|
|
|
|
* ausschließlich "starte einen der drei Jobs" kann — deutlich kleinerer Radius.
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
*
|
|
|
|
|
* Aufruf: https://www.tsv08kulmbach.de/cron.php?job=matchcenter&key=<cron.key>
|
|
|
|
|
* Schlüssel alternativ als Header X-Cron-Key (bevorzugt, landet nicht im Log
|
|
|
|
|
* des aufrufenden Dienstes) — die Datei akzeptiert beides.
|
|
|
|
|
*
|
|
|
|
|
* Grenzen, die den Endpoint harmlos halten:
|
|
|
|
|
* - Job-Whitelist, keine freien Skriptnamen aus der URL
|
|
|
|
|
* - Schlüssel aus config.php, Vergleich mit hash_equals; ohne echten Schlüssel ist zu
|
|
|
|
|
* - Mindestabstand zwischen zwei Läufen (cron.min_interval) — ein geleakter Link kann
|
|
|
|
|
* damit nicht dauerfeuern und uns die Server-IP bei BFV/Instagram sperren lassen
|
|
|
|
|
* - flock in den Skripten selbst: parallele Aufrufe überspringen sich
|
|
|
|
|
* - Antwort ist immer leer. 403 nur bei falschem Schlüssel/Job, damit ein falsch
|
|
|
|
|
* eingerichteter Cron-Dienst überhaupt auffällt (sonst syncte wochenlang nichts,
|
|
|
|
|
* ohne dass es jemand merkt). Der Schlüssel ist lang genug, dass das kein Orakel ist.
|
|
|
|
|
*
|
|
|
|
|
* Erreichbar ohne Rewrite-Anpassung: public/.htaccess leitet nur nicht-existierende
|
|
|
|
|
* Pfade auf index.php (RewriteCond %{REQUEST_FILENAME} !-f).
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
require dirname(__DIR__) . '/app/bootstrap.php';
|
|
|
|
|
|
|
|
|
|
header('X-Robots-Tag: noindex, nofollow');
|
|
|
|
|
header('Cache-Control: no-store');
|
|
|
|
|
|
Takt über public/cron.php: KAS-Cronjobs können nur URLs
Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.
Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.
Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.
Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 17:16:12 +02:00
|
|
|
// Whitelist — nie ein Skriptname aus der URL.
|
|
|
|
|
//
|
|
|
|
|
// 'logrotate' kam am 31.07.2026 dazu, und das ist eine bewusste Erweiterung: weil die
|
|
|
|
|
// KAS-Cronjobs nur URLs aufrufen können, ist dieser Endpoint der EINZIGE Weg, überhaupt
|
|
|
|
|
// etwas getaktet laufen zu lassen. Ohne den Eintrag liefe die Logpflege nie automatisch,
|
|
|
|
|
// und die Logs wüchsen unbegrenzt (die 2-MB-Grenze und die 90-Tage-Frist wären toter
|
|
|
|
|
// Code). Der Job passt in denselben Rahmen wie die zwei Syncs: kein Parameter aus der
|
|
|
|
|
// URL, keine Netzverbindung nach außen, er schreibt ausschließlich in storage/logs.
|
|
|
|
|
// Eine VIERTE Ausnahme braucht wieder denselben Aufwand an Begründung.
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
$jobs = [
|
|
|
|
|
'matchcenter' => 'bin/matchcenter-sync.php',
|
|
|
|
|
'instagram' => 'bin/instagram-sync.php',
|
Takt über public/cron.php: KAS-Cronjobs können nur URLs
Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.
Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.
Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.
Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 17:16:12 +02:00
|
|
|
'logrotate' => 'bin/log-rotate.php',
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$job = (string) ($_GET['job'] ?? '');
|
|
|
|
|
$key = (string) config('cron.key', '');
|
|
|
|
|
$given = (string) ($_SERVER['HTTP_X_CRON_KEY'] ?? $_GET['key'] ?? '');
|
|
|
|
|
|
|
|
|
|
$deny = static function (string $reason): never {
|
|
|
|
|
// Kein Detail in der Antwort, der Grund steht im Log. IP nur als gesalzener Hash.
|
|
|
|
|
log_spam('cron', $reason);
|
|
|
|
|
http_response_code(403);
|
|
|
|
|
exit;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Not-Aus wie in den Formular-Actions: ohne echte config.php liefe die Seite auf der
|
|
|
|
|
// Vorlage, deren Werte öffentlich im Repo stehen — der Endpoint wäre dann offen.
|
|
|
|
|
if ($key === '' || $key === 'CHANGE_ME' || strlen($key) < 24) {
|
|
|
|
|
$deny('kein brauchbarer cron.key konfiguriert');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ein gemeinsamer Grund für beide Fälle: verrät nicht, ob Job oder Schlüssel falsch war.
|
|
|
|
|
if (!isset($jobs[$job]) || !hash_equals($key, $given)) {
|
|
|
|
|
$deny('job oder key ungueltig');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Mindestabstand ---
|
|
|
|
|
// Marker wird bei JEDEM angenommenen Versuch gesetzt, nicht erst bei Erfolg: sonst
|
|
|
|
|
// dürfte ein dauerhaft fehlschlagender Sync beliebig oft neu angestoßen werden.
|
|
|
|
|
$minInterval = max(60, (int) config('cron.min_interval', 240));
|
|
|
|
|
$marker = STORAGE_PATH . '/cache/cron-' . $job . '.last';
|
|
|
|
|
clearstatcache(true, $marker);
|
|
|
|
|
if (is_file($marker) && filemtime($marker) > time() - $minInterval) {
|
|
|
|
|
log_write($job, 'WARN', "Cron-Trigger übersprungen — letzter Lauf vor weniger als {$minInterval} s.");
|
|
|
|
|
http_response_code(204);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
touch($marker);
|
|
|
|
|
|
|
|
|
|
// --- Lauf ---
|
|
|
|
|
// Status steht fest, BEVOR das Skript startet: es beendet sich per exit(), unser Code
|
|
|
|
|
// nach dem require läuft also nicht mehr zuverlässig.
|
|
|
|
|
http_response_code(204);
|
|
|
|
|
|
|
|
|
|
// Was die Skripte auf stdout schreiben (Debug-Ausgaben, PHP-Warnungen), darf nie im
|
|
|
|
|
// HTTP-Body landen. Der Shutdown-Handler greift auch bei exit() mitten im Skript.
|
|
|
|
|
ob_start();
|
|
|
|
|
register_shutdown_function(static function (): void {
|
|
|
|
|
while (ob_get_level() > 0) {
|
|
|
|
|
ob_end_clean();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
set_time_limit(180); // Steady State ~2,5 s; Luft nur für den Erstlauf mit allen Bildern
|
|
|
|
|
ignore_user_abort(true); // Timeout beim aufrufenden Dienst darf den Sync nicht zerreißen
|
|
|
|
|
|
|
|
|
|
// Hebt den CLI-Riegel in den Sync-Skripten auf — nur hier, nach der Prüfung oben.
|
|
|
|
|
define('CRON_HTTP', true);
|
|
|
|
|
|
|
|
|
|
// Die Skripte lesen ihre Schalter aus $argv; im Web-SAPI existiert das nicht.
|
|
|
|
|
$argv = [$jobs[$job]];
|
|
|
|
|
$argc = 1;
|
|
|
|
|
|
|
|
|
|
require ROOT_PATH . '/' . $jobs[$job];
|