<?php
declare(strict_types=1);
/**
 * MC-WEB · Front-Controller der dynamischen Schicht.
 * Apache leitet /konto/*, /downloads/*, /dokumente/*, /<admin_pfad>/*, /bezugsquellenanfrage/anfrage hierher (.htaccess);
 * lokal übernimmt router.php dasselbe für den PHP-Testserver.
 * Alles Weitere liegt in mcweb/ (außerhalb des Webroots auf dem Server).
 */

$MCWEB = getenv('MCWEB_DIR') ?: __DIR__ . '/mcweb';
$konfigDatei = getenv('MCWEB_KONFIG') ?: $MCWEB . '/konfig.php';   // lokal: zweite Website mit eigener Konfiguration
// Webspace (mehrere Websites, EINE gemeinsame PHP-Schicht): die Datei mcweb.pfad im Webroot nennt in Zeile 1 den Ordner der
// PHP-Schicht (relativ zum Webroot, z. B. ../mcweb) und in Zeile 2 die Konfigurationsdatei darin (z. B. konfig.garten.php).
// Erzeugt von scripts/paket.mjs; .htaccess liefert die Datei nie aus. Siehe doku/EINRICHTUNG-TEST.md.
if (is_file(__DIR__ . '/mcweb.pfad')) {
    $z = array_values(array_filter(array_map('trim', file(__DIR__ . '/mcweb.pfad')), 'strlen'));
    if (!empty($z[0])) { $MCWEB = realpath(__DIR__ . '/' . $z[0]) ?: __DIR__ . '/' . $z[0]; $konfigDatei = $MCWEB . '/' . (!empty($z[1]) ? basename($z[1]) : 'konfig.php'); }
}
if (!is_file($konfigDatei)) {
    http_response_code(503);
    exit('MC-WEB ist nicht konfiguriert (mcweb/konfig.php fehlt).');
}
date_default_timezone_set('Europe/Berlin');   // Zeitzone des Betreibers, nicht die des Hosters (Fristen, Protokolle, Mails)
$K = require $konfigDatei;
// Webroot = Ordner dieser app.php. Liegt die gebaute Website hier (huelle/index.html), gelten IMMER dieser Ordner und das wartung.flag
// darin – nicht 'dist' der Konfiguration. Seit dem gestaffelten Livegang (01.10.2026) teilen sich r27.<domain> (Test) und www.<domain>
// (live) eine konfig.<site>.php; nur so bedient jeder Webroot seinen eigenen Bau. Lokal (app.php in php/, ohne Hülle) gilt 'dist'.
if (is_file(__DIR__ . '/huelle/index.html')) { $K['dist'] = __DIR__; $K['wartung_datei'] = __DIR__ . '/wartung.flag'; }
// Öffentlicher Pfad der Verwaltung (geheim, aus konfig admin_pfad) – überall statt '/admin/' verwenden.
define('MC_ADMIN', '/' . trim((string) ($K['admin_pfad'] ?? 'mcv-unkonfiguriert'), '/') . '/');

require $MCWEB . '/kern/http.php';
// Stufe des Webroots (daten/stand.json): domain r27/www, debug und bilder_eigene_domain nur im Test, Newsletter-Adresse nach Livegang-Plan (01.10.2026).
// Fehlt kern/stufe.php (PHP-Schicht älter als diese app.php – Befund 01.10.2026: Teilpaket legte Verwaltung, Konto und Anfragen mit 500 still),
// läuft die Website mit den Werten der Konfiguration weiter und das Fehlerprotokoll nennt die Ursache.
if (is_file($MCWEB . '/kern/stufe.php')) { require $MCWEB . '/kern/stufe.php'; $K = stufeAnwenden($K); }
else error_log('[MC-WEB] kern/stufe.php fehlt – PHP-Schicht (mcweb) ist älter als app.php; vollständiges Paket einspielen');
require $MCWEB . '/kern/db.php';
require $MCWEB . '/kern/sitzung.php';
require $MCWEB . '/kern/auth.php';
require $MCWEB . '/kern/post.php';
require $MCWEB . '/kern/mails.php';
require $MCWEB . '/kern/vorlage.php';
require $MCWEB . '/kern/einstellungen.php';   // Einstellungen je Website (Formular-Empfänger, Analytics) – 05.09.2026
require $MCWEB . '/kern/pflege.php';
require $MCWEB . '/kern/spam.php';          // Spamschutz der Formulare (Zeitfeld, Telefon mit 0, Links, Wörter, Domains) – 02.10.2026         // inaktive Fachhändler erinnern/löschen (Cron) – 05.09.2026
require $MCWEB . '/kern/schema.php';         // Datenbankschema selbst angleichen (06.09.2026)
require $MCWEB . '/kern/freigabe.php';       // Freischaltung von Registrierungen: Verwaltung und Mail-Knopf (29.09.2026)

