2bf81134c7
Standardized code formatting across multiple files for improved readability and consistency.
271 lines
10 KiB
PHP
271 lines
10 KiB
PHP
<?php
|
|
|
|
// URL der öffentlichen ICS-Datei
|
|
define(
|
|
'CAL_URL',
|
|
'https://calendar.google.com/calendar/ical/jv1bq94un3ivoa8ka0rk9ngq4k%40group.calendar.google.com/public/basic.ics',
|
|
);
|
|
|
|
// ICS-Datei laden
|
|
$ics = @file_get_contents(CAL_URL);
|
|
if ( ! $ics) {
|
|
echo '<div class="text-red-600">Kalender konnte nicht geladen werden.</div>';
|
|
|
|
return;
|
|
}
|
|
|
|
// Termine parsen
|
|
function parse_ics($ics)
|
|
{
|
|
$lines = explode("\n", $ics);
|
|
$events = [];
|
|
$event = [];
|
|
$inEvent = false;
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if ($line === 'BEGIN:VEVENT') {
|
|
$inEvent = true;
|
|
$event = [];
|
|
} elseif ($line === 'END:VEVENT') {
|
|
$inEvent = false;
|
|
$events[] = $event;
|
|
} elseif ($inEvent) {
|
|
// Property kann Parameter enthalten, z.B. DTSTART;TZID=Europe/Berlin:20240701T19000000
|
|
$parts = explode(':', $line, 2);
|
|
if (count($parts) === 2) {
|
|
$key = $parts[0];
|
|
$val = $parts[1];
|
|
// Nur den eigentlichen Property-Namen extrahieren
|
|
$key = strtoupper(preg_replace('/;.+$/', '', $key));
|
|
$event[$key] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
function format_ics_date($date)
|
|
{
|
|
// Unterstützt sowohl ganztägige als auch Zeitangaben
|
|
if (strpos($date, 'T') !== false) {
|
|
$dt = DateTime::createFromFormat('Ymd\THis', substr($date, 0, 15));
|
|
|
|
return $dt ? $dt->format('d.m.Y H:i') : $date;
|
|
} else {
|
|
$dt = DateTime::createFromFormat('Ymd', $date);
|
|
|
|
return $dt ? $dt->format('d.m.Y') : $date;
|
|
}
|
|
}
|
|
|
|
$events = parse_ics($ics);
|
|
// Nur Events mit DTSTART berücksichtigen
|
|
$events = array_filter($events, function ($event) {
|
|
return isset($event['DTSTART']) && ! empty($event['DTSTART']);
|
|
});
|
|
// Nach Datum sortieren
|
|
usort($events, function ($a, $b) {
|
|
return strcmp($a['DTSTART'], $b['DTSTART']);
|
|
});
|
|
|
|
// Aktuelles Datum
|
|
$today = (new DateTime())->format('Ymd');
|
|
|
|
// Nur zukünftige Termine anzeigen
|
|
$future_events = array_filter($events, function ($event) use ($today) {
|
|
return isset($event['DTSTART']) && $event['DTSTART'] >= $today;
|
|
});
|
|
|
|
// --- Deutsche Monatsnamen ---
|
|
$de_months = [
|
|
'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',
|
|
];
|
|
|
|
// --- Gruppierung aller Termine nach Jahr und Monat für die Sidebar ---
|
|
function group_events_by_year_month($events)
|
|
{
|
|
$grouped = [];
|
|
foreach ($events as $event) {
|
|
if ( ! isset($event['DTSTART'])) {
|
|
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
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
$all_events_grouped = group_events_by_year_month($events);
|
|
|
|
// --- Filter aus URL ---
|
|
$filter_jahr = $_GET['jahr'] ?? null;
|
|
$filter_monat = $_GET['monat'] ?? null;
|
|
if ($filter_jahr && $filter_monat) {
|
|
$filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) {
|
|
$date = $event['DTSTART'] ?? '';
|
|
|
|
return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat;
|
|
});
|
|
} elseif ($filter_jahr) {
|
|
$filtered_events = array_filter($events, function ($event) use ($filter_jahr) {
|
|
$date = $event['DTSTART'] ?? '';
|
|
|
|
return substr($date, 0, 4) === $filter_jahr;
|
|
});
|
|
} else {
|
|
$filtered_events = $future_events;
|
|
}
|
|
?>
|
|
<section class="py-24 bg-sf_grau-50">
|
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex flex-col md:flex-row gap-8">
|
|
<!-- Sidebar -->
|
|
<aside class="md:w-1/4 w-full mb-8 md:mb-0">
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h2 class="text-lg font-semibold mb-3">Termine nach Jahr/Monat</h2>
|
|
<ul class="space-y-1">
|
|
<?php
|
|
foreach ($all_events_grouped as $year => $months): ?>
|
|
<li class="mb-2">
|
|
<div class="flex items-center">
|
|
<a href="?jahr=<?php
|
|
echo $year; ?>"
|
|
class="font-bold text-sf_blau-600 focus:outline-none flex items-center group<?php
|
|
if ($filter_jahr === $year && ! $filter_monat) {
|
|
echo ' underline';
|
|
} ?><?php
|
|
if ($filter_jahr === $year && ! $filter_monat) {
|
|
echo ' selected';
|
|
} ?>" onclick="event.stopPropagation(); openYear('<?php
|
|
echo $year; ?>')">
|
|
<span><?php
|
|
echo $year; ?></span>
|
|
</a>
|
|
<button type="button" class="ml-1 focus:outline-none" onclick="toggleYear('<?php
|
|
echo $year; ?>')">
|
|
<svg class="w-4 h-4 transition-transform" id="arrow-<?php
|
|
echo $year; ?>" fill="none" stroke="currentColor" stroke-width="2"
|
|
viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<ul class="ml-4 mt-1 hidden" id="months-<?php
|
|
echo $year; ?>">
|
|
<?php
|
|
foreach ($months as $month => $evts): ?>
|
|
<li>
|
|
<a href="?jahr=<?php
|
|
echo $year; ?>&monat=<?php
|
|
echo $month; ?>" class="text-sf_blau-500 hover:underline<?php
|
|
if ($filter_jahr === $year && $filter_monat === $month) {
|
|
echo ' font-bold underline selected';
|
|
} ?>">
|
|
<?php
|
|
echo $de_months[$month]; ?> (<?php
|
|
echo count($evts); ?>)
|
|
</a>
|
|
</li>
|
|
<?php
|
|
endforeach; ?>
|
|
</ul>
|
|
</li>
|
|
<?php
|
|
endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<script>
|
|
function toggleYear(year) {
|
|
var ul = document.getElementById('months-' + year);
|
|
var arrow = document.getElementById('arrow-' + year);
|
|
if (ul.classList.contains('hidden')) {
|
|
ul.classList.remove('hidden');
|
|
arrow.style.transform = 'rotate(90deg)';
|
|
} else {
|
|
ul.classList.add('hidden');
|
|
arrow.style.transform = '';
|
|
}
|
|
}
|
|
|
|
function openYear(year) {
|
|
var ul = document.getElementById('months-' + year);
|
|
var arrow = document.getElementById('arrow-' + year);
|
|
if (ul && ul.classList.contains('hidden')) {
|
|
ul.classList.remove('hidden');
|
|
arrow.style.transform = 'rotate(90deg)';
|
|
}
|
|
}
|
|
</script>
|
|
</aside>
|
|
<!-- Hauptinhalt Termine -->
|
|
<div class="md:w-3/4 w-full">
|
|
<?php
|
|
if (empty($filtered_events)): ?>
|
|
<div class="text-gray-500">Keine Termine gefunden.</div>
|
|
<?php
|
|
else: ?>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full border border-gray-200 bg-white rounded-lg shadow">
|
|
<thead>
|
|
<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">Uhrzeit</th>
|
|
<th class="py-2 px-4 border-b text-left">Titel</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
foreach ($filtered_events as $event): ?>
|
|
<?php
|
|
$start = $event['DTSTART'] ?? '';
|
|
$end = $event['DTEND'] ?? '';
|
|
$summary = $event['SUMMARY'] ?? '';
|
|
$location = $event['LOCATION'] ?? '';
|
|
$desc = $event['DESCRIPTION'] ?? '';
|
|
$date = format_ics_date($start);
|
|
$time = (str_contains($start, 'T')) ? substr($date, 11) : 'ganztägig';
|
|
$date = substr($date, 0, 10);
|
|
?>
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="py-2 px-4 border-b whitespace-nowrap"><?php
|
|
echo htmlspecialchars($date); ?></td>
|
|
<td class="py-2 px-4 border-b whitespace-nowrap"><?php
|
|
echo htmlspecialchars($time); ?></td>
|
|
<td class="py-2 px-4 border-b"><?php
|
|
echo htmlspecialchars($summary); ?></td>
|
|
</tr>
|
|
<?php
|
|
endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
endif; ?>
|
|
</div>
|
|
</div>
|
|
<style>
|
|
.selected {
|
|
text-decoration: underline;
|
|
text-underline-offset: 3px;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
</section>
|