<?php
/*
 * RPTool Webinstaller
 * Diese eine Datei in den Webspace (z. B. httpdocs) hochladen und im Browser öffnen.
 * Sie lädt die aktuelle Version von rptool.de, prüft die Prüfsumme, entpackt sie
 * und führt dich zur Einrichtung. Danach löscht sie sich selbst.
 */
declare(strict_types=1);
const RPTOOL_BASE = 'https://www.rptool.de';
const RPTOOL_DIR = 'gastro-kasse';
session_start();
header('X-Frame-Options: DENY');
header('Referrer-Policy: no-referrer');
$_SESSION['rpi_csrf'] ??= bin2hex(random_bytes(24));
$here = __DIR__;
$target = $here . '/' . RPTOOL_DIR;
$error = '';

function rpi_h(mixed $v): string { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); }
function rpi_get(string $url, ?string $saveTo = null): string {
  if (function_exists('curl_init')) {
    $ch = curl_init($url);
    $fh = $saveTo ? fopen($saveTo, 'wb') : null;
    curl_setopt_array($ch, [CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_TIMEOUT => 180, CURLOPT_CONNECTTIMEOUT => 15, CURLOPT_USERAGENT => 'RPTool-Webinstaller', CURLOPT_PROTOCOLS => CURLPROTO_HTTPS]);
    if ($fh) curl_setopt($ch, CURLOPT_FILE, $fh); else curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $body = curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $err = curl_error($ch);
    curl_close($ch); if ($fh) fclose($fh);
    if ($body === false || $code !== 200) throw new RuntimeException('Download von rptool.de fehlgeschlagen' . ($err ? ': ' . $err : ' (HTTP ' . $code . ')') . '.');
    return $saveTo ? '' : (string)$body;
  }
  if (!ini_get('allow_url_fopen')) throw new RuntimeException('Weder cURL noch allow_url_fopen ist verfügbar.');
  $ctx = stream_context_create(['http' => ['timeout' => 180, 'user_agent' => 'RPTool-Webinstaller', 'follow_location' => 1]]);
  $body = @file_get_contents($url, false, $ctx);
  if ($body === false) throw new RuntimeException('Download von rptool.de fehlgeschlagen.');
  if ($saveTo && file_put_contents($saveTo, $body) === false) throw new RuntimeException('Datei konnte nicht gespeichert werden.');
  return $saveTo ? '' : $body;
}

