This commit is contained in:
2025-11-19 00:49:53 +01:00
parent 553fefbeaa
commit 0834e1f733
12 changed files with 725 additions and 524 deletions

View File

@@ -1,177 +1,201 @@
<?php
// public/account.php
declare(strict_types=1);
require_once __DIR__ . '/../src/auth.php';
require __DIR__ . '/../src/auth.php';
$lang = auth_get_lang();
auth_require_login();
$user = auth_current_user();
$csrfToken = auth_csrf_token();
$lang = auth_get_lang();
$user = auth_current_user();
$errors = [];
$flashSuccess = '';
$flashError = '';
$profileErrors = [];
$profileSuccess = false;
// --- Profil-Update (Name + Sprache) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'update_profile') {
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
$profileErrors['csrf'] = 'Deine Sitzung ist abgelaufen. Bitte Seite neu laden.';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
$flashError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
} else {
$fullName = $_POST['full_name'] ?? '';
$preferredLang = $_POST['preferred_lang'] ?? $lang;
$fullName = $_POST['full_name'] ?? ($user['full_name'] ?? '');
$preferredLang = $_POST['preferred_lang'] ?? ($user['preferred_lang'] ?? $lang);
$result = auth_update_profile((int)$user['id'], $fullName, $preferredLang);
if ($result['success']) {
$profileSuccess = true;
if ($result['success'] ?? false) {
$flashSuccess = 'Profil wurde aktualisiert.';
$user = auth_current_user(); // neu laden
$lang = auth_get_lang(); // kann sich geändert haben
} else {
$profileErrors = $result['errors'];
$errors = $result['errors'] ?? [];
}
}
}
// --- Avatar-Initialen ---
$initials = auth_user_initials($user);
$csrfToken = auth_csrf_token();
$initials = auth_user_initials($user);
$avatarUrl = auth_user_avatar_url($user);
?>
<!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>">
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head>
<meta charset="UTF-8">
<title>Mein Konto USBCheck</title>
<title>Mein Konto usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Fonts -->
<!-- Fonts: Montserrat + Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Main CSS -->
<!-- Main stylesheet -->
<link rel="stylesheet" href="/assets/css/main.css?v=1">
</head>
<body>
<?php
$langVar = $lang;
include __DIR__ . '/partials/header.php';
?>
<?php include __DIR__ . '/partials/header.php'; ?>
<main class="page-main">
<section class="section">
<div class="container">
<h1 class="section-title" data-i18n="account_title">Mein Konto</h1>
<p class="section-lead" data-i18n="account_intro">
Verwalte deine Profildaten und behalte deine USB-Tests im Überblick.
</p>
<main class="account-page">
<div class="container">
<div class="account-grid">
<!-- Profil -->
<section class="account-card">
<h1 class="account-title">Mein Profil</h1>
<p class="account-subtitle">
Passe deinen Namen und deine bevorzugte Sprache an. Der Avatar wird aktuell aus deinen Initialen generiert.
</p>
<div class="account-layout">
<!-- Sidebar: Avatar + Basisinfos -->
<aside class="account-sidebar">
<div class="account-avatar">
<?php if ($avatarUrl): ?>
<img src="<?php echo htmlspecialchars($avatarUrl, ENT_QUOTES); ?>" alt="Avatar">
<?php else: ?>
<div class="avatar-circle">
<span><?php echo htmlspecialchars($initials, ENT_QUOTES); ?></span>
</div>
<?php if ($flashSuccess): ?>
<div class="auth-flash-success">
<?php echo htmlspecialchars($flashSuccess, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<?php if ($flashError): ?>
<div class="auth-flash-error">
<?php echo htmlspecialchars($flashError, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<form method="post" novalidate>
<input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<div class="form-row">
<label class="form-label" for="email">E-Mail-Adresse</label>
<input
class="form-input"
type="email"
id="email"
name="email"
value="<?php echo htmlspecialchars($user['email'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
readonly
>
<div class="form-help">
E-Mail-Änderungen bitte später über einen separaten Flow.
</div>
</div>
<div class="form-row">
<label class="form-label" for="username">Benutzername</label>
<input
class="form-input"
type="text"
id="username"
name="username"
value="<?php echo htmlspecialchars($user['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
readonly
>
<div class="form-help">
Benutzername ist aktuell nicht änderbar.
</div>
</div>
<div class="form-row">
<label class="form-label" for="full_name">Vollständiger Name</label>
<input
class="form-input"
type="text"
id="full_name"
name="full_name"
required
value="<?php echo htmlspecialchars($_POST['full_name'] ?? ($user['full_name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>"
>
<?php if (!empty($errors['full_name'])): ?>
<div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
<div class="account-basic-info">
<h2><?php echo htmlspecialchars($user['full_name'] ?? $user['username'], ENT_QUOTES); ?></h2>
<p class="muted">
<?php echo htmlspecialchars($user['email'], ENT_QUOTES); ?>
</p>
<p class="muted">
Username: <?php echo htmlspecialchars($user['username'], ENT_QUOTES); ?>
</p>
<div class="form-row">
<label class="form-label" for="preferred_lang">Bevorzugte Sprache</label>
<select class="form-select" id="preferred_lang" name="preferred_lang">
<?php
$selLang = $_POST['preferred_lang'] ?? ($user['preferred_lang'] ?? $lang);
$opts = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
foreach ($opts as $code => $label) {
$selected = ($code === $selLang) ? 'selected' : '';
echo '<option value="' . htmlspecialchars($code, ENT_QUOTES, 'UTF-8') . '" ' . $selected . '>'
. htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</option>';
}
?>
</select>
</div>
<div class="account-links">
<a class="btn btn-outline" href="/fakecheck/?lang=<?php echo urlencode($lang); ?>" data-i18n="account_start_test">
USB-Test starten
</a>
<a class="btn btn-ghost" href="/logout.php?lang=<?php echo urlencode($lang); ?>" data-i18n="account_logout">
Abmelden
<div class="form-actions">
<button type="submit" class="btn btn-primary">
Änderungen speichern
</button>
<a href="/logout.php" class="auth-link">
Logout
</a>
</div>
</aside>
</form>
</section>
<!-- Main: Profilformular + Platzhalter für später -->
<section class="account-main">
<div class="card">
<h2 data-i18n="account_profile_heading">Profil</h2>
<!-- Avatar / Meta -->
<section class="account-card">
<h2 class="account-title">Avatar &amp; Konto</h2>
<p class="account-subtitle">
Dein Avatar wird aktuell aus deinen Initialen erzeugt. Später kannst du hier ein eigenes Bild hochladen.
</p>
<?php if (!empty($profileErrors['csrf'])): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($profileErrors['csrf'], ENT_QUOTES); ?>
</div>
<?php endif; ?>
<?php if ($profileSuccess): ?>
<div class="alert alert-success" data-i18n="account_profile_updated">
Profil wurde aktualisiert.
</div>
<?php endif; ?>
<form method="post" action="/account.php?lang=<?php echo urlencode($lang); ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>">
<input type="hidden" name="action" value="update_profile">
<div class="form-row">
<label for="full_name" data-i18n="account_full_name_label">Vollständiger Name</label>
<input
type="text"
id="full_name"
name="full_name"
required
value="<?php echo htmlspecialchars($user['full_name'] ?? '', ENT_QUOTES); ?>"
>
<?php if (!empty($profileErrors['full_name'])): ?>
<p class="form-error"><?php echo htmlspecialchars($profileErrors['full_name'], ENT_QUOTES); ?></p>
<?php endif; ?>
</div>
<div class="form-row">
<label for="preferred_lang" data-i18n="account_lang_label">Bevorzugte Sprache</label>
<select id="preferred_lang" name="preferred_lang">
<?php
$pl = $user['preferred_lang'] ?? $lang;
?>
<option value="de" <?php echo $pl === 'de' ? 'selected' : ''; ?>>Deutsch</option>
<option value="en" <?php echo $pl === 'en' ? 'selected' : ''; ?>>English</option>
<option value="it" <?php echo $pl === 'it' ? 'selected' : ''; ?>>Italiano</option>
<option value="fr" <?php echo $pl === 'fr' ? 'selected' : ''; ?>>Français</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" data-i18n="account_profile_save">
Änderungen speichern
</button>
</div>
</form>
<div class="account-avatar-preview">
<?php if ($avatarUrl): ?>
<div class="user-avatar" style="background-image:url('<?php echo htmlspecialchars($avatarUrl, ENT_QUOTES, 'UTF-8'); ?>'); background-size:cover; background-position:center; color:transparent;">
&nbsp;
</div>
<?php else: ?>
<div class="user-avatar">
<?php echo htmlspecialchars($initials, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<div class="account-avatar-note">
<strong>Avatar-Vorschau</strong><br>
Standardmäßig Initialen aus deinem Namen.<br>
Upload-Funktion folgt in einem späteren Schritt.
</div>
</div>
<div class="card card-muted">
<h2 data-i18n="account_usb_heading">Deine USB-Sticks & Testergebnisse</h2>
<p class="muted" data-i18n="account_usb_placeholder">
Hier wirst du später eine Übersicht deiner registrierten USB-Sticks und Testergebnisse sehen.
</p>
</div>
<div class="card card-muted">
<h2 data-i18n="account_avatar_heading">Avatar</h2>
<p class="muted" data-i18n="account_avatar_placeholder">
Standardmäßig verwenden wir deine Initialen. Später kannst du hier ein eigenes Profilbild hochladen.
</p>
</div>
</section>
</div>
<div class="account-meta" style="margin-top:1.5rem;">
<p><strong>Account-ID:</strong> <?php echo (int)$user['id']; ?></p>
<p><strong>Registriert am:</strong>
<?php
if (!empty($user['created_at'])) {
echo htmlspecialchars($user['created_at'], ENT_QUOTES, 'UTF-8');
} else {
echo '';
}
?>
</p>
</div>
</section>
</div>
</section>
</div>
</main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script>
</body>
</html>

View File

@@ -553,3 +553,189 @@ body {
grid-template-columns: 1fr;
}
}
/* Auth / Forms / Account */
.auth-page {
padding: 4rem 0;
}
.auth-layout {
max-width: 480px;
margin: 0 auto;
}
.auth-card {
background: #fff;
border-radius: 18px;
padding: 1.6rem 1.8rem;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(200, 203, 208, 0.5);
}
.auth-title {
font-family: 'Montserrat', system-ui, sans-serif;
font-weight: 700;
font-size: 1.5rem;
margin: 0 0 0.35rem;
color: var(--deep-gray);
}
.auth-subtitle {
font-size: 0.95rem;
color: #555;
margin: 0 0 1.5rem;
}
.auth-flash-success,
.auth-flash-error {
border-radius: 12px;
padding: 0.75rem 0.9rem;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.auth-flash-success {
background: #e6f9ef;
border: 1px solid rgba(3, 193, 96, 0.4);
color: #145c32;
}
.auth-flash-error {
background: #fde7ea;
border: 1px solid rgba(230, 57, 70, 0.4);
color: #7f1d1d;
}
.form-row {
margin-bottom: 1rem;
}
.form-label {
display: block;
font-size: 0.85rem;
color: #555;
margin-bottom: 0.3rem;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.7rem;
font-size: 0.95rem;
font-family: inherit;
transition: border-color 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease;
background: #fff;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--brand-blue);
box-shadow: 0 0 0 1px rgba(0, 81, 255, 0.15);
background: #fff;
}
.form-input[readonly],
.form-select[readonly] {
background: #f3f4f6;
color: #666;
}
.form-error {
margin-top: 0.3rem;
font-size: 0.8rem;
color: var(--error-red);
}
.form-help {
margin-top: 0.3rem;
font-size: 0.8rem;
color: #777;
}
.form-actions {
margin-top: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.auth-link {
font-size: 0.9rem;
color: var(--brand-blue);
text-decoration: none;
}
.auth-link:hover {
text-decoration: underline;
}
/* Account page */
.account-page {
padding: 4rem 0;
}
.account-grid {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1.2fr);
gap: 2rem;
}
.account-card {
background: #fff;
border-radius: 18px;
padding: 1.6rem 1.8rem;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(200, 203, 208, 0.5);
}
.account-title {
font-family: 'Montserrat', system-ui, sans-serif;
font-weight: 700;
font-size: 1.4rem;
margin: 0 0 0.4rem;
color: var(--deep-gray);
}
.account-subtitle {
margin: 0 0 1.4rem;
font-size: 0.95rem;
color: #555;
}
.account-meta {
font-size: 0.85rem;
color: #777;
}
.account-avatar-preview {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 0.75rem;
}
.account-avatar-preview .user-avatar {
width: 44px;
height: 44px;
font-size: 1.05rem;
}
.account-avatar-note {
font-size: 0.85rem;
color: #666;
}
@media (max-width: 900px) {
.account-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -3,6 +3,18 @@
(function () {
const translations = {
de: {
header_slogan: "USB-Sticks testen",
btn_login: "Login",
nav_how: "Ablauf",
nav_problem: "Problem",
nav_features: "Funktionen",
nav_security: "Sicherheit",
nav_faq: "FAQ",
footer_imprint: "Impressum",
footer_privacy: "Datenschutz",
brand_wordmark: "usbcheck.it",
brand_subtitle: "USB-Sticks auf Fakes testen",
btn_login: "Login",
@@ -118,6 +130,17 @@
},
en: {
header_slogan: "Test USB drives",
btn_login: "Login",
nav_how: "How it works",
nav_problem: "Why it matters",
nav_features: "Features",
nav_security: "Security",
nav_faq: "FAQ",
footer_imprint: "Imprint",
footer_privacy: "Privacy policy",
brand_wordmark: "usbcheck.it",
brand_subtitle: "Test USB drives for fakes",
btn_login: "Login",
@@ -234,6 +257,17 @@
// Italienisch (kurz, sachlich)
it: {
header_slogan: "Test delle chiavette USB",
btn_login: "Accesso",
nav_how: "Come funziona",
nav_problem: "Perché è importante",
nav_features: "Funzioni",
nav_security: "Sicurezza",
nav_faq: "FAQ",
footer_imprint: "Imprint",
footer_privacy: "Privacy",
brand_wordmark: "usbcheck.it",
brand_subtitle: "Controlla le chiavette USB contraffatte",
btn_login: "Login",
@@ -351,6 +385,17 @@
// Französisch (kurz, sachlich)
fr: {
header_slogan: "Tester vos clés USB",
btn_login: "Connexion",
nav_how: "Fonctionnement",
nav_problem: "Problème",
nav_features: "Fonctionnalités",
nav_security: "Sécurité",
nav_faq: "FAQ",
footer_imprint: "Mentions légales",
footer_privacy: "Confidentialité",
brand_wordmark: "usbcheck.it",
brand_subtitle: "Tester les clés USB contrefaites",
btn_login: "Connexion",

View File

@@ -1,124 +1,42 @@
<?php
// public/index.php
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>USBCheck Test USB-Sticks auf Fakes</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
// Sprachlogik:
$lang = $_GET['lang'] ?? 'en';
$lang = in_array($lang, ['de','en','it','fr']) ? $lang : 'en';
<meta name="description" content="Prüfe deine USB-Sticks auf Fakes, langsame Geschwindigkeit und Datenverlust. USBCheck bietet einen kostenlosen Browser-Schnelltest und einen Pro-Modus für Profis.">
// User-Dummy (später über Login ersetzen)
$userInitials = null;
?>
<!DOCTYPE html>
<html lang="<?= htmlspecialchars($lang) ?>">
<head>
<meta charset="UTF-8">
<title>usbcheck.it Test USB-Sticks</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Description -->
<meta name="description" content="Prüfe deine USB-Sticks auf Geschwindigkeit, Integrität und mögliche Fakes direkt im Browser.">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet">
<!-- Tailwind CDN (für Dev; später durch eigenes CSS ersetzbar) -->
<!-- Tailwind (Dev) -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Montserrat', 'system-ui', 'sans-serif'],
},
colors: {
brand: {
bg: '#1A1A1A', // deep_gray
surface: '#232323', // dunkles Panel
primary: '#0051FF', // brand_blue
primarySoft: '#0b1535',
text: '#FAFAFA',
muted: '#C8CBD0', // silver
border: '#333333',
}
},
boxShadow: {
'soft': '0 18px 45px rgba(0,0,0,0.55)',
},
borderRadius: {
'xl2': '1.25rem',
}
}
}
}
</script>
<!-- Language JS -->
<script src="/assets/js/lang.js?v=1" defer></script>
</head>
<body class="bg-brand-bg text-brand-text font-sans antialiased scroll-smooth">
<!-- Page Wrapper -->
<div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="sticky top-0 z-40 border-b border-brand-border/70 backdrop-blur bg-brand-bg/85">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
<!-- Logo & Brand -->
<div class="flex items-center gap-3">
<a href="#hero" class="flex items-center gap-3">
<!-- Logo Pfad: public/img/logo_slogan.png -->
<img src="/img/logo_slogan.png" alt="usbcheck.it Logo" class="h-9 w-auto">
<div class="hidden sm:flex flex-col leading-tight">
<span class="font-heading font-bold text-sm uppercase tracking-[0.18em] text-brand-muted" data-i18n="brand_wordmark">
usbcheck.it
</span>
<span class="text-xs text-brand-muted" data-i18n="brand_subtitle">
Test USB drives for fakes
</span>
</div>
</a>
</div>
<!-- Navigation + Lang + Login -->
<div class="flex items-center gap-4 sm:gap-6">
<nav class="hidden md:flex items-center gap-6 text-xs font-medium text-brand-muted uppercase tracking-[0.18em]">
<a href="#how" class="hover:text-brand-primary transition-colors" data-i18n="nav_how">
How it works
</a>
<a href="#problem" class="hover:text-brand-primary transition-colors" data-i18n="nav_problem">
Why it matters
</a>
<a href="#features" class="hover:text-brand-primary transition-colors" data-i18n="nav_features">
Features
</a>
<a href="#security" class="hover:text-brand-primary transition-colors" data-i18n="nav_security">
Security
</a>
<a href="#faq" class="hover:text-brand-primary transition-colors" data-i18n="nav_faq">
FAQ
</a>
</nav>
<!-- HEADER -->
<?php include __DIR__ . "/partials/header.php"; ?>
<!-- Language Switch -->
<div class="flex items-center gap-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-brand-muted">
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="de">DE</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="en">EN</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="it">IT</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="fr">FR</button>
</div>
<!-- Login Button / Avatar -->
<button id="loginButton"
class="relative inline-flex items-center justify-center rounded-full bg-brand-primary px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-brand-bg shadow-soft hover:bg-blue-400 transition-colors">
<span id="loginLabel" data-i18n="btn_login">Login</span>
</button>
<!-- Avatar (wird per JS ein-/ausgeblendet) -->
<button id="userAvatar"
class="hidden h-9 w-9 rounded-full border border-brand-border bg-brand-surface flex items-center justify-center text-xs font-semibold text-brand-text shadow-soft hover:border-brand-primary transition"
aria-label="Mein Konto">
<span id="avatarInitials">UC</span>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<!-- MAIN CONTENT -->
<main class="flex-1">
<!-- Hero -->
<section id="hero" class="relative overflow-hidden">
@@ -512,20 +430,11 @@
</section>
</main>
<!-- Footer -->
<footer class="border-t border-brand-border/70">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-brand-muted">
<p data-i18n="footer_copy">© <?php echo date('Y'); ?> usbcheck.it. All rights reserved.</p>
<div class="flex items-center gap-4">
<a href="/impressum.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_imprint">Impressum</a>
<a href="/datenschutz.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_privacy">Datenschutz</a>
</div>
</div>
</footer>
<!-- FOOTER -->
<?php include __DIR__ . "/partials/footer.php"; ?>
</div>
<!-- I18n + Login/Avatar Logic -->
<script src="/assets/js/lang.js?v=1"></script>
<script src="/assets/js/header.js"></script>
</body>
</html>
</html>

View File

@@ -1,82 +1,146 @@
<?php
// public/login.php
declare(strict_types=1);
require __DIR__ . '/../config/db.php';
require __DIR__ . '/../src/Auth.php';
require __DIR__ . '/../src/auth.php'; // lädt auch config/db.php
$auth = new Auth($pdo);
$error = '';
$lang = auth_get_lang();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$identifier = trim($_POST['identifier'] ?? '');
$password = $_POST['password'] ?? '';
$errors = [];
$globalError = '';
if ($auth->login($identifier, $password)) {
header('Location: /'); // nach Login auf Startseite
exit;
} else {
$error = 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen.';
// Optional: Redirect-Ziel (z. B. ?redirect=/account.php)
$redirect = '/account.php';
if (!empty($_GET['redirect']) && is_string($_GET['redirect'])) {
// Nur interne Pfade erlauben, keine kompletten URLs
if (strpos($_GET['redirect'], '/') === 0) {
$redirect = $_GET['redirect'];
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// CSRF prüfen
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
$globalError = 'Sicherheitsfehler. Bitte die Seite neu laden und erneut versuchen.';
} else {
$identifier = trim((string)($_POST['identifier'] ?? ''));
$password = (string)($_POST['password'] ?? '');
$result = auth_login($identifier, $password);
if ($result['success'] === true) {
header('Location: ' . $redirect);
exit;
} else {
$errors = $result['errors'] ?? [];
}
}
}
$csrfToken = auth_csrf_token();
?>
<!DOCTYPE html>
<html lang="de">
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head>
<meta charset="UTF-8">
<title>Login usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Dein Tailwind CSS -->
<link rel="stylesheet" href="/css/tailwind.css">
<!-- Fonts: Montserrat + Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Haupt-CSS -->
<link rel="stylesheet" href="/assets/css/main.css?v=1">
</head>
<body class="bg-[#FAFAFA] text-[#1A1A1A] font-[Inter]">
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md bg-white shadow-lg rounded-2xl p-8">
<h1 class="text-2xl font-[Montserrat] font-bold mb-6 text-center">
Anmelden bei <span class="text-[#0051FF]">usbcheck.it</span>
</h1>
<body>
<?php include __DIR__ . '/partials/header.php'; ?>
<?php if ($error): ?>
<div class="mb-4 text-sm text-[#E63946]">
<?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
</div>
<?php endif; ?>
<main>
<section class="section">
<div class="container">
<div style="max-width: 480px; margin: 0 auto;">
<h1 class="section-title" style="font-size: 1.6rem; text-align: center; margin-bottom: 1.5rem;">
Anmelden bei usbcheck.it
</h1>
<p class="section-lead" style="text-align: center; margin-bottom: 2rem;">
Melde dich mit deiner E-Mail-Adresse oder deinem Benutzernamen an, um deine USB-Tests und Geräte zu verwalten.
</p>
<form method="post" class="space-y-4">
<div>
<label class="block text-sm mb-1" for="identifier">
E-Mail oder Benutzername
</label>
<input
type="text"
id="identifier"
name="identifier"
required
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]"
>
</div>
<?php if ($globalError !== ''): ?>
<div style="margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 12px; background: #ffe6e8; color: #a4001f; font-size: 0.9rem;">
<?php echo htmlspecialchars($globalError, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<div>
<label class="block text-sm mb-1" for="password">
Passwort
</label>
<input
type="password"
id="password"
name="password"
required
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]"
>
</div>
<?php if (!empty($errors['login'])): ?>
<div style="margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 12px; background: #ffe6e8; color: #a4001f; font-size: 0.9rem;">
<?php echo htmlspecialchars($errors['login'], ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<button
type="submit"
class="w-full bg-[#0051FF] text-white font-[Montserrat] font-semibold rounded-lg py-2 mt-4 hover:bg-blue-700 transition"
>
Login
</button>
</form>
<form method="post" class="step-card">
<div style="margin-bottom: 1rem;">
<label for="identifier" style="display:block; font-size:0.9rem; margin-bottom:0.25rem;">
E-Mail oder Benutzername
</label>
<input
type="text"
id="identifier"
name="identifier"
required
style="
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.75rem;
font-size: 0.95rem;
font-family: 'Inter', system-ui, sans-serif;
"
value="<?php echo htmlspecialchars($_POST['identifier'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
>
</div>
<div style="margin-bottom: 1rem;">
<label for="password" style="display:block; font-size:0.9rem; margin-bottom:0.25rem;">
Passwort
</label>
<input
type="password"
id="password"
name="password"
required
style="
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.75rem;
font-size: 0.95rem;
font-family: 'Inter', system-ui, sans-serif;
"
>
</div>
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<div style="margin-top: 1.5rem; display:flex; flex-direction:column; gap:0.5rem;">
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center;">
Login
</button>
<a href="/register.php" class="btn btn-ghost" style="width: 100%; justify-content: center;">
Noch kein Konto? Jetzt registrieren
</a>
</div>
</form>
</div>
</div>
</div>
</section>
</main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<?php
// public/partials/footer.php
?>
<footer class="border-t border-brand-border/70">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-brand-muted">
<p>© <?= date('Y') ?> usbcheck.it</p>
<div class="flex items-center gap-4">
<a href="/impressum.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_imprint"></a>
<a href="/datenschutz.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_privacy"></a>
</div>
</div>
</footer>

View File

@@ -1,75 +1,62 @@
<?php
// public/partials/header.php
// Fallback, falls $currentLang nicht gesetzt ist
if (!isset($currentLang)) {
$currentLang = 'en';
}
$supportedLangs = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
/**
* Baut eine URL und hängt immer ?lang=<code> dran.
* $path sollte mit / beginnen, z.B. "/", "/fakecheck/", "/impressum".
*/
function usbcheck_url_with_lang(string $path, string $lang): string
{
$path = $path ?: '/';
$separator = str_contains($path, '?') ? '&' : '?';
return $path . $separator . 'lang=' . urlencode($lang);
}
?>
<header class="site-header">
<div class="container header-inner">
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>" class="logo-wrap">
<img src="/img/logo.png" alt="usbcheck.it Logo" class="logo-img">
</a>
<header class="sticky top-0 z-40 border-b border-brand-border/70 backdrop-blur bg-brand-bg/85">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
<!-- Logo -->
<div class="flex items-center gap-3">
<a href="/?lang=<?= htmlspecialchars($lang) ?>" class="flex items-center gap-3">
<img src="/assets/img/logo_slogan.png" alt="usbcheck.it Logo" class="h-9 w-auto">
<nav class="main-nav">
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#how-it-works"
class="nav-link"
data-i18n="nav_how_it_works"></a>
<div class="hidden sm:flex flex-col leading-tight">
<span class="font-heading font-bold text-sm uppercase tracking-[0.18em] text-brand-muted">
usbcheck.it
</span>
<span class="text-xs text-brand-muted" data-i18n="header_slogan">
Test USB drives
</span>
</div>
</a>
</div>
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#features"
class="nav-link"
data-i18n="nav_features"></a>
<!-- Navigation -->
<div class="flex items-center gap-6">
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#pricing"
class="nav-link"
data-i18n="nav_pricing"></a>
<nav class="hidden md:flex items-center gap-6 text-xs font-medium text-brand-muted uppercase tracking-[0.18em]">
<a href="#how" class="hover:text-brand-primary transition-colors" data-i18n="nav_how"></a>
<a href="#problem" class="hover:text-brand-primary transition-colors" data-i18n="nav_problem"></a>
<a href="#features" class="hover:text-brand-primary transition-colors" data-i18n="nav_features"></a>
<a href="#security" class="hover:text-brand-primary transition-colors" data-i18n="nav_security"></a>
<a href="#faq" class="hover:text-brand-primary transition-colors" data-i18n="nav_faq"></a>
</nav>
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#faq"
class="nav-link"
data-i18n="nav_faq"></a>
</nav>
<div class="header-actions">
<div class="lang-switch" data-current-lang="<?php echo htmlspecialchars($currentLang); ?>">
<button id="lang-current" class="lang-current">
<?php echo strtoupper(htmlspecialchars($currentLang)); ?>
<!-- Language Switch -->
<div class="relative">
<button id="langCurrent"
class="text-xs uppercase tracking-[0.18em] text-brand-muted hover:text-brand-primary transition">
<?= strtoupper($lang) ?>
</button>
<div id="lang-menu" class="lang-menu hidden">
<?php foreach ($supportedLangs as $code => $label): ?>
<button data-lang="<?php echo htmlspecialchars($code); ?>">
<?php echo htmlspecialchars($label); ?>
</button>
<?php endforeach; ?>
<div id="langMenu"
class="hidden absolute right-0 mt-2 w-28 rounded-md bg-brand-surface border border-brand-border shadow-lg p-2 text-xs">
<a href="?lang=de" class="block py-1 hover:text-brand-primary">Deutsch</a>
<a href="?lang=en" class="block py-1 hover:text-brand-primary">English</a>
<a href="?lang=it" class="block py-1 hover:text-brand-primary">Italiano</a>
<a href="?lang=fr" class="block py-1 hover:text-brand-primary">Français</a>
</div>
</div>
<button id="login-button"
class="btn btn-outline"
data-i18n="btn_login"></button>
<!-- Login Button / Avatar -->
<button id="loginButton"
class="relative inline-flex items-center justify-center rounded-full bg-brand-primary px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-brand-bg shadow-soft hover:bg-cyan-400 transition-colors"
data-i18n="btn_login">
</button>
<div id="user-avatar" class="user-avatar hidden" title="Account">
<!-- Standard: Initialen, später durch Bild ersetzbar -->
<span id="user-avatar-initials">U</span>
</div>
<button id="userAvatar"
class="hidden h-9 w-9 rounded-full border border-brand-border bg-brand-surface flex items-center justify-center text-xs font-semibold text-brand-text shadow-soft hover:border-brand-primary transition"
aria-label="Mein Konto">
<span><?= strtoupper(substr($userInitials ?? 'U', 0, 2)) ?></span>
</button>
</div>
</div>
</header>

View File

@@ -1,42 +1,24 @@
<?php
// public/register.php
declare(strict_types=1);
require_once __DIR__ . '/../src/auth.php';
require __DIR__ . '/../src/auth.php';
$lang = auth_get_lang();
$csrfToken = auth_csrf_token();
$currentUser = auth_current_user();
if ($currentUser) {
// Bereits eingeloggt -> Accountseite
header('Location: /account.php?lang=' . urlencode($lang));
exit;
}
$errors = [];
$values = [
'email' => '',
'username' => '',
'full_name' => '',
'preferred_lang' => $lang,
];
$globalError = '';
$result = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
$errors['csrf'] = 'Deine Sitzung ist abgelaufen. Bitte Seite neu laden.';
if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
$globalError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
} else {
$email = $_POST['email'] ?? '';
$username = $_POST['username'] ?? '';
$fullName = $_POST['full_name'] ?? '';
$password = $_POST['password'] ?? '';
$passwordConfirm= $_POST['password_confirm'] ?? '';
$preferredLang = $_POST['preferred_lang'] ?? $lang;
$values = [
'email' => $email,
'username' => $username,
'full_name' => $fullName,
'preferred_lang' => $preferredLang,
];
$email = $_POST['email'] ?? '';
$username = $_POST['username'] ?? '';
$fullName = $_POST['full_name'] ?? '';
$password = $_POST['password'] ?? '';
$passwordConfirm = $_POST['password_confirm'] ?? '';
$preferredLang = $_POST['preferred_lang'] ?? $lang;
$result = auth_register_user(
$email,
@@ -47,152 +29,169 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$preferredLang
);
if ($result['success']) {
header('Location: /account.php?lang=' . urlencode($preferredLang));
if ($result['success'] ?? false) {
// Direkt nach Account-Seite
header('Location: /account.php');
exit;
} else {
$errors = array_merge($errors, $result['errors']);
}
$errors = $result['errors'] ?? [];
}
}
$csrfToken = auth_csrf_token();
?>
<!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>">
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head>
<meta charset="UTF-8">
<title>Registrierung USBCheck</title>
<title>Registrieren usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Fonts -->
<!-- Fonts: Montserrat + Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Main CSS -->
<!-- Main stylesheet -->
<link rel="stylesheet" href="/assets/css/main.css?v=1">
</head>
<body>
<?php
// Header-Partial, erwartet ggf. $lang
$langVar = $lang;
include __DIR__ . '/partials/header.php';
?>
<?php include __DIR__ . '/partials/header.php'; ?>
<main class="page-main">
<section class="section">
<div class="container narrow">
<h1 class="section-title" data-i18n="register_title">Konto erstellen</h1>
<p class="section-lead" data-i18n="register_intro">
Erstelle ein kostenloses Konto, um deine USB-Tests zu verwalten.
</p>
<main class="auth-page">
<div class="container">
<div class="auth-layout">
<div class="auth-card">
<h1 class="auth-title">Konto erstellen</h1>
<p class="auth-subtitle">
Erstelle einen kostenlosen Account, um deine USB-Tests zu speichern und mehrere Sticks zu verwalten.
</p>
<?php if (!empty($errors['csrf'])): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($errors['csrf'], ENT_QUOTES); ?>
</div>
<?php endif; ?>
<?php if ($globalError): ?>
<div class="auth-flash-error">
<?php echo htmlspecialchars($globalError, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<form class="form-card" method="post" action="/register.php?lang=<?php echo urlencode($lang); ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>">
<form method="post" novalidate>
<input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<div class="form-row">
<label for="email" data-i18n="register_email_label">E-Mail-Adresse</label>
<input
type="email"
id="email"
name="email"
required
value="<?php echo htmlspecialchars($values['email'], ENT_QUOTES); ?>"
>
<?php if (!empty($errors['email'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES); ?></p>
<?php endif; ?>
</div>
<div class="form-row">
<label for="username" data-i18n="register_username_label">Username</label>
<input
type="text"
id="username"
name="username"
required
value="<?php echo htmlspecialchars($values['username'], ENT_QUOTES); ?>"
>
<?php if (!empty($errors['username'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES); ?></p>
<?php endif; ?>
</div>
<div class="form-row">
<label for="full_name" data-i18n="register_full_name_label">Vollständiger Name</label>
<input
type="text"
id="full_name"
name="full_name"
required
value="<?php echo htmlspecialchars($values['full_name'], ENT_QUOTES); ?>"
>
<?php if (!empty($errors['full_name'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES); ?></p>
<?php endif; ?>
</div>
<div class="form-row form-row-inline">
<div>
<label for="password" data-i18n="register_password_label">Passwort</label>
<div class="form-row">
<label class="form-label" for="email">E-Mail-Adresse</label>
<input
class="form-input"
type="email"
id="email"
name="email"
required
value="<?php echo htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
>
<?php if (!empty($errors['email'])): ?>
<div class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
<div class="form-row">
<label class="form-label" for="username">Benutzername</label>
<input
class="form-input"
type="text"
id="username"
name="username"
required
value="<?php echo htmlspecialchars($_POST['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
>
<?php if (!empty($errors['username'])): ?>
<div class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
<div class="form-help">
332 Zeichen, Buchstaben/Zahlen/._- erlaubt.
</div>
</div>
<div class="form-row">
<label class="form-label" for="full_name">Vollständiger Name</label>
<input
class="form-input"
type="text"
id="full_name"
name="full_name"
required
value="<?php echo htmlspecialchars($_POST['full_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
>
<?php if (!empty($errors['full_name'])): ?>
<div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
<div class="form-row">
<label class="form-label" for="password">Passwort</label>
<input
class="form-input"
type="password"
id="password"
name="password"
required
minlength="10"
>
<?php if (!empty($errors['password'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['password'], ENT_QUOTES); ?></p>
<div class="form-error"><?php echo htmlspecialchars($errors['password'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
<div class="form-help">
Mindestens 10 Zeichen. Bitte ein sicheres Passwort wählen.
</div>
</div>
<div>
<label for="password_confirm" data-i18n="register_password_confirm_label">Passwort wiederholen</label>
<div class="form-row">
<label class="form-label" for="password_confirm">Passwort wiederholen</label>
<input
class="form-input"
type="password"
id="password_confirm"
name="password_confirm"
required
minlength="10"
>
<?php if (!empty($errors['password_confirm'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['password_confirm'], ENT_QUOTES); ?></p>
<div class="form-error"><?php echo htmlspecialchars($errors['password_confirm'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
</div>
<div class="form-row">
<label for="preferred_lang" data-i18n="register_lang_label">Bevorzugte Sprache</label>
<select id="preferred_lang" name="preferred_lang">
<option value="de" <?php echo $values['preferred_lang'] === 'de' ? 'selected' : ''; ?>>Deutsch</option>
<option value="en" <?php echo $values['preferred_lang'] === 'en' ? 'selected' : ''; ?>>English</option>
<option value="it" <?php echo $values['preferred_lang'] === 'it' ? 'selected' : ''; ?>>Italiano</option>
<option value="fr" <?php echo $values['preferred_lang'] === 'fr' ? 'selected' : ''; ?>>Français</option>
</select>
</div>
<div class="form-row">
<label class="form-label" for="preferred_lang">Bevorzugte Sprache</label>
<select class="form-select" id="preferred_lang" name="preferred_lang">
<?php
$selLang = $_POST['preferred_lang'] ?? $lang;
$opts = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
foreach ($opts as $code => $label) {
$selected = ($code === $selLang) ? 'selected' : '';
echo '<option value="' . htmlspecialchars($code, ENT_QUOTES, 'UTF-8') . '" ' . $selected . '>'
. htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</option>';
}
?>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" data-i18n="register_submit">
Konto erstellen
</button>
<p class="form-hint">
<span data-i18n="register_existing_hint">Du hast bereits ein Konto?</span>
<a href="/login.php?lang=<?php echo urlencode($lang); ?>" data-i18n="register_existing_link">
Zum Login
<div class="form-actions">
<button type="submit" class="btn btn-primary">
Konto erstellen
</button>
<a class="auth-link" href="/login.php">
Bereits ein Konto? Login
</a>
</p>
</div>
</form>
</div>
</form>
</div>
</div>
</section>
</div>
</main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script>
</body>
</html>

View File

@@ -1,25 +0,0 @@
<?php
// src/Database.php
declare(strict_types=1);
class Database
{
private static ?PDO $pdo = null;
public static function getConnection(): PDO
{
if (self::$pdo === null) {
$config = require __DIR__ . '/../config/database.php';
self::$pdo = new PDO(
$config['dsn'],
$config['user'],
$config['password'],
$config['options']
);
}
return self::$pdo;
}
}

View File

@@ -1 +0,0 @@
demo