false, 'error' => 'Bitte die Berechnung über das Formular starten.'], 405); } if (!isset($_SESSION['user_id'])) { observationReply(['ok' => false, 'error' => 'Bitte erneut anmelden.'], 401); } $token = $_POST['csrf_token'] ?? null; if (!is_string($token) || !isset($_SESSION['observation_csrf']) || !hash_equals($_SESSION['observation_csrf'], $token)) { observationReply(['ok' => false, 'error' => 'Sitzung abgelaufen. Bitte die Seite neu laden.'], 403); } try { $cfg = require __DIR__ . '/../../config/database.php'; $pdo = new PDO(sprintf('mysql:host=%s;dbname=%s;charset=%s', $cfg['host'], $cfg['dbname'], $cfg['charset'] ?? 'utf8mb4'), $cfg['user'], $cfg['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]); $userId = (int) $_SESSION['user_id']; if (!currentUserIsAdminLike()) { $permission = $pdo->prepare('SELECT 1 FROM app_user_page_permissions WHERE user_id = ? AND page_key = ? LIMIT 1'); $permission->execute([$userId, 'beobachtungsvorschlaege.php']); if (!$permission->fetchColumn()) { observationReply(['ok' => false, 'error' => 'Keine Berechtigung für Beobachtungsvorschläge.'], 403); } } $stmt = $pdo->prepare('SELECT name, latitude, longitude, elevation, timezone FROM app_user_locations WHERE user_id = ? AND is_default = 1 LIMIT 1'); $stmt->execute([$userId]); $location = $stmt->fetch(); if (!$location) { observationReply(['ok' => false, 'error' => 'Bitte zuerst einen Standardstandort in den Favoriten hinterlegen.'], 422); } $filters = []; foreach (['observation_date', 'object_size_min', 'object_size_max', 'object_magnitude', 'object_altitude_min', 'object_altitude_max', 'object_azimuth_start', 'object_azimuth_end', 'sun_altitude_max', 'moon_separation_min', 'include_unknown_magnitude'] as $key) { $value = $_POST[$key] ?? ''; if (!is_string($value) || strlen($value) > 32) { observationReply(['ok' => false, 'error' => 'Ungültige Filterangabe.'], 422); } $filters[$key] = $value; } foreach (['object_types' => ['moon', 'planets', 'open_clusters', 'globular_clusters', 'galaxies', 'nebulae', 'planetary_nebulae', 'double_stars', 'variable_stars', 'comets'], 'night_halves' => ['first', 'second']] as $key => $allowed) { $values = $_POST[$key] ?? []; if (!is_array($values) || !$values || count($values) > count($allowed)) { observationReply(['ok' => false, 'error' => 'Bitte mindestens einen Objekttyp und eine Nachthälfte auswählen.'], 422); } foreach ($values as $value) { if (!is_string($value) || !in_array($value, $allowed, true)) { observationReply(['ok' => false, 'error' => 'Ungültige Auswahl.'], 422); } } $filters[$key] = array_values(array_unique($values)); } // No session lock while Python is working; other pages remain usable. session_write_close(); set_time_limit(120); $payload = ['filters' => $filters, 'location' => $location, 'objects' => observationCatalog($pdo, $filters['object_types'])]; $result = runObservationSuggestions($payload); observationReply($result, $result['ok'] ? 200 : 422); } catch (Throwable $e) { error_log('Observation suggestions: ' . $e->getMessage()); observationReply(['ok' => false, 'error' => $e instanceof RuntimeException && !($e instanceof PDOException) ? $e->getMessage() : 'Die Beobachtungsvorschläge konnten nicht berechnet werden. Bitte später erneut versuchen.'], 500); }