wip: Mobile-Theme-Testphase und PHP-8.2-Guards (Stand Juni 2026)
This commit is contained in:
+152
-52
@@ -30,38 +30,104 @@ include_once './core/revocation.class.php';
|
||||
$revocation = new Revocation($base_object);
|
||||
|
||||
$view = 'form';
|
||||
$prefill = ['name' => '', 'order_number' => '', 'email' => ''];
|
||||
$prefill = ['name' => '', 'email' => '', 'order_number' => '', 'article' => ''];
|
||||
$error = null;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Tor-Exit-Liste — wird per Cron alle 4h aktualisiert.
|
||||
// Datei in /var/cache/tor/exit-list.txt, eine IP pro Zeile.
|
||||
// ------------------------------------------------------------
|
||||
function widerruf_is_tor_exit($ip) {
|
||||
static $set = null;
|
||||
if ($set === null) {
|
||||
$f = '/var/cache/tor/exit-list.txt';
|
||||
if (file_exists($f) && (time() - filemtime($f)) < 172800) {
|
||||
$arr = @file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$set = $arr ? array_flip(array_map('trim', $arr)) : [];
|
||||
} else {
|
||||
$set = []; // fail-open wenn Liste fehlt/zu alt
|
||||
}
|
||||
}
|
||||
return $ip !== '' && isset($set[$ip]);
|
||||
}
|
||||
|
||||
$client_ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$is_tor = widerruf_is_tor_exit($client_ip);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Request dispatch
|
||||
// ------------------------------------------------------------
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$form_name = isset($_POST['name']) ? trim((string)$_POST['name']) : '';
|
||||
$form_order_number = isset($_POST['order_number']) ? trim((string)$_POST['order_number']) : '';
|
||||
$form_email = isset($_POST['email']) ? trim((string)$_POST['email']) : '';
|
||||
$reason_id = isset($_POST['reason_id']) ? (int)$_POST['reason_id'] : 0;
|
||||
$reason_text = isset($_POST['reason_text']) ? (string)$_POST['reason_text'] : '';
|
||||
$confirm = isset($_POST['confirm']) && $_POST['confirm'] === '1';
|
||||
$ip_now = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$ua_now = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
|
||||
// Eingaben für Fehlerfall ins Formular zurückspielen
|
||||
$prefill = [
|
||||
'name' => $form_name,
|
||||
'order_number' => $form_order_number,
|
||||
'email' => $form_email,
|
||||
];
|
||||
// ------------------------------------------------------------
|
||||
// Bot-Schutz: tor-exit + honeypot + time-trap + session-token + rate-limit.
|
||||
// Bei Bot-Verdacht still "success" zurückgeben (Bot soll nichts merken).
|
||||
// ------------------------------------------------------------
|
||||
$is_bot = false;
|
||||
$bot_reason = '';
|
||||
|
||||
if (!$confirm) {
|
||||
$view = 'form';
|
||||
$error = 'no_confirm';
|
||||
if ($is_tor) {
|
||||
$is_bot = true; $bot_reason = 'tor-exit';
|
||||
}
|
||||
|
||||
if (!$is_bot && (!empty($_POST['website']) || !empty($_POST['fax']))) {
|
||||
$is_bot = true; $bot_reason = 'honeypot';
|
||||
}
|
||||
|
||||
if (!$is_bot) {
|
||||
$ts = isset($_POST['ts']) ? (int)$_POST['ts'] : 0;
|
||||
$delta = time() - $ts;
|
||||
if ($ts <= 0 || $delta < 4 || $delta > 7200) {
|
||||
$is_bot = true; $bot_reason = 'timetrap delta='.$delta;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$is_bot) {
|
||||
if (empty($_POST['form_token']) || empty($_SESSION['widerruf_token'])
|
||||
|| !hash_equals((string)$_SESSION['widerruf_token'], (string)$_POST['form_token'])) {
|
||||
$is_bot = true; $bot_reason = 'token-mismatch';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$is_bot) {
|
||||
$rl_hash = sha1($ip_now.'|widerruf');
|
||||
$rl_file = sys_get_temp_dir().'/widerruf_rl_'.$rl_hash;
|
||||
$last = file_exists($rl_file) ? (int)@file_get_contents($rl_file) : 0;
|
||||
if (time() - $last < 60) {
|
||||
$is_bot = true; $bot_reason = 'ratelimit last='.$last;
|
||||
} else {
|
||||
@file_put_contents($rl_file, (string)time());
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_bot) {
|
||||
error_log('[widerruf] BOT BLOCKED reason='.$bot_reason.' ip='.$ip_now.' ua='.substr($ua_now, 0, 120));
|
||||
// Token einmalig verbrauchen, damit derselbe nicht reusable ist
|
||||
unset($_SESSION['widerruf_token']);
|
||||
$view = 'success';
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
// Token verbrauchen
|
||||
unset($_SESSION['widerruf_token']);
|
||||
|
||||
$form_name = isset($_POST['name']) ? trim((string)$_POST['name']) : '';
|
||||
$form_email = isset($_POST['email']) ? trim((string)$_POST['email']) : '';
|
||||
$form_order_number = isset($_POST['order_number']) ? trim((string)$_POST['order_number']) : '';
|
||||
$form_article = isset($_POST['article']) ? trim((string)$_POST['article']) : '';
|
||||
|
||||
$prefill = [
|
||||
'name' => $form_name,
|
||||
'email' => $form_email,
|
||||
'order_number' => $form_order_number,
|
||||
'article' => $form_article,
|
||||
];
|
||||
|
||||
if ($revocation->submit_anonymous(
|
||||
$form_name, $form_order_number, $form_email,
|
||||
$reason_id, $reason_text, $ip, $ua
|
||||
$form_article, $ip_now, $ua_now
|
||||
)) {
|
||||
$view = 'success';
|
||||
} else {
|
||||
@@ -72,33 +138,40 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
} else {
|
||||
|
||||
// GET — bei Token-Link: Felder vorausfüllen
|
||||
$token = isset($_GET['t']) ? (string)$_GET['t'] : '';
|
||||
if ($token !== '') {
|
||||
$pf = $revocation->prefill_from_token($token);
|
||||
if ($pf) {
|
||||
$prefill = $pf;
|
||||
} elseif ($revocation->get_error() === 'feature_disabled') {
|
||||
$view = 'error';
|
||||
$error = 'feature_disabled';
|
||||
// GET — Tor-Exit-Nutzer:innen sehen statt Form einen Hinweis auf alternative Wege
|
||||
if ($is_tor) {
|
||||
error_log('[widerruf] TOR EXIT GET ip='.$client_ip);
|
||||
$view = 'tor_blocked';
|
||||
} else {
|
||||
// Bei Token-Link: Felder vorausfüllen
|
||||
$token = isset($_GET['t']) ? (string)$_GET['t'] : '';
|
||||
if ($token !== '') {
|
||||
$pf = $revocation->prefill_from_token($token);
|
||||
if ($pf) {
|
||||
$prefill = $pf;
|
||||
} elseif ($revocation->get_error() === 'feature_disabled') {
|
||||
$view = 'error';
|
||||
$error = 'feature_disabled';
|
||||
}
|
||||
// Bei anderen Token-Fehlern: leeres Formular anbieten ohne Hinweistext
|
||||
}
|
||||
// Bei anderen Token-Fehlern: leeres Formular anbieten ohne Hinweistext
|
||||
}
|
||||
}
|
||||
|
||||
// Neuen Form-Token + Timestamp für Render erzeugen
|
||||
if ($view === 'form') {
|
||||
$_SESSION['widerruf_token'] = bin2hex(random_bytes(16));
|
||||
$form_token_render = $_SESSION['widerruf_token'];
|
||||
$form_ts_render = time();
|
||||
}
|
||||
|
||||
$reasons = $revocation->fetch_reasons();
|
||||
|
||||
$messages = [
|
||||
'no_confirm' => 'Bitte bestätigen Sie den Widerruf durch Setzen des Hakens.',
|
||||
'missing_order' => 'Bitte geben Sie Ihre Bestellnummer an.',
|
||||
'missing_name' => 'Bitte geben Sie Ihren Namen an.',
|
||||
'invalid_email' => 'Bitte geben Sie eine gültige E-Mail-Adresse an.',
|
||||
'order_not_found' => 'Wir konnten keine Bestellung mit dieser Bestellnummer und E-Mail-Adresse finden. '
|
||||
. 'Bitte prüfen Sie Ihre Eingaben.',
|
||||
'expired' => 'Die 14-tägige Widerrufsfrist ist überschritten. '
|
||||
. 'Wenden Sie sich bitte direkt an uns.',
|
||||
'already_submitted' => 'Für diese Bestellung wurde bereits ein Widerruf erklärt.',
|
||||
'feature_disabled' => 'Die elektronische Widerrufsfunktion ist aktuell noch nicht aktiv.',
|
||||
'not_eligible' => 'Für diese Bestellung steht der elektronische Widerruf nicht zur Verfügung.',
|
||||
];
|
||||
$display_error = isset($messages[$error]) ? $messages[$error] : null;
|
||||
|
||||
@@ -140,39 +213,66 @@ $display_error = isset($messages[$error]) ? $messages[$error] : null;
|
||||
|
||||
<h1>Vertrag widerrufen</h1>
|
||||
<p>Gemäß § 355 BGB haben Sie das Recht, Ihren Vertrag binnen 14 Tagen
|
||||
ohne Angabe von Gründen zu widerrufen. Füllen Sie bitte die folgenden
|
||||
drei Pflichtfelder aus.</p>
|
||||
ohne Angabe von Gründen zu widerrufen. Pflichtfelder sind <strong>Name</strong>
|
||||
und <strong>E-Mail-Adresse</strong>. Bestellnummer und Artikel sind
|
||||
freiwillige Angaben, die uns die Zuordnung erleichtern.</p>
|
||||
|
||||
<?php if ($display_error): ?><div class="err"><?= htmlspecialchars($display_error) ?></div><?php endif; ?>
|
||||
|
||||
<form method="post" action="<?= htmlspecialchars(strtok($_SERVER['REQUEST_URI'], '?')) ?>">
|
||||
<form method="post" action="<?= htmlspecialchars(strtok($_SERVER['REQUEST_URI'], '?')) ?>" autocomplete="on">
|
||||
|
||||
<input type="hidden" name="ts" value="<?= (int)$form_ts_render ?>">
|
||||
<input type="hidden" name="form_token" value="<?= htmlspecialchars($form_token_render) ?>">
|
||||
|
||||
<div aria-hidden="true" style="position:absolute;left:-9999px;top:-9999px;height:0;width:0;overflow:hidden;" tabindex="-1">
|
||||
<label for="website_hp">Website (bitte leer lassen)</label>
|
||||
<input type="text" id="website_hp" name="website" autocomplete="off" tabindex="-1" value="">
|
||||
<label for="fax_hp">Fax</label>
|
||||
<input type="text" id="fax_hp" name="fax" autocomplete="off" tabindex="-1" value="">
|
||||
</div>
|
||||
|
||||
<label for="name">Vor- und Nachname</label>
|
||||
<input type="text" id="name" name="name" required maxlength="120"
|
||||
value="<?= htmlspecialchars($prefill['name']) ?>">
|
||||
|
||||
<label for="order_number">Bestellnummer</label>
|
||||
<input type="text" id="order_number" name="order_number" required maxlength="32"
|
||||
value="<?= htmlspecialchars($prefill['order_number']) ?>"
|
||||
placeholder="z.B. 102345">
|
||||
|
||||
<label for="email">E-Mail-Adresse</label>
|
||||
<input type="email" id="email" name="email" required maxlength="120"
|
||||
value="<?= htmlspecialchars($prefill['email']) ?>"
|
||||
placeholder="Die E-Mail, mit der Sie bestellt haben">
|
||||
|
||||
<label class="check">
|
||||
<input type="checkbox" name="confirm" value="1" required>
|
||||
Ich erkläre hiermit den Widerruf meiner oben genannten Bestellung.
|
||||
</label>
|
||||
<label for="order_number">Bestellnummer <span class="muted" style="font-weight:normal;">(optional – hilft uns bei der Zuordnung)</span></label>
|
||||
<input type="text" id="order_number" name="order_number" maxlength="64"
|
||||
value="<?= htmlspecialchars($prefill['order_number']) ?>"
|
||||
placeholder="z.B. 102345, AB-2026-001 – falls zur Hand">
|
||||
|
||||
<button type="submit" class="btn">Vertrag widerrufen</button>
|
||||
<label for="article">Artikel / Bemerkung <span class="muted" style="font-weight:normal;">(optional – Artikelbezeichnung, Bestelldatum o.ä.)</span></label>
|
||||
<input type="text" id="article" name="article" maxlength="200"
|
||||
value="<?= htmlspecialchars($prefill['article']) ?>"
|
||||
placeholder="z.B. Trafo, Schalter, Bestellung vom 14.06.2026">
|
||||
|
||||
<p class="muted" style="margin-top:20px;">Nach dem Absenden erhalten Sie eine
|
||||
Bestätigungs-E-Mail mit der Rücksendeadresse und den weiteren Informationen
|
||||
zur Rücksendung.</p>
|
||||
<button type="submit" class="btn" style="margin-top:24px;">Vertrag widerrufen</button>
|
||||
|
||||
<p class="muted" style="margin-top:20px;">Mit Klick auf den Button erklären Sie
|
||||
den Widerruf Ihres Vertrags. Sie erhalten anschließend eine Eingangs­bestätigung
|
||||
per E-Mail.</p>
|
||||
</form>
|
||||
|
||||
<?php elseif ($view === 'tor_blocked'): ?>
|
||||
|
||||
<h1>Widerruf über anonyme Verbindung</h1>
|
||||
<p>Sie scheinen über eine anonyme Verbindung (z. B. Tor) auf diese Seite zuzugreifen.
|
||||
Aus Spam-Schutz-Gründen ist das Online-Formular über anonyme Netzwerke deaktiviert.</p>
|
||||
<p>Ihr Widerrufsrecht bleibt selbstverständlich unverändert.
|
||||
Sie können Ihren Widerruf jederzeit auf einem der folgenden Wege erklären:</p>
|
||||
<ul style="line-height:1.7;">
|
||||
<li>per E-Mail an <a href="mailto:<?= htmlspecialchars(REVOCATION_OWNER_EMAIL) ?>"><?= htmlspecialchars(REVOCATION_OWNER_EMAIL) ?></a></li>
|
||||
<li>per Brief an die im Impressum genannte Anschrift</li>
|
||||
<li>mit unserem <a href="/Widerrufsformular-Muster/">Muster-Widerrufsformular</a> (Download / Ausdruck)</li>
|
||||
</ul>
|
||||
<p class="muted"><?= htmlspecialchars(REVOCATION_RETURN_COMPANY) ?> ·
|
||||
<?= htmlspecialchars(REVOCATION_RETURN_STREET) ?> ·
|
||||
<?= htmlspecialchars(REVOCATION_RETURN_CITY) ?></p>
|
||||
|
||||
<?php elseif ($view === 'success'): ?>
|
||||
|
||||
<h1>Widerruf entgegengenommen</h1>
|
||||
|
||||
Reference in New Issue
Block a user