diff --git a/public/assets/js/header.js b/public/assets/js/header.js index 0eda639..7b7555e 100644 --- a/public/assets/js/header.js +++ b/public/assets/js/header.js @@ -1,87 +1,114 @@ // public/assets/js/header.js -document.addEventListener('DOMContentLoaded', function () { +document.addEventListener("DOMContentLoaded", function () { // -------------------------------------------------- - // Sprachauflösung: basiert NUR auf usbConfig / i18n + // Sprachauflösung: basiert auf derselben Logik wie lang.js + // + // Priorität: + // a) ?lang= in der URL + // b) localStorage (usbcheck_lang) + // c) Browsersprache + // d) 'en' wenn vorhanden + // e) erste verfügbare Sprache // -------------------------------------------------- - const cfg = window.usbConfig || {}; - const i18nCfg = cfg.i18n || {}; - const availLangs = i18nCfg.available || {}; - const availCodes = Object.keys(availLangs); + const cfg = window.usbConfig || {}; + const i18nCfg = cfg.i18n || {}; + const availLangs = i18nCfg.available || {}; + const availCodes = Object.keys(availLangs); function normalizeLang(code) { - if (!code) return ''; + if (!code) return ""; return String(code).slice(0, 2).toLowerCase(); } + function detectBrowserLang() { + const nav = window.navigator || {}; + const candidates = []; + + if (Array.isArray(nav.languages)) { + candidates.push(...nav.languages); + } + if (typeof nav.language === "string") { + candidates.push(nav.language); + } + if (typeof nav.userLanguage === "string") { + candidates.push(nav.userLanguage); + } + + for (const raw of candidates) { + const code = normalizeLang(raw); + if (availCodes.includes(code)) { + return code; + } + } + return null; + } + function resolveCurrentLang() { const url = new URL(window.location.href); - const urlLang = normalizeLang(url.searchParams.get('lang')); + const urlLang = normalizeLang(url.searchParams.get("lang")); - // 1) ?lang= aus der URL, wenn gültig + // a) ?lang= aus der URL, wenn gültig if (urlLang && availCodes.includes(urlLang)) { return urlLang; } - // 2) localStorage (vom lang.js gesetzt) - const stored = normalizeLang(localStorage.getItem('usbcheck_lang')); + // b) localStorage (vom lang.js gesetzt) + const stored = normalizeLang(localStorage.getItem("usbcheck_lang")); if (stored && availCodes.includes(stored)) { return stored; } - - // 3) PHP-Konfig (fileload/app_config) - const cfgLang = normalizeLang(i18nCfg.current || cfg.lang); - if (cfgLang && availCodes.includes(cfgLang)) { - return cfgLang; + if (stored && !availCodes.includes(stored)) { + localStorage.removeItem("usbcheck_lang"); } - // 4) - const docLang = normalizeLang(document.documentElement.getAttribute('lang')); - if (docLang && availCodes.includes(docLang)) { - return docLang; + // c) Browsersprache + const browserLang = detectBrowserLang(); + if (browserLang) { + return browserLang; } - // 5) Fallback: en, wenn vorhanden - if (availCodes.includes('en')) { - return 'en'; + // d) Fallback: en, wenn vorhanden + if (availCodes.includes("en")) { + return "en"; } - // 6) sonst erste verfügbare Sprache + // e) Sonst erste verfügbare Sprache if (availCodes.length > 0) { return availCodes[0]; } - // 7) absolute Notlösung - return 'en'; + // f) absolute Notlösung + return "en"; } // -------------------------------------------------- // LOGIN-BUTTON → /login mit redirect + lang // -------------------------------------------------- - const loginBtn = document.getElementById('loginButton'); + const loginBtn = document.getElementById("loginButton"); if (loginBtn) { - loginBtn.addEventListener('click', function (event) { + loginBtn.addEventListener("click", function (event) { event.preventDefault(); - const currentPath = window.location.pathname || '/'; - const currentQuery = window.location.search || ''; + const currentPath = window.location.pathname || "/"; + const currentQuery = window.location.search || ""; const redirect = currentPath + currentQuery; const lang = resolveCurrentLang(); // Wenn wir bereits auf /login sind → nur zum #auth scrollen - if (currentPath === '/login' || currentPath === '/login/') { - const authEl = document.getElementById('auth'); + if (currentPath === "/login" || currentPath === "/login/") { + const authEl = document.getElementById("auth"); if (authEl) { - authEl.scrollIntoView({ behavior: 'smooth', block: 'start' }); + authEl.scrollIntoView({ behavior: "smooth", block: "start" }); } return; } // Sonst: auf Login-Seite mit lang + redirect-Parameter - const url = '/login/?lang=' + encodeURIComponent(lang) + - '&redirect=' + encodeURIComponent(redirect) + - '#auth'; + const url = "/login/?lang=" + encodeURIComponent(lang) + + "&redirect=" + encodeURIComponent(redirect) + + "#auth"; window.location.href = url; }); @@ -90,21 +117,21 @@ document.addEventListener('DOMContentLoaded', function () { // -------------------------------------------------- // AVATAR-MENÜ (userAvatar / userMenu) // -------------------------------------------------- - const avatarBtn = document.getElementById('userAvatar'); - const userMenu = document.getElementById('userMenu'); + const avatarBtn = document.getElementById("userAvatar"); + const userMenu = document.getElementById("userMenu"); if (avatarBtn && userMenu) { - avatarBtn.addEventListener('click', function (event) { + avatarBtn.addEventListener("click", function (event) { event.stopPropagation(); - userMenu.classList.toggle('hidden'); + userMenu.classList.toggle("hidden"); - const expanded = avatarBtn.getAttribute('aria-expanded') === 'true'; - avatarBtn.setAttribute('aria-expanded', expanded ? 'false' : 'true'); + const expanded = avatarBtn.getAttribute("aria-expanded") === "true"; + avatarBtn.setAttribute("aria-expanded", expanded ? "false" : "true"); }); // Klick außerhalb schließt Menü - document.addEventListener('click', function (event) { - if (userMenu.classList.contains('hidden')) { + document.addEventListener("click", function (event) { + if (userMenu.classList.contains("hidden")) { return; } @@ -112,8 +139,8 @@ document.addEventListener('DOMContentLoaded', function () { const clickedAvatarButton = avatarBtn.contains(event.target); if (!clickedInsideMenu && !clickedAvatarButton) { - userMenu.classList.add('hidden'); - avatarBtn.setAttribute('aria-expanded', 'false'); + userMenu.classList.add("hidden"); + avatarBtn.setAttribute("aria-expanded", "false"); } }); } @@ -122,9 +149,9 @@ document.addEventListener('DOMContentLoaded', function () { // LOGOUT-MODAL // -------------------------------------------------- const logoutButtons = document.querySelectorAll('[data-logout-link="true"]'); - const backdrop = document.getElementById('logoutModalBackdrop'); - const btnConfirm = document.getElementById('logoutConfirm'); - const btnCancel = document.getElementById('logoutCancel'); + const backdrop = document.getElementById("logoutModalBackdrop"); + const btnConfirm = document.getElementById("logoutConfirm"); + const btnCancel = document.getElementById("logoutCancel"); let logoutTarget = null; @@ -132,64 +159,64 @@ document.addEventListener('DOMContentLoaded', function () { logoutTarget = targetHref || null; if (!backdrop) return; - backdrop.classList.remove('hidden'); - document.body.classList.add('overflow-hidden'); + backdrop.classList.remove("hidden"); + document.body.classList.add("overflow-hidden"); } function hideLogoutModal() { if (!backdrop) return; - backdrop.classList.add('hidden'); - document.body.classList.remove('overflow-hidden'); + backdrop.classList.add("hidden"); + document.body.classList.remove("overflow-hidden"); logoutTarget = null; } if (logoutButtons.length && backdrop && btnConfirm && btnCancel) { logoutButtons.forEach(function (btn) { - btn.addEventListener('click', function (event) { + btn.addEventListener("click", function (event) { event.preventDefault(); event.stopPropagation(); - const href = btn.getAttribute('data-logout-href') - || btn.getAttribute('href') - || '/auth/logout'; + const href = btn.getAttribute("data-logout-href") + || btn.getAttribute("href") + || "/auth/logout"; showLogoutModal(href); // Avatar-Menü beim Öffnen des Modals schließen - if (userMenu && !userMenu.classList.contains('hidden')) { - userMenu.classList.add('hidden'); + if (userMenu && !userMenu.classList.contains("hidden")) { + userMenu.classList.add("hidden"); if (avatarBtn) { - avatarBtn.setAttribute('aria-expanded', 'false'); + avatarBtn.setAttribute("aria-expanded", "false"); } } }); }); // Bestätigen → weiter zur Logout-URL - btnConfirm.addEventListener('click', function () { + btnConfirm.addEventListener("click", function () { if (logoutTarget) { window.location.href = logoutTarget; } else { - window.location.href = '/auth/logout'; + window.location.href = "/auth/logout"; } }); // Abbrechen → Modal schließen - btnCancel.addEventListener('click', function () { + btnCancel.addEventListener("click", function () { hideLogoutModal(); }); // Klick auf Hintergrund schließt Modal (außer man klickt auf die Card) - backdrop.addEventListener('click', function (event) { - const card = backdrop.querySelector('.max-w-sm'); + backdrop.addEventListener("click", function (event) { + const card = backdrop.querySelector(".max-w-sm"); if (card && !card.contains(event.target)) { hideLogoutModal(); } }); // ESC-Taste schließt Modal - document.addEventListener('keydown', function (event) { - if (event.key === 'Escape' && !backdrop.classList.contains('hidden')) { + document.addEventListener("keydown", function (event) { + if (event.key === "Escape" && !backdrop.classList.contains("hidden")) { hideLogoutModal(); } }); diff --git a/public/assets/js/lang.js b/public/assets/js/lang.js index 08c3bd3..f0a403a 100644 --- a/public/assets/js/lang.js +++ b/public/assets/js/lang.js @@ -1,5 +1,4 @@ // public/assets/js/lang.js - (function () { // --------------------------------------------- // 1) Basis-Config aus PHP (app_config.php) @@ -7,25 +6,16 @@ const usbConfig = window.usbConfig || {}; const i18nConfig = usbConfig.i18n || {}; - // Map: { de: {code, label, flag}, en: {...}, dk: {...}, ... } + // Map: { de: {code, label, flag}, en: {...}, ... } const availableLangs = i18nConfig.available || {}; // Liste der unterstützten Sprachen als Codes const supportedLangs = Object.keys(availableLangs); - // Sprache, die PHP in fileload.php ermittelt hat - let currentLang = (i18nConfig.current || usbConfig.lang || "en").toLowerCase(); - - // Wenn PHP eine Sprache setzt, die nicht in available ist, nimm erste verfügbare - if (!supportedLangs.includes(currentLang) && supportedLangs.length > 0) { - currentLang = supportedLangs[0]; - } - - // Fallback, wenn gar nichts da ist - if (!currentLang) { - currentLang = "en"; - } + // Aktuelle Sprache (wird unten per getInitialLang() gesetzt) + let currentLang = "en"; + // Translations-Map (geflacht) let translations = {}; // --------------------------------------------- @@ -68,13 +58,39 @@ }, text); } + // ------------------------------------------------ + // 4) Browsersprache erkennen (2-Buchstaben-Code) + // ------------------------------------------------ + function detectBrowserLang() { + const nav = window.navigator || {}; + const candidates = []; + + if (Array.isArray(nav.languages)) { + candidates.push(...nav.languages); + } + if (typeof nav.language === "string") { + candidates.push(nav.language); + } + if (typeof nav.userLanguage === "string") { + candidates.push(nav.userLanguage); + } + + for (const raw of candidates) { + const code = (raw || "").slice(0, 2).toLowerCase(); + if (supportedLangs.includes(code)) { + return code; + } + } + return null; + } + // --------------------------------------------- - // 4) Initiale Sprache bestimmen + // 5) Initiale Sprache bestimmen // // Priorität: - // a) URL ?lang= (falls gültig und in available) - // b) PHP (usbConfig.i18n.current / usbConfig.lang) - // c) localStorage (falls gültig) + // a) URL ?lang= (falls gültig) + // b) localStorage (explizit gewählte Sprache) + // c) Browsersprache // d) 'en', wenn vorhanden // e) erste verfügbare Sprache // --------------------------------------------- @@ -87,22 +103,22 @@ return paramLang; } - // b) Server-seitig ermittelte Sprache (inkl. HTTP_ACCEPT_LANGUAGE) - const serverLang = (i18nConfig.current || usbConfig.lang || "").toLowerCase(); - if (serverLang && supportedLangs.includes(serverLang)) { - return serverLang; - } - - // c) localStorage, falls gültig + // b) localStorage (vom User über Switcher gewählt) const stored = (localStorage.getItem("usbcheck_lang") || "").toLowerCase(); if (stored) { if (supportedLangs.includes(stored)) { return stored; } - // ungültigen alten Wert aufräumen + // Ungültigen alten Wert aufräumen localStorage.removeItem("usbcheck_lang"); } + // c) Browsersprache + const browserLang = detectBrowserLang(); + if (browserLang) { + return browserLang; + } + // d) Wenn 'en' existiert → nimm 'en' if (supportedLangs.includes("en")) { return "en"; @@ -118,7 +134,7 @@ } // --------------------------------------------- - // 5) i18n JSON per Fetch laden + // 6) i18n JSON per Fetch laden // --------------------------------------------- async function loadLangFile(lang) { const code = (lang || "").toLowerCase(); @@ -142,7 +158,7 @@ } // --------------------------------------------- - // 6) Data-i18n-Texte einsetzen + // 7) Data-i18n-Texte einsetzen // --------------------------------------------- function applyTranslations() { document.documentElement.setAttribute("lang", currentLang); @@ -166,10 +182,7 @@ } // --------------------------------------------- - // 7) Label + Flag im Button aktualisieren - // - // Nutzt ausschließlich availableLangs aus PHP: - // availableLangs[code] = { code, label, flag } + // 8) Label + Flag im Button aktualisieren // --------------------------------------------- function updateLangCurrentLabel(lang) { const code = (lang || "").toLowerCase(); @@ -196,35 +209,32 @@ } // --------------------------------------------- - // 8) Sprache setzen + // 9) Sprache setzen (via Switcher) // - // - akzeptiert JEDE Sprache, die in availableLangs liegt + // - akzeptiert nur Sprachen aus availableLangs // - speichert in localStorage // - schreibt ?lang= in die URL // - lädt Seite neu, damit PHP + JS synchron sind // --------------------------------------------- - async function setLang(lang) { + function setLang(lang) { const code = (lang || "").toLowerCase(); if (!supportedLangs.includes(code)) { console.warn("Unsupported language:", code, supportedLangs); return; } - // URL-Parameter setzen const url = new URL(window.location.href); url.searchParams.set("lang", code); localStorage.setItem("usbcheck_lang", code); - // komplette Seite neu laden, - // damit PHP (fileload.php) + Meta-Tags + JS-Konfig konsistent sind window.location.href = url.toString(); } // --------------------------------------------- - // 9) DOM Ready + // 10) DOM Ready // --------------------------------------------- document.addEventListener("DOMContentLoaded", async function () { - // Initiale Sprache (kombiniert aus URL, PHP, localStorage) + // Initiale Sprache (kombiniert aus URL, localStorage, Browser) currentLang = getInitialLang(); updateLangCurrentLabel(currentLang); @@ -263,33 +273,11 @@ if (btn) { const lang = btn.getAttribute("data-lang"); if (lang) { + // Nur wenn data-lang gesetzt ist → JS-gesteuerter Wechsel setLang(lang); + e.preventDefault(); } } }); - - // (Login / Avatar Dummy kann bleiben – unverändert) - const loginButton = document.getElementById("loginButton"); - const userAvatar = document.getElementById("userAvatar"); - const avatarInitials = document.getElementById("avatarInitials"); - - if (loginButton && userAvatar) { - loginButton.addEventListener("click", function () { - loginButton.classList.add("hidden"); - userAvatar.classList.remove("hidden"); - - if (avatarInitials && !avatarInitials.textContent.trim()) { - avatarInitials.textContent = "UC"; - } - }); - - if (userAvatar) { - userAvatar.addEventListener("click", function () { - alert( - "Account-Menü Platzhalter – hier später Profil/Logout etc. einbauen." - ); - }); - } - } }); })(); diff --git a/src/auth/login.php b/src/auth/login.php index a1d7f35..2265641 100644 --- a/src/auth/login.php +++ b/src/auth/login.php @@ -2,24 +2,39 @@ // src/auth/login.php // wird durch public/auth/login.php aufgerufen -// Nur POST zulassen +// Basis-Setup: Session, $pdo, flash_*, Sprachlogik etc. +require_once $_SERVER['DOCUMENT_ROOT']. '/../config/fileload.php'; + +// Sprache aus der zentralen Sprachlogik holen +// (fileload.php sollte $GLOBALS['lang'] anhand von URL, Session, Browser etc. setzen) +$lang = $GLOBALS['lang'] ?? 'en'; + +// Nur POST zulassen – alles andere zurück zur Login-Seite if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - $lang = $_GET['lang'] ?? 'de'; - header('Location: /login/?lang=' . urlencode($lang) . '&view=login#auth'); + $target = '/login/?lang=' . urlencode($lang) . '&view=login#auth'; + header('Location: ' . $target); exit; } -// Basis-Setup: Session, $pdo, flash_*, Config etc. -require_once __DIR__ . '/../../config/fileload.php'; - +// --------------------------------------------------------- // Form-Daten einsammeln -$lang = $_POST['lang'] ?? 'de'; +// --------------------------------------------------------- $email = trim((string)($_POST['email'] ?? '')); $password = (string)($_POST['password'] ?? ''); $redirect = $_POST['redirect'] ?? '/'; +// (Optional) Falls das Formular noch ein hat, +// kannst du das nutzen, falls kein $GLOBALS['lang'] gesetzt ist: +if (empty($lang) && !empty($_POST['lang'])) { + $lang = substr((string)$_POST['lang'], 0, 2); +} + // Minimal-Validierung -if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL) || $password === '') { +if ( + $email === '' || + !filter_var($email, FILTER_VALIDATE_EMAIL) || + $password === '' +) { flash_set('error', 'Bitte E-Mail-Adresse und Passwort eingeben.', 'login'); header('Location: /login/?lang=' . urlencode($lang) . '&view=login#auth'); exit; diff --git a/src/auth/register.php b/src/auth/register.php index 5d541aa..b6dab9b 100644 --- a/src/auth/register.php +++ b/src/auth/register.php @@ -2,21 +2,33 @@ // src/auth/register.php // Wird durch public/auth/register.php aufgerufen +// Basis-Setup: Session, $pdo, flash_*, Sprachlogik etc. +require_once $_SERVER['DOCUMENT_ROOT']. '/../config/fileload.php'; + +// Sprache aus zentraler Sprachlogik holen +$lang = $GLOBALS['lang'] ?? 'en'; + +// Nur POST zulassen – alles andere zurück zur Login-/Register-Ansicht if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - $lang = $_GET['lang'] ?? 'de'; - header('Location: /login/?lang=' . urlencode($lang) . '&view=register#auth'); + $target = '/login/?lang=' . urlencode($lang) . '&view=register#auth'; + header('Location: ' . $target); exit; } -// Benötigt: $pdo, flash(), Session → via fileload -require_once __DIR__ . '/../../config/fileload.php'; - -$lang = $_POST['lang'] ?? 'de'; +// --------------------------------------------------------- +// Form-Daten einsammeln +// --------------------------------------------------------- $name = trim((string)($_POST['name'] ?? '')); $email = trim((string)($_POST['email'] ?? '')); $password = (string)($_POST['password'] ?? ''); $redirect = $_POST['redirect'] ?? '/'; +// Optional: Falls aus irgendeinem Grund $lang noch leer ist, +// kannst du (z.B. beim ersten Request) auf ein hidden-Feld im Formular zurückgreifen. +if (empty($lang) && !empty($_POST['lang'])) { + $lang = substr((string)$_POST['lang'], 0, 2); +} + /* --------------------------------------------------------- VALIDIERUNG --------------------------------------------------------- */ @@ -125,7 +137,6 @@ $_SESSION['user'] = [ 'initials' => $initials, ]; - /* --------------------------------------------------------- ERFOLGSMELDUNG + REDIRECT --------------------------------------------------------- */ @@ -138,9 +149,11 @@ if ($target === '' || !str_starts_with($target, '/')) { $target = '/'; } -$separator = (strpos($target, '?') === false) ? '?' : '&'; -$target .= $separator . 'lang=' . urlencode($lang); +// Sprache anhängen, falls noch nicht vorhanden +if (strpos($target, 'lang=') === false) { + $separator = (strpos($target, '?') === false) ? '?' : '&'; + $target .= $separator . 'lang=' . urlencode($lang); +} header('Location: ' . $target); exit; -