$planned->format(DATE_ATOM), 'when' => $actual?->format(DATE_ATOM), 'delay' => $actual ? $actual->getTimestamp() - $planned->getTimestamp() : null, 'cancelled' => $cancelled, 'line' => ['name' => $line], 'direction' => trim((string) ($departure['direction'] ?? '')), ]; } function dashboard_fetch_departures(): array { $directory = __DIR__ . '/../cache'; if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory)) { throw new RuntimeException('VBB-Cache nicht beschreibbar.'); } $file = fopen($directory . '/vbb-' . DASHBOARD_VBB_STOP_ID . '.json', 'c+'); if (!$file) { throw new RuntimeException('VBB-Cache nicht verfügbar.'); } try { if (!flock($file, LOCK_EX)) { throw new RuntimeException('VBB-Cache konnte nicht gesperrt werden.'); } $cache = json_decode(stream_get_contents($file), true) ?: []; if (microtime(true) >= ($cache['retryAt'] ?? 0)) { try { $query = http_build_query([ 'id' => DASHBOARD_VBB_STOP_ID, 'duration' => 120, 'maxJourneys' => 30, 'products' => DASHBOARD_VBB_BUS_PRODUCTS, 'type' => 'DEP', 'format' => 'json', 'lang' => 'de', ], '', '&', PHP_QUERY_RFC3986); $baseUrl = rtrim(intranet_env('VBB_API_BASE_URL'), '/'); $data = dashboard_http_json($baseUrl . '/departureBoard?' . $query, [ 'Authorization: Bearer ' . intranet_env('VBB_ACCESS_ID'), ]); if (!isset($data['Departure']) || !is_array($data['Departure'])) { throw new RuntimeException('Unerwartete VBB-Antwort.'); } $departures = []; foreach ($data['Departure'] as $departure) { if (is_array($departure) && ($normalized = dashboard_vbb_normalize_departure($departure))) { $departures[] = $normalized; } } $now = microtime(true); $cache = [ 'departures' => array_slice($departures, 0, 30), 'updatedAt' => (int) round($now * 1000), 'retryAt' => $now + 30, 'unavailable' => false, ]; } catch (Throwable $error) { // Eine letzte erfolgreiche Antwort behalten und die API nach Fehlern nicht belasten. error_log('Dashboard VBB: ' . $error->getMessage()); $cache['unavailable'] = true; $cache['retryAt'] = microtime(true) + 60; } rewind($file); $json = json_encode($cache, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); if (!ftruncate($file, 0) || fwrite($file, $json) !== strlen($json) || !fflush($file)) { throw new RuntimeException('VBB-Cache konnte nicht gespeichert werden.'); } } return $cache + ['departures' => [], 'updatedAt' => 0]; } finally { fclose($file); } }