Files
sufw-webseite-terminplanung/development-system/public/booking/api.php
T

77 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__.'/backend.php';
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');
function respond(int $status, array $payload): never
{
http_response_code($status);
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
exit;
}
function bookingSession(): void
{
if (session_status() === PHP_SESSION_ACTIVE) return;
session_name('sufw_booking');
session_set_cookie_params(['httponly' => true, 'samesite' => 'Lax', 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')]);
session_start();
}
function csrfToken(): string
{
bookingSession();
return $_SESSION['csrf_token'] ??= bin2hex(random_bytes(32));
}
function readPayload(): array
{
if ((int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 32768) respond(413, ['error' => 'Die Anfrage ist zu groß.']);
try { $payload = json_decode((string) file_get_contents('php://input'), true, 32, JSON_THROW_ON_ERROR); }
catch (JsonException) { respond(400, ['error' => 'Die Anfrage konnte nicht verarbeitet werden.']); }
if (!is_array($payload)) respond(400, ['error' => 'Die Anfrage konnte nicht verarbeitet werden.']);
return $payload;
}
function ensureRateLimit(): void
{
bookingSession();
$now = time();
$recent = array_values(array_filter($_SESSION['booking_requests'] ?? [], static fn ($value): bool => is_int($value) && $value > $now - 900));
if (count($recent) >= 10) respond(429, ['error' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.']);
$recent[] = $now;
$_SESSION['booking_requests'] = $recent;
}
try {
$database = bookingDatabase();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$project = (string) ($_GET['project'] ?? '');
$from = (string) ($_GET['from'] ?? '');
$to = (string) ($_GET['to'] ?? '');
$fromDate = DateTimeImmutable::createFromFormat('!Y-m-d', $from);
$toDate = DateTimeImmutable::createFromFormat('!Y-m-d', $to);
if (!bookingProjectIsValid($project) || !$fromDate || !$toDate || $fromDate >= $toDate || $fromDate->diff($toDate)->days > 93) respond(400, ['error' => 'Ungültige Kalenderanfrage.']);
respond(200, ['csrfToken' => csrfToken(), 'events' => bookingListEvents($database, $project, $from, $to)]);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
ensureRateLimit();
if (!hash_equals(csrfToken(), (string) ($_SERVER['HTTP_X_SUFW_BOOKING_CSRF'] ?? ''))) respond(403, ['error' => 'Die Sitzung ist abgelaufen. Bitte laden Sie den Kalender neu.']);
try { $registration = bookingCreateRegistration($database, readPayload()); }
catch (InvalidArgumentException $exception) { respond(422, ['error' => $exception->getMessage()]); }
catch (OutOfBoundsException $exception) { respond(404, ['error' => $exception->getMessage()]); }
catch (LogicException|RuntimeException $exception) { respond(409, ['error' => $exception->getMessage()]); }
respond(202, ['message' => 'Ihre Anfrage wurde gespeichert. Bitte bestätigen Sie den Link aus der E-Mail innerhalb von 30 Minuten.', 'expiresAt' => $registration['expiresAt']]);
}
header('Allow: GET, POST');
respond(405, ['error' => 'Diese Methode wird nicht unterstützt.']);
} catch (Throwable $exception) {
error_log('SUFW booking API: '.$exception->getMessage());
respond(500, ['error' => 'Der Kalender ist vorübergehend nicht verfügbar.']);
}