handleAjax();
$weatherManager->handleAjax();
if (isset($_GET['download_video'])) {
$videoDir = './videos/';
$latestVideo = null;
$latestTime = 0;
// Finde das neueste Video
foreach (glob($videoDir . '*.mp4') as $video) {
$mtime = filemtime($video);
if ($mtime > $latestTime) {
$latestTime = $mtime;
$latestVideo = $video;
}
}
if ($latestVideo) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($latestVideo).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($latestVideo));
readfile($latestVideo);
exit;
} else {
echo "Kein Video zum Herunterladen gefunden.";
exit;
}
}
// Funktion zur sicheren Umleitung
function safeRedirect($url) {
if (!headers_sent()) {
header("HTTP/1.1 301 Moved Permanently");
header("Location: " . $url);
} else {
echo '';
}
exit();
}
// Hauptlogik
$oldDomains = [
'www.aurora-wetter-lifecam.ch',
'www.aurora-wetter-livecam.ch'
];
$newDomain = 'www.aurora-weather-livecam.com';
if (in_array($_SERVER['HTTP_HOST'], $oldDomains)) {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$newUrl = $protocol . '://' . $newDomain . $_SERVER['REQUEST_URI'];
// Logging für Debugging
error_log("Umleitung von {$_SERVER['HTTP_HOST']} nach $newUrl");
if (!headers_sent()) {
header("HTTP/1.1 301 Moved Permanently");
header("Location: " . $newUrl);
} else {
echo '';
}
exit();
}
// Site-Konfiguration basierend auf Domain
$isSeecam = ($_SERVER['HTTP_HOST'] === 'www.seecam.ch' || $_SERVER['HTTP_HOST'] === 'seecam.ch');
if ($isSeecam) {
$siteConfig = [
'domain' => 'www.seecam.ch',
'domainUrl' => 'https://www.seecam.ch',
'logo' => 'seecam.jpg',
'siteName' => 'Seecam',
'siteNameFull' => 'Seecam Wetter Livecam',
'siteNameFullEn' => 'Seecam Weather Livecam',
'siteTitle' => 'Zürich Oberland Webcam Live - Zürichsee & Patrouille Suisse | Seecam 24/7',
'author' => 'Seecam Wetter Livecam',
'alternateName' => 'Seecam Webcam Schweiz',
'welcomeDe' => 'Willkommen bei Seecam Wetter Livecam',
'welcomeEn' => 'Welcome to Seecam Weather Livecam',
'aboutDe' => 'Seecam Wetter Livecam ist ein Herzensprojekt von Wetterbegeisterten. Wir möchten Ihnen die Schönheit der Natur und Faszination des Wetters näher bringen.',
'aboutEn' => 'Seecam Weather Livecam is a passion project...',
'blogTitle' => 'Seecam Wetter Blog',
'footerName' => 'Seecam Wetter Livecam',
'copyright' => '© 2024 Seecam Wetter Livecam - Webcam Zürich Oberland'
];
} else {
$siteConfig = [
'domain' => 'www.aurora-weather-livecam.com',
'domainUrl' => 'https://www.aurora-weather-livecam.com',
'logo' => 'logo.png',
'siteName' => 'Aurora',
'siteNameFull' => 'Aurora Wetter Livecam',
'siteNameFullEn' => 'Aurora Weather Livecam',
'siteTitle' => 'Zürich Oberland Webcam Live - Zürichsee & Patrouille Suisse | Aurora Livecam 24/7',
'author' => 'Aurora Wetter Livecam',
'alternateName' => 'Aurora Webcam Schweiz',
'welcomeDe' => 'Willkommen bei Aurora Wetter Livecam',
'welcomeEn' => 'Welcome to Aurora Weather Livecam',
'aboutDe' => 'Aurora Wetter Livecam ist ein Herzensprojekt von Wetterbegeisterten. Wir möchten Ihnen die Schönheit der Natur und Faszination des Wetters näher bringen.',
'aboutEn' => 'Aurora Weather Livecam is a passion project...',
'blogTitle' => 'Aurora Wetter Blog',
'footerName' => 'Aurora Wetter Livecam',
'copyright' => '© 2024 Aurora Wetter Lifecam - Webcam Zürich Oberland'
];
}
session_start();
error_reporting(E_ALL);
ini_set('display_errors', 1);
$imageDir = "./image"; // Angepasst an das Ausgabeverzeichnis des Bash-Skripts
$imageFiles = glob("$imageDir/screenshot_*.jpg");
rsort($imageFiles); // Sortiert die Dateien in umgekehrter Reihenfolge (neueste zuerst)
$imageFilesJson = json_encode($imageFiles);
class ViewerCounter {
private $file = 'active_viewers.json';
private $timeout = 30; // Zeit in Sekunden, bis ein User als "offline" gilt
public function handleHeartbeat() {
$ip = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); // Anonymisierte ID
$now = time();
$viewers = [];
// 1. Datei lesen (mit Lock für Sicherheit bei vielen Zugriffen)
if (file_exists($this->file)) {
$content = file_get_contents($this->file);
$viewers = json_decode($content, true) ?? [];
}
// 2. Aktuellen User updaten
$viewers[$ip] = $now;
// 3. Alte User entfernen & Zählen
$activeCount = 0;
$newViewers = [];
foreach ($viewers as $userIp => $lastSeen) {
if ($now - $lastSeen < $this->timeout) {
$newViewers[$userIp] = $lastSeen;
$activeCount++;
}
}
// 4. Speichern
file_put_contents($this->file, json_encode($newViewers));
// 5. Ergebnis zurückgeben
header('Content-Type: application/json');
echo json_encode(['count' => $activeCount]);
exit;
}
public function getInitialCount() {
if (file_exists($this->file)) {
$viewers = json_decode(file_get_contents($this->file), true) ?? [];
// Nur grob zählen, genaues Update macht das JS sofort nach Laden
return count($viewers);
}
return 1; // Zumindest man selbst ist da
}
}
// Instanz erstellen
$viewerCounter = new ViewerCounter();
class WebcamManager {
private $videoSrc = 'test_video.m3u8';
private $logoPath = 'logo.png';
// Zeigt NUR das Video ohne Schnickschnack
public function displayWebcam() {
return '
';
// Navigation
$output .= '
';
$output .= '◀ ';
$output .= '
' . $this->monthNames[$currentMonth]['de'] . ' ' . $currentYear . ' ';
$output .= '▶ ';
$output .= '';
// AI-Legende
$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;
$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 = 'qggk hsxz fdkq jgxa';
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 - 100% kostenlos!) ';
$output .= '
';
$output .= '
Wetter-Widget anzeigen ';
$output .= '
';
$output .= '';
$output .= ' get('weather.enabled') ? 'checked' : '') . '>';
$output .= ' ';
$output .= ' ';
$output .= '
';
$output .= '
';
$output .= '
';
$output .= '
Standort (Stadt,Land) ';
$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 $monthNames;
public function __construct($videoDir = './videos/') {
$this->videoDir = $videoDir;
$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() {
$videos = [];
foreach (glob($this->videoDir . '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)
];
}
}
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;
}
$videoDir = realpath($this->videoDir);
$requestedPath = realpath($videoPath);
if ($requestedPath && strpos($requestedPath, $videoDir) === 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;
}
}
}
}
$webcamManager = new WebcamManager();
$imageFilesJson = $webcamManager->getImageFiles();
$guestbookManager = new GuestbookManager();
$contactManager = new ContactManager();
$adminManager = new AdminManager();
$videoArchiveManager = new VideoArchiveManager('./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!
isWeatherEnabled()): ?>
getCurrentWeather();
if ($weather && !isset($weather['error'])):
?>
⚠️ Wetterdaten nicht verfügbar:
−
1.0x
+
⟲
displayStreamStats(); ?>
Zuschauer
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
isAdmin()): ?>