ini_set('display_errors', '0');
error_reporting(E_ALL);
set_exception_handler(function (Throwable $e) {
    error_log('[MC-WEB] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
    http_response_code(500);
    header('Content-Type: text/plain; charset=UTF-8');
    echo "Es ist ein Fehler aufgetreten. Bitte später erneut versuchen.\n";
    // Teststufe: 'debug' => true in der Konfiguration zeigt die Ursache im Browser (nie produktiv setzen)
    if (getenv('MCWEB_DEBUG') || !empty($K['debug'])) echo "\n" . $e;
    exit;
});

try {
    DB::verbinden($K['db']);
} catch (PDOException $e) {
    error_log('[MC-WEB] Datenbank nicht erreichbar: ' . $e->getMessage());
    http_response_code(503);
    exit('Die Datenbank ist derzeit nicht erreichbar.');
}
Schema::pruefen($MCWEB);      // neue schema.sql (nach Aktualisierung/Upload) einspielen – kein SQL von Hand (06.09.2026)
Sitzung::konfigurieren($K);   // Sitzung selbst erst weiter unten, nur wenn nötig
Auth::konfigurieren($K);
Post::konfigurieren($K);
Vorlage::konfigurieren($K);

// ------------------------------------------------------------------ Routing
$pfad = strtok($_SERVER['REQUEST_URI'] ?? '/', '?') ?: '/';
$pfad = preg_replace('#/+#', '/', $pfad);
// Selbstprüfung der Aktualisierung (01.10.2026): GET /app.php?mcweb-pruefung=<kennung> antwortet nach vollständigem Start (Konfiguration,
// Stufe, Datenbank, Schema) mit JSON – sonst nichts. Die Verwaltung ruft das nach jedem Dateitausch für jede Website des Pakets auf und
// stellt bei einem Fehler (5xx, keine Antwort, falsche Website) den vorherigen Stand wieder her (seiten/admin-aktualisierung.php).
// Auf der Teststufe ist diese Adresse vom Verzeichnisschutz ausgenommen (paket.mjs, Require expr) – sie gibt keine Inhalte preis.
if ($pfad === '/app.php' && isset($_GET['mcweb-pruefung'])) {
    header('Content-Type: application/json; charset=UTF-8'); header('Cache-Control: no-store'); header('X-Robots-Tag: noindex');
    exit(json_encode(['ok' => true, 'website' => (string) $K['website'], 'stufe' => (string) ($K['stufe'] ?? ''), 'zeit' => jetzt()]));
}

$teile = array_values(array_filter(explode('/', $pfad), 'strlen'));
$bereich = $teile[0] ?? '';
// Verwaltung nur unter dem geheimen Pfad; /admin/ und falsche mcv-Pfade sind 404
if ($bereich === trim(MC_ADMIN, '/')) $bereich = 'admin';
elseif ($bereich === 'admin') $bereich = '';
$aktion  = $teile[1] ?? '';
$rest    = array_slice($teile, 2);
// Cron über HTTP (Hoster ohne Shell): /cron/<token>/ führt Newsletter-Lauf und Aufräumen aus. Token = HMAC des Schlüssels,
// steht in der Verwaltung nicht – ablesen mit: php -r "echo hash_hmac('sha256','cron','<schluessel>');" (doku/EINRICHTUNG-TEST.md).
if ($bereich === 'cron') {
    header('Content-Type: text/plain; charset=UTF-8'); header('Cache-Control: no-store');
    $soll = hash_hmac('sha256', 'cron', (string) ($K['schluessel'] ?? ''));
    if ($aktion === '' || !hash_equals($soll, $aktion)) { http_response_code(404); exit("Nicht gefunden\n"); }
    require $MCWEB . '/newsletter-versand.php';
    ob_start(); newsletterLauf($K); $ausgabe = ob_get_clean();
    $aufgeraeumt = aufraeumenLauf();   // alle Fristen der Datenschutzerklärung (kern/pflege.php) – bis 01.10.2026 löschte der Cron-Weg nur Token und Anmeldeversuche
    $pflege = pflegeInaktiveFachhaendler($K);   // einmal täglich: Erinnerung nach 6 Monaten, Löschung nach 10 Tagen Frist (Vorgabe 05.09.2026)
    require_once $MCWEB . '/seiten/dokumente.php'; require_once $MCWEB . '/seiten/admin-aktualisierung.php'; require_once $MCWEB . '/seiten/admin-einstellungen.php'; require_once $MCWEB . '/seiten/admin-pruefung.php';
    $bestand = bestandTaeglich($K);   // Bestandsprüfung (08.09.2026): fehlen Dateien, Mail an die Verwaltung (einmal je Tag)
    echo "MC-WEB Cron " . date('c') . "\n" . $ausgabe . "$aufgeraeumt\n$pflege\n$bestand\n";
    exit;
}
// Portal mediacraft.AI (24.09.2026): /portal/preislisten/ nimmt PDF/XLSX/CSV vom Knopf VERÖFFENTLICHEN entgegen –
// ohne Sitzung, mit gemeinsamem Schlüssel im Kopf X-Portal-Schluessel (seiten/portal-preislisten.php).
if ($bereich === 'portal') {
    require $MCWEB . '/seiten/portal-preislisten.php';
    portalPreislisten($aktion, $K);
}
// Sitzung (Cookie „mcweb“) NUR, wenn sie gebraucht wird: vorhandenes Cookie, Verwaltung, Konto (außer der
// Status-Abfrage der Produktseiten) oder ein POST im Sitzungsbereich. Anonyme Aufrufe von /downloads/, /konto/status/
// und die Anfrageformulare der statischen Seiten setzen damit kein Cookie.
$formularOhneSitzung = $bereich === 'bezugsquellenanfrage' || ($bereich === 'daten' && $aktion === 'zaehlung');   // Zählung: ohne Sitzung, ohne Token (25.09.2026)
if (isset($_COOKIE['mcweb']) || $bereich === 'admin' || ($bereich === 'konto' && $aktion !== 'status') || (istPost() && !$formularOhneSitzung)) Sitzung::starten($K);
// CSRF-Token für alle POSTs – außer den Anfrageformularen, die aus den statischen Seiten kommen
// (dort gibt es keine Sitzung; es wird über Honigtopf und IP-Drossel geschützt).
// Anmeldung prüft ihr Token selbst (ohne Token → zurück zur Anmeldeseite statt 403; das Formular in der Aufklappfläche holt
// sein Token per Skript nach – ohne Skript landet man auf /konto/ mit vollständigem Formular)
if (istPost() && !$formularOhneSitzung && !($bereich === 'konto' && $aktion === 'anmelden')) Sitzung::csrfPruefen();

// Wartungsmodus (wartung.flag im Webroot): Apache/router schicken dann JEDE Anfrage hierher. Admin und Watcher
// sehen die Website normal (statische Dateien werden hier ausgeliefert), alle anderen die Wartungsseite mit 503.
// Anmelden/Abmelden und die Verwaltung bleiben frei, damit man sich überhaupt anmelden kann.
if (is_file($K['wartung_datei'])) {
    $frei = $bereich === 'admin' || ($bereich === 'konto' && in_array($aktion, ['anmelden', 'abmelden', 'status'], true));
    if (!$frei && !Auth::darfWartung()) {
        http_response_code(503);
        header('Retry-After: 3600');
        header('Content-Type: text/html; charset=UTF-8');
        header('Cache-Control: no-store');
        $w = rtrim($K['dist'], '/\\') . '/wartung/index.html';
        echo is_file($w) ? file_get_contents($w) : 'Wartungsarbeiten – bitte später erneut versuchen.';
        exit;
    }
    if (!in_array($bereich, ['konto', 'downloads', 'dokumente', 'admin', 'bezugsquellenanfrage'], true)) statischAusliefern($K['dist'], $pfad);
}

// Fachhändler-Netzwerk noch nicht freigeschaltet: kein Fachhändlerbereich. Anmelden/Abmelden/Status bleiben,
// damit die Verwaltung über /admin/ erreichbar ist (eigene Anmeldemaske dort).
if (empty($K['netzwerk_aktiv']) && $bereich === 'konto' && !in_array($aktion, ['anmelden', 'abmelden', 'status', 'token', 'freischalten'], true)) {
    $n = Auth::nutzer();
    // Admin landet NICHT mehr in der Verwaltung, sondern sieht den Fachhändlerbereich wie ein Fachhändler (Prüfen, Vorgabe 04.09.2026)
    if (!($n && $n['rolle'] === 'admin')) {   // Admin darf den Bereich zum Prüfen nutzen (Merkliste, Profil …)
    http_response_code(404);
    Vorlage::seite('Seite nicht gefunden', '<div class="wrap"><section class="abschnitt prosa"><h1>Seite nicht gefunden</h1><p><a href="/">Zur Startseite</a></p></section></div>');
    }
}

// KI-Dienste zählen (29.09.2026): .htaccess und php/router.php leiten Seitenaufrufe (Pfad endet mit /) bekannter KI-Kennungen hierher.
// Der Aufruf wird getrennt gezählt (herkunft ki, Name des Dienstes – seiten/zaehlung.php ZAEHLUNG_KI) und die statische Seite unverändert
// ausgeliefert; ohne Sitzung, ohne Cookie. Andere Pfade außerhalb der dynamischen Bereiche landen so ebenfalls in der statischen Auslieferung.
if (!istPost() && !in_array($bereich, ['konto', 'downloads', 'dokumente', 'daten', 'admin', 'bezugsquellenanfrage', 'cron', 'portal'], true)) {
    require $MCWEB . '/seiten/zaehlung.php';
    $kiDienst = zaehlungKiDienst((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''));
    if ($kiDienst) zaehlungKi($K, $pfad, $kiDienst);
    statischAusliefern($K['dist'], $pfad);
}

switch ($bereich) {
    case 'konto':
        require $MCWEB . '/seiten/konto.php';
        konto($aktion, $rest, $K);
        break;
    case 'downloads':
        require $MCWEB . '/seiten/downloads.php';
        downloads($aktion, $rest, $K);
        break;
    case 'dokumente':   // Herstellerdokumente aus dem zentralen Ordner mcweb/dokumente (öffentlich, alle Websites)
        require $MCWEB . '/seiten/dokumente.php';
        dokumente($aktion, $rest);
        break;
    case 'daten':       // /daten/suche.json: Suchindex ohne die in der Verwaltung gelöschten Dokumente (07.09.2026) · /daten/zaehlung: Aufrufzählung (25.09.2026)
        if ($aktion === 'zaehlung' && !$rest) { require $MCWEB . '/seiten/zaehlung.php'; zaehlungErfassen($K); }
        if ($aktion !== 'suche.json' || $rest) { http_response_code(404); exit('Nicht gefunden'); }
        require $MCWEB . '/seiten/dokumente.php';
        dokumenteSucheAusliefern($K);
    case 'admin':
        require $MCWEB . '/seiten/admin.php';
        admin($aktion, $rest, $K);
        break;
    case 'bezugsquellenanfrage':
        require $MCWEB . '/seiten/beratung.php';
        beratung($aktion, $K);
        break;
    default:
        http_response_code(404);
        Vorlage::seite('Seite nicht gefunden', '<div class="wrap"><section class="abschnitt prosa"><h1>Seite nicht gefunden</h1><p><a href="/">Zur Startseite</a></p></section></div>');
}
