Fix Calendar Loading Problems

This commit is contained in:
2025-12-18 19:00:05 +01:00
parent afadbbc835
commit bbe05bd765
3 changed files with 228 additions and 177 deletions
+23 -10
View File
@@ -1,4 +1,3 @@
<?php <?php
// URL der öffentlichen ICS-Datei // URL der öffentlichen ICS-Datei
define( define(
@@ -83,13 +82,10 @@ function format_ics_date_with_timezone($date, $timezone = null)
} }
} }
// ICS-Datei laden // Cache für 24 Stunden (in Minuten)
$ics = @file_get_contents(CAL_URL); $cacheDuration = 24 * 60;
if (!$ics) { $cacheId = 'google_calendar_events';
echo '<div class="text-red-600">Kalender konnte nicht geladen werden.</div>'; $cache = kirby()->cache('pages');
return;
}
// Termine parsen // Termine parsen
function parse_ics($ics) function parse_ics($ics)
@@ -134,9 +130,26 @@ function parse_ics($ics)
return $events; return $events;
} }
$events = parse_ics($ics); return function () use ($cache, $cacheId, $cacheDuration) {
// Versuche Daten aus dem Cache zu laden
$events = $cache->get($cacheId);
if ($events === null) {
// Wenn nicht im Cache, neu laden
$ics = @file_get_contents(CAL_URL);
if (!$ics) {
// Fehler beim Laden (z.B. 429 Error)
return null;
}
// Termine parsen
$events = parse_ics($ics);
// In Cache speichern
$cache->set($cacheId, $events, $cacheDuration);
}
return function () use ($events) {
return $events; return $events;
}; };
+100 -71
View File
@@ -1,33 +1,40 @@
<?php <?php
$events = collection('termine'); $events = collection('termine');
if ($events === null): ?>
<div class="container mx-auto py-8">
<h2>Bevorstehende Termine</h2>
<div class="text-red-600">Die Termine können gerade nicht geladen werden.</div>
</div>
<?php return; endif;
// Aktuelles Datum - PHP 8.3 kompatibel // Aktuelles Datum - PHP 8.3 kompatibel
$today = (new DateTime('now'))->format('Ymd'); $today = (new DateTime('now'))->format('Ymd');
// Nur zukünftige Termine anzeigen // Nur zukünftige Termine anzeigen
$future_events = array_filter($events, function ($event) use ($today) { $future_events = array_filter($events, function ($event) use ($today) {
return isset($event['DTSTART']) && $event['DTSTART'] >= $today; return isset($event['DTSTART']) && $event['DTSTART'] >= $today;
}); });
// Termine nach DTSTART in aufsteigender Reihenfolge sortieren // Termine nach DTSTART in aufsteigender Reihenfolge sortieren
usort($future_events, function ($a, $b) { usort($future_events, function ($a, $b) {
return $a['DTSTART'] <=> $b['DTSTART']; return $a['DTSTART'] <=> $b['DTSTART'];
}); });
// --- Deutsche Monatsnamen --- // --- Deutsche Monatsnamen ---
$de_months = [ $de_months = [
'01' => 'Jan', '01' => 'Jan',
'02' => 'Feb', '02' => 'Feb',
'03' => 'Mrz', '03' => 'Mrz',
'04' => 'Apr', '04' => 'Apr',
'05' => 'Mai', '05' => 'Mai',
'06' => 'Jun', '06' => 'Jun',
'07' => 'Jul', '07' => 'Jul',
'08' => 'Aug', '08' => 'Aug',
'09' => 'Sep', '09' => 'Sep',
'10' => 'Okt', '10' => 'Okt',
'11' => 'Nov', '11' => 'Nov',
'12' => 'Dez', '12' => 'Dez',
]; ];
?> ?>
@@ -35,8 +42,12 @@ $de_months = [
<h2>Bevorstehende Termine</h2> <h2>Bevorstehende Termine</h2>
<div class="relative flex items-center justify-center"> <div class="relative flex items-center justify-center">
<button id="scroll-left" class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block mr-4"> <button id="scroll-left"
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg> class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block mr-4">
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</button> </button>
<div id="termine-wrapper" class="relative w-full max-w-7xl overflow-hidden"> <div id="termine-wrapper" class="relative w-full max-w-7xl overflow-hidden">
@@ -49,18 +60,18 @@ $de_months = [
$location = $event['LOCATION'] ?? ''; $location = $event['LOCATION'] ?? '';
$desc = $event['DESCRIPTION'] ?? ''; $desc = $event['DESCRIPTION'] ?? '';
$timezone = $event['DTSTART_TZID'] ?? null; $timezone = $event['DTSTART_TZID'] ?? null;
// Sichere Funktionsaufrufe mit Null-Checks // Sichere Funktionsaufrufe mit Null-Checks
$date_info = function_exists('format_ics_date_with_timezone') $date_info = function_exists('format_ics_date_with_timezone')
? format_ics_date_with_timezone($start, $timezone) ? format_ics_date_with_timezone($start, $timezone)
: ['display' => '', 'has_time' => false, 'iso' => '']; : ['display' => '', 'has_time' => false, 'iso' => ''];
$date = $date_info['display'] ?? ''; $date = $date_info['display'] ?? '';
$time = ($date_info['has_time'] ?? false) ? substr($date, 11) : 'ganztägig'; $time = ($date_info['has_time'] ?? false) ? substr($date, 11) : 'ganztägig';
$day = substr($date, 0, 2); $day = substr($date, 0, 2);
$month = substr($date, 3, 2); $month = substr($date, 3, 2);
$iso_date = $date_info['iso'] ?? ''; $iso_date = $date_info['iso'] ?? '';
// Sichere Array-Zugriffe // Sichere Array-Zugriffe
$month_name = $de_months[$month] ?? 'Unbek'; $month_name = $de_months[$month] ?? 'Unbek';
$event_summary = htmlspecialchars($summary, ENT_QUOTES, 'UTF-8'); $event_summary = htmlspecialchars($summary, ENT_QUOTES, 'UTF-8');
@@ -69,57 +80,64 @@ $de_months = [
$safe_time = htmlspecialchars($time, ENT_QUOTES, 'UTF-8'); $safe_time = htmlspecialchars($time, ENT_QUOTES, 'UTF-8');
?> ?>
<div class="termine-card flex-none w-full md:w-1/2 bg-gradient-to-br from-blue-50 to-blue-100 rounded-2xl transform hover:scale-102 transition-all duration-300 ease-in-out border border-blue-200"> <div
<!-- Inhalt der ersten Karte bleibt unverändert --> class="termine-card flex-none w-full md:w-1/2 bg-gradient-to-br from-blue-50 to-blue-100 rounded-2xl transform hover:scale-102 transition-all duration-300 ease-in-out border border-blue-200">
<div class="p-6 flex flex-col h-full"> <!-- Inhalt der ersten Karte bleibt unverändert -->
<div class="flex items-top mb-5"> <div class="p-6 flex flex-col h-full">
<div class="relative flex-shrink-0 bg-white rounded-lg shadow-md overflow-hidden w-16 h-16 flex flex-col border border-blue-200"> <div class="flex items-top mb-5">
<!-- Kopfzeile des Kalenders mit Monat --> <div
<div class="bg-blue-600 text-white text-xs font-semibold py-1 text-center uppercase"> class="relative flex-shrink-0 bg-white rounded-lg shadow-md overflow-hidden w-16 h-16 flex flex-col border border-blue-200">
<span data-month><?= $month_name ?></span> <!-- Kopfzeile des Kalenders mit Monat -->
</div> <div class="bg-blue-600 text-white text-xs font-semibold py-1 text-center uppercase">
<span data-month><?= $month_name ?></span>
</div>
<!-- Datumsanzeige --> <!-- Datumsanzeige -->
<div class="flex-grow flex items-center justify-center"> <div class="flex-grow flex items-center justify-center">
<div class="text-3xl font-bold text-sf_grau-800" data-day><?= $safe_day ?></div> <div class="text-3xl font-bold text-sf_grau-800" data-day><?= $safe_day ?></div>
</div> </div>
<!-- Dekorative Elemente - kleine Punkte für Kalendertage --> <!-- Dekorative Elemente - kleine Punkte für Kalendertage -->
<div class="absolute bottom-1 left-0 right-0 flex justify-center space-x-0.5 px-1"> <div class="absolute bottom-1 left-0 right-0 flex justify-center space-x-0.5 px-1">
<div class="w-1 h-1 rounded-full bg-sf_blau-200"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-200"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-300"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-300"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-400"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-400"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-500"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-500"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-400"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-400"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-300"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-300"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-200"></div> <div class="w-1 h-1 rounded-full bg-sf_blau-200"></div>
</div>
</div>
<div class="ml-5 flex-grow">
<p class="text-xl font-medium text-sf_grau-900"><?= $event_summary ?></p>
<p class="text-sf_blau-700 text-lg font-medium mt-1">
<?php if ($date_info['has_time'] ?? false): ?>
ab <span class="local-time"
data-iso-date="<?= $safe_iso_date ?>"><?= $safe_time ?></span> Uhr
<?php else: ?>
ganztägig
<?php endif; ?>
</p>
</div> </div>
</div>
<div class="ml-5 flex-grow">
<p class="text-xl font-medium text-sf_grau-900"><?= $event_summary ?></p>
<p class="text-sf_blau-700 text-lg font-medium mt-1">
<?php if ($date_info['has_time'] ?? false): ?>
ab <span class="local-time" data-iso-date="<?= $safe_iso_date ?>"><?= $safe_time ?></span> Uhr
<?php else: ?>
ganztägig
<?php endif; ?>
</p>
</div> </div>
</div> </div>
</div> </div>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>
<button id="scroll-right" class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block ml-4"> <button id="scroll-right"
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block ml-4">
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button> </button>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
const termineWrapper = document.getElementById('termine-wrapper'); const termineWrapper = document.getElementById('termine-wrapper');
const termineContainer = document.getElementById('termine-container'); const termineContainer = document.getElementById('termine-container');
const scrollLeftBtn = document.getElementById('scroll-left'); const scrollLeftBtn = document.getElementById('scroll-left');
@@ -143,7 +161,7 @@ $de_months = [
scrollLeftBtn.classList.remove('opacity-50', 'cursor-not-allowed'); scrollLeftBtn.classList.remove('opacity-50', 'cursor-not-allowed');
scrollLeftBtn.classList.add('hover:bg-gray-100'); scrollLeftBtn.classList.add('hover:bg-gray-100');
} }
// Rechter Pfeil deaktivieren, wenn es keine weiteren Termine gibt // Rechter Pfeil deaktivieren, wenn es keine weiteren Termine gibt
if (currentPosition >= maxPosition) { if (currentPosition >= maxPosition) {
scrollRightBtn.disabled = true; scrollRightBtn.disabled = true;
@@ -200,23 +218,23 @@ $de_months = [
// Erstelle ein Date-Objekt aus der ISO-Date // Erstelle ein Date-Objekt aus der ISO-Date
// JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00) // JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00)
const date = new Date(isoDate); const date = new Date(isoDate);
// Prüfe ob das Datum gültig ist // Prüfe ob das Datum gültig ist
if (isNaN(date.getTime())) { if (isNaN(date.getTime())) {
console.warn('Ungültiges Datum:', isoDate); console.warn('Ungültiges Datum:', isoDate);
return; return;
} }
// Formatiere die Zeit in der lokalen Zeitzone des Browsers // Formatiere die Zeit in der lokalen Zeitzone des Browsers
const localTime = date.toLocaleTimeString('de-DE', { const localTime = date.toLocaleTimeString('de-DE', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
hour12: false hour12: false
}); });
// Aktualisiere den Text // Aktualisiere den Text
element.textContent = localTime; element.textContent = localTime;
// Debug-Information (kann später entfernt werden) // Debug-Information (kann später entfernt werden)
console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone); console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
} catch (error) { } catch (error) {
@@ -250,32 +268,41 @@ $de_months = [
<style> <style>
/* Versteckt die Scrollbar vollständig, behält aber die Scrollfunktionalität */ /* Versteckt die Scrollbar vollständig, behält aber die Scrollfunktionalität */
#termine-container { #termine-container {
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none;
scrollbar-width: none; /* Firefox */ /* IE and Edge */
scrollbar-width: none;
/* Firefox */
overflow-x: visible; overflow-x: visible;
} }
#termine-container::-webkit-scrollbar { #termine-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */ display: none;
/* Chrome, Safari, Opera */
} }
/* Gleiche Kartenbreiten sicherstellen */ /* Gleiche Kartenbreiten sicherstellen */
@media (min-width: 1280px) { @media (min-width: 1280px) {
.termine-card { .termine-card {
width: 100%; /* 100% minus 2 Gaps (2 * 24px) geteilt durch 3 */ width: 100%;
/* 100% minus 2 Gaps (2 * 24px) geteilt durch 3 */
} }
#termine-container { #termine-container {
space-x-0; /* Kein Abstand zwischen Karten nötig */ space-x-0;
/* Kein Abstand zwischen Karten nötig */
} }
} }
/* Auf mobilen Geräten eine Karte anzeigen */ /* Auf mobilen Geräten eine Karte anzeigen */
@media (max-width: 1280px) { @media (max-width: 1280px) {
.termine-card { .termine-card {
width: 100%; /* Volle Breite */ width: 100%;
/* Volle Breite */
} }
#termine-container { #termine-container {
space-x-0; /* Kein Abstand zwischen Karten nötig */ space-x-0;
/* Kein Abstand zwischen Karten nötig */
} }
} }
@@ -283,9 +310,11 @@ $de_months = [
.termine-card h3 { .termine-card h3 {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
/* white-space: nowrap; */ /* Diese Zeile entfernen oder kommentieren */ /* white-space: nowrap; */
/* Diese Zeile entfernen oder kommentieren */
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; /* Maximal 2 Zeilen anzeigen */ -webkit-line-clamp: 2;
/* Maximal 2 Zeilen anzeigen */
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
line-height: 1.3; line-height: 1.3;
} }
+105 -96
View File
@@ -2,14 +2,22 @@
$events = collection('termine'); $events = collection('termine');
if ($events === null): ?>
<section class="py-24 bg-sf_grau-50">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="text-red-600 bg-white p-6 rounded-lg shadow">Die Termine können gerade nicht geladen werden.</div>
</div>
</section>
<?php return; endif;
// Nur Events mit DTSTART berücksichtigen // Nur Events mit DTSTART berücksichtigen
$events = array_filter($events, function ($event) { $events = array_filter($events, function ($event) {
return isset($event['DTSTART']) && !empty($event['DTSTART']); return isset($event['DTSTART']) && !empty($event['DTSTART']);
}); });
// Nach Datum sortieren // Nach Datum sortieren
usort($events, function ($a, $b) { usort($events, function ($a, $b) {
return strcmp($a['DTSTART'], $b['DTSTART']); return strcmp($a['DTSTART'], $b['DTSTART']);
}); });
// Aktuelles Datum - PHP 8.3 kompatibel // Aktuelles Datum - PHP 8.3 kompatibel
@@ -17,44 +25,44 @@ $today = (new DateTime('now'))->format('Ymd');
// Nur zukünftige Termine anzeigen // Nur zukünftige Termine anzeigen
$future_events = array_filter($events, function ($event) use ($today) { $future_events = array_filter($events, function ($event) use ($today) {
return isset($event['DTSTART']) && $event['DTSTART'] >= $today; return isset($event['DTSTART']) && $event['DTSTART'] >= $today;
}); });
// --- Deutsche Monatsnamen --- // --- Deutsche Monatsnamen ---
$de_months = [ $de_months = [
'01' => 'Januar', '01' => 'Januar',
'02' => 'Februar', '02' => 'Februar',
'03' => 'März', '03' => 'März',
'04' => 'April', '04' => 'April',
'05' => 'Mai', '05' => 'Mai',
'06' => 'Juni', '06' => 'Juni',
'07' => 'Juli', '07' => 'Juli',
'08' => 'August', '08' => 'August',
'09' => 'September', '09' => 'September',
'10' => 'Oktober', '10' => 'Oktober',
'11' => 'November', '11' => 'November',
'12' => 'Dezember', '12' => 'Dezember',
]; ];
// --- Gruppierung aller Termine nach Jahr und Monat für die Sidebar --- // --- Gruppierung aller Termine nach Jahr und Monat für die Sidebar ---
function group_events_by_year_month($events) function group_events_by_year_month($events)
{ {
$grouped = []; $grouped = [];
foreach ($events as $event) { foreach ($events as $event) {
if (!isset($event['DTSTART'])) { if (!isset($event['DTSTART'])) {
continue; continue;
}
$date = $event['DTSTART'];
$year = substr($date, 0, 4);
$month = substr($date, 4, 2);
$grouped[$year][$month][] = $event;
}
krsort($grouped); // Jahre absteigend
foreach ($grouped as &$months) {
krsort($months); // Monate absteigend
} }
$date = $event['DTSTART'];
$year = substr($date, 0, 4);
$month = substr($date, 4, 2);
$grouped[$year][$month][] = $event;
}
krsort($grouped); // Jahre absteigend
foreach ($grouped as &$months) {
krsort($months); // Monate absteigend
}
return $grouped; return $grouped;
} }
$all_events_grouped = group_events_by_year_month($events); $all_events_grouped = group_events_by_year_month($events);
@@ -64,19 +72,19 @@ $filter_jahr = filter_input(INPUT_GET, 'jahr', FILTER_SANITIZE_FULL_SPECIAL_CHAR
$filter_monat = filter_input(INPUT_GET, 'monat', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? null; $filter_monat = filter_input(INPUT_GET, 'monat', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? null;
if ($filter_jahr && $filter_monat) { if ($filter_jahr && $filter_monat) {
$filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) { $filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) {
$date = $event['DTSTART'] ?? ''; $date = $event['DTSTART'] ?? '';
return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat; return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat;
}); });
} elseif ($filter_jahr) { } elseif ($filter_jahr) {
$filtered_events = array_filter($events, function ($event) use ($filter_jahr) { $filtered_events = array_filter($events, function ($event) use ($filter_jahr) {
$date = $event['DTSTART'] ?? ''; $date = $event['DTSTART'] ?? '';
return substr($date, 0, 4) === $filter_jahr; return substr($date, 0, 4) === $filter_jahr;
}); });
} else { } else {
$filtered_events = $future_events; $filtered_events = $future_events;
} }
?> ?>
@@ -90,33 +98,34 @@ if ($filter_jahr && $filter_monat) {
<?php foreach ($all_events_grouped as $year => $months): ?> <?php foreach ($all_events_grouped as $year => $months): ?>
<li class="mb-2"> <li class="mb-2">
<div class="flex items-center"> <div class="flex items-center">
<?php <?php
$safe_year = htmlspecialchars($year, ENT_QUOTES, 'UTF-8'); $safe_year = htmlspecialchars($year, ENT_QUOTES, 'UTF-8');
$is_year_selected = ($filter_jahr === $year && !$filter_monat); $is_year_selected = ($filter_jahr === $year && !$filter_monat);
?> ?>
<a href="?jahr=<?= $safe_year ?>" <a href="?jahr=<?= $safe_year ?>"
class="font-bold text-sf_blau-600 focus:outline-none flex items-center group<?= $is_year_selected ? ' underline selected' : '' ?>" class="font-bold text-sf_blau-600 focus:outline-none flex items-center group<?= $is_year_selected ? ' underline selected' : '' ?>"
onclick="event.stopPropagation(); openYear('<?= $safe_year ?>')"> onclick="event.stopPropagation(); openYear('<?= $safe_year ?>')">
<span><?= $safe_year ?></span> <span><?= $safe_year ?></span>
</a> </a>
<button type="button" class="ml-1 focus:outline-none" onclick="toggleYear('<?= $safe_year ?>')"> <button type="button" class="ml-1 focus:outline-none"
<svg class="w-4 h-4 transition-transform" id="arrow-<?= $safe_year ?>" fill="none" stroke="currentColor" stroke-width="2" onclick="toggleYear('<?= $safe_year ?>')">
viewBox="0 0 24 24"> <svg class="w-4 h-4 transition-transform" id="arrow-<?= $safe_year ?>" fill="none"
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg> </svg>
</button> </button>
</div> </div>
<ul class="ml-4 mt-1 hidden" id="months-<?= $safe_year ?>"> <ul class="ml-4 mt-1 hidden" id="months-<?= $safe_year ?>">
<?php foreach ($months as $month => $evts): ?> <?php foreach ($months as $month => $evts): ?>
<li> <li>
<?php <?php
$safe_month = htmlspecialchars($month, ENT_QUOTES, 'UTF-8'); $safe_month = htmlspecialchars($month, ENT_QUOTES, 'UTF-8');
$month_name = $de_months[$month] ?? 'Unbekannt'; $month_name = $de_months[$month] ?? 'Unbekannt';
$is_month_selected = ($filter_jahr === $year && $filter_monat === $month); $is_month_selected = ($filter_jahr === $year && $filter_monat === $month);
$event_count = count($evts); $event_count = count($evts);
?> ?>
<a href="?jahr=<?= $safe_year ?>&monat=<?= $safe_month ?>" <a href="?jahr=<?= $safe_year ?>&monat=<?= $safe_month ?>"
class="text-sf_blau-500 hover:underline<?= $is_month_selected ? ' font-bold underline selected' : '' ?>"> class="text-sf_blau-500 hover:underline<?= $is_month_selected ? ' font-bold underline selected' : '' ?>">
<?= htmlspecialchars($month_name, ENT_QUOTES, 'UTF-8') ?> (<?= $event_count ?>) <?= htmlspecialchars($month_name, ENT_QUOTES, 'UTF-8') ?> (<?= $event_count ?>)
</a> </a>
</li> </li>
@@ -157,50 +166,50 @@ if ($filter_jahr && $filter_monat) {
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="min-w-full border border-gray-200 bg-white rounded-lg shadow"> <table class="min-w-full border border-gray-200 bg-white rounded-lg shadow">
<thead> <thead>
<tr class="bg-gray-100"> <tr class="bg-gray-100">
<th class="py-2 px-4 border-b text-left">Datum</th> <th class="py-2 px-4 border-b text-left">Datum</th>
<th class="py-2 px-4 border-b text-left">Uhrzeit</th> <th class="py-2 px-4 border-b text-left">Uhrzeit</th>
<th class="py-2 px-4 border-b text-left">Titel</th> <th class="py-2 px-4 border-b text-left">Titel</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($filtered_events as $event): ?> <?php foreach ($filtered_events as $event): ?>
<?php <?php
$start = $event['DTSTART'] ?? ''; $start = $event['DTSTART'] ?? '';
$end = $event['DTEND'] ?? ''; $end = $event['DTEND'] ?? '';
$summary = $event['SUMMARY'] ?? ''; $summary = $event['SUMMARY'] ?? '';
$location = $event['LOCATION'] ?? ''; $location = $event['LOCATION'] ?? '';
$desc = $event['DESCRIPTION'] ?? ''; $desc = $event['DESCRIPTION'] ?? '';
$timezone = $event['DTSTART_TZID'] ?? null; $timezone = $event['DTSTART_TZID'] ?? null;
// Sichere Funktionsaufrufe mit Null-Checks // Sichere Funktionsaufrufe mit Null-Checks
$date_info = function_exists('format_ics_date_with_timezone') $date_info = function_exists('format_ics_date_with_timezone')
? format_ics_date_with_timezone($start, $timezone) ? format_ics_date_with_timezone($start, $timezone)
: ['display' => '', 'has_time' => false, 'iso' => '']; : ['display' => '', 'has_time' => false, 'iso' => ''];
$date = $date_info['display'] ?? ''; $date = $date_info['display'] ?? '';
$time = ($date_info['has_time'] ?? false) ? substr($date, 11) : 'ganztägig'; $time = ($date_info['has_time'] ?? false) ? substr($date, 11) : 'ganztägig';
$date = substr($date, 0, 10); $date = substr($date, 0, 10);
$iso_date = $date_info['iso'] ?? ''; $iso_date = $date_info['iso'] ?? '';
// Sichere HTML-Ausgabe // Sichere HTML-Ausgabe
$safe_date = htmlspecialchars($date, ENT_QUOTES, 'UTF-8'); $safe_date = htmlspecialchars($date, ENT_QUOTES, 'UTF-8');
$safe_time = htmlspecialchars($time, ENT_QUOTES, 'UTF-8'); $safe_time = htmlspecialchars($time, ENT_QUOTES, 'UTF-8');
$safe_iso_date = htmlspecialchars($iso_date, ENT_QUOTES, 'UTF-8'); $safe_iso_date = htmlspecialchars($iso_date, ENT_QUOTES, 'UTF-8');
$safe_summary = htmlspecialchars($summary, ENT_QUOTES, 'UTF-8'); $safe_summary = htmlspecialchars($summary, ENT_QUOTES, 'UTF-8');
?> ?>
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50">
<td class="py-2 px-4 border-b whitespace-nowrap"><?= $safe_date ?></td> <td class="py-2 px-4 border-b whitespace-nowrap"><?= $safe_date ?></td>
<td class="py-2 px-4 border-b whitespace-nowrap"> <td class="py-2 px-4 border-b whitespace-nowrap">
<?php if ($date_info['has_time'] ?? false): ?> <?php if ($date_info['has_time'] ?? false): ?>
<span class="local-time" data-iso-date="<?= $safe_iso_date ?>"><?= $safe_time ?></span> <span class="local-time" data-iso-date="<?= $safe_iso_date ?>"><?= $safe_time ?></span>
<?php else: ?> <?php else: ?>
ganztägig ganztägig
<?php endif; ?> <?php endif; ?>
</td> </td>
<td class="py-2 px-4 border-b"><?= $safe_summary ?></td> <td class="py-2 px-4 border-b"><?= $safe_summary ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -214,7 +223,7 @@ if ($filter_jahr && $filter_monat) {
font-weight: bold; font-weight: bold;
} }
</style> </style>
<script> <script>
// Zeitzonenkonvertierung für lokale Zeiten // Zeitzonenkonvertierung für lokale Zeiten
function convertTimesToLocal() { function convertTimesToLocal() {
@@ -226,23 +235,23 @@ if ($filter_jahr && $filter_monat) {
// Erstelle ein Date-Objekt aus der ISO-Date // Erstelle ein Date-Objekt aus der ISO-Date
// JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00) // JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00)
const date = new Date(isoDate); const date = new Date(isoDate);
// Prüfe ob das Datum gültig ist // Prüfe ob das Datum gültig ist
if (isNaN(date.getTime())) { if (isNaN(date.getTime())) {
console.warn('Ungültiges Datum:', isoDate); console.warn('Ungültiges Datum:', isoDate);
return; return;
} }
// Formatiere die Zeit in der lokalen Zeitzone des Browsers // Formatiere die Zeit in der lokalen Zeitzone des Browsers
const localTime = date.toLocaleTimeString('de-DE', { const localTime = date.toLocaleTimeString('de-DE', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
hour12: false hour12: false
}); });
// Aktualisiere den Text // Aktualisiere den Text
element.textContent = localTime; element.textContent = localTime;
// Debug-Information (kann später entfernt werden) // Debug-Information (kann später entfernt werden)
console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone); console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
} catch (error) { } catch (error) {
@@ -253,7 +262,7 @@ if ($filter_jahr && $filter_monat) {
} }
// Zeitzonenkonvertierung beim Laden der Seite // Zeitzonenkonvertierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
convertTimesToLocal(); convertTimesToLocal();
}); });
</script> </script>