232 lines
11 KiB
PHP
Executable File
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, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
content.innerHTML = '<pre>'+escape(text)+'</pre>';
|
|
})
|
|
.catch(()=> content.innerHTML = '<p class="muted">Konnte Log nicht laden.</p>');
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
<?php endif; ?>
|