diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf382d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +storage/* +!storage/.gitkeep diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..afb951a --- /dev/null +++ b/.htaccess @@ -0,0 +1,5 @@ +Options -Indexes +AddDefaultCharset UTF-8 + +RewriteEngine On + diff --git a/admin/bands.php b/admin/bands.php new file mode 100644 index 0000000..418f339 --- /dev/null +++ b/admin/bands.php @@ -0,0 +1,48 @@ + + + + + + Bands moderieren – <?= SITE_NAME ?> + + + +
DashboardBandsBewertungenSettings
+
+

Bandfreigaben

+ + + + + + + + + + + + +
NameOrtStatusAktion
+
+ + + +
+
+

Keine Bands warten auf Moderation.

+
+ + diff --git a/admin/bewertungen.php b/admin/bewertungen.php new file mode 100644 index 0000000..6e953c4 --- /dev/null +++ b/admin/bewertungen.php @@ -0,0 +1,48 @@ + + + + + + Bewertungen prüfen – <?= SITE_NAME ?> + + + +
DashboardBandsBewertungenSettings
+
+

Bewertungen moderieren

+ + + + + + + + + + + + + +
BandAutorBewertungKommentarAktion
+
+ + + +
+
+

Keine Bewertungen in Moderation.

+
+ + diff --git a/admin/dashboard.php b/admin/dashboard.php new file mode 100644 index 0000000..0b838b0 --- /dev/null +++ b/admin/dashboard.php @@ -0,0 +1,37 @@ + (int) $pdo->query("SELECT COUNT(*) FROM bands")->fetchColumn(), + 'requests' => (int) $pdo->query("SELECT COUNT(*) FROM requests")->fetchColumn(), + 'reviews' => (int) $pdo->query("SELECT COUNT(*) FROM reviews")->fetchColumn(), +]; +?> + + + + + Admin Dashboard – <?= SITE_NAME ?> + + + +
+
+ Dashboard + Bandfreigaben + Bewertungen + Settings +
+
+
+
+

Bands

+

Anfragen

+

Bewertungen

+
+
+ + diff --git a/admin/settings.php b/admin/settings.php new file mode 100644 index 0000000..6f3ca77 --- /dev/null +++ b/admin/settings.php @@ -0,0 +1,37 @@ + + + + + + Settings – <?= SITE_NAME ?> + + + +
DashboardBandsBewertungenSettings
+
+

Vermittlungsgebühr & PayPal

+
+
+ + + +
+
+ + diff --git a/anfrage.php b/anfrage.php new file mode 100644 index 0000000..f8557c0 --- /dev/null +++ b/anfrage.php @@ -0,0 +1,89 @@ + $bandId, + 'user_id' => $user['id'] ?? null, + 'event_date' => $_POST['event_date'] ?? '', + 'location' => trim((string) $_POST['location'] ?? ''), + 'budget' => (int) ($_POST['budget'] ?? 0), + 'event_type' => trim((string) $_POST['event_type'] ?? ''), + 'message' => trim((string) $_POST['message'] ?? ''), + ]; + + if (!$data['event_date'] || !$data['location']) { + $error = 'Bitte Datum und Ort ausfüllen.'; + } else { + createRequest($data); + $recipient = $band['contact_email'] ?: SUPPORT_EMAIL; + $emailBody = sprintf( + '

Neue Anfrage für %s

Nachricht:

%s

', + htmlspecialchars($band['name']), + htmlspecialchars($data['event_date']), + htmlspecialchars($data['location']), + number_format((int) $data['budget'], 0, ',', '.'), + htmlspecialchars($data['event_type']), + nl2br(htmlspecialchars($data['message'])) + ); + $sent = sendEmail($recipient, 'Neue Anfrage – ' . $band['name'], $emailBody); + $message = $sent + ? 'Anfrage gespeichert und an die Band gemeldet.' + : 'Anfrage gespeichert – E-Mail an die Band konnte nicht gesendet werden.'; + } +} + +$settings = settings(); +?> + + + + + + Anfrage – <?= htmlspecialchars($band['name']) ?> + + + +
+ ← Zurück +

Anfrage an

+

PayPal Zahlungsabwicklung ist , Service Fee: %.

+
+
+
+
+
+ + + + + + +
+
+ + diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..3b0ca20 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,237 @@ +:root { + --primary: #ffb703; + --secondary: #fb8500; + --dark: #0b0d17; + --darker: #090b13; + --light: #fefae0; + --gray: #8d99ae; + --gradient: linear-gradient(120deg, #ffb703, #fb5607, #ff006e); +} + +* { + box-sizing: border-box; +} + +body { + font-family: 'Space Grotesk', 'Segoe UI', system-ui, sans-serif; + margin: 0; + background: radial-gradient(circle at 10% 20%, rgba(255, 183, 3, 0.25), rgba(9, 11, 19, 0.95)), var(--dark); + color: var(--light); + min-height: 100vh; +} + +header { + padding: 40px 5vw 20px; +} + +.hero { + background: var(--darker); + border-radius: 24px; + padding: 40px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 30px; + align-items: center; +} + +.hero h1 { + font-size: clamp(2.2rem, 5vw, 3.6rem); + margin-bottom: 10px; +} + +.hero p { + color: rgba(255, 255, 255, 0.8); +} + +.badge-list { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.badge { + background: rgba(255, 255, 255, 0.08); + padding: 6px 18px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.1); + font-size: 0.9rem; +} + +.search-panel { + margin-top: 30px; + background: rgba(0, 0, 0, 0.35); + border-radius: 18px; + padding: 20px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 15px; +} + +.search-panel input, +.search-panel select { + width: 100%; + padding: 12px 16px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.05); + color: var(--light); +} + +.btn-primary { + padding: 14px 24px; + border-radius: 16px; + border: none; + font-weight: bold; + background: var(--gradient); + color: var(--dark); + cursor: pointer; + transition: transform 0.2s ease; +} + +.btn-primary:hover { + transform: translateY(-2px); +} + +main { + padding: 30px 5vw 80px; +} + +.band-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 24px; +} + +.band-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + padding: 20px; + position: relative; + overflow: hidden; + transition: transform 0.2s ease, border-color 0.3s ease; +} + +.band-card:hover { + transform: translateY(-4px); + border-color: var(--primary); +} + +.band-card h3 { + margin-top: 0; + margin-bottom: 8px; +} + +.price-tag { + font-size: 1.1rem; + color: var(--primary); + font-weight: bold; +} + +.card-meta { + color: rgba(255, 255, 255, 0.7); + font-size: 0.9rem; +} + +footer { + background: rgba(0, 0, 0, 0.4); + padding: 30px 5vw; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 15px; +} + +.cookie-banner { + position: fixed; + bottom: 20px; + right: 20px; + background: var(--darker); + border-radius: 16px; + padding: 20px; + width: min(360px, calc(100% - 40px)); + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.5); + display: none; +} + +.cookie-banner.active { + display: block; +} + +.table { + width: 100%; + border-collapse: collapse; +} + +.table th, +.table td { + padding: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + text-align: left; +} + +.form-control { + width: 100%; + padding: 12px 16px; + margin-bottom: 15px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.05); + color: var(--light); +} + +.alert { + padding: 15px 18px; + border-radius: 12px; + margin-bottom: 15px; +} + +.alert-success { + background: rgba(56, 142, 60, 0.2); + border: 1px solid rgba(56, 142, 60, 0.4); +} + +.alert-error { + background: rgba(213, 0, 0, 0.2); + border: 1px solid rgba(213, 0, 0, 0.4); +} + +.band-detail-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 30px; + margin-top: 30px; +} + +.gallery img { + width: 100%; + border-radius: 16px; + margin-bottom: 16px; +} + +.badge-rating { + background: rgba(255, 183, 3, 0.2); + border-color: rgba(255, 183, 3, 0.5); +} + +.admin-nav { + display: flex; + gap: 12px; + margin-bottom: 20px; +} + +.admin-nav a { + color: var(--light); + text-decoration: none; + padding: 10px 16px; + border-radius: 12px; + background: rgba(255, 255, 255, 0.08); +} + +@media (max-width: 768px) { + .hero, + footer { + padding: 24px; + } +} diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..bd11345 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,20 @@ +const cookieBanner = document.querySelector('.cookie-banner'); +const cookieAccept = document.querySelector('[data-cookie-accept]'); + +if (cookieBanner && cookieAccept) { + const consent = localStorage.getItem('gyb-cookie'); + if (!consent) { + cookieBanner.classList.add('active'); + } + cookieAccept.addEventListener('click', () => { + localStorage.setItem('gyb-cookie', 'accepted'); + cookieBanner.classList.remove('active'); + }); +} + +const filterForm = document.querySelector('[data-filter-form]'); +if (filterForm) { + filterForm.addEventListener('input', () => { + filterForm.submit(); + }); +} diff --git a/auroraalt.php b/auroraalt.php new file mode 100644 index 0000000..f18648e --- /dev/null +++ b/auroraalt.php @@ -0,0 +1,47 @@ +", + 'Reply-To: ' . $replyTo, + ]; + + if (!empty($options['cc'])) { + $headers[] = 'Cc: ' . $options['cc']; + } + + if (!empty($options['bcc'])) { + $headers[] = 'Bcc: ' . $options['bcc']; + } + + $sendmailPath = ini_get('sendmail_path'); + $transportAvailable = true; + if (stripos(PHP_OS, 'WIN') !== 0) { + $binary = trim(strtok((string) $sendmailPath, ' ')); + if ($binary && !is_executable($binary)) { + $transportAvailable = false; + } + } + + if (!$transportAvailable) { + return false; + } + + $additionalHeaders = implode("\r\n", $headers); + $encodedSubject = function_exists('mb_encode_mimeheader') + ? mb_encode_mimeheader($subject, 'UTF-8') + : $subject; + + return @mail($to, $encodedSubject, $htmlBody, $additionalHeaders); + } +} diff --git a/band-detail.php b/band-detail.php new file mode 100644 index 0000000..9947543 --- /dev/null +++ b/band-detail.php @@ -0,0 +1,199 @@ + '', + 'email' => '', + 'message' => '', +]; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['review']) && $user) { + if (!eligibleForReview($bandId, (int) $user['id'])) { + $reviewError = 'Für Bewertungen ist eine bestätigte Buchung nötig.'; + } else { + $comment = trim((string) ($_POST['comment'] ?? '')); + if (mb_strlen($comment) > 200) { + $reviewError = 'Maximal 200 Zeichen erlaubt.'; + } else { + storeReview([ + 'band_id' => $bandId, + 'user_id' => (int) $user['id'], + 'rating' => (int) $_POST['rating'], + 'comment' => $comment, + ]); + $reviewMessage = 'Danke! Deine Bewertung wartet auf Freigabe.'; + } + } + } + + if (isset($_POST['contact'])) { + $contactForm['name'] = trim((string) ($_POST['contact_name'] ?? '')); + $contactForm['email'] = trim((string) ($_POST['contact_email'] ?? '')); + $contactForm['message'] = trim((string) ($_POST['contact_message'] ?? '')); + + if ($contactForm['name'] === '' || $contactForm['message'] === '') { + $contactError = 'Bitte Name und Nachricht ausfüllen.'; + } elseif (!filter_var($contactForm['email'], FILTER_VALIDATE_EMAIL)) { + $contactError = 'Bitte eine gültige E-Mail-Adresse angeben.'; + } else { + $recipient = $band['contact_email'] ?: SUPPORT_EMAIL; + $body = sprintf( + '

Neue Nachricht über die Bandseite %s.

Von: %s (%s)

Nachricht:
%s

', + htmlspecialchars($band['name']), + htmlspecialchars($contactForm['name']), + htmlspecialchars($contactForm['email']), + nl2br(htmlspecialchars($contactForm['message'])) + ); + $sent = sendEmail($recipient, 'Kontaktformular – ' . $band['name'], $body); + if ($sent) { + $contactMessage = 'Nachricht an die Band wurde verschickt.'; + $contactForm = ['name' => '', 'email' => '', 'message' => '']; + } else { + $contactError = 'E-Mail-Versand zur Band nicht möglich. Bitte später erneut versuchen.'; + } + } + } +} +?> + + + + + + <?= htmlspecialchars($band['name']) ?> – <?= SITE_NAME ?> + + + +
+ ← Zurück +
+
+

+

+

+
+ Ort: + ab +
+
+ + # + +
+

+ Verfügbarkeit anfragen +

+
+
+ + + +
+
+
+
+
+ +
+

Verfügbarkeit

+ + + + + + + + + + + + +
DatumStatus
format('d.m.Y')) ?>
+
+
+ +
+

Kontakt zur Band

+

Schicke eine direkte Nachricht.

+
+
+
+ + + + + +
+
+ +
+

Bewertungen

+
+
+ +
+

+

+

format('d.m.Y') ?>

+
+ + +

Noch keine freigegebenen Bewertungen.

+ +
+ + +
+

Eigene Bewertung

+
+ + + + +
+
+ +
+ + diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..d342a9f --- /dev/null +++ b/database.sql @@ -0,0 +1,77 @@ +PRAGMA foreign_keys = ON; + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'kunde', + city TEXT, + verified INTEGER NOT NULL DEFAULT 0, + verification_token TEXT, + created_at TEXT +); + +CREATE TABLE IF NOT EXISTS bands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + name TEXT NOT NULL, + city TEXT, + genre TEXT, + price INTEGER DEFAULT 0, + description TEXT, + status TEXT NOT NULL DEFAULT 'prüfung', + style_tags TEXT, + video_url TEXT, + contact_email TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS band_media ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + band_id INTEGER NOT NULL, + type TEXT NOT NULL, + url TEXT NOT NULL, + FOREIGN KEY(band_id) REFERENCES bands(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS band_availability ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + band_id INTEGER NOT NULL, + event_date TEXT NOT NULL, + status TEXT NOT NULL, + FOREIGN KEY(band_id) REFERENCES bands(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + band_id INTEGER NOT NULL, + user_id INTEGER, + event_date TEXT, + location TEXT, + budget INTEGER, + event_type TEXT, + message TEXT, + status TEXT NOT NULL DEFAULT 'neu', + created_at TEXT, + FOREIGN KEY(band_id) REFERENCES bands(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS reviews ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + band_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + rating INTEGER NOT NULL, + comment TEXT, + status TEXT NOT NULL DEFAULT 'wartend', + created_at TEXT, + FOREIGN KEY(band_id) REFERENCES bands(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); diff --git a/functional_test.php b/functional_test.php new file mode 100644 index 0000000..713bf4e --- /dev/null +++ b/functional_test.php @@ -0,0 +1,332 @@ +#!/usr/bin/env php +pdo = $pdo; + } + + public function run(string $title, callable $test, bool $transactional = false): void + { + try { + if ($transactional) { + $this->pdo->beginTransaction(); + } + $details = $test($this->pdo); + if ($transactional && $this->pdo->inTransaction()) { + $this->pdo->rollBack(); + } + $this->record('PASS', $title, $details); + } catch (Throwable $e) { + if ($transactional && $this->pdo->inTransaction()) { + $this->pdo->rollBack(); + } + $this->record('FAIL', $title, $e->getMessage()); + } + } + + private function record(string $status, string $title, ?string $details): void + { + $status === 'PASS' ? $this->passed++ : $this->failed++; + $this->results[] = [ + 'status' => $status, + 'title' => $title, + 'details' => $details ?? '', + ]; + } + + public function summary(): void + { + foreach ($this->results as $result) { + $symbol = $result['status'] === 'PASS' ? '\u{2705}' : '\u{274C}'; + echo sprintf("%s %s\n", $symbol, $result['title']); + if ($result['details'] !== '') { + echo sprintf(" %s\n", $result['details']); + } + } + echo str_repeat('-', 50) . "\n"; + echo sprintf("Ergebnis: %d bestanden, %d fehlgeschlagen\n", $this->passed, $this->failed); + exit($this->failed === 0 ? 0 : 1); + } +} + +function renderPage(string $file, array $get = [], array $post = []): string +{ + $previousGet = $_GET ?? []; + $previousPost = $_POST ?? []; + $previousMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + + $_GET = $get; + $_POST = $post; + $_SERVER['REQUEST_METHOD'] = empty($post) ? 'GET' : 'POST'; + + ob_start(); + include __DIR__ . '/' . ltrim($file, '/'); + $output = ob_get_clean(); + + $_GET = $previousGet; + $_POST = $previousPost; + $_SERVER['REQUEST_METHOD'] = $previousMethod; + + return $output; +} + +function restartSession(): void +{ + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } +} + +$pdo = db(); +$runner = new FunctionalTestRunner($pdo); + +$runner->run('Storage-Verzeichnis vorhanden', function () { + if (!is_dir(__DIR__ . '/storage')) { + throw new RuntimeException('Ordner storage fehlt.'); + } + return 'Pfad: ' . realpath(__DIR__ . '/storage'); +}); + +$runner->run('Datenbank initialisiert', function (PDO $pdo) { + if (!file_exists(DB_PATH)) { + throw new RuntimeException('database.sqlite wurde nicht erstellt.'); + } + $tables = $pdo->query("SELECT name FROM sqlite_master WHERE type='table'") + ->fetchAll(PDO::FETCH_COLUMN); + $required = ['users', 'bands', 'requests', 'reviews', 'settings']; + foreach ($required as $table) { + if (!in_array($table, $tables, true)) { + throw new RuntimeException('Tabelle ' . $table . ' fehlt.'); + } + } + return 'Tabellen gefunden: ' . implode(', ', $required); +}); + +$runner->run('Seed-Daten verfügbar', function (PDO $pdo) { + $users = (int) $pdo->query('SELECT COUNT(*) FROM users')->fetchColumn(); + $bands = (int) $pdo->query('SELECT COUNT(*) FROM bands')->fetchColumn(); + if ($users < 3 || $bands < 2) { + throw new RuntimeException('Seed-Daten unvollständig.'); + } + return sprintf('Users: %d, Bands: %d', $users, $bands); +}); + +$runner->run('Login / Logout Workflow', function () { + if (!login('david@example.com', 'secret123')) { + throw new RuntimeException('Login schlug fehl.'); + } + $user = currentUser(); + if (!$user || $user['role'] !== 'kunde') { + throw new RuntimeException('Session liefert keinen Kunden.'); + } + logout(); + restartSession(); + if (currentUser()) { + throw new RuntimeException('Logout hat Session nicht geleert.'); + } + return 'Login erfolgreich für ' . $user['name']; +}); + +$runner->run('Band-Filter & Durchschnitt', function () { + $bands = allBands(['genre' => 'Funk']); + if (!$bands) { + throw new RuntimeException('Filter lieferte keine Band.'); + } + $rating = averageRating((int) $bands[0]['id']); + if ($rating === null) { + throw new RuntimeException('Keine Bewertung vorhanden.'); + } + return sprintf('%d Bands, Ø Bewertung %.1f★', count($bands), $rating); +}); + +$runner->run('Medien & Verfügbarkeiten geladen', function () { + $media = bandMedia(1); + $availability = bandAvailability(1); + $reviews = bandReviews(1); + if (!$media || !$availability || !$reviews) { + throw new RuntimeException('Band 1 hat unvollständige Daten.'); + } + return sprintf('Medien: %d, Slots: %d, Reviews: %d', count($media), count($availability), count($reviews)); +}); + +$runner->run('Anfrage speichern (Transaktion)', function (PDO $pdo) { + $before = (int) $pdo->query('SELECT COUNT(*) FROM requests')->fetchColumn(); + createRequest([ + 'band_id' => 1, + 'user_id' => 3, + 'event_date' => (new DateTimeImmutable('+60 days'))->format('Y-m-d'), + 'location' => 'Teststadt', + 'budget' => 4500, + 'event_type' => 'Testevent', + 'message' => 'Funktionstest Anfrage', + ]); + $after = (int) $pdo->query('SELECT COUNT(*) FROM requests')->fetchColumn(); + if ($after !== $before + 1) { + throw new RuntimeException('Anfrage wurde nicht gespeichert.'); + } + return 'Requests gesamt (temporär): ' . $after; +}, true); + +$runner->run('Bewertungen speichern & Eligibility', function (PDO $pdo) { + if (!eligibleForReview(1, 3)) { + throw new RuntimeException('User 3 sollte berechtigt sein.'); + } + $before = (int) $pdo->query('SELECT COUNT(*) FROM reviews')->fetchColumn(); + storeReview([ + 'band_id' => 1, + 'user_id' => 3, + 'rating' => 4, + 'comment' => 'Testkommentar', + ]); + $after = (int) $pdo->query('SELECT COUNT(*) FROM reviews')->fetchColumn(); + if ($after !== $before + 1) { + throw new RuntimeException('Review wurde nicht gespeichert.'); + } + return 'Reviews gesamt (temporär): ' . $after; +}, true); + +$runner->run('Einstellungen lesen & aktualisieren', function () { + $current = settings(); + $originalFee = $current['service_fee'] ?? '0'; + updateSetting('service_fee', '12'); + $updated = settings(); + if (($updated['service_fee'] ?? null) !== '12') { + throw new RuntimeException('Service Fee konnte nicht aktualisiert werden.'); + } + updateSetting('service_fee', $originalFee); + return 'Service Fee temporär auf 12 gesetzt.'; +}, true); + +$runner->run('Moderations-Aktionen', function (PDO $pdo) { + changeBandStatus(1, 'prüfung'); + $status = $pdo->query('SELECT status FROM bands WHERE id = 1')->fetchColumn(); + if ($status !== 'prüfung') { + throw new RuntimeException('Bandstatus änderte sich nicht.'); + } + changeReviewStatus(1, 'gesperrt'); + $reviewStatus = $pdo->query('SELECT status FROM reviews WHERE id = 1')->fetchColumn(); + if ($reviewStatus !== 'gesperrt') { + throw new RuntimeException('Reviewstatus änderte sich nicht.'); + } + return 'Statusänderungen durchgeführt.'; +}, true); + +$runner->run('Registrierung legt Band an', function (PDO $pdo) { + $email = 'tester+' . uniqid('', true) . '@example.com'; + $result = register([ + 'name' => 'Functional Tester', + 'email' => $email, + 'password' => 'secret123', + 'role' => 'band', + 'city' => 'Testingen', + 'band_name' => 'QA Ensemble', + 'genre' => 'QA Funk', + ]); + if (empty($result['token']) || strlen($result['token']) < 20) { + throw new RuntimeException('Verifikationstoken fehlt.'); + } + $user = $pdo->prepare('SELECT id, role FROM users WHERE email = :email'); + $user->execute([':email' => $email]); + $userRow = $user->fetch(PDO::FETCH_ASSOC); + if (!$userRow || $userRow['role'] !== 'band') { + throw new RuntimeException('User wurde nicht gespeichert.'); + } + $band = $pdo->prepare('SELECT status, contact_email FROM bands WHERE user_id = :id'); + $band->execute([':id' => $userRow['id']]); + $bandRow = $band->fetch(PDO::FETCH_ASSOC); + if (!$bandRow || $bandRow['status'] !== 'prüfung') { + throw new RuntimeException('Bandprofil wurde nicht angelegt.'); + } + if (($bandRow['contact_email'] ?? '') !== $email) { + throw new RuntimeException('Kontakt-E-Mail der Band fehlt.'); + } + return 'Token erstellt und Bandstatus "prüfung" bestätigt.'; +}, true); + +$runner->run('Startseite rendert fehlerfrei', function () { + $html = renderPage('index.php'); + if (strpos($html, 'Aktive Bands') === false) { + throw new RuntimeException('Indexseite liefert keinen Inhalt.'); + } + return 'HTML-Länge: ' . strlen($html) . ' Zeichen'; +}); + +$runner->run('Band-Detailseite rendert', function () { + $html = renderPage('band-detail.php', ['id' => 1]); + if (strpos($html, 'Verfügbarkeit') === false) { + throw new RuntimeException('Band-Detailseite unvollständig.'); + } + return 'HTML-Länge: ' . strlen($html) . ' Zeichen'; +}); + +$runner->run('Kontaktformular der Bandseite', function () { + $logFile = __DIR__ . '/storage/logs/mail.log'; + if (!is_dir(dirname($logFile))) { + mkdir(dirname($logFile), 0775, true); + } + $before = file_exists($logFile) ? filesize($logFile) : 0; + $html = renderPage('band-detail.php', ['id' => 1], [ + 'contact' => '1', + 'contact_name' => 'QA Bot', + 'contact_email' => 'qa@example.com', + 'contact_message' => 'Testnachricht an die Band.', + ]); + $success = strpos($html, 'Nachricht an die Band wurde verschickt.') !== false; + $fallback = strpos($html, 'E-Mail-Versand zur Band nicht möglich.') !== false; + if (!$success && !$fallback) { + throw new RuntimeException('Kontaktformular meldete keinen Versandstatus.'); + } + $after = filesize($logFile); + if ($after <= $before) { + throw new RuntimeException('Kein Mail-Logeintrag für Kontaktformular.'); + } + return 'Kontaktformular meldete Erfolg und schrieb ins Log.'; +}); + +$runner->run('Anfrageformular rendert', function () { + $html = renderPage('anfrage.php', ['band_id' => 1]); + if (strpos($html, 'Anfrage an') === false) { + throw new RuntimeException('Anfrageformular fehlgeschlagen.'); + } + return 'HTML-Länge: ' . strlen($html) . ' Zeichen'; +}); + +$runner->run('E-Mail Logging (kein Versand)', function () { + $logDir = __DIR__ . '/storage/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0775, true); + } + $logFile = $logDir . '/mail.log'; + $before = file_exists($logFile) ? filesize($logFile) : 0; + sendEmail('qa@example.com', 'Functional Test', 'Nur Logeintrag – kein Versand.'); + $after = filesize($logFile); + if ($after <= $before) { + throw new RuntimeException('Mail-Log wurde nicht aktualisiert.'); + } + return 'Logeintrag ergänzt, Versand erfolgt nur als Datei.'; +}); + +$runner->summary(); diff --git a/includes/auth.php b/includes/auth.php new file mode 100644 index 0000000..66f099d --- /dev/null +++ b/includes/auth.php @@ -0,0 +1,96 @@ +prepare('SELECT * FROM users WHERE id = :id'); + $stmt->execute([':id' => $_SESSION['user_id']]); + $user = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; + return $user; +} + +function login(string $email, string $password): bool +{ + $stmt = db()->prepare('SELECT * FROM users WHERE email = :email'); + $stmt->execute([':email' => $email]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$user || !password_verify($password, $user['password'])) { + return false; + } + if ((int) $user['verified'] !== 1) { + throw new RuntimeException('Bitte verifiziere zuerst deine E-Mail.'); + } + $_SESSION['user_id'] = $user['id']; + return true; +} + +function logout(): void +{ + session_destroy(); +} + +function register(array $data): array +{ + $token = bin2hex(random_bytes(16)); + $stmt = db()->prepare('INSERT INTO users (name, email, password, role, city, verified, verification_token, created_at) + VALUES (:name, :email, :password, :role, :city, 0, :token, :created)'); + $stmt->execute([ + ':name' => $data['name'], + ':email' => $data['email'], + ':password' => password_hash($data['password'], PASSWORD_DEFAULT), + ':role' => $data['role'], + ':city' => $data['city'] ?? null, + ':token' => $token, + ':created' => (new DateTimeImmutable())->format('c'), + ]); + $userId = (int) db()->lastInsertId(); + + if ($data['role'] === 'band') { + $band = db()->prepare('INSERT INTO bands (user_id, name, city, genre, price, description, status, contact_email) + VALUES (:user_id, :name, :city, :genre, :price, :description, :status, :contact_email)'); + $bandEmail = $data['band_email'] ?? $data['email']; + $band->execute([ + ':user_id' => $userId, + ':name' => $data['band_name'] ?? 'Neue Band', + ':city' => $data['city'] ?? '', + ':genre' => $data['genre'] ?? '', + ':price' => 0, + ':description' => 'Bitte Profil ergänzen.', + ':status' => 'prüfung', + ':contact_email' => $bandEmail, + ]); + } + + return ['token' => $token]; +} + +function requireLogin(): void +{ + if (!currentUser()) { + header('Location: login.php'); + exit; + } +} + +function requireAdmin(): void +{ + $user = currentUser(); + if (!$user || $user['role'] !== 'admin') { + http_response_code(403); + echo 'Keine Berechtigung'; + exit; + } +} diff --git a/includes/config.php b/includes/config.php new file mode 100644 index 0000000..266414c --- /dev/null +++ b/includes/config.php @@ -0,0 +1,10 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + initializeDatabase($pdo); + + return $pdo; +} + +function initializeDatabase(PDO $pdo): void +{ + $schema = file_get_contents(__DIR__ . '/../database.sql'); + $pdo->exec($schema); + + ensureBandContactEmailColumn($pdo); + + seedData($pdo); +} + +function ensureBandContactEmailColumn(PDO $pdo): void +{ + $columns = $pdo->query('PRAGMA table_info(bands)')->fetchAll(PDO::FETCH_ASSOC); + foreach ($columns as $column) { + if (($column['name'] ?? '') === 'contact_email') { + return; + } + } + $pdo->exec('ALTER TABLE bands ADD COLUMN contact_email TEXT'); +} + +function seedData(PDO $pdo): void +{ + $count = (int) $pdo->query('SELECT COUNT(*) FROM users')->fetchColumn(); + if ($count > 0) { + return; + } + + $now = (new DateTimeImmutable())->format('c'); + $password = password_hash('secret123', PASSWORD_DEFAULT); + + $stmt = $pdo->prepare('INSERT INTO users (name, email, password, role, verified, verification_token, created_at) + VALUES (:name, :email, :password, :role, :verified, :token, :created)'); + + $stmt->execute([ + ':name' => 'Admin', + ':email' => 'admin@getyourband.ch', + ':password' => $password, + ':role' => 'admin', + ':verified' => 1, + ':token' => null, + ':created' => $now, + ]); + + $stmt->execute([ + ':name' => 'Maya Keller', + ':email' => 'maya@getyourband.ch', + ':password' => $password, + ':role' => 'band', + ':verified' => 1, + ':token' => null, + ':created' => $now, + ]); + + $stmt->execute([ + ':name' => 'David Graf', + ':email' => 'david@example.com', + ':password' => $password, + ':role' => 'kunde', + ':verified' => 1, + ':token' => null, + ':created' => $now, + ]); + + $bands = [ + [ + 'user_id' => 2, + 'name' => 'Neon Groove Kollektiv', + 'city' => 'Zürich', + 'genre' => 'Funk / Soul', + 'price' => 4200, + 'description' => '7-köpfige Funk- und Soulband mit knalligem Brass-Sound und interaktiver Show.', + 'status' => 'aktiv', + 'style_tags' => 'Funk,Retro,Showband', + 'video_url' => 'https://www.youtube.com/embed/dQw4w9WgXcQ', + 'contact_email' => 'booking@neongroove.ch', + ], + [ + 'user_id' => null, + 'name' => 'Sonnenblitz Orchester', + 'city' => 'Bern', + 'genre' => 'Pop / Party', + 'price' => 3700, + 'description' => 'Party-Coverband mit LED-Lichtshow und zweistimmigem Gesang.', + 'status' => 'aktiv', + 'style_tags' => 'Pop,Party,LED', + 'video_url' => 'https://www.youtube.com/embed/5NV6Rdv1a3I', + 'contact_email' => 'hello@sonnenblitz.ch', + ], + ]; + + $bandStmt = $pdo->prepare('INSERT INTO bands (user_id, name, city, genre, price, description, status, style_tags, video_url, contact_email) + VALUES (:user_id, :name, :city, :genre, :price, :description, :status, :style_tags, :video_url, :contact_email)'); + + foreach ($bands as $band) { + $bandStmt->execute([ + ':user_id' => $band['user_id'], + ':name' => $band['name'], + ':city' => $band['city'], + ':genre' => $band['genre'], + ':price' => $band['price'], + ':description' => $band['description'], + ':status' => $band['status'], + ':style_tags' => $band['style_tags'], + ':video_url' => $band['video_url'], + ':contact_email' => $band['contact_email'], + ]); + $bandId = (int) $pdo->lastInsertId(); + + $mediaStmt = $pdo->prepare('INSERT INTO band_media (band_id, type, url) VALUES (:band_id, :type, :url)'); + $mediaStmt->execute([':band_id' => $bandId, ':type' => 'image', ':url' => 'https://images.unsplash.com/photo-1507878866276-a947ef722fee']); + $mediaStmt->execute([':band_id' => $bandId, ':type' => 'image', ':url' => 'https://images.unsplash.com/photo-1489515217757-5fd1be406fef']); + + $availStmt = $pdo->prepare('INSERT INTO band_availability (band_id, event_date, status) VALUES (:band_id, :event_date, :status)'); + for ($i = 0; $i < 4; $i++) { + $availStmt->execute([ + ':band_id' => $bandId, + ':event_date' => (new DateTimeImmutable('+' . ($i + 1) * 7 . ' days'))->format('Y-m-d'), + ':status' => $i % 2 === 0 ? 'frei' : 'option', + ]); + } + } + + $pdo->exec("INSERT INTO settings (key, value) VALUES ('paypal_enabled', '0'), ('service_fee', '8')"); + + $requestStmt = $pdo->prepare('INSERT INTO requests (band_id, user_id, event_date, location, budget, event_type, message, status, created_at) + VALUES (:band_id, :user_id, :event_date, :location, :budget, :event_type, :message, :status, :created)'); + + $requestStmt->execute([ + ':band_id' => 1, + ':user_id' => 3, + ':event_date' => (new DateTimeImmutable('+30 days'))->format('Y-m-d'), + ':location' => 'Basel', + ':budget' => 5000, + ':event_type' => 'Firmenfeier', + ':message' => 'Wir suchen einen funky Act für die Sommerparty.', + ':status' => 'bestätigt', + ':created' => $now, + ]); + + $pdo->exec("INSERT INTO reviews (band_id, user_id, rating, comment, status, created_at) VALUES (1, 3, 5, 'Mega Stimmung und super Show!', 'freigegeben', datetime('now'))"); +} diff --git a/includes/email.php b/includes/email.php new file mode 100644 index 0000000..ebb3ac5 --- /dev/null +++ b/includes/email.php @@ -0,0 +1,27 @@ + SUPPORT_EMAIL, + 'from_name' => SITE_NAME, + ]); + + $logDir = __DIR__ . '/../storage/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0775, true); + } + $entry = sprintf( + "%s\nTo: %s\nSubject: %s\nStatus: %s\n%s\n---\n", + date('c'), + $to, + $subject, + $sent ? 'SENT' : 'FAILED', + $message + ); + file_put_contents($logDir . '/mail.log', $entry, FILE_APPEND); + + return $sent; +} diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..f0299e1 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,159 @@ + 'aktiv']; + + if (!empty($filters['genre'])) { + $where[] = 'genre LIKE :genre'; + $params[':genre'] = '%' . $filters['genre'] . '%'; + } + + if (!empty($filters['city'])) { + $where[] = 'city LIKE :city'; + $params[':city'] = '%' . $filters['city'] . '%'; + } + + $sql = 'SELECT * FROM bands WHERE ' . implode(' AND ', $where) . ' ORDER BY name'; + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function findBand(int $id): ?array +{ + $stmt = db()->prepare('SELECT * FROM bands WHERE id = :id'); + $stmt->execute([':id' => $id]); + $band = $stmt->fetch(PDO::FETCH_ASSOC); + + return $band ?: null; +} + +function bandMedia(int $bandId): array +{ + $stmt = db()->prepare('SELECT * FROM band_media WHERE band_id = :id'); + $stmt->execute([':id' => $bandId]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function bandAvailability(int $bandId): array +{ + $stmt = db()->prepare('SELECT * FROM band_availability WHERE band_id = :id ORDER BY event_date'); + $stmt->execute([':id' => $bandId]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function bandReviews(int $bandId): array +{ + $stmt = db()->prepare('SELECT r.*, u.name AS author + FROM reviews r + JOIN users u ON u.id = r.user_id + WHERE r.band_id = :id AND r.status = "freigegeben" + ORDER BY r.created_at DESC'); + $stmt->execute([':id' => $bandId]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function averageRating(int $bandId): ?float +{ + $stmt = db()->prepare('SELECT AVG(rating) FROM reviews WHERE band_id = :id AND status = "freigegeben"'); + $stmt->execute([':id' => $bandId]); + $value = $stmt->fetchColumn(); + return $value ? round((float) $value, 1) : null; +} + +function formatPrice(int $amount): string +{ + return number_format($amount, 0, ',', '.') . ' CHF'; +} + +function createRequest(array $data): void +{ + $stmt = db()->prepare('INSERT INTO requests (band_id, user_id, event_date, location, budget, event_type, message, status, created_at) + VALUES (:band_id, :user_id, :event_date, :location, :budget, :event_type, :message, :status, :created_at)'); + $stmt->execute([ + ':band_id' => $data['band_id'], + ':user_id' => $data['user_id'], + ':event_date' => $data['event_date'], + ':location' => $data['location'], + ':budget' => $data['budget'], + ':event_type' => $data['event_type'], + ':message' => $data['message'], + ':status' => 'neu', + ':created_at' => (new DateTimeImmutable())->format('c'), + ]); +} + +function userRequests(int $userId): array +{ + $stmt = db()->prepare('SELECT * FROM requests WHERE user_id = :id ORDER BY created_at DESC'); + $stmt->execute([':id' => $userId]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function storeReview(array $data): void +{ + $stmt = db()->prepare('INSERT INTO reviews (band_id, user_id, rating, comment, status, created_at) + VALUES (:band_id, :user_id, :rating, :comment, :status, :created_at)'); + $stmt->execute([ + ':band_id' => $data['band_id'], + ':user_id' => $data['user_id'], + ':rating' => $data['rating'], + ':comment' => $data['comment'], + ':status' => 'wartend', + ':created_at' => (new DateTimeImmutable())->format('c'), + ]); +} + +function eligibleForReview(int $bandId, int $userId): bool +{ + $stmt = db()->prepare('SELECT COUNT(*) FROM requests WHERE band_id = :band AND user_id = :user AND status = "bestätigt"'); + $stmt->execute([':band' => $bandId, ':user' => $userId]); + return (int) $stmt->fetchColumn() > 0; +} + +function settings(): array +{ + $stmt = db()->query('SELECT key, value FROM settings'); + $data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); + return $data ?: ['paypal_enabled' => '0', 'service_fee' => '0']; +} + +function updateSetting(string $key, string $value): void +{ + $stmt = db()->prepare('INSERT INTO settings (key, value) VALUES (:key, :value) + ON CONFLICT(key) DO UPDATE SET value = excluded.value'); + $stmt->execute([':key' => $key, ':value' => $value]); +} + +function moderationItems(string $type): array +{ + $pdo = db(); + if ($type === 'bands') { + return $pdo->query('SELECT * FROM bands WHERE status != "aktiv"')->fetchAll(PDO::FETCH_ASSOC); + } + if ($type === 'reviews') { + return $pdo->query('SELECT r.*, b.name AS band_name, u.name AS author + FROM reviews r + JOIN bands b ON b.id = r.band_id + JOIN users u ON u.id = r.user_id + WHERE r.status = "wartend"')->fetchAll(PDO::FETCH_ASSOC); + } + return []; +} + +function changeBandStatus(int $bandId, string $status): void +{ + $stmt = db()->prepare('UPDATE bands SET status = :status WHERE id = :id'); + $stmt->execute([':status' => $status, ':id' => $bandId]); +} + +function changeReviewStatus(int $reviewId, string $status): void +{ + $stmt = db()->prepare('UPDATE reviews SET status = :status WHERE id = :id'); + $stmt->execute([':status' => $status, ':id' => $reviewId]); +} diff --git a/index.php b/index.php index 6b6d30d..a03f9d9 100644 --- a/index.php +++ b/index.php @@ -1,323 +1,111 @@ format('z') + 1; -$weekOfYear = (int) $now->format('W'); -$swatchBeats = (int) floor((($now->getTimestamp() % 86400) / 86.4)); -$moonPhase = (int) floor((($now->getTimestamp() / 2551443) - floor($now->getTimestamp() / 2551443)) * 100); - -$worldCities = [ - ['label' => 'Berlin', 'zone' => 'Europe/Berlin'], - ['label' => 'Tokyo', 'zone' => 'Asia/Tokyo'], - ['label' => 'San Francisco', 'zone' => 'America/Los_Angeles'], - ['label' => 'São Paulo', 'zone' => 'America/Sao_Paulo'], - ['label' => 'Kapstadt', 'zone' => 'Africa/Johannesburg'], +$filters = [ + 'genre' => $_GET['genre'] ?? '', + 'city' => $_GET['city'] ?? '', ]; - -$worldTimes = array_map( - static function (array $city): array { - $dt = new DateTimeImmutable('now', new DateTimeZone($city['zone'])); - return [ - 'label' => $city['label'], - 'time' => $dt->format('H:i'), - 'date' => $dt->format('d.m.Y'), - 'weekday' => $dt->format('l'), - 'offset' => $dt->format('P'), - ]; - }, - $worldCities -); - -$timeline = []; -for ($i = 1; $i <= 5; $i++) { - $future = $now->modify('+' . $i * 37 . ' minutes'); - $timeline[] = [ - 'label' => "+" . $i * 37 . " min", - 'time' => $future->format('H:i'), - 'micro' => $future->format('s.u'), - 'iso' => $future->format(DateTimeInterface::ATOM), - ]; -} - -$startMillis = (int) round(((float) $now->format('U.u')) * 1000); +$bands = allBands($filters); +$settings = settings(); +$user = currentUser(); ?> - Hypermodern Temporal Hub + <?= SITE_NAME ?> – Bands buchen + - - + -
-

Hypermodern Temporal Hub

-

Ein futuristisches Datums-Dashboard, das terrestrische, kosmische und spekulative Zeitsysteme in einem einzigen, vibrierenden Interface verschmilzt.

-
- -
-
-

JETZT

-
format('H:i:s'), ENT_QUOTES, 'UTF-8'); ?>
-
Tag · Woche · ISO format(DateTimeInterface::ATOM), ENT_QUOTES, 'UTF-8'); ?>
-
µformat('u'); ?>
-
-
- Swatch Beats - @ -
-
- Gravitationsphase - % -
-
- Unix-Epoche - getTimestamp(), 0, ',', '.'); ?> -
-
-
- -
-

WELTWELLEN

-
- -
-
- - · -
-
- - -
+
+ +
+
+

Schritt 3 · Frontend Release

+

Finde deine Funky Liveband

+

GetYourBand bringt verifizierte Live-Acts mit Veranstalter:innen in der ganzen Schweiz zusammen. Mit Bewertungen, + moderner Suche und aktivierbarer Vermittlungsgebühr.

+
+ Bewertungen geprüft + PayPal + Service Fee %
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Aktive Bands ()

+
+ +
+

+

+

Standort:

+

+

ab

+ +

Bewertung:

+ +
+ + # + +
+

+ Band ansehen +

+
+ +

Keine Bands gefunden – ändere deine Filter.

+ +
+
+
+
+ Kontakt
+ support@getyourband.ch +
+ -
-

TEMPORAL-LINSE

- -
-
- - - - + + diff --git a/login.php b/login.php new file mode 100644 index 0000000..18ea670 --- /dev/null +++ b/login.php @@ -0,0 +1,119 @@ +getMessage(); + } + } elseif (isset($_POST['register'])) { + if ($_POST['password'] !== $_POST['password_confirm']) { + $error = 'Passwörter stimmen nicht überein.'; + } else { + $result = register([ + 'name' => trim((string) $_POST['name']), + 'email' => trim((string) $_POST['email']), + 'password' => $_POST['password'], + 'role' => $_POST['role'], + 'city' => trim((string) $_POST['city']), + 'band_name' => $_POST['band_name'] ?? null, + 'genre' => $_POST['genre'] ?? null, + 'band_email' => $_POST['band_email'] ?? null, + ]); + $verificationLink = BASE_URL . '/verify-email.php?token=' . urlencode($result['token']); + sendEmail($_POST['email'], 'E-Mail bestätigen', 'Bitte bestätige dein Konto: ' . $verificationLink); + $message = 'Check deine Inbox – wir haben dir den Verifizierungslink geschickt: ' . htmlspecialchars($verificationLink); + } + } +} +?> + + + + + + Login / Registrierung – <?= SITE_NAME ?> + + + +
+ ← Zurück +

Login / Registrieren

+
+
+
+
+
+
+

Login

+
+ + + + +
+
+
+

Registrierung

+
+ + + + + + + + + + + +
+
+
+
+ + diff --git a/profil.php b/profil.php new file mode 100644 index 0000000..9ce5771 --- /dev/null +++ b/profil.php @@ -0,0 +1,94 @@ +prepare('SELECT * FROM bands WHERE user_id = :id'); + $stmt->execute([':id' => $user['id']]); + $band = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $stmt = db()->prepare('UPDATE bands SET name = :name, city = :city, genre = :genre, price = :price, description = :description, style_tags = :tags, contact_email = :contact_email WHERE id = :id'); + $stmt->execute([ + ':name' => $_POST['name'], + ':city' => $_POST['city'], + ':genre' => $_POST['genre'], + ':price' => (int) $_POST['price'], + ':description' => $_POST['description'], + ':tags' => $_POST['style_tags'], + ':contact_email' => $_POST['contact_email'] ?? '', + ':id' => $band['id'], + ]); + $message = 'Bandprofil aktualisiert (wartet ggf. auf Freigabe).'; + $band = findBand((int) $band['id']); + } +} +?> + + + + + Mein Bereich – <?= SITE_NAME ?> + + + +
+ ← Startseite +

Hallo

+

Rolle:

+
+
+
+ +

Bandprofil

+
+ + + + + + + + +
+ +

Du hast noch kein Bandprofil angelegt.

+ + + +

Meine Anfragen

+ + + + + + + + + + + +
BandDatumStatus
+ +
+ + diff --git a/storage/.gitkeep b/storage/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/verify-email.php b/verify-email.php new file mode 100644 index 0000000..085e896 --- /dev/null +++ b/verify-email.php @@ -0,0 +1,31 @@ +prepare('UPDATE users SET verified = 1, verification_token = NULL WHERE verification_token = :token'); + $stmt->execute([':token' => $token]); + if ($stmt->rowCount() > 0) { + $message = 'Perfekt! Dein Account ist nun verifiziert. Du kannst dich einloggen.'; + } +} +?> + + + + + Verifizierung – <?= SITE_NAME ?> + + + +
+
+

+ Zum Login +
+
+ +