Files
schachfreunde-badsteben/site/snippets/termine.php
T
tfeigel 7e541e1244 Refactors content structure and adds blueprints
Restructures content files for better organization and consistency.

Moves static content from template files to content files.

Adds blueprints for new pages, including Archive, Datenschutz, DWZ, Error, Impressum, Kontakt, Satzung, Spielbetrieb, Termine, and Verein.

Updates the "termine" snippet to improve date filtering and security, escaping output for safety.
2025-09-06 18:37:29 +02:00

260 lines
12 KiB
PHP

<?php
$events = collection('termine');
// 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 - PHP 8.3 kompatibel
$today = (new DateTime('now'))->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 - sichere Eingabe ---
$filter_jahr = filter_input(INPUT_GET, 'jahr', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? null;
$filter_monat = filter_input(INPUT_GET, 'monat', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 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">
<?php
$safe_year = htmlspecialchars($year, ENT_QUOTES, 'UTF-8');
$is_year_selected = ($filter_jahr === $year && !$filter_monat);
?>
<a href="?jahr=<?= $safe_year ?>"
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 ?>')">
<span><?= $safe_year ?></span>
</a>
<button type="button" class="ml-1 focus:outline-none" onclick="toggleYear('<?= $safe_year ?>')">
<svg class="w-4 h-4 transition-transform" id="arrow-<?= $safe_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-<?= $safe_year ?>">
<?php foreach ($months as $month => $evts): ?>
<li>
<?php
$safe_month = htmlspecialchars($month, ENT_QUOTES, 'UTF-8');
$month_name = $de_months[$month] ?? 'Unbekannt';
$is_month_selected = ($filter_jahr === $year && $filter_monat === $month);
$event_count = count($evts);
?>
<a href="?jahr=<?= $safe_year ?>&monat=<?= $safe_month ?>"
class="text-sf_blau-500 hover:underline<?= $is_month_selected ? ' font-bold underline selected' : '' ?>">
<?= htmlspecialchars($month_name, ENT_QUOTES, 'UTF-8') ?> (<?= $event_count ?>)
</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'] ?? '';
$timezone = $event['DTSTART_TZID'] ?? null;
// Sichere Funktionsaufrufe mit Null-Checks
$date_info = function_exists('format_ics_date_with_timezone')
? format_ics_date_with_timezone($start, $timezone)
: ['display' => '', 'has_time' => false, 'iso' => ''];
$date = $date_info['display'] ?? '';
$time = ($date_info['has_time'] ?? false) ? substr($date, 11) : 'ganztägig';
$date = substr($date, 0, 10);
$iso_date = $date_info['iso'] ?? '';
// Sichere HTML-Ausgabe
$safe_date = htmlspecialchars($date, ENT_QUOTES, 'UTF-8');
$safe_time = htmlspecialchars($time, ENT_QUOTES, 'UTF-8');
$safe_iso_date = htmlspecialchars($iso_date, ENT_QUOTES, 'UTF-8');
$safe_summary = htmlspecialchars($summary, ENT_QUOTES, 'UTF-8');
?>
<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">
<?php if ($date_info['has_time'] ?? false): ?>
<span class="local-time" data-iso-date="<?= $safe_iso_date ?>"><?= $safe_time ?></span>
<?php else: ?>
ganztägig
<?php endif; ?>
</td>
<td class="py-2 px-4 border-b"><?= $safe_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>
<script>
// Zeitzonenkonvertierung für lokale Zeiten
function convertTimesToLocal() {
const timeElements = document.querySelectorAll('.local-time');
timeElements.forEach(element => {
const isoDate = element.getAttribute('data-iso-date');
if (isoDate) {
try {
// Erstelle ein Date-Objekt aus der ISO-Date
// JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00)
const date = new Date(isoDate);
// Prüfe ob das Datum gültig ist
if (isNaN(date.getTime())) {
console.warn('Ungültiges Datum:', isoDate);
return;
}
// Formatiere die Zeit in der lokalen Zeitzone des Browsers
const localTime = date.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Aktualisiere den Text
element.textContent = localTime;
// Debug-Information (kann später entfernt werden)
console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
} catch (error) {
console.warn('Fehler bei der Zeitzonenkonvertierung:', error, 'für Datum:', isoDate);
}
}
});
}
// Zeitzonenkonvertierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
convertTimesToLocal();
});
</script>
</section>