Files
papa-kind-treff.info/partials/structure/layout_start.php
Lars Gebhardt-Kusche eb16827fdf
All checks were successful
Deploy / deploy (push) Successful in 51s
community und dsgvo
2026-07-22 21:29:50 +02:00

232 lines
11 KiB
PHP
Executable File

<?php
/** @var \App\App $app */
$app = app();
// globale Assets (Brand-Styles & JS)
$app->assets()->addStyle('/assets/css/styles.css', 'early');
$app->assets()->addStyle('/assets/css/app.css', 'normal');
$app->assets()->addScript('/assets/js/app.js', 'footer', true, false);
$childGender = $_SESSION['child_gender_summary'] ?? ''; // 'male' | 'female' | 'mixed' | ''
if (!in_array($childGender, ['male', 'female', 'mixed'], true)) {
$childGender = '';
}
$debugEnabled = defined('APP_DEBUG') && APP_DEBUG === true;
$locationTrackingPreference = 'prompt';
if (isset($_SESSION['user_id'])) {
try {
$pdo = $app->pdo();
if ($pdo) {
$profileSettings = new \App\ProfileSettings($pdo);
$locationTrackingPreference = $profileSettings->getLocationTrackingPreference((int)$_SESSION['user_id']);
}
} catch (\Throwable) {
$locationTrackingPreference = 'prompt';
}
}
$debugFiles = [];
if ($debugEnabled) {
$debugDir = __DIR__ . '/../../debug';
if (is_dir($debugDir)) {
foreach (scandir($debugDir) as $f) {
if ($f === '.' || $f === '..') continue;
$path = $debugDir . '/' . $f;
if (is_file($path)) {
$debugFiles[] = $f;
}
}
}
}
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars(t('common.title'), ENT_QUOTES) ?></title>
<meta name="description" content="Papa-Kind-Treff: Väter vernetzen sich für Treffen mit und ohne Kinder, Events in der Nähe entdecken und sicher Kontakte knüpfen.">
<?php if (defined('APP_ROBOTS')): ?>
<meta name="robots" content="<?= htmlspecialchars(APP_ROBOTS, ENT_QUOTES) ?>">
<?php endif; ?>
<?php
$host = APP_DOMAIN_NAME ?? ($_SERVER['HTTP_HOST'] ?? 'papa-kind-treff.info');
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'https';
$canonical = $scheme . '://' . $host . ($_SERVER['REQUEST_URI'] ?? '/');
$tlds = ['de' => 'papa-kind-treff.de', 'at' => 'papa-kind-treff.at', 'ch' => 'papa-kind-treff.ch', 'info' => 'papa-kind-treff.info'];
$canonTld = 'info';
if (preg_match('/\\.(de|at|ch|info)$/i', $host, $m)) {
$canonTld = strtolower($m[1]);
}
$canonicalRoot = $scheme . '://' . $tlds[$canonTld] . ($_SERVER['REQUEST_URI'] ?? '/');
?>
<link rel="canonical" href="<?= htmlspecialchars($canonicalRoot, ENT_QUOTES) ?>">
<link rel="alternate" hreflang="de-DE" href="https://<?= $tlds['de'] ?>/">
<link rel="alternate" hreflang="de-CH" href="https://<?= $tlds['ch'] ?>/">
<link rel="alternate" hreflang="de-AT" href="https://<?= $tlds['at'] ?>/">
<link rel="alternate" hreflang="de" href="https://<?= $tlds['info'] ?>/">
<link rel="alternate" hreflang="en" href="https://<?= $tlds['info'] ?>/">
<?php asset_styles(); ?>
<?php asset_scripts('header'); ?>
</head>
<body data-auth="<?= isset($_SESSION['user_id']) ? '1' : '0' ?>" data-child-gender="<?= htmlspecialchars($childGender, ENT_QUOTES) ?>" data-location-preference="<?= htmlspecialchars($locationTrackingPreference, ENT_QUOTES) ?>">
<script>
(function () {
const storageKey = 'pkt_cookie_consent';
const read = () => {
try {
const raw = localStorage.getItem(storageKey);
return raw ? JSON.parse(raw) : null;
} catch (err) {
return null;
}
};
window.PKTConsent = window.PKTConsent || {
storageKey,
get() {
return read();
},
has(category) {
if (category === 'necessary') return true;
const consent = read();
return Boolean(consent && consent[category] === true);
},
openPreferences() {
document.dispatchEvent(new CustomEvent('pkt:open-consent'));
},
};
})();
</script>
<?php tpl('matomo', 'structure'); ?>
<?php tpl('nav', 'structure'); ?>
<div class="cookie-consent" id="cookieConsentBanner" hidden>
<div class="cookie-consent__inner">
<div class="cookie-consent__copy">
<strong>Cookies, Analyse und externe Dienste</strong>
<p>Notwendige Cookies für Sitzung und Login sind immer aktiv. Analyse mit Matomo sowie Karten-, Standort- und Geocoding-Funktionen werden erst nach deiner Auswahl geladen.</p>
</div>
<div class="cookie-consent__actions">
<button class="btn ghost" type="button" data-consent-necessary>Nur notwendige</button>
<button class="btn ghost" type="button" data-consent-open>Auswahl</button>
<button class="btn" type="button" data-consent-accept-all>Alle akzeptieren</button>
</div>
</div>
</div>
<div class="modal" id="cookieConsentModal">
<div class="panel">
<div class="head flex between center-y">
<h3 style="margin:0;">Cookie- und Diensteinstellungen</h3>
<button class="btn ghost" type="button" data-consent-close>✕</button>
</div>
<div class="stack gap-12" style="margin-top:12px;">
<p class="muted" style="margin:0;">Du kannst nicht notwendige Funktionen freiwillig aktivieren oder später wieder widerrufen.</p>
<div class="card">
<strong>Notwendig</strong>
<p class="muted small">Sitzung, Login und Sicherheitsfunktionen. Immer aktiv.</p>
</div>
<label class="card" style="display:block; cursor:pointer;">
<input type="checkbox" id="consentAnalytics" style="margin-right:8px;">
<strong>Analyse mit Matomo</strong>
<p class="muted small" style="margin:8px 0 0;">Reichweitenmessung und Nutzungsanalyse.</p>
</label>
<label class="card" style="display:block; cursor:pointer;">
<input type="checkbox" id="consentExternalServices" style="margin-right:8px;">
<strong>Externe Dienste, Karten und Standort</strong>
<p class="muted small" style="margin:8px 0 0;">Browser-Geolocation, lokale Standortspeicherung, Leaflet von `unpkg.com` und Geocoding/Reverse-Geocoding über OpenStreetMap Nominatim.</p>
</label>
<div class="flex gap-12" style="flex-wrap:wrap;">
<button class="btn ghost" type="button" data-consent-necessary>Nur notwendige</button>
<button class="btn" type="button" data-consent-save>Auswahl speichern</button>
</div>
</div>
</div>
</div>
<div class="modal" id="locationPreferenceModal">
<div class="panel">
<div class="head flex between center-y">
<h3 style="margin:0;">Standortfreigabe</h3>
<button class="btn ghost" type="button" data-location-pref-close>✕</button>
</div>
<div class="stack gap-12" style="margin-top:12px;">
<p class="muted" style="margin:0;">Damit wir dir passende Treffen in deiner Nähe zeigen können, dürfen wir deinen Standort verwenden. Wie soll das gehandhabt werden?</p>
<div class="stack gap-10">
<button class="btn" type="button" data-location-pref-choice="session">Nur für diesen Besuch</button>
<button class="btn ghost" type="button" data-location-pref-choice="enabled">Immer erlauben</button>
<button class="btn ghost" type="button" data-location-pref-choice="disabled">Niemals</button>
</div>
</div>
</div>
</div>
<?php if ($debugEnabled): ?>
<style>
.debug-fab { position: fixed; right: 18px; bottom: 18px; z-index: 200; background:#111827; color:#fff; border:none; border-radius: 999px; width: 52px; height:52px; display:flex; align-items:center; justify-content:center; font-size:24px; box-shadow:0 10px 30px rgba(0,0,0,0.2); cursor:pointer; }
.debug-modal { position:fixed; inset:0; background: rgba(0,0,0,0.4); display:none; align-items:center; justify-content:center; z-index: 210; padding:16px; }
.debug-modal.open { display:flex; }
.debug-panel { background:#fff; border-radius: 12px; padding:16px; width: min(90vw, 640px); max-height: 80vh; overflow:auto; box-shadow:0 10px 30px rgba(0,0,0,0.25); }
.debug-modal pre { background:#f8fafc; border:1px solid #e5e7eb; padding:10px; border-radius:8px; max-height: 50vh; overflow:auto; white-space: pre-wrap; }
</style>
<button class="debug-fab" aria-label="Debug öffnen" data-debug-open>🐞</button>
<div class="debug-modal" id="debugModal">
<div class="debug-panel">
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
<h3 style="margin:0;">Debug-Logs</h3>
<button class="btn ghost" type="button" data-debug-close>✕</button>
</div>
<?php if (!$debugFiles): ?>
<p class="muted">Keine Logs in /debug/ gefunden.</p>
<?php else: ?>
<form method="get" id="debugForm" style="margin:12px 0;">
<label class="label" for="debugSelect">Log auswählen</label>
<select class="select" id="debugSelect" name="file">
<option value="">-- auswählen --</option>
<?php foreach ($debugFiles as $f): ?>
<option value="<?= htmlspecialchars($f, ENT_QUOTES) ?>"><?= htmlspecialchars($f, ENT_QUOTES) ?></option>
<?php endforeach; ?>
</select>
</form>
<div id="debugContent"><p class="muted">Log auswählen, um Inhalt zu sehen.</p></div>
<?php endif; ?>
</div>
</div>
<script>
(function(){
const openBtn = document.querySelector('[data-debug-open]');
const modal = document.getElementById('debugModal');
const closeBtn = modal?.querySelector('[data-debug-close]');
const select = document.getElementById('debugSelect');
const content = document.getElementById('debugContent');
if (openBtn && modal) {
openBtn.addEventListener('click', ()=> modal.classList.add('open'));
}
closeBtn?.addEventListener('click', ()=> modal.classList.remove('open'));
modal?.addEventListener('click', (e)=>{ if(e.target === modal) modal.classList.remove('open'); });
if (select && content) {
select.addEventListener('change', ()=>{
const file = select.value;
if (!file) { content.innerHTML = '<p class="muted">Log auswählen, um Inhalt zu sehen.</p>'; return; }
fetch('/debug?file=' + encodeURIComponent(file) + '&raw=1')
.then(r => {
if (!r.ok) throw new Error('Request failed');
return r.text();
})
.then(text => {
if (!text.trim()) {
content.innerHTML = '<p class="muted">Log ist leer.</p>';
return;
}
const escape = (s) => s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
content.innerHTML = '<pre>'+escape(text)+'</pre>';
})
.catch(()=> content.innerHTML = '<p class="muted">Konnte Log nicht laden.</p>');
});
}
})();
</script>
<?php endif; ?>