551 lines
23 KiB
PHP
551 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'],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Planeten & Sonnensystem',
|
||
'links' => [
|
||
['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'],
|
||
['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>
|