Files
skyview.astronomiemuseum.de/public/header.php
T
2026-09-15 15:58:51 +02:00

553 lines
23 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 &amp; 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 &amp; 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 &amp; Karten',
'links' => [
['href' => 'sternenhimmel.php', 'label' => 'Sternenhimmel'],
['href' => 'beobachtungsvorschlaege.php', 'label' => 'Beobachtungsvorschläge'],
['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 &amp; 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,
// Schweif­lä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 &amp; 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>