$checks = [
  ['PHP 8.3 oder neuer', PHP_VERSION_ID >= 80300, true],
  ['ZIP-Erweiterung', class_exists('ZipArchive'), true],
  ['OpenSSL (für Updates)', function_exists('openssl_verify'), true],
  ['Internetzugriff (cURL oder allow_url_fopen)', function_exists('curl_init') || (bool)ini_get('allow_url_fopen'), true],
  ['Ordner beschreibbar', is_writable($here), true],
  ['PDO MariaDB/MySQL', extension_loaded('pdo_mysql'), false],
  ['PDO SQLite', extension_loaded('pdo_sqlite'), false],
  ['HTTPS', (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || in_array($_SERVER['REMOTE_ADDR'] ?? '', ['127.0.0.1', '::1'], true), true],
];
$ready = !in_array(false, array_map(fn($c) => $c[1] || !$c[2], $checks), true) && (extension_loaded('pdo_mysql') || extension_loaded('pdo_sqlite'));
$installed = is_file($target . '/app/release.json');
$step = !$installed ? 1 : 2;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  try {
    if (!hash_equals($_SESSION['rpi_csrf'], (string)($_POST['csrf'] ?? ''))) throw new DomainException('Sitzung abgelaufen – Seite neu laden.');
    $action = $_POST['action'] ?? '';
    if ($action === 'download') {
      if (!$ready) throw new DomainException('Bitte zuerst die rot markierten Punkte beheben.');
      if (file_exists($target)) throw new DomainException('Der Ordner ' . RPTOOL_DIR . ' existiert bereits. Für Updates bitte das Update im Tool verwenden.');
      $feed = json_decode(rpi_get(RPTOOL_BASE . '/releases.php'), true);
      if (!is_array($feed) || empty($feed['full']) || !preg_match('/^[a-f0-9]{64}$/', (string)($feed['full_sha256'] ?? ''))) throw new DomainException('Auf rptool.de ist noch kein Vollpaket mit Prüfsumme veröffentlicht.');
      if (!str_starts_with((string)$feed['full'], RPTOOL_BASE . '/')) throw new DomainException('Unerwartete Download-Adresse.');
      $tmp = $here . '/.rptool-' . bin2hex(random_bytes(6)) . '.zip';
      try {
        rpi_get((string)$feed['full'], $tmp);
        if (!hash_equals($feed['full_sha256'], hash_file('sha256', $tmp))) throw new DomainException('Die Prüfsumme des Downloads stimmt nicht – bitte erneut versuchen.');
        $zip = new ZipArchive();
        if ($zip->open($tmp) !== true) throw new DomainException('Das Paket ist kein gültiges ZIP.');
        if ($zip->locateName(RPTOOL_DIR . '/app/release.json') === false) throw new DomainException('Das Paket enthält kein RPTool.');
        for ($i = 0; $i < $zip->numFiles; $i++) {
          $name = (string)$zip->getNameIndex($i);
          if (!str_starts_with($name, RPTOOL_DIR . '/') || str_contains($name, '..') || str_contains($name, '\\')) throw new DomainException('Das Paket enthält unerwartete Pfade.');
          $ops = 0; $attr = 0; $zip->getExternalAttributesIndex($i, $ops, $attr);
          if ((($attr >> 16) & 0170000) === 0120000) throw new DomainException('Das Paket enthält symbolische Verknüpfungen.');
        }
        if (!$zip->extractTo($here)) throw new RuntimeException('Entpacken fehlgeschlagen – Schreibrechte prüfen.');
        $zip->close();
      } finally { @unlink($tmp); }
      if (!is_dir($target . '/storage')) mkdir($target . '/storage', 0755, true);
      $key = bin2hex(random_bytes(24));
      $keyFile = $target . '/app/install-key.txt';
      if (file_put_contents($keyFile, $key, LOCK_EX) === false) throw new RuntimeException('Installationsschlüssel konnte nicht angelegt werden.');
      @chmod($keyFile, 0600);
      $_SESSION['rpi_key'] = $key;
      $_SESSION['rpi_version'] = ($feed['display_version'] ?? '') ?: $feed['version'];
      header('Location: ' . strtok((string)$_SERVER['REQUEST_URI'], '?') . '?step=2', true, 303); exit;
    }
    if ($action === 'finish') {
      unset($_SESSION['rpi_key']);
      @unlink(__FILE__);
      header('Location: /install.php', true, 303); exit;
    }
    throw new DomainException('Unbekannte Aktion.');
  } catch (Throwable $e) { $error = $e->getMessage(); }
}
$key = $_SESSION['rpi_key'] ?? '';
$docroot = basename($here) . '/' . RPTOOL_DIR . '/public';
$version = $_SESSION['rpi_version'] ?? '';
?><!doctype html>
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>RPTool installieren</title>
<style>
:root{color-scheme:light;--bg:#F4F1EB;--card:#FFF;--soft:#FAF8F4;--line:#E3DDD2;--line-soft:#ECE7DE;--text:#17181B;--muted:#6B665D;--faint:#8F897F;--gold:#C9A55C;--gold-soft:#F5EDDA;--gold-ink:#7A5B1E;--ok:#2F7D4F;--ok-soft:#E6F2EA;--warn:#A4471F;--warn-soft:#FBEAE2;--dark:#121316;--dark-text:#F1EDE6;--dark-muted:#A39E95;--serif:Lora,Georgia,"Times New Roman",serif;--sans:Inter,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
@media (prefers-color-scheme:dark){:root{color-scheme:dark;--bg:#141517;--card:#1C1D20;--soft:#18191C;--line:#2E2F33;--line-soft:#26272B;--text:#F1EDE6;--muted:#A9A398;--faint:#857F75;--gold-soft:#2C2619;--gold-ink:#E2C589;--ok:#7BC496;--ok-soft:#1C2A21;--warn:#F0A07C;--warn-soft:#2E1E18;--dark:#0C0D0E}}
*{box-sizing:border-box}body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);font:15px/1.55 var(--sans)}
.setup{display:grid;grid-template-columns:minmax(260px,360px) minmax(0,1fr);min-height:100vh}
.side{position:sticky;top:0;height:100vh;padding:36px 32px;background:var(--dark);color:var(--dark-text);display:flex;flex-direction:column;gap:32px;background-image:radial-gradient(circle at 100% 0,rgba(201,165,92,.22),transparent 55%)}
.brand{display:flex;align-items:center;gap:12px;font:500 24px/1 var(--serif)}
.pearl{width:26px;height:26px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#fff 0 18%,#EDE6DA 40%,#C9BFAE 100%);box-shadow:0 2px 8px rgba(0,0,0,.35)}
.side h1{margin:0;font:500 30px/1.15 var(--serif)}.side p{margin:10px 0 0;color:var(--dark-muted)}
.steps{list-style:none;margin:0;padding:0;display:grid;gap:4px;counter-reset:s}
.steps li{counter-increment:s;display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:12px;color:var(--dark-muted);font-weight:500}
.steps li::before{content:counter(s);flex:none;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;border:1px solid #34363C;font:500 14px/1 var(--serif)}
.steps .done{color:var(--dark-text)}.steps .done::before{content:"✓";background:#26282D;border-color:#26282D;color:var(--gold)}
.steps .now{background:#1E2024;color:var(--dark-text)}.steps .now::before{background:var(--gold);border-color:var(--gold);color:#17181B}
.side small{margin-top:auto;color:#6F6A62;font-size:12.5px}
main{padding:48px clamp(20px,5vw,72px) 72px;max-width:900px}
.eyebrow{margin:0;font-size:11.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
main>h2{margin:6px 0;font:500 34px/1.15 var(--serif)}.lead{margin:0 0 28px;color:var(--muted);max-width:44em}
.card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:24px;margin-bottom:16px}
.card h3{display:flex;align-items:center;gap:10px;margin:0 0 4px;font:500 20px/1.25 var(--serif)}
.card h3 span{display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--gold-soft);color:var(--gold-ink);font:500 14px/1 var(--serif)}
.card>p,.card li{color:var(--muted);font-size:14px}.card>p{margin:0 0 14px}
.checks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0;padding:0;list-style:none}
.checks li{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;background:var(--soft);border:1px solid var(--line-soft);color:var(--text)}
.checks li::before{content:"✓";flex:none;display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--ok-soft);color:var(--ok);font-weight:700;font-size:12px}
.checks .bad{background:var(--warn-soft);border-color:transparent}.checks .bad::before{content:"!";background:var(--warn);color:#fff}
.checks .opt::before{content:"–";background:var(--line-soft);color:var(--faint)}
ol.how{margin:0;padding-left:20px;display:grid;gap:8px}ol.how b{color:var(--text)}
code{font:500 13px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;background:var(--soft);border:1px solid var(--line-soft);border-radius:6px;padding:1px 6px;word-break:break-all;color:var(--text)}
.keybox{display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:12px;background:var(--gold-soft);border:1px solid color-mix(in srgb,var(--gold) 45%,transparent);font:600 15px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;word-break:break-all}
.keybox span{flex:1}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:48px;padding:0 22px;border-radius:13px;border:1px solid transparent;background:var(--text);color:var(--bg);font:600 15px var(--sans);text-decoration:none;cursor:pointer}
.btn.gold{background:var(--gold);color:#17181B}.btn.small{height:34px;padding:0 12px;font-size:13px;border-radius:9px;background:var(--card);color:var(--text);border-color:var(--line)}
.btn[disabled]{opacity:.5;cursor:not-allowed}
.actions{display:flex;flex-wrap:wrap;align-items:center;gap:12px}.hint{color:var(--muted);font-size:13.5px}
.alert{padding:14px 16px;border-radius:14px;margin:0 0 18px;background:var(--warn-soft);color:var(--warn)}
.busy .btn.gold{pointer-events:none;opacity:.7}
@media (max-width:900px){.setup{grid-template-columns:minmax(0,1fr)}.side{position:static;height:auto;padding:22px 20px;gap:18px}.side h1{font-size:24px}.side small{display:none}.steps{grid-auto-flow:column;grid-auto-columns:minmax(0,1fr)}.steps li{flex-direction:column;gap:6px;padding:6px 2px;font-size:11.5px;text-align:center}main{padding:28px 16px 56px}.checks{grid-template-columns:minmax(0,1fr)}.card{padding:18px}.actions .btn{width:100%}}
</style></head>
<body><div class="setup">
<aside class="side">
  <div class="brand"><span class="pearl" aria-hidden="true"></span>RPTool</div>
  <div><h1>Webinstaller</h1><p>Die Gastro-Kasse für deinen RP-Betrieb – in wenigen Minuten auf deinem Webspace.</p></div>
  <ol class="steps">
    <li class="<?= $step > 1 ? 'done' : 'now' ?>">Prüfen &amp; herunterladen</li>
    <li class="<?= $step === 2 ? 'now' : '' ?>">Dokumentenstamm</li>
    <li>Einrichten</li>
    <li>Loslegen</li>
  </ol>
  <small>Quelle: <?= rpi_h(preg_replace('#^https://#', '', RPTOOL_BASE)) ?> · kostenlos</small>
</aside>
<main>
<?php if ($error): ?><div class="alert" role="alert"><b>Hinweis:</b> <?= rpi_h($error) ?></div><?php endif ?>
<?php if ($step === 1): ?>
  <p class="eyebrow">Schritt 1</p><h2>Server prüfen &amp; RPTool laden</h2>
  <p class="lead">Der Installer lädt die aktuelle Version direkt von rptool.de, prüft die Prüfsumme und entpackt sie in den Ordner <code><?= rpi_h(RPTOOL_DIR) ?></code> neben dieser Datei.</p>
  <section class="card"><h3>Systemprüfung</h3><p>Bei den Datenbanktreibern reicht einer.</p>
    <ul class="checks"><?php foreach ($checks as [$label, $ok, $req]): ?><li class="<?= $ok ? '' : ($req ? 'bad' : 'opt') ?>"><?= rpi_h($label) ?></li><?php endforeach ?></ul>
  </section>
  <form method="post" class="actions" onsubmit="this.classList.add('busy');this.querySelector('.gold').textContent='Wird geladen …'">
    <input type="hidden" name="csrf" value="<?= rpi_h($_SESSION['rpi_csrf']) ?>"><input type="hidden" name="action" value="download">
    <button class="btn gold" <?= $ready ? '' : 'disabled' ?>>RPTool herunterladen &amp; entpacken →</button>
    <span class="hint"><?= $ready ? 'Dauert meist nur ein paar Sekunden.' : 'Rot markierte Punkte zuerst in den PHP-Einstellungen deines Hostings beheben.' ?></span>
  </form>
<?php else: ?>
  <p class="eyebrow">Schritt 2</p><h2>Fast geschafft<?= $version ? ' – ' . rpi_h($version) . ' ist entpackt' : '' ?>.</h2>
  <p class="lead">Damit Datenbank und Konfiguration nicht öffentlich erreichbar sind, zeigt die Domain nur auf den Unterordner <code>public</code>.</p>
  <section class="card"><h3><span>1</span>Dokumentenstamm umstellen</h3>
    <ol class="how">
      <li>In der Verwaltung deines Hostings die Einstellungen der Domain öffnen.</li>
      <li>Beim <b>Dokumentenstamm</b> (Document Root) eintragen: <code><?= rpi_h($docroot) ?></code></li>
      <li><b>Speichern</b> – dann hier weiter.</li>
    </ol>
  </section>
  <section class="card"><h3><span>2</span>Installationsschlüssel</h3>
    <?php if ($key): ?>
      <p>Den brauchst du gleich bei der Einrichtung. Er schützt davor, dass jemand Fremdes deine Installation übernimmt.</p>
      <div class="keybox"><span id="key"><?= rpi_h($key) ?></span><button type="button" class="btn small" onclick="navigator.clipboard?.writeText(document.getElementById('key').textContent).then(()=>this.textContent='Kopiert ✓')">Kopieren</button></div>
    <?php else: ?>
      <p>Den Schlüssel findest du im Dateimanager deines Hostings in <code><?= rpi_h(basename($here) . '/' . RPTOOL_DIR . '/app/install-key.txt') ?></code>.</p>
    <?php endif ?>
  </section>
  <form method="post" class="actions">
    <input type="hidden" name="csrf" value="<?= rpi_h($_SESSION['rpi_csrf']) ?>"><input type="hidden" name="action" value="finish">
    <button class="btn gold">Erledigt – zur Einrichtung →</button>
    <span class="hint">Dieser Installer löscht sich dabei selbst.</span>
  </form>
<?php endif ?>
</main></div></body></html>
