Initial CAT application and deployment update
This commit is contained in:
@@ -0,0 +1,662 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
session_set_cookie_params([
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'use_strict_mode' => true,
|
||||
]);
|
||||
session_start();
|
||||
if ((int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 1048576) {
|
||||
http_response_code(413);
|
||||
exit('Anfrage zu groß.');
|
||||
}
|
||||
header('Cache-Control: no-store');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: DENY');
|
||||
header('Referrer-Policy: no-referrer');
|
||||
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
|
||||
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'none'");
|
||||
|
||||
require_once __DIR__ . '/inc/db.inc.php';
|
||||
require_once __DIR__ . '/inc/mail.inc.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
header('Allow: POST');
|
||||
exit('Methode nicht erlaubt.');
|
||||
}
|
||||
|
||||
$submittedCsrf = $_POST['csrf_token'] ?? '';
|
||||
$storedCsrf = $_SESSION['registration_csrf'] ?? '';
|
||||
if (!is_string($submittedCsrf) || $storedCsrf === '' || !hash_equals($storedCsrf, $submittedCsrf)) {
|
||||
http_response_code(403);
|
||||
exit('Ungültige Anfrage.');
|
||||
}
|
||||
unset($_SESSION['registration_csrf']);
|
||||
|
||||
function postString(string $key): string
|
||||
{
|
||||
$value = $_POST[$key] ?? '';
|
||||
|
||||
return is_string($value) ? trim($value) : '';
|
||||
}
|
||||
|
||||
function postStringList(string $key): array
|
||||
{
|
||||
$value = $_POST[$key] ?? [];
|
||||
$values = is_array($value) ? $value : [$value];
|
||||
|
||||
return array_values(array_filter(
|
||||
array_map(
|
||||
static fn ($item): string => is_string($item) ? trim($item) : '',
|
||||
$values
|
||||
),
|
||||
static fn (string $item): bool => $item !== ''
|
||||
));
|
||||
}
|
||||
|
||||
function postMealSelectionsByDay(): array
|
||||
{
|
||||
$value = $_POST['meals'] ?? [];
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($value as $day => $meals) {
|
||||
if (!is_array($meals)) {
|
||||
continue;
|
||||
}
|
||||
$result[(string) $day] = array_values(array_filter(
|
||||
array_map(static fn ($item): string => is_string($item) ? trim($item) : '', $meals),
|
||||
static fn (string $item): bool => $item !== ''
|
||||
));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function formatMealSummary(array $days, array $mealsByDay): string
|
||||
{
|
||||
$dayLabels = ['1' => 'Fr.', '2' => 'Sa.', '3' => 'So.'];
|
||||
$mealLabels = [
|
||||
'breakfast' => 'Frühstück',
|
||||
'lunch' => 'Mittagessen',
|
||||
'coffee' => 'Kaffee',
|
||||
'dinner' => 'Abendessen',
|
||||
'drinks' => 'Getränkepauschale',
|
||||
];
|
||||
$parts = [];
|
||||
foreach ($days as $day) {
|
||||
$labels = array_map(static fn (string $meal): string => $mealLabels[$meal] ?? $meal, $mealsByDay[$day] ?? []);
|
||||
$labels[] = $mealLabels['drinks'];
|
||||
$labels[] = $mealLabels['rehearsal_room'];
|
||||
$parts[] = ($dayLabels[$day] ?? $day) . ': ' . implode(', ', $labels);
|
||||
}
|
||||
|
||||
return implode('; ', $parts) ?: '–';
|
||||
}
|
||||
|
||||
function escapeHtml(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function smtpResponse($socket): string
|
||||
{
|
||||
$response = '';
|
||||
do {
|
||||
$line = fgets($socket);
|
||||
if ($line === false) {
|
||||
throw new RuntimeException('Keine gültige SMTP-Antwort erhalten.');
|
||||
}
|
||||
$response .= $line;
|
||||
} while (isset($line[3]) && $line[3] === '-');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function smtpCommand($socket, string $command, array $expectedCodes): void
|
||||
{
|
||||
fwrite($socket, $command . "\r\n");
|
||||
$response = smtpResponse($socket);
|
||||
$code = (int) substr($response, 0, 3);
|
||||
|
||||
if (!in_array($code, $expectedCodes, true)) {
|
||||
throw new RuntimeException('SMTP-Befehl wurde abgelehnt.');
|
||||
}
|
||||
}
|
||||
|
||||
function encodedHeader(string $value): string
|
||||
{
|
||||
return '=?UTF-8?B?' . base64_encode($value) . '?=';
|
||||
}
|
||||
|
||||
function calculateBookingTotal(PDO $pdo, bool $under27, string $guestType, ?string $roomType, array $mealsByDay): float
|
||||
{
|
||||
$ageSuffix = $under27 ? 'under_27' : 'over_27';
|
||||
$keys = [];
|
||||
if ($guestType === 'regular_guest' && $roomType !== null) {
|
||||
$keys[] = $roomType . '_' . $ageSuffix;
|
||||
}
|
||||
if ($guestType === 'day_guest') {
|
||||
foreach ($mealsByDay as $meals) {
|
||||
foreach (array_unique(array_merge($meals, ['drinks'])) as $meal) {
|
||||
if (in_array($meal, ['breakfast', 'lunch', 'coffee', 'dinner'], true)) {
|
||||
$keys[] = $meal . '_' . $ageSuffix;
|
||||
} elseif ($meal === 'drinks') {
|
||||
$keys[] = 'drinks';
|
||||
}
|
||||
}
|
||||
$keys[] = 'rehearsal_room';
|
||||
}
|
||||
}
|
||||
if ($keys === []) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$uniqueKeys = array_values(array_unique($keys));
|
||||
$placeholders = implode(',', array_fill(0, count($uniqueKeys), '?'));
|
||||
$statement = $pdo->prepare("SELECT setting_key, price FROM pricing_settings WHERE setting_key IN ($placeholders)");
|
||||
$statement->execute($uniqueKeys);
|
||||
$prices = [];
|
||||
foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$prices[(string) $row['setting_key']] = (float) $row['price'];
|
||||
}
|
||||
|
||||
return round(array_sum(array_map(
|
||||
static fn (string $key): float => $prices[$key] ?? 0.0,
|
||||
$keys
|
||||
)), 2);
|
||||
}
|
||||
|
||||
function sendRegistrationEmail(
|
||||
array $mailConfig,
|
||||
string $recipient,
|
||||
string $name,
|
||||
string $participantPassword,
|
||||
string $guestType,
|
||||
bool $under27,
|
||||
?string $roomType,
|
||||
array $days,
|
||||
array $mealsByDay,
|
||||
string $allergies,
|
||||
string $roommates,
|
||||
string $otherNotes
|
||||
): void {
|
||||
$roomLabels = [
|
||||
'single' => 'Einzelzimmer',
|
||||
'double' => 'Doppelzimmer',
|
||||
'multi' => 'Mehrbettzimmer',
|
||||
];
|
||||
$dayLabels = ['1' => 'Fr.', '2' => 'Sa.', '3' => 'So.'];
|
||||
$mealLabels = [
|
||||
'breakfast' => 'Frühstück',
|
||||
'lunch' => 'Mittagessen',
|
||||
'coffee' => 'Kaffee',
|
||||
'drinks' => 'Getränkepauschale',
|
||||
'dinner' => 'Abendessen',
|
||||
'rehearsal_room' => 'Proberaumpauschale',
|
||||
];
|
||||
$guestLabel = $guestType === 'day_guest' ? 'Tagesgast' : 'Dauergast';
|
||||
$ageLabel = $under27 ? 'Unter 27' : 'Ab 27';
|
||||
$roomLabel = $roomLabels[$roomType ?? ''] ?? '–';
|
||||
$dayText = implode(', ', array_map(static fn (string $day): string => $dayLabels[$day] ?? $day, $days)) ?: '–';
|
||||
$mealText = formatMealSummary($days, $mealsByDay);
|
||||
$roommates = $roommates !== '' ? $roommates : '–';
|
||||
$allergies = $allergies !== '' ? $allergies : '–';
|
||||
$otherNotes = $otherNotes !== '' ? $otherNotes : '–';
|
||||
|
||||
$textBody = "Hallo {$name},\n\n"
|
||||
. "deine Registrierung für das Chorwochenende wurde erfolgreich gespeichert.\n\n"
|
||||
. "Zugang zur Änderung oder Löschung:\n"
|
||||
. "E-Mail-Adresse: {$recipient}\n"
|
||||
. "Passwort: {$participantPassword}\n\n"
|
||||
. "Zusammenfassung:\n"
|
||||
. "Altersgruppe: {$ageLabel}\n"
|
||||
. "Gasttyp: {$guestLabel}\n"
|
||||
. "Zimmerart: {$roomLabel}\n"
|
||||
. "Anwesenheitstage: {$dayText}\n"
|
||||
. "Mahlzeiten: {$mealText}\n"
|
||||
. "Zimmerpartner-Wunsch: {$roommates}\n"
|
||||
. "Allergien / Essensvorlieben: {$allergies}\n"
|
||||
. "Sonstiges: {$otherNotes}\n\n"
|
||||
. "Viele Grüße\n{$mailConfig['from_name']}";
|
||||
|
||||
$html = static fn (string $value): string => escapeHtml($value);
|
||||
$htmlBody = '<!doctype html><html lang="de"><body>'
|
||||
. '<p>Hallo ' . $html($name) . ',</p>'
|
||||
. '<p>deine Registrierung für das Chorwochenende wurde erfolgreich gespeichert.</p>'
|
||||
. '<h2>Deine Zugangsdaten</h2><p><strong>E-Mail-Adresse:</strong> ' . $html($recipient)
|
||||
. '<br><strong>Passwort:</strong> <code>' . $html($participantPassword) . '</code></p>'
|
||||
. '<h2>Zusammenfassung</h2><ul>'
|
||||
. '<li><strong>Altersgruppe:</strong> ' . $html($ageLabel) . '</li>'
|
||||
. '<li><strong>Gasttyp:</strong> ' . $html($guestLabel) . '</li>'
|
||||
. '<li><strong>Zimmerart:</strong> ' . $html($roomLabel) . '</li>'
|
||||
. '<li><strong>Anwesenheitstage:</strong> ' . $html($dayText) . '</li>'
|
||||
. '<li><strong>Mahlzeiten:</strong> ' . $html($mealText) . '</li>'
|
||||
. '<li><strong>Zimmerpartner-Wunsch:</strong> ' . $html($roommates) . '</li>'
|
||||
. '<li><strong>Allergien / Essensvorlieben:</strong> ' . $html($allergies) . '</li>'
|
||||
. '<li><strong>Sonstiges:</strong> ' . $html($otherNotes) . '</li></ul>'
|
||||
. '<p>Viele Grüße<br>' . $html($mailConfig['from_name']) . '</p></body></html>';
|
||||
|
||||
$boundary = '=_cat_' . bin2hex(random_bytes(12));
|
||||
$message = 'From: ' . encodedHeader($mailConfig['from_name']) . ' <' . $mailConfig['from_address'] . ">\r\n"
|
||||
. 'To: ' . $recipient . "\r\n"
|
||||
. 'Subject: ' . encodedHeader('Deine Registrierung für das Chorwochenende') . "\r\n"
|
||||
. "MIME-Version: 1.0\r\n"
|
||||
. 'Content-Type: multipart/alternative; boundary="' . $boundary . "\"\r\n\r\n"
|
||||
. '--' . $boundary . "\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n"
|
||||
. $textBody . "\r\n\r\n"
|
||||
. '--' . $boundary . "\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n"
|
||||
. $htmlBody . "\r\n\r\n"
|
||||
. '--' . $boundary . "--\r\n";
|
||||
|
||||
$context = stream_context_create([
|
||||
'ssl' => [
|
||||
'peer_name' => $mailConfig['host'],
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
$socket = stream_socket_client(
|
||||
'tcp://' . $mailConfig['host'] . ':' . $mailConfig['port'],
|
||||
$errorNumber,
|
||||
$errorMessage,
|
||||
20,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
$context
|
||||
);
|
||||
if ($socket === false) {
|
||||
throw new RuntimeException('SMTP-Server nicht erreichbar.');
|
||||
}
|
||||
stream_set_timeout($socket, 20);
|
||||
|
||||
try {
|
||||
$response = smtpResponse($socket);
|
||||
if ((int) substr($response, 0, 3) !== 220) {
|
||||
throw new RuntimeException('SMTP-Verbindung wurde abgelehnt.');
|
||||
}
|
||||
smtpCommand($socket, 'EHLO localhost', [250]);
|
||||
smtpCommand($socket, 'STARTTLS', [220]);
|
||||
if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
|
||||
throw new RuntimeException('STARTTLS konnte nicht aktiviert werden.');
|
||||
}
|
||||
smtpCommand($socket, 'EHLO localhost', [250]);
|
||||
smtpCommand($socket, 'AUTH LOGIN', [334]);
|
||||
smtpCommand($socket, base64_encode($mailConfig['username']), [334]);
|
||||
smtpCommand($socket, base64_encode($mailConfig['password']), [235]);
|
||||
smtpCommand($socket, 'MAIL FROM:<' . $mailConfig['from_address'] . '>', [250]);
|
||||
smtpCommand($socket, 'RCPT TO:<' . $recipient . '>', [250, 251]);
|
||||
smtpCommand($socket, 'DATA', [354]);
|
||||
$message = preg_replace('/^\./m', '..', $message);
|
||||
fwrite($socket, $message . "\r\n.\r\n");
|
||||
$response = smtpResponse($socket);
|
||||
if ((int) substr($response, 0, 3) !== 250) {
|
||||
throw new RuntimeException('E-Mail konnte nicht angenommen werden.');
|
||||
}
|
||||
fwrite($socket, "QUIT\r\n");
|
||||
} finally {
|
||||
fclose($socket);
|
||||
}
|
||||
}
|
||||
|
||||
function sendAdminNotificationEmail(
|
||||
array $mailConfig,
|
||||
string $recipient,
|
||||
string $name,
|
||||
string $participantEmail,
|
||||
string $guestLabel,
|
||||
string $ageLabel,
|
||||
string $roomLabel,
|
||||
string $dayText,
|
||||
string $mealText,
|
||||
string $roommates,
|
||||
string $allergies,
|
||||
string $otherNotes
|
||||
): void {
|
||||
$textBody = "Neue Registrierung für das Chorwochenende\n\n"
|
||||
. "Name: {$name}\n"
|
||||
. "E-Mail: {$participantEmail}\n"
|
||||
. "Altersgruppe: {$ageLabel}\n"
|
||||
. "Gasttyp: {$guestLabel}\n"
|
||||
. "Zimmerart: {$roomLabel}\n"
|
||||
. "Anwesenheitstage: {$dayText}\n"
|
||||
. "Mahlzeiten: {$mealText}\n"
|
||||
. "Zimmerpartner-Wunsch: {$roommates}\n"
|
||||
. "Allergien / Essensvorlieben: {$allergies}\n"
|
||||
. "Sonstiges: {$otherNotes}\n";
|
||||
$subject = encodedHeader('Neue Registrierung – Chorwochenende');
|
||||
$message = 'From: ' . encodedHeader($mailConfig['from_name']) . ' <' . $mailConfig['from_address'] . ">\r\n"
|
||||
. 'To: ' . $recipient . "\r\n"
|
||||
. 'Subject: ' . $subject . "\r\n"
|
||||
. "MIME-Version: 1.0\r\n"
|
||||
. "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
. "Content-Transfer-Encoding: 8bit\r\n\r\n"
|
||||
. $textBody;
|
||||
|
||||
$context = stream_context_create([
|
||||
'ssl' => [
|
||||
'peer_name' => $mailConfig['host'],
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
$socket = stream_socket_client(
|
||||
'tcp://' . $mailConfig['host'] . ':' . $mailConfig['port'],
|
||||
$errorNumber,
|
||||
$errorMessage,
|
||||
20,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
$context
|
||||
);
|
||||
if ($socket === false) {
|
||||
throw new RuntimeException('SMTP-Server nicht erreichbar.');
|
||||
}
|
||||
stream_set_timeout($socket, 20);
|
||||
|
||||
try {
|
||||
$response = smtpResponse($socket);
|
||||
if ((int) substr($response, 0, 3) !== 220) {
|
||||
throw new RuntimeException('SMTP-Verbindung wurde abgelehnt.');
|
||||
}
|
||||
smtpCommand($socket, 'EHLO localhost', [250]);
|
||||
smtpCommand($socket, 'STARTTLS', [220]);
|
||||
if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
|
||||
throw new RuntimeException('STARTTLS konnte nicht aktiviert werden.');
|
||||
}
|
||||
smtpCommand($socket, 'EHLO localhost', [250]);
|
||||
smtpCommand($socket, 'AUTH LOGIN', [334]);
|
||||
smtpCommand($socket, base64_encode($mailConfig['username']), [334]);
|
||||
smtpCommand($socket, base64_encode($mailConfig['password']), [235]);
|
||||
smtpCommand($socket, 'MAIL FROM:<' . $mailConfig['from_address'] . '>', [250]);
|
||||
smtpCommand($socket, 'RCPT TO:<' . $recipient . '>', [250, 251]);
|
||||
smtpCommand($socket, 'DATA', [354]);
|
||||
fwrite($socket, preg_replace('/^\./m', '..', $message) . "\r\n.\r\n");
|
||||
$response = smtpResponse($socket);
|
||||
if ((int) substr($response, 0, 3) !== 250) {
|
||||
throw new RuntimeException('E-Mail konnte nicht angenommen werden.');
|
||||
}
|
||||
fwrite($socket, "QUIT\r\n");
|
||||
} finally {
|
||||
fclose($socket);
|
||||
}
|
||||
}
|
||||
|
||||
$name = postString('name');
|
||||
$email = postString('email');
|
||||
$under27 = isset($_POST['under_27']) && $_POST['under_27'] === '1';
|
||||
$guestType = postString('guest_type');
|
||||
$roomType = postString('room_type');
|
||||
$roommates = postString('roommates');
|
||||
$earlyDeparture = postString('early_departure');
|
||||
$allergies = postString('allergies');
|
||||
$otherNotes = postString('other_notes');
|
||||
$days = postStringList('days');
|
||||
$mealsByDay = postMealSelectionsByDay();
|
||||
|
||||
$errors = [];
|
||||
$allowedDays = ['1', '2', '3'];
|
||||
$allowedEarlyDepartures = [
|
||||
'sat_breakfast',
|
||||
'sat_lunch',
|
||||
'sat_coffee',
|
||||
'sat_dinner',
|
||||
'sun_breakfast',
|
||||
];
|
||||
$allowedMealsByDay = [
|
||||
'1' => ['dinner'],
|
||||
'2' => ['breakfast', 'lunch', 'coffee', 'dinner'],
|
||||
'3' => ['breakfast', 'lunch'],
|
||||
];
|
||||
$days = array_values(array_unique($days));
|
||||
foreach ($mealsByDay as $day => $dayMeals) {
|
||||
$mealsByDay[$day] = array_values(array_unique($dayMeals));
|
||||
}
|
||||
|
||||
if (count($days) > count($allowedDays) || array_diff($days, $allowedDays) !== []) {
|
||||
$errors[] = 'Bitte nur gültige Anwesenheitstage auswählen.';
|
||||
}
|
||||
if (array_diff(array_keys($mealsByDay), $allowedDays) !== []) {
|
||||
$errors[] = 'Bitte nur gültige Mahlzeitentage auswählen.';
|
||||
}
|
||||
foreach ($mealsByDay as $day => $dayMeals) {
|
||||
$allowedMeals = $allowedMealsByDay[(string) $day] ?? [];
|
||||
if (array_diff($dayMeals, $allowedMeals) !== []) {
|
||||
$errors[] = 'Bitte nur gültige Mahlzeiten auswählen.';
|
||||
}
|
||||
if (!in_array((string) $day, $days, true) && $dayMeals !== []) {
|
||||
$errors[] = 'Mahlzeiten dürfen nur für ausgewählte Anwesenheitstage angegeben werden.';
|
||||
}
|
||||
}
|
||||
foreach ($days as $day) {
|
||||
$mealsByDay[$day] ??= [];
|
||||
}
|
||||
if (mb_strlen($roommates) > 1000 || mb_strlen($allergies) > 1000) {
|
||||
$errors[] = 'Zimmerwunsch und Allergieangaben dürfen höchstens 1000 Zeichen enthalten.';
|
||||
}
|
||||
|
||||
if ($name === '' || mb_strlen($name) > 255) {
|
||||
$errors[] = 'Bitte einen gültigen Namen eingeben.';
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 255) {
|
||||
$errors[] = 'Bitte eine gültige E-Mail-Adresse eingeben.';
|
||||
}
|
||||
|
||||
if ($otherNotes !== '' && mb_strlen($otherNotes) > 5000) {
|
||||
$errors[] = 'Das Feld Sonstiges darf höchstens 5000 Zeichen enthalten.';
|
||||
}
|
||||
|
||||
if (!in_array($guestType, ['day_guest', 'regular_guest'], true)) {
|
||||
$errors[] = 'Bitte einen gültigen Gasttyp auswählen.';
|
||||
}
|
||||
|
||||
if ($guestType === 'regular_guest' && !in_array($roomType, ['single', 'double', 'multi'], true)) {
|
||||
$errors[] = 'Bitte eine gültige Zimmerart auswählen.';
|
||||
}
|
||||
if ($earlyDeparture !== '' && ($guestType !== 'regular_guest' || !in_array($earlyDeparture, $allowedEarlyDepartures, true))) {
|
||||
$errors[] = 'Bitte einen gültigen Abreisezeitpunkt auswählen.';
|
||||
}
|
||||
|
||||
if ($errors !== []) {
|
||||
http_response_code(422);
|
||||
exit(implode("\n", $errors));
|
||||
}
|
||||
|
||||
$roommateEligible = $guestType === 'regular_guest' && in_array($roomType, ['double', 'multi'], true);
|
||||
if (!$roommateEligible) {
|
||||
$roommates = '';
|
||||
}
|
||||
|
||||
$role = $guestType === 'day_guest' ? 'Tagesgast' : 'Dauergast';
|
||||
$bookingType = $guestType === 'day_guest' ? 'Day' : 'Stay';
|
||||
$accommodationType = $guestType === 'regular_guest' ? $roomType : null;
|
||||
$roommateRequest = $roommateEligible && $roommates !== '' ? $roommates : null;
|
||||
$earlyDeparture = $guestType === 'regular_guest' && in_array($earlyDeparture, $allowedEarlyDepartures, true)
|
||||
? $earlyDeparture
|
||||
: null;
|
||||
|
||||
$mealPreferences = null;
|
||||
if ($guestType === 'day_guest') {
|
||||
$mealPreferences = json_encode(
|
||||
[
|
||||
'days' => $days,
|
||||
'meals_by_day' => $mealsByDay,
|
||||
],
|
||||
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$participantId = sprintf(
|
||||
'%s-%s-%s-%s-%s',
|
||||
bin2hex(random_bytes(4)),
|
||||
bin2hex(random_bytes(2)),
|
||||
bin2hex(random_bytes(2)),
|
||||
bin2hex(random_bytes(2)),
|
||||
bin2hex(random_bytes(6))
|
||||
);
|
||||
$participantPassword = bin2hex(random_bytes(8));
|
||||
$participantPasswordHash = password_hash($participantPassword, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
|
||||
$dbConfig['host'],
|
||||
$dbConfig['port'],
|
||||
$dbConfig['database'],
|
||||
$dbConfig['charset']
|
||||
);
|
||||
|
||||
$pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
$totalPrice = calculateBookingTotal($pdo, $under27, $guestType, $accommodationType, $mealsByDay);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$participantStatement = $pdo->prepare(
|
||||
'INSERT INTO participants (id, name, email, allergies, role, under_27, password_hash)
|
||||
VALUES (:id, :name, :email, :allergies, :role, :under_27, :password_hash)'
|
||||
);
|
||||
$participantStatement->execute([
|
||||
':id' => $participantId,
|
||||
':name' => $name,
|
||||
':email' => $email,
|
||||
':allergies' => $allergies !== '' ? $allergies : null,
|
||||
':role' => $role,
|
||||
':under_27' => $under27 ? 1 : 0,
|
||||
':password_hash' => $participantPasswordHash,
|
||||
]);
|
||||
|
||||
$bookingStatement = $pdo->prepare(
|
||||
'INSERT INTO bookings
|
||||
(participant_id, type, accommodation_type, meal_preferences, roommate_requests, early_departure, other_notes, total_price)
|
||||
VALUES
|
||||
(:participant_id, :type, :accommodation_type, :meal_preferences, :roommate_requests, :early_departure, :other_notes, :total_price)'
|
||||
);
|
||||
$bookingStatement->execute([
|
||||
':participant_id' => $participantId,
|
||||
':type' => $bookingType,
|
||||
':accommodation_type' => $accommodationType,
|
||||
':meal_preferences' => $mealPreferences,
|
||||
':roommate_requests' => $roommateRequest,
|
||||
':early_departure' => $earlyDeparture,
|
||||
':other_notes' => $otherNotes !== '' ? $otherNotes : null,
|
||||
':total_price' => number_format($totalPrice, 2, '.', ''),
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$mailError = '';
|
||||
try {
|
||||
sendRegistrationEmail(
|
||||
$mailConfig,
|
||||
$email,
|
||||
$name,
|
||||
$participantPassword,
|
||||
$guestType,
|
||||
$under27,
|
||||
$roomType,
|
||||
$days,
|
||||
$mealsByDay,
|
||||
$allergies,
|
||||
$roommates,
|
||||
$otherNotes
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
error_log('Registrierung gespeichert, E-Mail konnte nicht versendet werden: ' . $exception->getMessage());
|
||||
$mailError = '<p class="message error">Die Registrierung wurde gespeichert, aber die Bestätigungs-E-Mail konnte nicht versendet werden. Bitte bewahre die Zugangsdaten auf dieser Seite auf.</p>';
|
||||
}
|
||||
|
||||
$roomLabels = ['single' => 'Einzelzimmer', 'double' => 'Doppelzimmer', 'multi' => 'Mehrbettzimmer'];
|
||||
$dayLabels = ['1' => 'Fr.', '2' => 'Sa.', '3' => 'So.'];
|
||||
$mealLabels = [
|
||||
'breakfast' => 'Frühstück',
|
||||
'lunch' => 'Mittagessen',
|
||||
'coffee' => 'Kaffee',
|
||||
'drinks' => 'Getränkepauschale',
|
||||
'dinner' => 'Abendessen',
|
||||
'rehearsal_room' => 'Proberaumpauschale',
|
||||
];
|
||||
$guestLabel = $guestType === 'day_guest' ? 'Tagesgast' : 'Dauergast';
|
||||
$ageLabel = $under27 ? 'Unter 27' : 'Ab 27';
|
||||
$roomLabel = $roomLabels[$roomType] ?? '–';
|
||||
$dayText = implode(', ', array_map(static fn (string $day): string => $dayLabels[$day] ?? $day, $days)) ?: '–';
|
||||
$mealText = formatMealSummary($days, $mealsByDay);
|
||||
$adminEmails = $pdo->query(
|
||||
"SELECT email FROM admin_users WHERE is_active = 1 AND email <> ''"
|
||||
)->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($adminEmails as $adminEmail) {
|
||||
if (!is_string($adminEmail) || !filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
sendAdminNotificationEmail(
|
||||
$mailConfig,
|
||||
(string) $adminEmail,
|
||||
$name,
|
||||
$email,
|
||||
$guestLabel,
|
||||
$ageLabel,
|
||||
$roomLabel,
|
||||
$dayText,
|
||||
$mealText,
|
||||
$roommates !== '' ? $roommates : '–',
|
||||
$allergies !== '' ? $allergies : '–',
|
||||
$otherNotes !== '' ? $otherNotes : '–'
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
error_log('Admin-Benachrichtigung konnte nicht versendet werden: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
http_response_code(201);
|
||||
$safeEmail = escapeHtml($email);
|
||||
$safePassword = escapeHtml($participantPassword);
|
||||
exit(<<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Registrierung erfolgreich</title>
|
||||
<link rel="stylesheet" href="./css/booking.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="success-card">
|
||||
<h1>Registrierung erfolgreich</h1>
|
||||
{$mailError}
|
||||
<p>Bitte bewahre die folgenden Zugangsdaten auf. Du benötigst sie, um deine Registrierung später zu ändern oder zu löschen.</p>
|
||||
<dl class="credentials">
|
||||
<dt>E-Mail-Adresse</dt>
|
||||
<dd>{$safeEmail}</dd>
|
||||
<dt>Passwort</dt>
|
||||
<dd><code>{$safePassword}</code></dd>
|
||||
</dl>
|
||||
<p><a href="edit_registration.php">Registrierung ändern oder löschen</a></p>
|
||||
<p><a href="index.php">Zur Startseite</a></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
HTML);
|
||||
} catch (Throwable $exception) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
error_log($exception->getMessage());
|
||||
if ($exception instanceof PDOException && $exception->getCode() === '23000'
|
||||
&& str_contains($exception->getMessage(), "for key 'email'")) {
|
||||
http_response_code(409);
|
||||
exit('Diese E-Mail-Adresse ist bereits registriert. Bitte nutze die Änderungs- oder Löschfunktion deiner bestehenden Registrierung.');
|
||||
}
|
||||
|
||||
http_response_code(500);
|
||||
exit('Die Registrierung konnte nicht gespeichert werden.');
|
||||
}
|
||||
Reference in New Issue
Block a user