Mitglied werden: Hub-Seite mit Online-Anmeldung (membership-form + membership-submit-Action, Honeypot + Time-Trap), Abteilungswahl (department-choice), Beitragstabelle (fee-table) und Turn-Aufnahmeantrag als PDF-Download. Daten in data/mitglied-werden.json. Partner werden & Sportheimbuchung: neue Seiten samt Routing und Navigation. Ansprechpartner: echter Hero (Vereinsgelände), Bereiche aus dem group-Feld der Single Source (Fußball / Turnen) zu zwei Sektionen gebündelt, Disziplin als Karten-Überschrift. Fehlende Turn-Disziplin-Leads (Frisque, Lorz, Kornatz, Trotzke) aus den Turnen-Unterseiten extrahiert und in data/ansprechpartner.json ergänzt (allgemeine Turnabteilungs-Mail als Kontakt). WhatsApp für alle Personen mit Telefonnummer. person-grid: kompaktere Karten, Hairline unter der Bereichsüberschrift, 5 Spalten am Desktop. Startseite auf die Haupt-Ansprechpartner je Bereich begrenzt. Dazu: neue Icons (check-circle, download, file-earmark-text, whatsapp), Anpassungen an Header/Footer/CTA/age-groups sowie tokens/layout/utilities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZsKTL5kRSG4KkK8w2LKkE
132 lines
4.7 KiB
PHP
132 lines
4.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* POST /mitglied-werden-senden — Mitgliedsantrag Fußball (schlank, ohne Bankdaten).
|
||
* Versendet eine interne Vereins-Mail (erfolgskritisch) und eine Eingangsbestätigung
|
||
* an den Antragsteller (optional, Fehler dort wird nicht an den Besucher geleakt).
|
||
* Antwort: PRG-Redirect zu /mitglied-werden#anmeldung-fussball (?sent=1 | ?error=…)
|
||
* oder JSON bei fetch (form.js).
|
||
*/
|
||
|
||
use PHPMailer\PHPMailer\PHPMailer;
|
||
|
||
$wantsJson = str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json');
|
||
|
||
$respond = static function (bool $ok, string $error = '') use ($wantsJson): never {
|
||
if ($wantsJson) {
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
echo json_encode(['ok' => $ok, 'error' => $error ?: null]);
|
||
} else {
|
||
header('Location: ' . url('mitglied-werden') . ($ok ? '?sent=1' : '?error=' . $error) . '#anmeldung-fussball', true, 303);
|
||
}
|
||
exit;
|
||
};
|
||
|
||
$field = static fn (string $key): string => trim((string) ($_POST[$key] ?? ''));
|
||
|
||
// --- Spam-Checks: Bots bekommen ein stilles "OK" (kein Feedback-Kanal) ---
|
||
if ($field('website') !== '' || !form_token_valid($field('ft'))) {
|
||
$respond(true);
|
||
}
|
||
|
||
// --- Eingaben ---
|
||
$firstName = $field('first_name');
|
||
$lastName = $field('last_name');
|
||
$birthDate = $field('birth_date');
|
||
$phone = $field('phone');
|
||
$street = $field('street');
|
||
$zip = $field('zip');
|
||
$city = $field('city');
|
||
$email = $field('email');
|
||
$feeClass = $field('fee_class');
|
||
|
||
// Beitragsklassen-Whitelist identisch zur Seite aus data/mitglied-werden.json ableiten.
|
||
$data = json_load('mitglied-werden');
|
||
$feeOptions = array_map(
|
||
static fn (array $r): string => $r['label'] . ' — ' . $r['price'],
|
||
$data['fussball']['fees']['rows'] ?? []
|
||
);
|
||
|
||
// --- Validierung (Whitelist-Prinzip) ---
|
||
$valid = $firstName !== '' && mb_strlen($firstName) <= 100
|
||
&& $lastName !== '' && mb_strlen($lastName) <= 100
|
||
&& preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthDate) === 1
|
||
&& $phone !== '' && mb_strlen($phone) <= 50
|
||
&& $street !== '' && mb_strlen($street) <= 200
|
||
&& $zip !== '' && mb_strlen($zip) <= 10
|
||
&& $city !== '' && mb_strlen($city) <= 100
|
||
&& filter_var($email, FILTER_VALIDATE_EMAIL) !== false
|
||
&& in_array($feeClass, $feeOptions, true)
|
||
&& ($_POST['statute'] ?? '') === '1'
|
||
&& ($_POST['privacy'] ?? '') === '1'
|
||
&& !preg_match('/[\r\n]/', $firstName . $lastName . $city . $street);
|
||
|
||
if (!$valid) {
|
||
$respond(false, 'validation');
|
||
}
|
||
|
||
// --- Versand ---
|
||
$smtp = config('smtp');
|
||
|
||
$body = "Neuer Mitgliedsantrag (Fußball) über tsv08kulmbach.de\n"
|
||
. str_repeat('-', 40) . "\n"
|
||
. "Vorname: {$firstName}\n"
|
||
. "Nachname: {$lastName}\n"
|
||
. "Geboren am: {$birthDate}\n"
|
||
. "Adresse: {$street}, {$zip} {$city}\n"
|
||
. "Telefon: {$phone}\n"
|
||
. "E-Mail: {$email}\n"
|
||
. "Beitragsklasse: {$feeClass}\n"
|
||
. str_repeat('-', 40) . "\n"
|
||
. "Satzung anerkannt: ja\n"
|
||
. "Datenschutz: ja\n"
|
||
. 'Eingegangen: ' . date('d.m.Y H:i') . "\n\n"
|
||
. "Hinweis: Bankdaten werden online nicht erhoben – Beitrittserklärung\n"
|
||
. "per Post zur Unterschrift zusenden.\n";
|
||
|
||
$buildMailer = static function () use ($smtp): PHPMailer {
|
||
$mail = new PHPMailer(true);
|
||
$mail->isSMTP();
|
||
$mail->Host = $smtp['host'];
|
||
$mail->Port = (int) $smtp['port'];
|
||
$mail->SMTPAuth = true;
|
||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||
$mail->Username = $smtp['username'];
|
||
$mail->Password = $smtp['password'];
|
||
$mail->CharSet = PHPMailer::CHARSET_UTF8;
|
||
return $mail;
|
||
};
|
||
|
||
// Mail 1 (intern) — erfolgskritisch.
|
||
try {
|
||
$mail = $buildMailer();
|
||
$mail->setFrom($smtp['from'], $smtp['from_name']);
|
||
$mail->addAddress($smtp['to']);
|
||
$mail->addReplyTo($email, $firstName . ' ' . $lastName);
|
||
$mail->Subject = 'Mitgliedsantrag Fußball: ' . $firstName . ' ' . $lastName;
|
||
$mail->Body = $body;
|
||
$mail->send();
|
||
} catch (Throwable $e) {
|
||
error_log('[' . date('c') . '] Mitgliedsantrag-Versand fehlgeschlagen: ' . $e->getMessage() . "\n", 3, STORAGE_PATH . '/logs/mail.log');
|
||
$respond(false, 'mail');
|
||
}
|
||
|
||
// Mail 2 (Eingangsbestätigung an Antragsteller) — optional, Fehler nicht an Besucher leaken.
|
||
$welcome = $data['welcome_mail'] ?? null;
|
||
if (is_array($welcome) && !empty($welcome['subject']) && !empty($welcome['body'])) {
|
||
try {
|
||
$confirm = $buildMailer();
|
||
$confirm->setFrom($smtp['from'], $smtp['from_name']);
|
||
$confirm->addAddress($email, $firstName . ' ' . $lastName);
|
||
$confirm->Subject = $welcome['subject'];
|
||
$confirm->Body = $welcome['body'];
|
||
$confirm->send();
|
||
} catch (Throwable $e) {
|
||
error_log('[' . date('c') . '] Bestätigungsmail fehlgeschlagen: ' . $e->getMessage() . "\n", 3, STORAGE_PATH . '/logs/mail.log');
|
||
}
|
||
}
|
||
|
||
$respond(true);
|