'; ai wieder einblende das unten rausnehmendas rein
+ $output .= '
';
+ $output .= '🤖 AI-Erkennung: ';
+ foreach ($this->aiCategories as $cat => $info) {
+ $output .= '' . $info['icon'] . ' ';
+ }
+ $output .= '
';
+
+ // Kalender-Grid
+ $output .= '
';
+
+ // Wochentage Header
+ $weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
+ foreach ($weekdays as $day) {
+ $output .= '
' . $day . '
';
+ }
+
+ // Tage des Monats
+ $firstDay = mktime(0, 0, 0, $currentMonth, 1, $currentYear);
+ $daysInMonth = date('t', $firstDay);
+ $dayOfWeek = date('N', $firstDay) - 1;
+
+ // Leere Zellen vor dem ersten Tag
+ for ($i = 0; $i < $dayOfWeek; $i++) {
+ $output .= '
';
+ }
+
+ // Tage mit Videos/AI-Events markieren
+ for ($day = 1; $day <= $daysInMonth; $day++) {
+ $hasVideos = $this->hasVideosForDate($currentYear, $currentMonth, $day);
+ $hasAiEvents = $this->hasAiEventsForDate($currentYear, $currentMonth, $day);
+ $aiIcons = $this->getAiIconsForDate($currentYear, $currentMonth, $day);
+ $isSelected = ($selectedDay == $day);
+ $isToday = ($currentYear == date('Y') && $currentMonth == date('n') && $day == date('j'));
+
+ $classes = 'calendar-day';
+ if ($hasVideos) $classes .= ' has-video';
+ if ($hasAiEvents) $classes .= ' has-ai-events';
+ if ($isSelected) $classes .= ' selected';
+ if ($isToday) $classes .= ' today';
+
+ $output .= '
';
+ $output .= '
' . $day . ' ';
+
+ // Video-Indikator
+ if ($hasVideos) {
+ $output .= '
📹 ';
+ }
+
+ // AI-Event-Icons (max 3 anzeigen)
+ if (!empty($aiIcons)) {
+ $output .= '
';
+ $displayIcons = array_slice($aiIcons, 0, 3);
+ foreach ($displayIcons as $icon) {
+ $output .= '' . $icon . ' ';
+ }
+ if (count($aiIcons) > 3) {
+ $output .= '+' . (count($aiIcons) - 3) . ' ';
+ }
+ $output .= '
';
+ }
+
+ $output .= '
';
+ }
+
+ $output .= '
'; // calendar-grid
+
+ // Video-Liste + AI-Events für ausgewählten Tag
+ if ($selectedDay) {
+ $videos = $this->getVideosForDate($currentYear, $currentMonth, $selectedDay);
+ $aiEvents = $this->getAiEventsForDate($currentYear, $currentMonth, $selectedDay);
+
+ $output .= '
';
+ $output .= '
📅 ' . sprintf('%02d.%02d.%04d', $selectedDay, $currentMonth, $currentYear) . ' ';
+
+ // === TAGESVIDEOS ===
+ if (!empty($videos)) {
+ $output .= '
';
+ $output .= '
📹 Tagesvideos ';
+ $output .= '
';
+
+ foreach ($videos as $video) {
+ $sizeInMb = round($video['filesize'] / (1024 * 1024), 2);
+ $token = hash_hmac('sha256', $video['path'], session_id());
+ $videoUrl = '?download_specific_video=' . urlencode($video['path']) . '&token=' . $token;
+ $videoWebUrl = $this->videoPathToUrl($video['path']);
+
+ $output .= '';
+ $output .= '🕐 ' . $video['time'] . ' Uhr ';
+ $output .= '' . $sizeInMb . ' MB ';
+ $output .= '';
+ $output .= ' ';
+ }
+
+ $output .= ' ';
+ $output .= '
';
+ }
+
+ // === AI-EREIGNISSE ===
+ if (!empty($aiEvents) && (!$this->settingsManager || $this->settingsManager->isAIEventsEnabled())) {
+ $output .= '
';
+ $output .= '
🤖 AI-erkannte Ereignisse ';
+ $output .= '
';
+
+ foreach ($aiEvents as $category => $event) {
+ $output .= '
';
+ $output .= '';
+
+ // Videos für dieses Event
+ $output .= '
';
+
+ $output .= '
'; // ai-event-card
+ }
+
+ $output .= '
'; // ai-events-grid
+ $output .= '
'; // ai-events-section
+ }
+
+ // Keine Inhalte
+ if (empty($videos) && empty($aiEvents)) {
+ $output .= '
';
+ $output .= '
📭 Keine Videos oder AI-Ereignisse für diesen Tag verfügbar.
';
+ $output .= '
';
+ }
+
+ $output .= '
'; // day-details
+ }
+
+ $output .= '
'; // visual-calendar-container
+
+ return $output;
+ }
+
+ /**
+ * Handler für AI-Video Downloads
+ */
+ public function handleAiVideoDownload() {
+ if (isset($_GET['download_ai_video']) && isset($_GET['token'])) {
+ $videoPath = $_GET['download_ai_video'];
+ $token = $_GET['token'];
+
+ // Token-Validierung
+ $expectedToken = hash_hmac('sha256', $videoPath, session_id());
+ if (!hash_equals($expectedToken, $token)) {
+ echo "Ungültiger Token. Zugriff verweigert.";
+ exit;
+ }
+
+ // Sicherheitsüberprüfung
+ $aiDir = realpath($this->aiDir);
+ $requestedPath = realpath($videoPath);
+
+ if ($requestedPath && strpos($requestedPath, $aiDir) === 0 && file_exists($requestedPath)) {
+ $extension = pathinfo($requestedPath, PATHINFO_EXTENSION);
+ if (strtolower($extension) !== 'mp4') {
+ echo "Nur MP4-Dateien können heruntergeladen werden.";
+ exit;
+ }
+
+ header('Content-Description: File Transfer');
+ header('Content-Type: video/mp4');
+ header('Content-Disposition: attachment; filename="'.basename($requestedPath).'"');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($requestedPath));
+ readfile($requestedPath);
+ exit;
+ } else {
+ echo "Datei nicht gefunden oder ungültiger Dateipfad.";
+ exit;
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class GuestbookManager {
+ private $entries = [];
+ private $dbFile = 'guestbook.json';
+
+ public function __construct() {
+ if (file_exists($this->dbFile)) {
+ $this->entries = json_decode(file_get_contents($this->dbFile), true);
+ }
+ }
+
+ public function handleFormSubmission() {
+ if (isset($_POST['guestbook'], $_POST['guest-name'], $_POST['guest-message'])) {
+ $this->addEntry($_POST['guest-name'], $_POST['guest-message']);
+ $this->saveEntries();
+ }
+ }
+
+ private function addEntry($name, $message) {
+ $this->entries[] = [
+ 'name' => $name,
+ 'message' => $message,
+ 'date' => date('Y-m-d H:i:s')
+ ];
+ }
+
+ public function deleteEntry($index) {
+ if (isset($this->entries[$index])) {
+ unset($this->entries[$index]);
+ $this->entries = array_values($this->entries); // Re-indizieren des Arrays
+ $this->saveEntries();
+ return true;
+ }
+ return false;
+ }
+
+
+
+ private function saveEntries() {
+ file_put_contents($this->dbFile, json_encode($this->entries));
+ }
+
+ public function displayForm() {
+ return '
+
';
+ foreach ($this->entries as $index => $entry) {
+ $output .= "
+
+
{$entry['name']}
+
{$entry['message']}
+
{$entry['date']}";
+ if ($isAdmin) {
+ $output .= "
";
+ }
+
+ }
+ $output .= '
';
+ return $output;
+}
+
+
+}
+
+
+class ContactManager {
+ private $adminEmail = 'metacube@gmail.com';
+ private $feedbackFile = 'feedbacks.json';
+ private $gmailUser = 'metacube@gmail.com';
+ private $gmailAppPassword = 'hrfb smpy jcrb rnfw';
+
+ public function displayForm() {
+ return '
+
+
';
+ }
+
+ public function handleSubmission($name, $email, $message) {
+ if (empty($name) || empty($email) || empty($message)) {
+ return ['success' => false, 'message' => 'Alle Felder sind erforderlich'];
+ }
+
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ return ['success' => false, 'message' => 'Ungültige E-Mail-Adresse'];
+ }
+
+ if (strlen($message) < 10) {
+ return ['success' => false, 'message' => 'Nachricht zu kurz (mindestens 10 Zeichen)'];
+ }
+
+ $name = htmlspecialchars(trim($name), ENT_QUOTES, 'UTF-8');
+ $email = filter_var(trim($email), FILTER_SANITIZE_EMAIL);
+ $message = htmlspecialchars(trim($message), ENT_QUOTES, 'UTF-8');
+
+ $feedback = [
+ 'name' => $name,
+ 'email' => $email,
+ 'message' => $message,
+ 'date' => date('Y-m-d H:i:s'),
+ 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
+ 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
+ ];
+
+ $feedbacks = file_exists($this->feedbackFile)
+ ? json_decode(file_get_contents($this->feedbackFile), true)
+ : [];
+
+ if (!is_array($feedbacks)) $feedbacks = [];
+
+ $feedbacks[] = $feedback;
+ file_put_contents($this->feedbackFile, json_encode($feedbacks, JSON_PRETTY_PRINT));
+
+ $mailSent = $this->sendEmailViaGmail($name, $email, $message, $feedback['date'], $feedback['ip']);
+
+ if ($mailSent) {
+ return ['success' => true, 'message' => 'Vielen Dank! Ihre Nachricht wurde gesendet.'];
+ } else {
+ error_log("Mail-Fehler: Nachricht von {$email} konnte nicht gesendet werden");
+ return ['success' => false, 'message' => 'Nachricht wurde gespeichert, aber E-Mail konnte nicht gesendet werden.'];
+ }
+ }
+
+ private function sendEmailViaGmail($name, $email, $message, $date, $ip) {
+ $mail = new PHPMailer(true);
+
+ try {
+ $mail->SMTPDebug = 2;
+ $mail->Debugoutput = function($str, $level) {
+ error_log("PHPMailer Debug: $str");
+ };
+
+ $mail->isSMTP();
+ $mail->Host = 'smtp.gmail.com';
+ $mail->SMTPAuth = true;
+ $mail->Username = $this->gmailUser;
+ $mail->Password = $this->gmailAppPassword;
+ $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+ $mail->Port = 587;
+
+ $mail->setFrom($this->gmailUser, 'Aurora Livecam');
+ $mail->addAddress($this->adminEmail);
+ $mail->addReplyTo($email, $name);
+
+ $mail->isHTML(true);
+ $mail->CharSet = 'UTF-8';
+ $mail->Subject = '🔔 Neue Kontaktanfrage von ' . $name;
+ $mail->Body = $this->getEmailTemplate($name, $email, $message, $date, $ip);
+
+ $mail->send();
+ return true;
+ } catch (Exception $e) {
+ error_log("PHPMailer Error: {$mail->ErrorInfo}");
+ return false;
+ }
+ }
+
+ public function deleteFeedback($index) {
+ if (!file_exists($this->feedbackFile)) return false;
+
+ $feedbacks = json_decode(file_get_contents($this->feedbackFile), true);
+ if (isset($feedbacks[$index])) {
+ unset($feedbacks[$index]);
+ $feedbacks = array_values($feedbacks);
+ file_put_contents($this->feedbackFile, json_encode($feedbacks, JSON_PRETTY_PRINT));
+ return true;
+ }
+ return false;
+ }
+
+ private function getEmailTemplate($name, $email, $message, $date, $ip) {
+ return "
+
+
+
+
+
+
+
+
+
+
+
👤 Name: {$name}
+
+
💬 Nachricht:
+
" . nl2br($message) . "
+
+
+
+
+
+ ";
+ }
+}
+
+
+class AdminManager {
+ public function isAdmin() {
+ return isset($_SESSION['admin']) && $_SESSION['admin'] === true;
+ }
+
+ public function handleLogin($username, $password) {
+ if ($username === 'admin' && $password === 'sonne4000$$$$Q') {
+ $_SESSION['admin'] = true;
+ return true;
+ }
+ return false;
+ }
+
+ public function handleImageUpload($file) {
+ if (!$this->isAdmin()) return false;
+ if (!isset($file['tmp_name']) || empty($file['tmp_name'])) { echo "Keine Datei."; return false; }
+
+ $target_dir = "uploads/";
+ if (!file_exists($target_dir)) mkdir($target_dir, 0777, true);
+
+ $target_file = $target_dir . basename($file["name"]);
+ $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
+
+ $check = @getimagesize($file["tmp_name"]);
+ if($check === false) { echo "Kein Bild."; return false; }
+ if ($file["size"] > 5000000) { echo "Zu groß (>5MB)."; return false; }
+ if(!in_array($imageFileType, ['jpg','png','jpeg','gif'])) { echo "Falsches Format."; return false; }
+
+ if (move_uploaded_file($file["tmp_name"], $target_file)) {
+ echo "
✅ Bild erfolgreich hochgeladen.
";
+ return true;
+ } else {
+ echo "Upload Fehler.";
+ return false;
+ }
+ }
+
+ public function displayLoginForm() {
+ return '
+
+
+ Benutzername:
+ Passwort:
+ Einloggen
+ ';
+ }
+
+ public function displayAdminContent() {
+ global $settingsManager, $siteConfig;
+
+ $feedbacks = json_decode(file_get_contents('feedbacks.json') ?: '[]', true);
+
+ // NEUES SETTINGS PANEL
+ $output = '
';
+ $output .= '
⚙️ Anzeige-Einstellungen ';
+
+ // Zuschauer-Anzeige Settings
+ $output .= '
';
+ $output .= '
👥 Zuschauer-Anzeige ';
+
+ $output .= '
';
+ $output .= '
Zuschauer-Anzahl anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('viewer_display.enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Mindestanzahl für Anzeige ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Video-Modus Settings
+ $output .= '
';
+ $output .= '
🎬 Video-Modus ';
+
+ $output .= '
';
+ $output .= '
Videos im Player abspielen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('video_mode.play_in_player') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Download erlauben ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('video_mode.allow_download') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // UI Display Settings (Punkt 2)
+ $output .= '
';
+ $output .= '
🖼️ UI Anzeige ';
+
+ $output .= '
';
+ $output .= '
Empfehlungs-Banner anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('ui_display.show_recommendation_banner') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
QR-Code Section anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('ui_display.show_qr_code') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Social Media Links anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('ui_display.show_social_media') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Patrouille Suisse Section anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('ui_display.show_patrouille_suisse') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Zoom & Timelapse Settings (Punkt 3)
+ $output .= '
';
+ $output .= '
🔍 Zoom & Timelapse ';
+
+ $output .= '
';
+ $output .= '
Zoom-Controls anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('zoom_timelapse.show_zoom_controls') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Max Zoom-Level ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Timelapse Rückwärts-Modus ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('zoom_timelapse.timelapse_reverse_enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Content Management Settings (Punkt 5)
+ $output .= '
';
+ $output .= '
📝 Content Management ';
+
+ $output .= '
';
+ $output .= '
Gästebuch aktivieren ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('content.guestbook_enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Galerie aktivieren ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('content.gallery_enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
KI-Events anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('content.ai_events_enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Max Gästebuch-Einträge ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Technical Settings (Punkt 6)
+ $output .= '
';
+ $output .= '
⚙️ Technische Einstellungen ';
+
+ $output .= '
';
+ $output .= '
Viewer Update-Intervall (Sekunden) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Session Timeout (Sekunden) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Theme Settings (Punkt 7)
+ $output .= '
';
+ $output .= '
🎨 Theme & Design ';
+
+ $output .= '
';
+ $output .= '
Standard-Theme ';
+ $output .= '
';
+ $output .= '';
+ $currentTheme = $settingsManager->get('theme.default_theme');
+ $output .= 'Klassisch ';
+ $output .= 'Alpin ';
+ $output .= 'Modern ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Theme-Switcher anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('theme.show_theme_switcher') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // SEO Settings (Punkt 8)
+ $output .= '
';
+ $output .= '
🔍 SEO & Meta ';
+
+ $output .= '
';
+ $output .= '
Custom Title (leer = Standard) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Meta Description ';
+ $output .= '
';
+ $output .= '' . htmlspecialchars($settingsManager->get('seo.meta_description')) . ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Meta Keywords ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+ $output .= '
'; // settings-group
+
+ // Weather Settings
+ $output .= '
';
+ $output .= '
🌤️ Wetter-Widget (Open-Meteo - kostenlos, kein API-Key nötig) ';
+
+ $output .= '
';
+ $output .= '
Wetter-Widget anzeigen ';
+ $output .= '
';
+ $output .= '';
+ $output .= ' get('weather.enabled') ? 'checked' : '') . '>';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ // API-KEY FELD KOMPLETT ENTFERNT
+
+ $output .= '
';
+ $output .= '
Standort (Anzeigename) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Latitude (Breitengrad) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Longitude (Längengrad) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Update-Intervall (Minuten) ';
+ $output .= '
';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
Einheit ';
+ $output .= '
';
+ $output .= '';
+ $currentUnits = $settingsManager->get('weather.units');
+ $output .= 'Metrisch (°C, km/h) ';
+ $output .= 'Imperial (°F, mph) ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '
';
+
+ $output .= '
'; // settings-group
+
+
+ $output .= '
'; // admin-settings-panel
+
+ // Bestehender Admin-Content
+ $output .= '
';
+ $output .= '
📩 Posteingang (Kontaktformular) ';
+
+ if (empty($feedbacks)) {
+ $output .= '
Keine Nachrichten vorhanden.
';
+ } else {
+ $output .= '
';
+ foreach ($feedbacks as $index => $feedback) {
+ $ip = isset($feedback['ip']) ? $feedback['ip'] : 'Unbekannt';
+
+ $output .= "
";
+ $output .= "
";
+ $output .= "
";
+
+ $output .= "
+
+
+ 🗑️ Löschen
+ ";
+ $output .= "
";
+
+ $output .= "
{$feedback['message']}
";
+ $output .= "
📅 {$feedback['date']} | IP: {$ip} ";
+ $output .= "
";
+ }
+ $output .= '
';
+ }
+ $output .= '
';
+
+ $output .= '
';
+ $output .= '
🖼️ Bildergalerie verwalten ';
+
+ $output .= $this->displayGalleryImages(true);
+
+ $output .= '⬆️ Neues Bild hochladen
+
+
+
+ ';
+ $output .= '';
+
+ $output .= '
';
+ $output .= '
📲 Social Media Links
+
+
+
+ Facebook
+ Instagram
+ TikTok
+
+
+ Link aktualisieren
+ ';
+ $output .= '';
+
+ return $output;
+ }
+
+ public function displayGalleryImages($isAdmin = false) {
+ $output = '
';
+ $files = glob("uploads/*.{jpg,jpeg,png,gif}", GLOB_BRACE);
+
+ if ($files) {
+ foreach($files as $file) {
+ $filename = basename($file);
+ $output .= '
';
+ $output .= '
';
+
+ if ($isAdmin) {
+ $output .= '
+
+
+
+ X
+ ';
+ }
+
+ $output .= '
';
+ }
+ } else {
+ $output .= '
Keine Bilder in der Galerie.
';
+ }
+ $output .= '
';
+ return $output;
+ }
+
+ public function handleSocialMediaUpdate($platform, $url) {
+ $socialLinks = json_decode(file_get_contents('social_links.json') ?: '{}', true);
+ $socialLinks[$platform] = $url;
+ file_put_contents('social_links.json', json_encode($socialLinks));
+ }
+}
+
+
+class VideoArchiveManager {
+ private $videoDir;
+ private $videoDirs;
+ private $monthNames;
+
+ public function __construct($videoDir = './videos/') {
+ $this->videoDirs = is_array($videoDir) ? $videoDir : [$videoDir];
+ $this->videoDir = $this->videoDirs[0];
+ $this->monthNames = [
+ '01' => 'Januar', '02' => 'Februar', '03' => 'März', '04' => 'April',
+ '05' => 'Mai', '06' => 'Juni', '07' => 'Juli', '08' => 'August',
+ '09' => 'September', '10' => 'Oktober', '11' => 'November', '12' => 'Dezember'
+ ];
+ }
+
+ public function getVideosGroupedByDate() {
+ global $remoteCache;
+ $videos = [];
+
+ // Lokale Videos direkt (schnell)
+ foreach (glob($this->videoDirs[0] . 'daily_video_*.mp4') as $video) {
+ if (preg_match('/daily_video_(\d{8})_\d{6}\.mp4/', basename($video), $matches)) {
+ $dateStr = $matches[1];
+ $year = substr($dateStr, 0, 4);
+ $month = substr($dateStr, 4, 2);
+ $day = substr($dateStr, 6, 2);
+ $videos[$year][$month][] = [
+ 'path' => $video, 'filename' => basename($video),
+ 'day' => $day, 'filesize' => filesize($video), 'modified' => filemtime($video)
+ ];
+ }
+ }
+
+ // Remote-Videos aus Cache (kein glob auf SMB!)
+ foreach ($remoteCache->getVideos() as $rv) {
+ if (preg_match('/daily_video_(\d{8})_\d{6}\.mp4/', $rv['filename'], $matches)) {
+ $dateStr = $matches[1];
+ $year = substr($dateStr, 0, 4);
+ $month = substr($dateStr, 4, 2);
+ $day = substr($dateStr, 6, 2);
+ $videos[$year][$month][] = [
+ 'path' => $rv['path'], 'filename' => $rv['filename'],
+ 'day' => $day, 'filesize' => $rv['filesize'], 'modified' => $rv['mtime']
+ ];
+ }
+ }
+
+ foreach ($videos as $year => $months) {
+ foreach ($months as $month => $days) {
+ usort($videos[$year][$month], function($a, $b) {
+ return $b['day'] - $a['day'];
+ });
+ }
+ }
+
+ return $videos;
+ }
+
+ public function handleSpecificVideoDownload() {
+ if (isset($_GET['download_specific_video']) && isset($_GET['token'])) {
+ $videoPath = $_GET['download_specific_video'];
+ $token = $_GET['token'];
+
+ $expectedToken = hash_hmac('sha256', $videoPath, session_id());
+ if (!hash_equals($expectedToken, $token)) {
+ echo "Ungültiger Token. Zugriff verweigert.";
+ exit;
+ }
+
+ $validPath = false;
+ foreach ($this->videoDirs as $vDir) {
+ $resolvedDir = realpath($vDir);
+ $requestedPath = realpath($videoPath);
+ if ($requestedPath && $resolvedDir && strpos($requestedPath, $resolvedDir) === 0) {
+ $validPath = true;
+ break;
+ }
+ }
+ if ($validPath && file_exists($videoPath)) {
+ $extension = pathinfo($videoPath, PATHINFO_EXTENSION); // ← DAS FEHLT
+ if (strtolower($extension) !== 'mp4') {
+ echo "Nur MP4-Dateien können heruntergeladen werden.";
+ exit;
+ }
+
+ header('Content-Description: File Transfer');
+ header('Content-Type: video/mp4');
+ header('Content-Disposition: attachment; filename="'.basename($requestedPath).'"');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($requestedPath));
+ readfile($requestedPath);
+ exit;
+ } else {
+ echo "Datei nicht gefunden oder ungültiger Dateipfad.";
+ exit;
+ }
+ }
+}
+
+}
+
+
+$webcamManager = new WebcamManager();
+$imageFilesJson = $webcamManager->getImageFiles();
+$guestbookManager = new GuestbookManager();
+$contactManager = new ContactManager();
+$adminManager = new AdminManager();
+
+$videoArchiveManager = new VideoArchiveManager(['./videos/', '/mnt/aurora-remote/videos/']);
+$videoArchiveManager->handleSpecificVideoDownload();
+
+// AI-Video Download Handler
+if (isset($_GET['download_ai_video']) && isset($_GET['token'])) {
+ $videoPath = $_GET['download_ai_video'];
+ $token = $_GET['token'];
+
+ $expectedToken = hash_hmac('sha256', $videoPath, session_id());
+ if (hash_equals($expectedToken, $token)) {
+ $aiDir = realpath('./ai/');
+ $requestedPath = realpath($videoPath);
+
+ if ($requestedPath && strpos($requestedPath, $aiDir) === 0 && file_exists($requestedPath)) {
+ header('Content-Description: File Transfer');
+ header('Content-Type: video/mp4');
+ header('Content-Disposition: attachment; filename="'.basename($requestedPath).'"');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($requestedPath));
+ readfile($requestedPath);
+ exit;
+ }
+ }
+ echo "Download fehlgeschlagen.";
+ exit;
+}
+
+if (isset($_GET['action'])) {
+ switch ($_GET['action']) {
+ case 'snapshot':
+ $webcamManager->captureSnapshot();
+ break;
+ case 'sequence':
+ $webcamManager->captureVideoSequence();
+ break;
+ }
+}
+
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ // Viewer Heartbeat
+ if (isset($_POST['action']) && $_POST['action'] === 'viewer_heartbeat') {
+ $viewerCounter->handleHeartbeat();
+ }
+
+ // GÄSTEBUCH
+ if (isset($_POST['guestbook'])) {
+ $guestbookManager->handleFormSubmission();
+ header("Location: " . $_SERVER['PHP_SELF'] . "#guestbook");
+ exit;
+ }
+
+ // KONTAKTFORMULAR
+ elseif (isset($_POST['contact'])) {
+ $result = $contactManager->handleSubmission($_POST['name'], $_POST['email'], $_POST['message']);
+
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
+ header('Content-Type: application/json');
+ echo json_encode($result);
+ exit;
+ }
+ $_SESSION['contact_result'] = $result;
+ header('Location: ' . $_SERVER['PHP_SELF'] . '#kontakt');
+ exit;
+ }
+
+ // ADMIN LOGIN
+ elseif (isset($_POST['admin-login'])) {
+ $adminManager->handleLogin($_POST['username'], $_POST['password']);
+ header('Location: ' . $_SERVER['PHP_SELF'] . '#admin');
+ exit;
+ }
+
+ // ADMIN AKTIONEN
+ elseif ($adminManager->isAdmin()) {
+ if (isset($_POST['action']) && $_POST['action'] === 'delete_guestbook' && isset($_POST['delete_entry'])) {
+ $guestbookManager->deleteEntry(intval($_POST['delete_entry']));
+ header("Location: " . $_SERVER['PHP_SELF'] . "#guestbook");
+ exit;
+ }
+
+ if (isset($_POST['action']) && $_POST['action'] === 'delete_feedback' && isset($_POST['delete_index'])) {
+ $contactManager->deleteFeedback(intval($_POST['delete_index']));
+ header("Location: " . $_SERVER['PHP_SELF'] . "#admin");
+ exit;
+ }
+
+ if (isset($_POST['action']) && $_POST['action'] === 'delete_image' && isset($_POST['image_name'])) {
+ $filename = basename($_POST['image_name']);
+ $filepath = "uploads/" . $filename;
+ if (file_exists($filepath)) { unlink($filepath); }
+ header("Location: " . $_SERVER['PHP_SELF'] . "#admin");
+ exit;
+ }
+
+ if (isset($_POST['update-social-media'])) {
+ $adminManager->handleSocialMediaUpdate($_POST['social-platform'], $_POST['social-url']);
+ header("Location: " . $_SERVER['PHP_SELF'] . "#admin");
+ exit;
+ }
+
+ if (isset($_FILES["fileToUpload"])) {
+ $adminManager->handleImageUpload($_FILES["fileToUpload"]);
+ }
+ }
+}
+
+// Viewer-Anzeige Einstellungen für JavaScript
+$viewerCount = $viewerCounter->getInitialCount();
+$showViewers = $settingsManager->shouldShowViewers($viewerCount);
+$minViewersToShow = $settingsManager->get('viewer_display.min_viewers');
+?>
+
+
+
+
+
+
+
+
+
+
+
getCustomTitle() ?: $siteConfig['siteTitle']; ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Erleben Sie faszinierende Ausblicke der Züricher Region - in Echtzeit!
+
+
+
+
+
+
+
Unsere Empfehlungen
+
+
+
+
+
+
+
+
+
+ isWeatherEnabled()): ?>
+ getCurrentWeather();
+ } catch (Exception $e) {
+ $weather = ['error' => 'Fehler: ' . $e->getMessage()];
+ }
+ if ($weather && !isset($weather['error'])):
+ ?>
+
+
+
+ ⚠️ Wetterdaten nicht verfügbar:
+
+
+
+
+
+
+ displayWebcam(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ −
+
+ 1.0x
+ +
+ ⟲
+
+
+
+
+
+
+
+ displayStreamStats(); ?>
+
+
+
+
+
+ Zuschauer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gefällt dir die Livecam? Unterstütze das Projekt – jeder Franken zählt und hilft die Betriebskosten zu decken. So kannst du auch weiterhin Fotos und Videos kostenlos nutzen.
+
+
+
+
+
+
+
+
+
+ Unterstütze die Aurora Livecam mit deiner Spende. Du hilfst dabei, Kamera, Server, Starlink-Verbindung und die 100 % autarke Stromversorgung mit Solarenergie und EcoFlow-Speichern zu finanzieren. So bleibt das Projekt nachhaltig, unabhängig und für alle frei zugänglich.
+
+
+
+ 🌱 724 Green Label
+
+
+
+
+ Live & 100% autark: Unsere Aurora-Webcam
+
+
+
+ Powered by EcoFlow & Swiss Sun
+
+
+
+ Erleben Sie Dürnten in Echtzeit – angetrieben durch die reine Kraft der Natur. Unsere Infrastruktur wird zu 100 % mit Solarstrom und EcoFlow-Batteriespeichern betrieben.
+
+
+
+
+ 724 Green Label Swiss:.
+
+
+
+ 24/7 Green Power: Dank modernster Speichertechnologie.
+
+
+
+ Emissionsfrei: Innovation direkt aus dem Herzen der Schweiz.
+
+
+
+
+
+
+
+
🛰️ Schnelles Internet für Ihre Livestreams
+
+ Für hochwertige Webcam-Übertragungen empfehlen wir Starlink –
+ Highspeed-Internet überall verfügbar, perfekt für abgelegene Standorte!
+
+
+
+
+
+
+
+
+
+
+
+
+
Videoarchiv Tagesvideos
+
+ displayVisualCalendar();
+ ?>
+
+
+
+
+
+
+
+
Kamera-Blickrichtung
+
+
+
+
+
+
📍 Ungefährer Standort
+
⛰️ Höhe: ca. 616 m ü.M.
+
🧭 Blickrichtung: Südwest (SW)
+
📍 Region: Zürich Oberland
+
+
+
+
+
+
+
+
+
+
+ Folge uns und kopiere den Code und sende es deinen Freunden
+
+
+
+
+ Klicke auf den QR-Code, um die URL zu kopieren
+
+
+
+
+
+
+
+
+
+
+
Gästebuch
+ displayForm();
+ echo $guestbookManager->displayEntries($adminManager->isAdmin());
+ ?>
+
+
+
+
+
+
+
Kontakt
+
+ Haben Sie Fragen, Anregungen oder möchten uns unterstützen? Wir freuen uns auf Ihre Nachricht!
+
+ displayForm(); ?>
+
+
+
+
+
+
+
Bildergalerie
+
+
+ displayGalleryImages(); ?>
+
+
+
+
+
+
+
+
+
Über unser Projekt
+
+
+
+
+
+
+ Dazu betreiben wir seit 2010 rund um die Uhr hochauflösende Webcams. Besonders stolz sind wir auf einzigartige Einblicke, wie z.B. die Trainingsflüge der Patrouille Suisse jeden Montagmorgen.
+
+
+
+
+
+
+
+isAdmin()): ?>
+
+
+
Admin-Bereich
+ displayAdminContent(); ?>
+
+
+
+
+
+
Admin Login
+ displayLoginForm(); ?>
+
+
+
+
+
+
+
+
Patrouille Suisse Live - Trainingsflüge Beobachten
+
+
+
Jeden Montag Live!
+
+ Die Patrouille Suisse, das offizielle Kunstflugteam der Schweizer Luftwaffe, trainiert jeden Montagmorgen in der Region Zürich Oberland. Unsere Webcam bietet einen einzigartigen Blick auf die spektakulären Flugmanöver der sechs F-5E Tiger II Jets.
+
+
+ Trainingszeit: ca. 09:00 - 11:00 Uhr
+ Bei gutem Wetter sichtbar
+ Einzigartige Perspektive aus dem Zürcher Oberland
+
+
+
+
Geschichte der Patrouille Suisse
+
+ Gegründet 1964, ist die Patrouille Suisse eines der renommiertesten Kunstflugteams Europas. Das Team fliegt seit 1995 die Northrop F-5E Tiger II und begeistert bei Shows in der ganzen Schweiz und international.
+
+
+ Heimatbasis: Payerne (VD)
+ Flugzeuge: 6x F-5E Tiger II
+ Teamgrösse: 6 Piloten + Crew
+
+
+
+
Beste Beobachtungstipps
+
+ Für die beste Sicht auf die Trainingsflüge empfehlen wir:
+
+
+ Nutzen Sie die Zoom-Funktion unserer Webcam
+ Timelapse-Modus für beschleunigte Ansicht
+ Tagesvideos zum Nachschauen
+ AI-Erkennung markiert Flugzeug-Sichtungen
+
+
+ Hinweis: Bei schlechtem Wetter können Trainings abgesagt werden.
+
+
+
+
+
+
+
+
+
+
+
Aktuelle Wetter-News, Webcam-Updates und Naturbeobachtungen aus dem Zürcher Oberland
+
+
+
+
+
+ 🌅
+
+
+
Sonnenaufgänge über dem Zürichsee
+
Januar 2024
+
+ Die Wintermonate bieten spektakuläre Sonnenaufgänge über dem Zürichsee. Unsere AI-Erkennung identifiziert automatisch die schönsten Momente und speichert sie in der Galerie.
+
+
+ Besonders bei Hochnebel entstehen eindrucksvolle Lichtstimmungen, wenn die Sonne durch die Wolkendecke bricht.
+
+
+
+
+
+
+
+ 🏔️
+
+
+
Alpenpanorama im Winter
+
Dezember 2023
+
+ An klaren Wintertagen reicht die Sicht von unserer Webcam auf 616m Höhe bis zu den schneebedeckten Gipfeln der Glarner Alpen. Säntis, Glärnisch und weitere Bergspitzen sind sichtbar.
+
+
+ Nutzen Sie die Zoom-Funktion für detaillierte Ansichten der Berglandschaft.
+
+
+
+
+
+
+
+ ✈️
+
+
+
Patrouille Suisse Saison 2024
+
März 2024
+
+ Die neue Flugsaison der Patrouille Suisse hat begonnen! Jeden Montag trainiert das Kunstflugteam über dem Zürcher Oberland - unsere Webcam fängt die Flugmanöver live ein.
+
+
+ Die AI-Erkennung markiert Flugzeug-Sichtungen automatisch in unserer Galerie.
+
+
+
+
+
+
+
+ Weitere Wetter-Updates und Beobachtungen finden Sie auf unseren Social Media Kanälen.
+
+
+
+
+
+
+
+
+
Impressum
+
+
M. Kessler
+
Dürnten, Schweiz
+
Anfragen per Kontaktformular
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gefällt dir die Livecam? Unterstütze das Projekt – jeder Franken zählt und hilft die Betriebskosten zu decken. So kannst du auch weiterhin Fotos und Videos kostenlos nutzen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+isEmailSharingEnabled()): ?>
+
+
+
+
📤 Per E-Mail teilen
+ ×
+
+
+
+
+
+ Dein Name
+
+
+
+ E-Mail-Adresse des Empfängers *
+
+
+
+ Nachricht (optional)
+
+
+
+ Abbrechen
+ 📧 Senden
+
+
+
+
+
+
+
+
+
+isAdmin()): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+