Files
cat/edit_registration.php
T

732 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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();
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
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';
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 escapeHtml(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function earlyDepartureLabel(?string $value): string
{
return match ($value) {
'sat_breakfast' => 'Samstag nach dem Frühstück',
'sat_lunch' => 'Samstag nach dem Mittagessen',
'sat_coffee' => 'Samstag nach dem Kaffee',
'sat_dinner' => 'Samstag nach dem Abendessen',
'sun_breakfast' => 'Sonntag nach dem Frühstück',
null, '' => 'Keine vorzeitige Abreise',
default => $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 createPdo(array $dbConfig): PDO
{
$dsn = sprintf(
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
$dbConfig['host'],
$dbConfig['port'],
$dbConfig['database'],
$dbConfig['charset']
);
return new PDO($dsn, $dbConfig['username'], $dbConfig['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
function editSmtpResponse($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 editSmtpCommand($socket, string $command, array $expectedCodes): void
{
fwrite($socket, $command . "\r\n");
$response = editSmtpResponse($socket);
if (!in_array((int) substr($response, 0, 3), $expectedCodes, true)) {
throw new RuntimeException('SMTP-Befehl wurde abgelehnt.');
}
}
function editEncodedHeader(string $value): string
{
return '=?UTF-8?B?' . base64_encode($value) . '?=';
}
function sendDeletionAdminEmail(array $mailConfig, string $recipient, array $booking): void
{
$message = 'From: ' . editEncodedHeader($mailConfig['from_name']) . ' <' . $mailConfig['from_address'] . ">\r\n"
. 'To: ' . $recipient . "\r\n"
. 'Subject: ' . editEncodedHeader('Registrierung gelöscht Chorwochenende') . "\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"
. "Eine Teilnehmerregistrierung wurde selbstständig gelöscht.\n\n"
. "Name: {$booking['name']}\n"
. "E-Mail: {$booking['email']}\n"
. "Gasttyp: {$booking['role']}\n"
. "Zimmerart: {$booking['accommodation_type']}\n"
. "Zimmerpartner-Wunsch: {$booking['roommate_requests']}\n"
. "Sonstiges: {$booking['other_notes']}\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 {
if ((int) substr(editSmtpResponse($socket), 0, 3) !== 220) {
throw new RuntimeException('SMTP-Verbindung wurde abgelehnt.');
}
editSmtpCommand($socket, 'EHLO localhost', [250]);
editSmtpCommand($socket, 'STARTTLS', [220]);
if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
throw new RuntimeException('STARTTLS konnte nicht aktiviert werden.');
}
editSmtpCommand($socket, 'EHLO localhost', [250]);
editSmtpCommand($socket, 'AUTH LOGIN', [334]);
editSmtpCommand($socket, base64_encode($mailConfig['username']), [334]);
editSmtpCommand($socket, base64_encode($mailConfig['password']), [235]);
editSmtpCommand($socket, 'MAIL FROM:<' . $mailConfig['from_address'] . '>', [250]);
editSmtpCommand($socket, 'RCPT TO:<' . $recipient . '>', [250, 251]);
editSmtpCommand($socket, 'DATA', [354]);
fwrite($socket, preg_replace('/^\./m', '..', $message) . "\r\n.\r\n");
if ((int) substr(editSmtpResponse($socket), 0, 3) !== 250) {
throw new RuntimeException('E-Mail konnte nicht angenommen werden.');
}
fwrite($socket, "QUIT\r\n");
} finally {
fclose($socket);
}
}
function redirectToEdit(): never
{
header('Location: edit_registration.php');
exit;
}
function editCsrfToken(): string
{
if (!isset($_SESSION['edit_csrf'])) {
$_SESSION['edit_csrf'] = bin2hex(random_bytes(32));
}
return $_SESSION['edit_csrf'];
}
function requireEditCsrf(): void
{
$submitted = postString('csrf_token');
$stored = $_SESSION['edit_csrf'] ?? '';
if ($stored === '' || !hash_equals($stored, $submitted)) {
http_response_code(403);
exit('Ungültige Anfrage.');
}
}
function validateBookingInput(): array
{
$data = [
'name' => postString('name'),
'email' => postString('email'),
'guest_type' => postString('guest_type'),
'room_type' => postString('room_type'),
'roommates' => postString('roommates'),
'early_departure' => postString('early_departure'),
'allergies' => postString('allergies'),
'other_notes' => postString('other_notes'),
'days' => postStringList('days'),
'meals_by_day' => postMealSelectionsByDay(),
];
$errors = [];
if ($data['name'] === '' || mb_strlen($data['name']) > 255) {
$errors[] = 'Bitte einen gültigen Namen eingeben.';
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL) || mb_strlen($data['email']) > 255) {
$errors[] = 'Bitte eine gültige E-Mail-Adresse eingeben.';
}
if (!in_array($data['guest_type'], ['day_guest', 'regular_guest'], true)) {
$errors[] = 'Bitte einen gültigen Gasttyp auswählen.';
}
if ($data['guest_type'] === 'regular_guest'
&& !in_array($data['room_type'], ['single', 'double', 'multi'], true)) {
$errors[] = 'Bitte eine gültige Zimmerart auswählen.';
}
$allowedEarlyDepartures = ['sat_breakfast', 'sat_lunch', 'sat_coffee', 'sat_dinner', 'sun_breakfast'];
if ($data['early_departure'] !== ''
&& ($data['guest_type'] !== 'regular_guest' || !in_array($data['early_departure'], $allowedEarlyDepartures, true))) {
$errors[] = 'Bitte einen gültigen Abreisezeitpunkt auswählen.';
}
if (mb_strlen($data['other_notes']) > 5000) {
$errors[] = 'Das Feld Sonstiges darf höchstens 5000 Zeichen enthalten.';
}
if (mb_strlen($data['roommates']) > 1000 || mb_strlen($data['allergies']) > 1000) {
$errors[] = 'Zimmerwunsch und Allergieangaben dürfen höchstens 1000 Zeichen enthalten.';
}
$allowedDays = ['1', '2', '3'];
$allowedMealsByDay = [
'1' => ['dinner'],
'2' => ['breakfast', 'lunch', 'coffee', 'dinner'],
'3' => ['breakfast', 'lunch'],
];
$data['days'] = array_values(array_unique($data['days']));
foreach ($data['meals_by_day'] as $day => $dayMeals) {
$data['meals_by_day'][$day] = array_values(array_unique($dayMeals));
}
if (count($data['days']) > count($allowedDays) || array_diff($data['days'], $allowedDays) !== []) {
$errors[] = 'Bitte nur gültige Anwesenheitstage auswählen.';
}
if (array_diff(array_keys($data['meals_by_day']), $allowedDays) !== []) {
$errors[] = 'Bitte nur gültige Mahlzeitentage auswählen.';
}
foreach ($data['meals_by_day'] 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, $data['days'], true) && $dayMeals !== []) {
$errors[] = 'Mahlzeiten dürfen nur für ausgewählte Anwesenheitstage angegeben werden.';
}
}
foreach ($data['days'] as $day) {
$data['meals_by_day'][$day] ??= [];
}
if ($errors !== []) {
throw new InvalidArgumentException(implode(' ', $errors));
}
return $data;
}
$pdo = createPdo($dbConfig);
$message = $_SESSION['edit_message'] ?? '';
unset($_SESSION['edit_message']);
if (postString('action') === 'login') {
$email = postString('email');
$password = postString('password');
$lockedUntil = (int) ($_SESSION['edit_locked_until'] ?? 0);
if ($lockedUntil > time()) {
$message = 'Zu viele Fehlversuche. Bitte später erneut versuchen.';
} else {
$statement = $pdo->prepare(
'SELECT p.id AS participant_id, b.id AS booking_id, p.password_hash
FROM participants p
INNER JOIN bookings b ON b.participant_id = p.id
WHERE p.email = :email
LIMIT 1'
);
$statement->execute([':email' => $email]);
$participant = $statement->fetch(PDO::FETCH_ASSOC);
if ($participant !== false && password_verify($password, $participant['password_hash'])) {
unset($_SESSION['edit_login_failures'], $_SESSION['edit_locked_until']);
session_regenerate_id(true);
$_SESSION['edit_participant_id'] = $participant['participant_id'];
$_SESSION['edit_booking_id'] = (int) $participant['booking_id'];
$_SESSION['edit_csrf'] = bin2hex(random_bytes(32));
$_SESSION['edit_last_activity'] = time();
redirectToEdit();
}
$failures = (int) ($_SESSION['edit_login_failures'] ?? 0) + 1;
$_SESSION['edit_login_failures'] = $failures;
if ($failures >= 5) {
$_SESSION['edit_locked_until'] = time() + 300;
}
usleep(500000);
$message = 'E-Mail-Adresse oder Passwort ist ungültig.';
}
}
if (isset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'])) {
$lastActivity = (int) ($_SESSION['edit_last_activity'] ?? 0);
if ($lastActivity > 0 && $lastActivity < time() - 1800) {
unset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'], $_SESSION['edit_csrf']);
session_regenerate_id(true);
} else {
$_SESSION['edit_last_activity'] = time();
}
}
if (!isset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'])) {
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registrierung ändern</title>
<link rel="stylesheet" href="./css/booking.css">
</head>
<body>
<main class="success-card">
<h1>Registrierung ändern</h1>
<p>Gib die E-Mail-Adresse und das Passwort ein, die du nach der Registrierung erhalten hast.</p>
<?php if ($message !== ''): ?>
<p class="message error"><?= escapeHtml($message) ?></p>
<?php endif; ?>
<form method="post" autocomplete="off">
<input type="hidden" name="action" value="login">
<div class="form-group">
<label for="email">E-Mail-Adresse:</label>
<input type="email" id="email" name="email" required autocomplete="username">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<button type="submit">Registrierung laden</button>
</form>
<p><a href="index.php">Zur Startseite</a></p>
</main>
</body>
</html><?php
exit;
}
$participantId = (string) $_SESSION['edit_participant_id'];
$bookingId = (int) $_SESSION['edit_booking_id'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
requireEditCsrf();
$action = postString('action');
try {
if ($action === 'logout') {
unset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'], $_SESSION['edit_csrf']);
setcookie(session_name(), '', [
'expires' => time() - 42000,
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
'httponly' => true,
'samesite' => 'Strict',
]);
session_destroy();
redirectToEdit();
}
if ($action === 'delete_booking') {
$deletedStatement = $pdo->prepare(
'SELECT p.name, p.email, p.role, b.accommodation_type,
b.roommate_requests, b.early_departure, b.other_notes
FROM participants p
INNER JOIN bookings b ON b.participant_id = p.id
WHERE p.id = :participant_id AND b.id = :booking_id'
);
$deletedStatement->execute([
':participant_id' => $participantId,
':booking_id' => $bookingId,
]);
$deletedBooking = $deletedStatement->fetch(PDO::FETCH_ASSOC);
if ($deletedBooking === false) {
throw new InvalidArgumentException('Registrierung nicht gefunden.');
}
$deletedBooking['accommodation_type'] = match ($deletedBooking['accommodation_type']) {
'single' => 'Einzelzimmer',
'double' => 'Doppelzimmer',
'multi' => 'Mehrbettzimmer',
default => '',
};
$deletedBooking['roommate_requests'] = $deletedBooking['roommate_requests'] ?: '';
$deletedBooking['other_notes'] = $deletedBooking['other_notes'] ?: '';
$pdo->beginTransaction();
$deleteBooking = $pdo->prepare(
'DELETE FROM bookings WHERE id = :booking_id AND participant_id = :participant_id'
);
$deleteBooking->execute([
':booking_id' => $bookingId,
':participant_id' => $participantId,
]);
$deleteParticipant = $pdo->prepare('DELETE FROM participants WHERE id = :participant_id');
$deleteParticipant->execute([':participant_id' => $participantId]);
$pdo->commit();
$adminEmails = $pdo->query(
"SELECT email FROM admin_users WHERE is_active = 1 AND email <> ''"
)->fetchAll(PDO::FETCH_COLUMN);
foreach ($adminEmails as $adminEmail) {
try {
sendDeletionAdminEmail($mailConfig, (string) $adminEmail, $deletedBooking);
} catch (Throwable $exception) {
error_log('Admin-Benachrichtigung über Löschung konnte nicht versendet werden: ' . $exception->getMessage());
}
}
unset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'], $_SESSION['edit_csrf']);
$_SESSION['edit_message'] = 'Die Registrierung wurde gelöscht.';
redirectToEdit();
}
if ($action === 'update_booking') {
$data = validateBookingInput();
$role = $data['guest_type'] === 'day_guest' ? 'Tagesgast' : 'Dauergast';
$bookingType = $data['guest_type'] === 'day_guest' ? 'Day' : 'Stay';
$accommodationType = $data['guest_type'] === 'regular_guest' ? $data['room_type'] : null;
$roommateRequest = $data['guest_type'] === 'regular_guest' && $data['roommates'] !== ''
? $data['roommates']
: null;
$allowedEarlyDepartures = ['sat_breakfast', 'sat_lunch', 'sat_coffee', 'sat_dinner', 'sun_breakfast'];
$earlyDeparture = $data['guest_type'] === 'regular_guest'
&& in_array($data['early_departure'], $allowedEarlyDepartures, true)
? $data['early_departure']
: null;
$mealPreferences = $data['guest_type'] === 'day_guest'
? json_encode(['days' => $data['days'], 'meals_by_day' => $data['meals_by_day']], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)
: null;
$pdo->beginTransaction();
$existingPriceStatement = $pdo->prepare(
'SELECT manual_total_price FROM bookings WHERE id = :booking_id AND participant_id = :participant_id FOR UPDATE'
);
$existingPriceStatement->execute([
':booking_id' => $bookingId,
':participant_id' => $participantId,
]);
$existingManualPrice = $existingPriceStatement->fetchColumn();
if ($existingManualPrice === false) {
throw new InvalidArgumentException('Registrierung nicht gefunden.');
}
$participantStatement = $pdo->prepare(
'UPDATE participants
SET name = :name, email = :email, allergies = :allergies, role = :role, under_27 = :under_27
WHERE id = :participant_id'
);
$participantStatement->execute([
':name' => $data['name'],
':email' => $data['email'],
':allergies' => $data['allergies'] !== '' ? $data['allergies'] : null,
':role' => $role,
':under_27' => isset($_POST['under_27']) && $_POST['under_27'] === '1' ? 1 : 0,
':participant_id' => $participantId,
]);
$totalPrice = calculateBookingTotal(
$pdo,
isset($_POST['under_27']) && $_POST['under_27'] === '1',
$data['guest_type'],
$accommodationType,
$data['meals_by_day']
);
$bookingStatement = $pdo->prepare(
'UPDATE bookings
SET type = :type, accommodation_type = :accommodation_type,
meal_preferences = :meal_preferences, roommate_requests = :roommate_requests,
early_departure = :early_departure,
other_notes = :other_notes, total_price = :total_price
WHERE id = :booking_id AND participant_id = :participant_id'
);
$bookingStatement->execute([
':type' => $bookingType,
':accommodation_type' => $accommodationType,
':meal_preferences' => $mealPreferences,
':roommate_requests' => $roommateRequest,
':early_departure' => $earlyDeparture,
':other_notes' => $data['other_notes'] !== '' ? $data['other_notes'] : null,
':total_price' => $existingManualPrice !== null
? number_format((float) $existingManualPrice, 2, '.', '')
: number_format($totalPrice, 2, '.', ''),
':booking_id' => $bookingId,
':participant_id' => $participantId,
]);
$pdo->commit();
$_SESSION['edit_message'] = 'Die Registrierung wurde aktualisiert.';
redirectToEdit();
}
} catch (InvalidArgumentException | JsonException $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$message = $exception->getMessage();
} catch (PDOException $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log($exception->getMessage());
$message = 'Die Änderung konnte nicht gespeichert werden. Prüfe bitte deine Eingaben.';
}
}
$statement = $pdo->prepare(
'SELECT p.name, p.email, p.role, p.under_27, p.allergies,
b.type, b.accommodation_type, b.meal_preferences,
b.roommate_requests, b.early_departure, b.other_notes
FROM participants p
INNER JOIN bookings b ON b.participant_id = p.id
WHERE p.id = :participant_id AND b.id = :booking_id'
);
$statement->execute([':participant_id' => $participantId, ':booking_id' => $bookingId]);
$booking = $statement->fetch(PDO::FETCH_ASSOC);
if ($booking === false) {
unset($_SESSION['edit_participant_id'], $_SESSION['edit_booking_id'], $_SESSION['edit_csrf']);
http_response_code(404);
exit('Registrierung nicht gefunden.');
}
$mealData = $booking['meal_preferences'] !== null
? json_decode($booking['meal_preferences'], true)
: [];
$mealData = is_array($mealData) ? $mealData : [];
$selectedMealsByDay = is_array($mealData['meals_by_day'] ?? null) ? $mealData['meals_by_day'] : [];
if ($selectedMealsByDay === [] && is_array($mealData['meals'] ?? null)) {
foreach ($mealData['days'] ?? [] as $day) {
$selectedMealsByDay[(string) $day] = $mealData['meals'];
}
}
$guestType = $booking['type'] === 'Day' ? 'day_guest' : 'regular_guest';
$csrf = editCsrfToken();
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registrierung ändern</title>
<link rel="stylesheet" href="./css/booking.css">
</head>
<body>
<main class="success-card">
<h1>Registrierung ändern</h1>
<?php if ($message !== ''): ?>
<p class="message"><?= escapeHtml($message) ?></p>
<?php endif; ?>
<form method="post">
<input type="hidden" name="action" value="update_booking">
<input type="hidden" name="csrf_token" value="<?= escapeHtml($csrf) ?>">
<div class="form-group">
<label for="name">Name des Gastes:</label>
<input type="text" id="name" name="name" maxlength="255" value="<?= escapeHtml($booking['name']) ?>" required>
<label for="email">E-Mail-Adresse:</label>
<input type="email" id="email" name="email" maxlength="255" value="<?= escapeHtml($booking['email']) ?>" required>
<label class="checkbox-label" for="under_27">
<input type="checkbox" id="under_27" name="under_27" value="1" <?= (int) $booking['under_27'] === 1 ? 'checked' : '' ?>>
Unter 27
</label>
</div>
<div class="form-group">
<label for="guest_type">Gasttyp:</label>
<select id="guest_type" name="guest_type" required>
<option value="">Bitte auswählen</option>
<option value="day_guest" <?= $guestType === 'day_guest' ? 'selected' : '' ?>>Tagesgast</option>
<option value="regular_guest" <?= $guestType === 'regular_guest' ? 'selected' : '' ?>>Dauergast</option>
</select>
</div>
<div class="form-group" id="room_details">
<div id="regular_guest_options">
<label for="room_type">Zimmerart:</label>
<select id="room_type" name="room_type">
<option value="single" <?= $booking['accommodation_type'] === 'single' ? 'selected' : '' ?>>Einzelzimmer</option>
<option value="double" <?= $booking['accommodation_type'] === 'double' ? 'selected' : '' ?>>Doppelzimmer</option>
<option value="multi" <?= $booking['accommodation_type'] === 'multi' ? 'selected' : '' ?>>Mehrbettzimmer</option>
</select>
<div id="roommate_info" style="display: none;">
<label for="roommates">Mit wem möchtest du das Zimmer teilen?</label>
<input type="text" id="roommates" name="roommates" value="<?= escapeHtml((string) ($booking['roommate_requests'] ?? '')) ?>" placeholder="Name des Mitbewohners">
</div>
<label class="checkbox-label" for="early_departure_enabled">
<input type="checkbox" id="early_departure_enabled" name="early_departure_enabled" value="1" <?= !empty($booking['early_departure']) ? 'checked' : '' ?>>
Ich reise vorzeitig ab.
</label>
<div id="early_departure_options" style="display: none;">
<label>Abreisezeitpunkt:</label>
<?php $earlyDepartureOptions = [
'sat_breakfast' => 'Samstag nach dem Frühstück',
'sat_lunch' => 'Samstag nach dem Mittagessen',
'sat_coffee' => 'Samstag nach dem Kaffee',
'sat_dinner' => 'Samstag nach dem Abendessen',
'sun_breakfast' => 'Sonntag nach dem Frühstück',
]; ?>
<div class="checkbox-group">
<?php foreach ($earlyDepartureOptions as $value => $label): ?>
<label><input type="radio" name="early_departure" value="<?= $value ?>" <?= $booking['early_departure'] === $value ? 'checked' : '' ?>> <?= $label ?></label>
<?php endforeach; ?>
</div>
</div>
</div>
<div id="day_guest_options" style="display: none;">
<label>Anwesenheitstage:</label>
<div class="checkbox-group">
<?php foreach (['1' => 'Fr.', '2' => 'Sa.', '3' => 'So.'] as $value => $label): ?>
<label><input type="checkbox" name="days[]" value="<?= $value ?>" <?= in_array($value, $mealData['days'] ?? [], true) ? 'checked' : '' ?>> <?= $label ?></label>
<?php endforeach; ?>
</div>
<label>Gewünschte Mahlzeiten je Anwesenheitstag:</label>
<?php $mealOptionsByDay = [
'1' => ['dinner' => 'Abendessen'],
'2' => ['breakfast' => 'Frühstück', 'lunch' => 'Mittagessen', 'coffee' => 'Kaffee', 'dinner' => 'Abendessen'],
'3' => ['breakfast' => 'Frühstück', 'lunch' => 'Mittagessen'],
]; ?>
<?php foreach (['1' => 'Fr.', '2' => 'Sa.', '3' => 'So.'] as $dayValue => $dayLabel): ?>
<fieldset class="meal-day">
<legend><?= escapeHtml($dayLabel) ?></legend>
<div class="checkbox-group">
<?php foreach ($mealOptionsByDay[$dayValue] as $value => $label): ?>
<label><input type="checkbox" name="meals[<?= $dayValue ?>][]" value="<?= $value ?>" <?= in_array($value, $selectedMealsByDay[$dayValue] ?? [], true) ? 'checked' : '' ?>> <?= $label ?></label>
<?php endforeach; ?>
</div>
</fieldset>
<?php endforeach; ?>
<p class="form-help">Die Getränkepauschale und die Proberaumpauschale werden für Tagesgäste automatisch pro Anwesenheitstag berechnet.</p>
</div>
</div>
<div class="form-group">
<label for="allergies">Allergien, Essensvorlieben oder religiöse Einschränkungen:</label>
<input type="text" id="allergies" name="allergies" value="<?= escapeHtml((string) ($booking['allergies'] ?? '')) ?>" placeholder="z.B. Vegetarier, Vegan, Glutenunverträglichkeit, etc.">
</div>
<div class="form-group">
<label for="other_notes">Sonstiges:</label>
<textarea id="other_notes" name="other_notes" rows="5" maxlength="5000" placeholder="Weitere Hinweise oder Wünsche"><?= escapeHtml((string) ($booking['other_notes'] ?? '')) ?></textarea>
</div>
<button type="submit">Änderungen speichern</button>
</form>
<form method="post" data-confirm="Möchtest du die Buchung wirklich löschen?" style="margin-top: 16px;">
<input type="hidden" name="action" value="delete_booking">
<input type="hidden" name="csrf_token" value="<?= escapeHtml($csrf) ?>">
<button class="button-danger" type="submit">Gesamte Buchung löschen</button>
</form>
<p><a href="index.php">Zur Startseite</a></p>
</main>
<script src="./scripts/functions.js?v=20260918-2"></script>
<script src="./scripts/security.js"></script>
</body>
</html>