Berechnet Bedeckungen der Planeten Merkur bis Neptun durch den Mond fuer ein ganzes Jahr am Standardstandort, inklusive Detailkarte. Ereignisse unter dem Horizont werden weiterhin angezeigt statt gefiltert, dafuer mit eigenem Sichtbarkeits-Hinweis. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
553 lines
23 KiB
PHP
553 lines
23 KiB
PHP
<?php
|
||
require_once __DIR__ . '/auth_helpers.php';
|
||
applyLocalDevLoginBypass();
|
||
$loggedIn = isset($_SESSION['user_id']);
|
||
|
||
$title = isset($pageTitle) && is_string($pageTitle) && $pageTitle !== ''
|
||
? $pageTitle
|
||
: 'Skyview & AstroTools - Astronomiemuseum der Sternwarte Sonneberg';
|
||
|
||
$bodyClassAttr = '';
|
||
if (isset($bodyClass) && is_string($bodyClass) && trim($bodyClass) !== '') {
|
||
$bodyClassAttr = ' class="' . htmlspecialchars(trim($bodyClass), ENT_QUOTES, 'UTF-8') . '"';
|
||
}
|
||
|
||
$headerIntroPre = isset($headerIntroPre) && is_string($headerIntroPre) ? trim($headerIntroPre) : '';
|
||
$headerIntroTitle = isset($headerIntroTitle) && is_string($headerIntroTitle) ? trim($headerIntroTitle) : '';
|
||
$headerIntroSub = isset($headerIntroSub) && is_string($headerIntroSub) ? trim($headerIntroSub) : '';
|
||
$showHeaderIntro = $headerIntroPre !== '' || $headerIntroTitle !== '' || $headerIntroSub !== '';
|
||
$publicBasePath = isset($publicBasePath) && is_string($publicBasePath) ? rtrim($publicBasePath, '/') . '/' : '';
|
||
$pdoHeader = isset($pdo) ? $pdo : null;
|
||
$hasFullAdminAccess = $loggedIn && currentUserIsAdminLike();
|
||
$canAccessCurrentPage = $hasFullAdminAccess;
|
||
$pendingApprovals = 0;
|
||
$publicScriptName = currentPublicPageKey();
|
||
$publicAccessAllowlist = publicAccessAllowlist();
|
||
$manageablePublicPages = manageablePublicPages();
|
||
|
||
if ($loggedIn && $pdoHeader === null) {
|
||
$cfgHeader = require __DIR__ . '/../config/database.php';
|
||
$pdoHeader = new PDO(
|
||
sprintf('mysql:host=%s;dbname=%s;charset=%s', $cfgHeader['host'], $cfgHeader['dbname'], $cfgHeader['charset'] ?? 'utf8mb4'),
|
||
$cfgHeader['user'],
|
||
$cfgHeader['pass'],
|
||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||
);
|
||
}
|
||
|
||
if (!in_array($publicScriptName, $publicAccessAllowlist, true) && !$canAccessCurrentPage) {
|
||
if (!$loggedIn) {
|
||
header('Location: ' . $publicBasePath . 'login.php');
|
||
exit;
|
||
}
|
||
|
||
$userHasPagePermission = false;
|
||
if ($pdoHeader !== null && isset($manageablePublicPages[$publicScriptName], $_SESSION['user_id'])) {
|
||
try {
|
||
$stmtPagePermission = $pdoHeader->prepare("
|
||
SELECT 1
|
||
FROM `app_user_page_permissions`
|
||
WHERE `user_id` = :user_id
|
||
AND `page_key` = :page_key
|
||
LIMIT 1
|
||
");
|
||
$stmtPagePermission->execute([
|
||
':user_id' => (int) $_SESSION['user_id'],
|
||
':page_key' => $publicScriptName,
|
||
]);
|
||
$userHasPagePermission = (bool) $stmtPagePermission->fetchColumn();
|
||
} catch (Throwable $e) {
|
||
$userHasPagePermission = false;
|
||
}
|
||
}
|
||
|
||
if ($userHasPagePermission) {
|
||
$canAccessCurrentPage = true;
|
||
}
|
||
}
|
||
|
||
if (!in_array($publicScriptName, $publicAccessAllowlist, true) && !$canAccessCurrentPage) {
|
||
if (!$loggedIn) {
|
||
header('Location: ' . $publicBasePath . 'login.php');
|
||
exit;
|
||
}
|
||
|
||
http_response_code(403);
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Zugriff verweigert</title>
|
||
<link rel="stylesheet" href="<?= htmlspecialchars($publicBasePath . 'css/style.css', ENT_QUOTES, 'UTF-8') ?>">
|
||
</head>
|
||
<body>
|
||
<main class="container" style="padding-top: 4rem; padding-bottom: 4rem;">
|
||
<section class="card">
|
||
<h1>Zugriff verweigert</h1>
|
||
<p>Diese Seite ist aktuell nur für Benutzer mit der Rolle <strong>admin</strong> oder <strong>master</strong> freigegeben.</p>
|
||
<p><a class="btn btn-primary" href="<?= htmlspecialchars($publicBasePath . 'logout.php', ENT_QUOTES, 'UTF-8') ?>">Abmelden</a></p>
|
||
</section>
|
||
</main>
|
||
</body>
|
||
</html>
|
||
<?php
|
||
exit;
|
||
}
|
||
// Standard-Standort des eingeloggten Benutzers laden
|
||
$currentLocation = null;
|
||
if (!empty($defaultLocation)) {
|
||
// Seite hat den Standort bereits geladen – direkt übernehmen
|
||
$currentLocation = $defaultLocation;
|
||
} elseif ($loggedIn && isset($_SESSION['user_id'])) {
|
||
// Eigene DB-Verbindung aufbauen falls die Seite keine bereitstellt
|
||
$stmtLoc = $pdoHeader->prepare(
|
||
'SELECT id, name, latitude, longitude, elevation, timezone
|
||
FROM app_user_locations
|
||
WHERE user_id = ? AND is_default = 1
|
||
LIMIT 1'
|
||
);
|
||
$stmtLoc->execute([(int)$_SESSION['user_id']]);
|
||
$currentLocation = $stmtLoc->fetch() ?: null;
|
||
}
|
||
|
||
if ($hasFullAdminAccess && $pdoHeader !== null) {
|
||
$stmtPendingUsers = $pdoHeader->query('SELECT COUNT(*) FROM app_users WHERE is_active = 0');
|
||
$pendingApprovals = (int)$stmtPendingUsers->fetchColumn();
|
||
}
|
||
|
||
// Favoriten-Kometen des eingeloggten Users für den Sternenhimmel laden
|
||
$favCometsForCanvas = [];
|
||
if ($loggedIn && isset($_SESSION['user_id']) && $pdoHeader !== null) {
|
||
try {
|
||
$stmtFavComets = $pdoHeader->prepare(
|
||
'SELECT c.designation_and_name
|
||
FROM app_user_comets uc
|
||
JOIN comets_mpc c ON c.id = uc.comet_id
|
||
WHERE uc.user_id = ? AND uc.is_favorite = 1
|
||
ORDER BY c.designation_and_name
|
||
LIMIT 8'
|
||
);
|
||
$stmtFavComets->execute([(int)$_SESSION['user_id']]);
|
||
$favCometsForCanvas = $stmtFavComets->fetchAll(PDO::FETCH_COLUMN);
|
||
} catch (Throwable $e) {
|
||
$favCometsForCanvas = [];
|
||
}
|
||
}
|
||
|
||
$menuGroups = [
|
||
[
|
||
'title' => 'Mond & Finsternisse',
|
||
'links' => [
|
||
['href' => 'moonphase.php', 'label' => 'Mondphase'],
|
||
['href' => 'moonyear.php', 'label' => 'Mondphasen-Jahresübersicht'],
|
||
['href' => 'mondfinsternis.php', 'label' => 'Mondfinsternis'],
|
||
['href' => 'sonnenfinsternis.php', 'label' => 'Sonnenfinsternis'],
|
||
['href' => 'bedeckung.php', 'label' => 'Bedeckungsphänomene'],
|
||
['href' => 'sternbedeckungen.php', 'label' => 'Mond-Stern-Bedeckungen'],
|
||
['href' => 'mondplanetenbedeckungen.php', 'label' => 'Mond-Planeten-Bedeckungen'],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Planeten & Sonnensystem',
|
||
'links' => [
|
||
['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'],
|
||
['href' => 'sonnensternbilder.php', 'label' => 'Sonne in Sternbildern'],
|
||
['href' => 'ephemeriden.php', 'label' => 'Ephemeriden'],
|
||
['href' => 'solarsystem.php', 'label' => 'Sonnensystem'],
|
||
['href' => 'barycenter.php', 'label' => 'Baryzentrum'],
|
||
['href' => 'mars.php', 'label' => 'Mars'],
|
||
['href' => 'jupitersystem.php', 'label' => 'Jupitersystem'],
|
||
['href' => 'asteroid_temp.php', 'label' => 'Asteroid-Temperatur'],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Sternenhimmel & Karten',
|
||
'links' => [
|
||
['href' => 'sternenhimmel.php', 'label' => 'Sternenhimmel'],
|
||
['href' => 'drehbare_sternkarte.php', 'label' => 'Drehbare Sternkarte'],
|
||
['href' => 'satellitenhimmel.php', 'label' => 'Satellitenhimmel'],
|
||
['href' => 'tagbogen.php', 'label' => 'Tagbogen'],
|
||
['href' => 'daemmerungszeiten.php', 'label' => 'Dämmerungszeiten'],
|
||
['href' => 'meteorshowers.php', 'label' => 'Meteorströme'],
|
||
['href' => 'comets.php', 'label' => 'Kometen'],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Tools & Simulationen',
|
||
'links' => [
|
||
['href' => 'tcrb_lightcurve.php', 'label' => 'T CrB Lichtkurve'],
|
||
['href' => 'astronomical_conversions.php', 'label' => 'Astronomical Conversions'],
|
||
['href' => 'blackbody_radiation.php', 'label' => 'Schwarzkörperstrahlung'],
|
||
['href' => 'geocron/index.php', 'label' => 'Geocron'],
|
||
['href' => 'geocron3d.php', 'label' => 'Geocron 3D'],
|
||
['href' => 'geocron3d_C.php', 'label' => 'Geocron 3D Cesium'],
|
||
['href' => 'universesandbox.php', 'label' => 'Universe Sandbox'],
|
||
],
|
||
],
|
||
];
|
||
|
||
$adminLinks = [
|
||
['href' => 'admin_users.php', 'label' => 'Benutzerverwaltung'],
|
||
['href' => 'monatsvorhersage.php', 'label' => 'Monatsvorhersage'],
|
||
['href' => 'telegram/broadcast_test.php', 'label' => 'Telegram-Test'],
|
||
];
|
||
$accountLinks = [
|
||
['href' => 'settings.php', 'label' => 'Einstellungen'],
|
||
['href' => 'my_favorites.php', 'label' => 'Favoriten'],
|
||
];
|
||
|
||
if ($loggedIn && !$hasFullAdminAccess && $pdoHeader !== null) {
|
||
$allowedPageKeys = [];
|
||
try {
|
||
$stmtAllowedPages = $pdoHeader->prepare("
|
||
SELECT `page_key`
|
||
FROM `app_user_page_permissions`
|
||
WHERE `user_id` = :user_id
|
||
");
|
||
$stmtAllowedPages->execute([
|
||
':user_id' => (int) ($_SESSION['user_id'] ?? 0),
|
||
]);
|
||
foreach ($stmtAllowedPages as $allowedPageRow) {
|
||
$pageKey = trim((string) ($allowedPageRow['page_key'] ?? ''));
|
||
if ($pageKey !== '') {
|
||
$allowedPageKeys[$pageKey] = true;
|
||
}
|
||
}
|
||
} catch (Throwable $e) {
|
||
$allowedPageKeys = [];
|
||
}
|
||
|
||
foreach ($menuGroups as &$group) {
|
||
$group['links'] = array_values(array_filter(
|
||
$group['links'],
|
||
static function (array $link) use ($allowedPageKeys, $publicAccessAllowlist): bool {
|
||
$href = trim((string) ($link['href'] ?? ''));
|
||
return $href !== '' && (isset($allowedPageKeys[$href]) || in_array($href, $publicAccessAllowlist, true));
|
||
}
|
||
));
|
||
}
|
||
unset($group);
|
||
|
||
$menuGroups = array_values(array_filter(
|
||
$menuGroups,
|
||
static fn (array $group): bool => !empty($group['links'])
|
||
));
|
||
|
||
$accountLinks = array_values(array_filter(
|
||
$accountLinks,
|
||
static function (array $link) use ($allowedPageKeys, $publicAccessAllowlist): bool {
|
||
$href = trim((string) ($link['href'] ?? ''));
|
||
return $href !== '' && (isset($allowedPageKeys[$href]) || in_array($href, $publicAccessAllowlist, true));
|
||
}
|
||
));
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></title>
|
||
<link rel="stylesheet" href="<?= htmlspecialchars($publicBasePath . 'css/style.css', ENT_QUOTES, 'UTF-8') ?>">
|
||
</head>
|
||
<body<?= $bodyClassAttr ?>>
|
||
|
||
<!-- Sternenhimmel -->
|
||
<canvas id="starfield"></canvas>
|
||
<script>
|
||
(function () {
|
||
const canvas = document.getElementById('starfield');
|
||
const ctx = canvas.getContext('2d');
|
||
let stars = [];
|
||
|
||
// Bilder vorladen
|
||
const imgRound = new Image();
|
||
const imgRays = new Image();
|
||
imgRound.src = <?= json_encode($publicBasePath . 'images/star16x16.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
|
||
imgRays.src = <?= json_encode($publicBasePath . 'images/star16x16_rays.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
|
||
|
||
function resize() {
|
||
canvas.width = window.innerWidth;
|
||
canvas.height = window.innerHeight;
|
||
}
|
||
|
||
function generateStars() {
|
||
stars = [];
|
||
const count = Math.floor((canvas.width * canvas.height) / 2800);
|
||
for (let i = 0; i < count; i++) {
|
||
const r = Math.random();
|
||
// Klassen: 80% klein-rund, 17% mittel-rund, 3% mit Strahlen
|
||
const hasRays = r > 0.97;
|
||
const size = hasRays ? (Math.random() * 6 + 8) // 8–14 px
|
||
: r > 0.80 ? (Math.random() * 4 + 5) // 5–9 px
|
||
: (Math.random() * 3 + 2); // 2–5 px
|
||
stars.push({
|
||
x: Math.random() * canvas.width,
|
||
y: Math.random() * canvas.height,
|
||
size,
|
||
img: hasRays ? imgRays : imgRound,
|
||
base: hasRays ? Math.random() * 0.20 + 0.12
|
||
: Math.random() * 0.18 + 0.04,
|
||
amp: Math.random() * 0.08 + 0.01,
|
||
phase: Math.random() * Math.PI * 2,
|
||
speed: Math.random() * 0.0003 + 0.00008,
|
||
});
|
||
}
|
||
}
|
||
|
||
let t = 0;
|
||
function draw() {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
t++;
|
||
for (const s of stars) {
|
||
const a = s.base + s.amp * Math.sin(s.phase + t * s.speed * Math.PI * 2);
|
||
ctx.globalAlpha = a;
|
||
ctx.drawImage(s.img, s.x - s.size / 2, s.y - s.size / 2, s.size, s.size);
|
||
}
|
||
ctx.globalAlpha = 1;
|
||
requestAnimationFrame(draw);
|
||
}
|
||
|
||
// Erst zeichnen wenn beide Bilder geladen sind
|
||
let loaded = 0;
|
||
function onLoad() { if (++loaded === 2) { resize(); generateStars(); draw(); } }
|
||
imgRound.onload = onLoad;
|
||
imgRays.onload = onLoad;
|
||
|
||
window.addEventListener('resize', () => { resize(); generateStars(); });
|
||
})();
|
||
</script>
|
||
|
||
<?php if (!empty($favCometsForCanvas)): ?>
|
||
<script>
|
||
(function () {
|
||
const COMET_NAMES = <?= json_encode(array_values($favCometsForCanvas), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||
if (!COMET_NAMES.length) return;
|
||
|
||
const canvas = document.getElementById('starfield');
|
||
// Warten bis das Sternfeld-Canvas bereit ist
|
||
function initComets() {
|
||
if (!canvas.width) { setTimeout(initComets, 100); return; }
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
// Jeden Kometen mit Startposition und Geschwindigkeit initialisieren
|
||
const comets = COMET_NAMES.map(function (name, i) {
|
||
return makeComet(name, i, COMET_NAMES.length, canvas.width, canvas.height);
|
||
});
|
||
|
||
function makeComet(name, index, total, w, h) {
|
||
// Kometen gleichmäßig über die Höhe verteilt, aber mit leichtem Versatz
|
||
const seedY = (index + 0.5) / total;
|
||
return {
|
||
name: name,
|
||
x: Math.random() * w,
|
||
y: seedY * h * 0.85 + h * 0.05,
|
||
// Langsame Bewegung leicht schräg nach rechts-unten
|
||
vx: 0.12 + Math.random() * 0.10,
|
||
vy: (Math.random() - 0.5) * 0.04,
|
||
// Schweiflänge und Coma-Größe
|
||
tailLen: 60 + Math.random() * 50,
|
||
comaR: 2.5 + Math.random() * 1.5,
|
||
// Leichtes Funkeln
|
||
phase: Math.random() * Math.PI * 2,
|
||
speed: 0.0004 + Math.random() * 0.0003,
|
||
};
|
||
}
|
||
|
||
let t = 0;
|
||
function drawComets() {
|
||
t++;
|
||
const w = canvas.width;
|
||
const h = canvas.height;
|
||
|
||
for (const c of comets) {
|
||
c.x += c.vx;
|
||
c.y += c.vy;
|
||
|
||
// Wenn rechts raus → links neu eintreten, y leicht variieren
|
||
if (c.x - c.tailLen > w) {
|
||
c.x = -c.tailLen;
|
||
c.y = Math.random() * h * 0.85 + h * 0.05;
|
||
}
|
||
|
||
const alpha = 0.55 + 0.18 * Math.sin(c.phase + t * c.speed * Math.PI * 2);
|
||
|
||
// --- Schweif (Gradient nach links) ---
|
||
const grd = ctx.createLinearGradient(c.x, c.y, c.x - c.tailLen, c.y);
|
||
grd.addColorStop(0, `rgba(201,168,76,${(alpha * 0.7).toFixed(3)})`);
|
||
grd.addColorStop(0.4, `rgba(240,217,144,${(alpha * 0.25).toFixed(3)})`);
|
||
grd.addColorStop(1, 'rgba(201,168,76,0)');
|
||
|
||
ctx.beginPath();
|
||
ctx.moveTo(c.x, c.y);
|
||
ctx.lineTo(c.x - c.tailLen, c.y - c.tailLen * 0.06);
|
||
ctx.strokeStyle = grd;
|
||
ctx.lineWidth = c.comaR * 0.9;
|
||
ctx.lineCap = 'round';
|
||
ctx.stroke();
|
||
|
||
// --- Coma (Leuchtkern) ---
|
||
const glow = ctx.createRadialGradient(c.x, c.y, 0, c.x, c.y, c.comaR * 3);
|
||
glow.addColorStop(0, `rgba(255,245,200,${alpha.toFixed(3)})`);
|
||
glow.addColorStop(0.4, `rgba(201,168,76,${(alpha * 0.6).toFixed(3)})`);
|
||
glow.addColorStop(1, 'rgba(201,168,76,0)');
|
||
|
||
ctx.beginPath();
|
||
ctx.arc(c.x, c.y, c.comaR * 3, 0, Math.PI * 2);
|
||
ctx.fillStyle = glow;
|
||
ctx.fill();
|
||
|
||
// --- Namens-Label ---
|
||
ctx.font = '11px sans-serif';
|
||
ctx.fillStyle = `rgba(240,217,144,${(alpha * 0.85).toFixed(3)})`;
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(c.name, c.x + c.comaR * 3 + 4, c.y - 8);
|
||
}
|
||
}
|
||
|
||
// In den bestehenden Animation-Loop einhängen
|
||
const origRAF = window.requestAnimationFrame;
|
||
(function loop() {
|
||
drawComets();
|
||
origRAF(loop);
|
||
})();
|
||
}
|
||
|
||
// Kurze Verzögerung damit der Stern-Canvas zuerst initialisiert wird
|
||
setTimeout(initComets, 200);
|
||
})();
|
||
</script>
|
||
<?php endif; ?>
|
||
|
||
<header>
|
||
<div class="container">
|
||
<div class="header-inner">
|
||
<div class="header-brand">
|
||
<a href="<?= htmlspecialchars($publicBasePath . 'index.php', ENT_QUOTES, 'UTF-8') ?>">Skyview & AstroTools</a>
|
||
<span class="header-sub"><a href="https://www.astronomiemuseum.de" target="_blank" rel="noopener">Astronomiemuseum der Sternwarte Sonneberg</a></span>
|
||
</div>
|
||
<?php if ($hasFullAdminAccess && $pendingApprovals > 0): ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . 'admin_users.php', ENT_QUOTES, 'UTF-8') ?>" class="admin-alert" aria-live="polite">
|
||
<span class="admin-alert-icon" aria-hidden="true">!</span>
|
||
<span class="admin-alert-text">Achtung: <?= $pendingApprovals ?> <?= $pendingApprovals === 1 ? 'Benutzer wartet' : 'Benutzer warten' ?> auf Freischaltung</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
<div class="header-menu">
|
||
<button type="button" class="menu-toggle" id="menuToggle" aria-expanded="false" aria-controls="headerMenuPanel" aria-label="Menü öffnen">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</button>
|
||
<nav class="menu-panel" id="headerMenuPanel" hidden>
|
||
<?php if ($loggedIn): ?>
|
||
<div class="menu-cols">
|
||
<?php foreach ($menuGroups as $group): ?>
|
||
<div class="menu-group">
|
||
<div class="menu-title"><?= $group['title'] ?></div>
|
||
<?php foreach ($group['links'] as $link): ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . $link['href'], ENT_QUOTES, 'UTF-8') ?>">
|
||
<?= htmlspecialchars($link['label'], ENT_QUOTES, 'UTF-8') ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<div class="menu-divider"></div>
|
||
<?php endif; ?>
|
||
<div class="menu-bottom">
|
||
<div class="menu-group">
|
||
<div class="menu-title">Konto</div>
|
||
<?php if ($loggedIn): ?>
|
||
<?php foreach ($accountLinks as $accountLink): ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . $accountLink['href'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($accountLink['label'], ENT_QUOTES, 'UTF-8') ?></a>
|
||
<?php endforeach; ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . 'logout.php', ENT_QUOTES, 'UTF-8') ?>" class="nav-logout">Abmelden</a>
|
||
<?php else: ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . 'login.php', ENT_QUOTES, 'UTF-8') ?>">Anmelden</a>
|
||
<a href="<?= htmlspecialchars($publicBasePath . 'register.php', ENT_QUOTES, 'UTF-8') ?>">Registrieren</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php if ($hasFullAdminAccess): ?>
|
||
<div class="menu-group">
|
||
<div class="menu-title">Administration</div>
|
||
<?php foreach ($adminLinks as $adminLink): ?>
|
||
<a href="<?= htmlspecialchars($publicBasePath . $adminLink['href'], ENT_QUOTES, 'UTF-8') ?>">
|
||
<?= htmlspecialchars($adminLink['label'], ENT_QUOTES, 'UTF-8') ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
<?php if ($showHeaderIntro): ?>
|
||
<div class="page-header-intro">
|
||
<?php if ($headerIntroPre !== ''): ?>
|
||
<div class="page-header-pre"><?= htmlspecialchars($headerIntroPre, ENT_QUOTES, 'UTF-8') ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($headerIntroTitle !== ''): ?>
|
||
<h1 class="page-header-title"><?= htmlspecialchars($headerIntroTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
||
<?php endif; ?>
|
||
<?php if ($headerIntroSub !== ''): ?>
|
||
<div class="page-header-sub"><?= htmlspecialchars($headerIntroSub, ENT_QUOTES, 'UTF-8') ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<div class="container">
|
||
<script>
|
||
(function () {
|
||
const toggle = document.getElementById('menuToggle');
|
||
const panel = document.getElementById('headerMenuPanel');
|
||
|
||
if (!toggle || !panel) {
|
||
return;
|
||
}
|
||
|
||
function closeMenu() {
|
||
toggle.setAttribute('aria-expanded', 'false');
|
||
panel.hidden = true;
|
||
document.body.classList.remove('menu-open');
|
||
}
|
||
|
||
function openMenu() {
|
||
toggle.setAttribute('aria-expanded', 'true');
|
||
panel.hidden = false;
|
||
document.body.classList.add('menu-open');
|
||
}
|
||
|
||
toggle.addEventListener('click', function () {
|
||
if (toggle.getAttribute('aria-expanded') === 'true') {
|
||
closeMenu();
|
||
return;
|
||
}
|
||
|
||
openMenu();
|
||
});
|
||
|
||
document.addEventListener('click', function (event) {
|
||
if (panel.hidden) {
|
||
return;
|
||
}
|
||
|
||
if (!panel.contains(event.target) && !toggle.contains(event.target)) {
|
||
closeMenu();
|
||
}
|
||
});
|
||
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key === 'Escape') {
|
||
closeMenu();
|
||
}
|
||
});
|
||
})();
|
||
</script>
|