diff --git a/aurora-livecam/SettingsManager.php b/aurora-livecam/SettingsManager.php index 6c8b7a6..3dc0415 100644 --- a/aurora-livecam/SettingsManager.php +++ b/aurora-livecam/SettingsManager.php @@ -26,7 +26,8 @@ class SettingsManager { return [ 'viewer_display' => [ 'enabled' => true, - 'min_viewers' => 1 + 'min_viewers' => 1, + 'update_interval' => 5 // Sekunden ], 'video_mode' => [ 'play_in_player' => true, @@ -36,6 +37,52 @@ class SettingsManager { 'default_speed' => 1, 'available_speeds' => [1, 10, 100] ], + // Punkt 2: UI-Anzeige Features + 'ui_display' => [ + 'show_recommendation_banner' => true, + 'show_qr_code' => true, + 'show_social_media' => true, + 'show_patrouille_suisse' => true + ], + // Punkt 3: Zoom & Timelapse + 'zoom_timelapse' => [ + 'show_zoom_controls' => true, + 'max_zoom_level' => 4.0, + 'timelapse_reverse_enabled' => true + ], + // Punkt 5: Content Management + 'content' => [ + 'guestbook_enabled' => true, + 'gallery_enabled' => true, + 'ai_events_enabled' => true, + 'max_guestbook_entries' => 50 + ], + // Punkt 6: Technische Settings + 'technical' => [ + 'viewer_update_interval' => 5, // Sekunden + 'session_timeout' => 30 // Sekunden + ], + // Punkt 7: Theme & Design + 'theme' => [ + 'default_theme' => 'theme-legacy', + 'show_theme_switcher' => false + ], + // Punkt 8: SEO & Meta + 'seo' => [ + 'custom_title' => '', + 'meta_description' => '', + 'meta_keywords' => '' + ], + // Weather Widget + 'weather' => [ + 'enabled' => true, + 'api_key' => '', + 'location' => 'Oberdürnten,CH', + 'lat' => '47.2833', + 'lon' => '8.7167', + 'update_interval' => 5, // Minuten + 'units' => 'metric' // metric (Celsius) oder imperial (Fahrenheit) + ], 'last_updated' => null, 'updated_by' => null ]; @@ -123,4 +170,111 @@ class SettingsManager { public function shouldAllowDownload() { return $this->get('video_mode.allow_download') === true; } + + // UI Display Helper + public function shouldShowRecommendationBanner() { + return $this->get('ui_display.show_recommendation_banner') === true; + } + + public function shouldShowQRCode() { + return $this->get('ui_display.show_qr_code') === true; + } + + public function shouldShowSocialMedia() { + return $this->get('ui_display.show_social_media') === true; + } + + public function shouldShowPatrouillesuisse() { + return $this->get('ui_display.show_patrouille_suisse') === true; + } + + // Content Management Helper + public function isGuestbookEnabled() { + return $this->get('content.guestbook_enabled') === true; + } + + public function isGalleryEnabled() { + return $this->get('content.gallery_enabled') === true; + } + + public function isAIEventsEnabled() { + return $this->get('content.ai_events_enabled') === true; + } + + public function getMaxGuestbookEntries() { + return $this->get('content.max_guestbook_entries') ?? 50; + } + + // Theme Helper + public function getDefaultTheme() { + return $this->get('theme.default_theme') ?? 'theme-legacy'; + } + + public function shouldShowThemeSwitcher() { + return $this->get('theme.show_theme_switcher') === true; + } + + // Technical Helper + public function getViewerUpdateInterval() { + return $this->get('technical.viewer_update_interval') ?? 5; + } + + public function getSessionTimeout() { + return $this->get('technical.session_timeout') ?? 30; + } + + // Zoom & Timelapse Helper + public function shouldShowZoomControls() { + return $this->get('zoom_timelapse.show_zoom_controls') === true; + } + + public function getMaxZoomLevel() { + return $this->get('zoom_timelapse.max_zoom_level') ?? 4.0; + } + + public function isTimelapseReverseEnabled() { + return $this->get('zoom_timelapse.timelapse_reverse_enabled') === true; + } + + // SEO Helper + public function getCustomTitle() { + $title = $this->get('seo.custom_title'); + return !empty($title) ? $title : null; + } + + public function getMetaDescription() { + return $this->get('seo.meta_description') ?? ''; + } + + public function getMetaKeywords() { + return $this->get('seo.meta_keywords') ?? ''; + } + + // Weather Helper + public function isWeatherEnabled() { + return $this->get('weather.enabled') === true; + } + + public function getWeatherApiKey() { + return $this->get('weather.api_key') ?? ''; + } + + public function getWeatherLocation() { + return $this->get('weather.location') ?? 'Oberdürnten,CH'; + } + + public function getWeatherCoords() { + return [ + 'lat' => $this->get('weather.lat') ?? '47.2833', + 'lon' => $this->get('weather.lon') ?? '8.7167' + ]; + } + + public function getWeatherUpdateInterval() { + return $this->get('weather.update_interval') ?? 5; + } + + public function getWeatherUnits() { + return $this->get('weather.units') ?? 'metric'; + } } diff --git a/aurora-livecam/WeatherManager.php b/aurora-livecam/WeatherManager.php new file mode 100644 index 0000000..bd96cec --- /dev/null +++ b/aurora-livecam/WeatherManager.php @@ -0,0 +1,215 @@ +settingsManager = $settingsManager; + } + + /** + * Holt aktuelle Wetterdaten (cached) + */ + public function getCurrentWeather() { + // Prüfe ob Weather aktiviert ist + if (!$this->settingsManager->isWeatherEnabled()) { + return null; + } + + // Prüfe Cache + $cached = $this->getCache(); + if ($cached !== null) { + return $cached; + } + + // Hole frische Daten von API (Open-Meteo) + $coords = $this->settingsManager->getWeatherCoords(); + + // Open-Meteo API URL - komplett kostenlos, kein API Key! + $url = "https://api.open-meteo.com/v1/forecast?" . http_build_query([ + 'latitude' => $coords['lat'], + 'longitude' => $coords['lon'], + 'current' => 'temperature_2m,relative_humidity_2m,precipitation,weather_code,wind_speed_10m,wind_direction_10m,pressure_msl,cloud_cover', + 'timezone' => 'Europe/Zurich' + ]); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200 || !$response) { + return ['error' => 'API Fehler']; + } + + $data = json_decode($response, true); + if (!$data || !isset($data['current'])) { + return ['error' => 'Ungültige API Antwort']; + } + + $current = $data['current']; + + // Formatiere Daten + $weather = [ + 'temp' => round($current['temperature_2m'], 1), + 'feels_like' => round($current['temperature_2m'], 1), // Open-Meteo hat keine "feels like" + 'humidity' => $current['relative_humidity_2m'], + 'pressure' => round($current['pressure_msl'], 0), + 'wind_speed' => round($current['wind_speed_10m'], 1), // Schon in km/h! + 'wind_deg' => $current['wind_direction_10m'], + 'wind_direction' => $this->getWindDirection($current['wind_direction_10m']), + 'clouds' => $current['cloud_cover'] ?? 0, + 'description' => $this->getWeatherDescription($current['weather_code']), + 'icon' => $this->getWeatherIcon($current['weather_code']), + 'rain_1h' => $current['precipitation'] ?? 0, + 'snow_1h' => 0, // Open-Meteo gibt Niederschlag gesamt + 'location' => $this->settingsManager->getWeatherLocation(), + 'timestamp' => time() + ]; + + // Cache speichern + $this->saveCache($weather); + + return $weather; + } + + /** + * Wandelt WMO Weather Code in Beschreibung um + * https://open-meteo.com/en/docs + */ + private function getWeatherDescription($code) { + $descriptions = [ + 0 => 'Klar', + 1 => 'Überwiegend klar', + 2 => 'Teilweise bewölkt', + 3 => 'Bewölkt', + 45 => 'Neblig', + 48 => 'Nebel mit Reifablagerung', + 51 => 'Leichter Nieselregen', + 53 => 'Mäßiger Nieselregen', + 55 => 'Dichter Nieselregen', + 61 => 'Leichter Regen', + 63 => 'Mäßiger Regen', + 65 => 'Starker Regen', + 71 => 'Leichter Schneefall', + 73 => 'Mäßiger Schneefall', + 75 => 'Starker Schneefall', + 77 => 'Schneegraupeln', + 80 => 'Leichte Regenschauer', + 81 => 'Mäßige Regenschauer', + 82 => 'Starke Regenschauer', + 85 => 'Leichte Schneeschauer', + 86 => 'Starke Schneeschauer', + 95 => 'Gewitter', + 96 => 'Gewitter mit leichtem Hagel', + 99 => 'Gewitter mit starkem Hagel' + ]; + + return $descriptions[$code] ?? 'Unbekannt'; + } + + /** + * Wandelt WMO Weather Code in Icon-Code um (OpenWeatherMap kompatibel) + */ + private function getWeatherIcon($code) { + if ($code == 0) return '01d'; // Klar + if ($code >= 1 && $code <= 2) return '02d'; // Teilweise bewölkt + if ($code == 3) return '04d'; // Bewölkt + if ($code >= 45 && $code <= 48) return '50d'; // Nebel + if ($code >= 51 && $code <= 55) return '09d'; // Nieselregen + if ($code >= 61 && $code <= 65) return '10d'; // Regen + if ($code >= 71 && $code <= 77) return '13d'; // Schnee + if ($code >= 80 && $code <= 82) return '09d'; // Regenschauer + if ($code >= 85 && $code <= 86) return '13d'; // Schneeschauer + if ($code >= 95 && $code <= 99) return '11d'; // Gewitter + + return '01d'; // Default + } + + /** + * Wandelt Windrichtung (Grad) in Kompassrichtung um + */ + private function getWindDirection($deg) { + $directions = ['N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']; + $index = round($deg / 22.5) % 16; + return $directions[$index]; + } + + /** + * Holt Daten aus Cache (wenn noch gültig) + */ + private function getCache() { + if (!file_exists($this->cacheFile)) { + return null; + } + + $content = file_get_contents($this->cacheFile); + $data = json_decode($content, true); + + if (!$data || !isset($data['timestamp'])) { + return null; + } + + // Update-Intervall aus Settings holen (in Minuten) + $updateInterval = $this->settingsManager->getWeatherUpdateInterval() * 60; // Minuten -> Sekunden + + // Prüfe ob Cache noch gültig + if (time() - $data['timestamp'] < $updateInterval) { + return $data; + } + + return null; + } + + /** + * Speichert Daten im Cache + */ + private function saveCache($data) { + $json = json_encode($data, JSON_PRETTY_PRINT); + file_put_contents($this->cacheFile, $json, LOCK_EX); + } + + /** + * Gibt Wetter-Icon-Emoji zurück + */ + public function getWeatherEmoji($iconCode) { + $map = [ + '01d' => '☀️', '01n' => '🌙', + '02d' => '⛅', '02n' => '☁️', + '03d' => '☁️', '03n' => '☁️', + '04d' => '☁️', '04n' => '☁️', + '09d' => '🌧️', '09n' => '🌧️', + '10d' => '🌦️', '10n' => '🌧️', + '11d' => '⛈️', '11n' => '⛈️', + '13d' => '❄️', '13n' => '❄️', + '50d' => '🌫️', '50n' => '🌫️' + ]; + return $map[$iconCode] ?? '🌤️'; + } + + /** + * AJAX Handler für Wetter-Updates + */ + public function handleAjax() { + if ($_SERVER['REQUEST_METHOD'] !== 'GET') return; + if (!isset($_GET['weather_action'])) return; + + header('Content-Type: application/json'); + + if ($_GET['weather_action'] === 'get') { + $weather = $this->getCurrentWeather(); + echo json_encode(['success' => true, 'data' => $weather]); + exit; + } + } +} diff --git a/aurora-livecam/clear-cache.php b/aurora-livecam/clear-cache.php new file mode 100644 index 0000000..b1363fa --- /dev/null +++ b/aurora-livecam/clear-cache.php @@ -0,0 +1,15 @@ + diff --git a/aurora-livecam/index.php b/aurora-livecam/index.php index 3b12eed..bdde27c 100644 --- a/aurora-livecam/index.php +++ b/aurora-livecam/index.php @@ -4,12 +4,17 @@ use PHPMailer\PHPMailer\Exception; require __DIR__ . '/vendor/autoload.php'; require_once 'SettingsManager.php'; +require_once 'WeatherManager.php'; // SettingsManager initialisieren $settingsManager = new SettingsManager(); -// AJAX-Handler für Settings (VOR anderen Ausgaben!) +// WeatherManager initialisieren +$weatherManager = new WeatherManager($settingsManager); + +// AJAX-Handler für Settings und Weather (VOR anderen Ausgaben!) $settingsManager->handleAjax(); +$weatherManager->handleAjax(); if (isset($_GET['download_video'])) { $videoDir = './videos/'; @@ -559,7 +564,7 @@ class VisualCalendarManager { } // === AI-EREIGNISSE === - if (!empty($aiEvents)) { + if (!empty($aiEvents) && (!$this->settingsManager || $this->settingsManager->isAIEventsEnabled())) { $output .= '