From 0ef68cad81d36591b9f98c34857922970101734c Mon Sep 17 00:00:00 2001 From: Metacube Date: Mon, 3 Nov 2025 17:05:18 +0100 Subject: [PATCH] Adjust user list filter for adult chats --- chat1.php | 2704 ++++++++++++++++++++++------------------------------- 1 file changed, 1121 insertions(+), 1583 deletions(-) diff --git a/chat1.php b/chat1.php index dece074..6560b81 100644 --- a/chat1.php +++ b/chat1.php @@ -339,6 +339,20 @@ function logSecurityEvent($userId, $action, $details = '') { $stmt->execute(); } +function canUsersChatByAge($ageGroupA, $ageGroupB) { + if (!$ageGroupA || !$ageGroupB) { + return false; + } + + // Wenn einer minderjährig ist, müssen beide minderjährig sein + if ($ageGroupA === 'U18' || $ageGroupB === 'U18') { + return $ageGroupA === 'U18' && $ageGroupB === 'U18'; + } + + // Volljährige dürfen miteinander chatten + return true; +} + function isBlocked($userId, $otherUserId) { $db = getDB(); $stmt = $db->prepare(' @@ -375,6 +389,7 @@ function cleanupOldData() { // ═══════════════════════════════════════════════════════════ session_start(); +$isAdminPage = isset($_GET['admin']); function isLoggedIn() { return isset($_SESSION['user_id']) && isset($_SESSION['username']); @@ -584,48 +599,59 @@ if (isset($_POST['action']) || isset($_GET['action'])) { $currentAgeGroup = getCurrentAgeGroup(); $query = ' - SELECT + SELECT u.id, u.username, u.user_id as display_id, u.age_group, u.last_seen, - CASE - WHEN os.last_ping IS NOT NULL + CASE + WHEN os.last_ping IS NOT NULL AND (julianday("now") - julianday(os.last_ping)) * 86400 < ' . ONLINE_TIMEOUT_SECONDS . ' - THEN 1 - ELSE 0 + THEN 1 + ELSE 0 END as is_online, ( - SELECT COUNT(*) - FROM messages - WHERE from_user_id = u.id - AND to_user_id = :current_user_id + SELECT COUNT(*) + FROM messages + WHERE from_user_id = u.id + AND to_user_id = :current_user_id AND is_read = 0 ) as unread_count, ( - SELECT COUNT(*) - FROM blocks - WHERE blocker_id = :current_user_id + SELECT COUNT(*) + FROM blocks + WHERE blocker_id = :current_user_id AND blocked_id = u.id ) as is_blocked_by_me, ( - SELECT COUNT(*) - FROM blocks - WHERE blocker_id = u.id + SELECT COUNT(*) + FROM blocks + WHERE blocker_id = u.id AND blocked_id = :current_user_id ) as has_blocked_me FROM users u LEFT JOIN online_status os ON u.id = os.user_id WHERE u.id != :current_user_id AND u.is_banned = 0 - AND u.age_group = :age_group - ORDER BY is_online DESC, u.username ASC '; - + + if ($currentAgeGroup === 'U18') { + $query .= ' AND u.age_group = :allowed_group'; + } else { + $query .= ' AND u.age_group != :blocked_group'; + } + + $query .= ' ORDER BY is_online DESC, u.username ASC'; + $stmt = $db->prepare($query); $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); - $stmt->bindValue(':age_group', $currentAgeGroup, SQLITE3_TEXT); + + if ($currentAgeGroup === 'U18') { + $stmt->bindValue(':allowed_group', 'U18', SQLITE3_TEXT); + } else { + $stmt->bindValue(':blocked_group', 'U18', SQLITE3_TEXT); + } $result = $stmt->execute(); $users = []; @@ -655,23 +681,40 @@ if (isset($_POST['action']) || isset($_GET['action'])) { // ─────────────────────────────────────────────────────── if ($action === 'get_messages') { $otherUserId = intval($_GET['user_id'] ?? 0); - + if ($otherUserId <= 0) { echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); exit; } - + // Check if blocked if (isBlocked(getCurrentUserId(), $otherUserId)) { echo json_encode(['success' => false, 'error' => 'Chat nicht verfügbar']); exit; } - + $db = getDB(); $currentUserId = getCurrentUserId(); - + $currentAgeGroup = getCurrentAgeGroup(); + + $stmt = $db->prepare('SELECT age_group FROM users WHERE id = :user_id AND is_banned = 0'); + $stmt->bindValue(':user_id', $otherUserId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $otherUser = $result->fetchArray(SQLITE3_ASSOC); + + if (!$otherUser) { + echo json_encode(['success' => false, 'error' => 'Benutzer nicht gefunden']); + exit; + } + + if (!canUsersChatByAge($currentAgeGroup, $otherUser['age_group'])) { + logSecurityEvent($currentUserId, 'AGE_RESTRICTION_BLOCKED', "GET_MESSAGES -> User $otherUserId"); + echo json_encode(['success' => false, 'error' => 'Chat zwischen Altersgruppen nicht erlaubt']); + exit; + } + $query = ' - SELECT + SELECT m.id, m.from_user_id, m.to_user_id, @@ -745,7 +788,23 @@ if (isset($_POST['action']) || isset($_GET['action'])) { $db = getDB(); $currentUserId = getCurrentUserId(); $currentAgeGroup = getCurrentAgeGroup(); - + + $stmt = $db->prepare('SELECT age_group FROM users WHERE id = :user_id AND is_banned = 0'); + $stmt->bindValue(':user_id', $toUserId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $targetUser = $result->fetchArray(SQLITE3_ASSOC); + + if (!$targetUser) { + echo json_encode(['success' => false, 'error' => 'Empfänger nicht gefunden']); + exit; + } + + if (!canUsersChatByAge($currentAgeGroup, $targetUser['age_group'])) { + logSecurityEvent($currentUserId, 'AGE_RESTRICTION_BLOCKED', "SEND_MESSAGE -> User $toUserId"); + echo json_encode(['success' => false, 'error' => 'Nachrichten zwischen Altersgruppen nicht erlaubt']); + exit; + } + // Rate Limiting $rateLimitCheck = checkRateLimit($currentUserId, $currentAgeGroup); if (!$rateLimitCheck['allowed']) { @@ -758,9 +817,8 @@ if (isset($_POST['action']) || isset($_GET['action'])) { $keywordCheck = checkKeywordBlacklist($message); if ($keywordCheck['blocked']) { logSecurityEvent($currentUserId, 'KEYWORD_BLOCKED', "Keyword: {$keywordCheck['keyword']}"); - echo echo json_encode([ - 'success' => false, + 'success' => false, 'error' => 'Deine Nachricht enthält nicht erlaubte Inhalte', 'details' => 'Verbotenes Wort erkannt: ' . $keywordCheck['keyword'] ]); @@ -853,14 +911,31 @@ if (isset($_POST['action']) || isset($_GET['action'])) { echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); exit; } - + $db = getDB(); $currentUserId = getCurrentUserId(); - + $currentAgeGroup = getCurrentAgeGroup(); + + $stmt = $db->prepare('SELECT age_group FROM users WHERE id = :user_id AND is_banned = 0'); + $stmt->bindValue(':user_id', $otherUserId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $otherUser = $result->fetchArray(SQLITE3_ASSOC); + + if (!$otherUser) { + echo json_encode(['success' => false, 'error' => 'Benutzer nicht gefunden']); + exit; + } + + if (!canUsersChatByAge($currentAgeGroup, $otherUser['age_group'])) { + logSecurityEvent($currentUserId, 'AGE_RESTRICTION_BLOCKED', "MARK_READ -> User $otherUserId"); + echo json_encode(['success' => false, 'error' => 'Aktion zwischen Altersgruppen nicht erlaubt']); + exit; + } + $stmt = $db->prepare(' - UPDATE messages - SET is_read = 1 - WHERE from_user_id = :other_user_id + UPDATE messages + SET is_read = 1 + WHERE from_user_id = :other_user_id AND to_user_id = :current_user_id AND is_read = 0 '); @@ -1156,7 +1231,32 @@ if (isset($_POST['action']) || isset($_GET['action'])) { ]); exit; } - + + if ($action === 'admin_get_banned_users') { + $db = getDB(); + + $result = $db->query(' + SELECT id, username, user_id as display_id, ban_reason, last_seen + FROM users + WHERE is_banned = 1 + ORDER BY last_seen DESC + LIMIT 100 + '); + + $banned = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $banned[] = [ + 'id' => $row['id'], + 'display_name' => $row['username'] . '#' . $row['display_id'], + 'reason' => $row['ban_reason'] ?? 'keine Angabe', + 'last_seen' => $row['last_seen'] + ]; + } + + echo json_encode(['success' => true, 'banned' => $banned]); + exit; + } + // ─────────────────────────────────────────────────────── // ADMIN: BAN USER // ─────────────────────────────────────────────────────── @@ -1280,29 +1380,39 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { header('X-Accel-Buffering: no'); $currentUserId = getCurrentUserId(); + $currentAgeGroup = getCurrentAgeGroup(); $lastMessageId = intval($_GET['last_message_id'] ?? 0); set_time_limit(0); ob_implicit_flush(true); - ob_end_flush(); + while (ob_get_level() > 0) { + @ob_end_flush(); + } + + echo ": connected\n\n"; + echo "retry: " . SSE_RETRY_MS . "\n\n"; + flush(); $db = getDB(); $stmt = $db->prepare(' - SELECT + SELECT m.id, m.from_user_id, m.to_user_id, m.message, m.timestamp, - u.username as from_username, - u.user_id as from_display_id + uf.username as from_username, + uf.user_id as from_display_id, + uf.age_group as from_age_group, + ut.age_group as to_age_group FROM messages m - JOIN users u ON m.from_user_id = u.id + JOIN users uf ON m.from_user_id = uf.id + JOIN users ut ON m.to_user_id = ut.id WHERE m.id > :last_message_id AND (m.to_user_id = :current_user_id OR m.from_user_id = :current_user_id) AND NOT EXISTS ( - SELECT 1 FROM blocks + SELECT 1 FROM blocks WHERE (blocker_id = :current_user_id AND blocked_id = m.from_user_id) OR (blocker_id = m.from_user_id AND blocked_id = :current_user_id) ) @@ -1314,6 +1424,12 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { $messages = []; while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $otherAgeGroup = $row['from_user_id'] === $currentUserId ? $row['to_age_group'] : $row['from_age_group']; + + if (!canUsersChatByAge($currentAgeGroup, $otherAgeGroup)) { + continue; + } + $messages[] = [ 'id' => $row['id'], 'from_user_id' => $row['from_user_id'], @@ -1342,1347 +1458,6 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { ?> - - - - 🔒 Sicherer Private Chat - echo json_encode([ - 'success' => false, - 'error' => 'Diese Nachricht enthält nicht erlaubte Inhalte: "' . $keywordCheck['keyword'] . '"', - 'blocked_keyword' => true - ]); - exit; - } - - // Profanity Filter - $profanityCheck = checkProfanityFilter($message); - if ($profanityCheck['blocked']) { - logSecurityEvent($currentUserId, 'PROFANITY_BLOCKED', "Word: {$profanityCheck['word']}"); - echo json_encode([ - 'success' => false, - 'error' => 'Bitte verwende keine Schimpfwörter', - 'blocked_profanity' => true - ]); - exit; - } - - // Link Filter - $linkCheck = checkLinkFilter($message); - if ($linkCheck['blocked']) { - logSecurityEvent($currentUserId, 'LINK_BLOCKED', "Message: $message"); - echo json_encode([ - 'success' => false, - 'error' => 'Links sind nicht erlaubt', - 'blocked_link' => true - ]); - exit; - } - - // Insert message - $stmt = $db->prepare(' - INSERT INTO messages (from_user_id, to_user_id, message) - VALUES (:from_user_id, :to_user_id, :message) - '); - $stmt->bindValue(':from_user_id', $currentUserId, SQLITE3_INTEGER); - $stmt->bindValue(':to_user_id', $toUserId, SQLITE3_INTEGER); - $stmt->bindValue(':message', $message, SQLITE3_TEXT); - $stmt->execute(); - - $messageId = $db->lastInsertRowID(); - - // Log rate limit - logRateLimit($currentUserId); - - echo json_encode([ - 'success' => true, - 'message_id' => $messageId, - 'timestamp' => date('Y-m-d H:i:s') - ]); - exit; - } - - // ─────────────────────────────────────────────────────── - // MARK AS READ - // ─────────────────────────────────────────────────────── - if ($action === 'mark_read') { - $otherUserId = intval($_POST['user_id'] ?? 0); - - if ($otherUserId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - $db = getDB(); - $currentUserId = getCurrentUserId(); - - $stmt = $db->prepare(' - UPDATE messages - SET is_read = 1 - WHERE from_user_id = :other_user_id - AND to_user_id = :current_user_id - AND is_read = 0 - '); - $stmt->bindValue(':other_user_id', $otherUserId, SQLITE3_INTEGER); - $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); - $stmt->execute(); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // BLOCK USER - // ─────────────────────────────────────────────────────── - if ($action === 'block_user') { - $blockUserId = intval($_POST['user_id'] ?? 0); - - if ($blockUserId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - $db = getDB(); - $currentUserId = getCurrentUserId(); - - $stmt = $db->prepare(' - INSERT OR IGNORE INTO blocks (blocker_id, blocked_id) - VALUES (:blocker_id, :blocked_id) - '); - $stmt->bindValue(':blocker_id', $currentUserId, SQLITE3_INTEGER); - $stmt->bindValue(':blocked_id', $blockUserId, SQLITE3_INTEGER); - $stmt->execute(); - - logSecurityEvent($currentUserId, 'USER_BLOCKED', "Blocked user ID: $blockUserId"); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // UNBLOCK USER - // ─────────────────────────────────────────────────────── - if ($action === 'unblock_user') { - $unblockUserId = intval($_POST['user_id'] ?? 0); - - if ($unblockUserId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - $db = getDB(); - $currentUserId = getCurrentUserId(); - - $stmt = $db->prepare(' - DELETE FROM blocks - WHERE blocker_id = :blocker_id - AND blocked_id = :blocked_id - '); - $stmt->bindValue(':blocker_id', $currentUserId, SQLITE3_INTEGER); - $stmt->bindValue(':blocked_id', $unblockUserId, SQLITE3_INTEGER); - $stmt->execute(); - - logSecurityEvent($currentUserId, 'USER_UNBLOCKED', "Unblocked user ID: $unblockUserId"); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // REPORT USER - // ─────────────────────────────────────────────────────── - if ($action === 'report_user') { - $reportedUserId = intval($_POST['user_id'] ?? 0); - $reason = trim($_POST['reason'] ?? ''); - $messageId = intval($_POST['message_id'] ?? 0); - - if ($reportedUserId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - if (empty($reason)) { - echo json_encode(['success' => false, 'error' => 'Bitte gib einen Grund an']); - exit; - } - - $db = getDB(); - $currentUserId = getCurrentUserId(); - - $stmt = $db->prepare(' - INSERT INTO reports (reporter_id, reported_user_id, reason, message_id) - VALUES (:reporter_id, :reported_user_id, :reason, :message_id) - '); - $stmt->bindValue(':reporter_id', $currentUserId, SQLITE3_INTEGER); - $stmt->bindValue(':reported_user_id', $reportedUserId, SQLITE3_INTEGER); - $stmt->bindValue(':reason', $reason, SQLITE3_TEXT); - $stmt->bindValue(':message_id', $messageId > 0 ? $messageId : null, SQLITE3_INTEGER); - $stmt->execute(); - - logSecurityEvent($currentUserId, 'USER_REPORTED', "Reported user ID: $reportedUserId, Reason: $reason"); - - // Flag message if provided - if ($messageId > 0) { - $stmt = $db->prepare(' - UPDATE messages - SET is_flagged = 1, flag_reason = :reason - WHERE id = :message_id - '); - $stmt->bindValue(':reason', $reason, SQLITE3_TEXT); - $stmt->bindValue(':message_id', $messageId, SQLITE3_INTEGER); - $stmt->execute(); - } - - echo json_encode(['success' => true, 'message' => 'Meldung wurde erfasst. Danke!']); - exit; - } - - // ─────────────────────────────────────────────────────── - // GET BLOCKED USERS - // ─────────────────────────────────────────────────────── - if ($action === 'get_blocked_users') { - $db = getDB(); - $currentUserId = getCurrentUserId(); - - $query = ' - SELECT - u.id, - u.username, - u.user_id as display_id, - b.timestamp as blocked_at - FROM blocks b - JOIN users u ON b.blocked_id = u.id - WHERE b.blocker_id = :current_user_id - ORDER BY b.timestamp DESC - '; - - $stmt = $db->prepare($query); - $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); - $result = $stmt->execute(); - - $blocked = []; - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $blocked[] = [ - 'id' => $row['id'], - 'username' => $row['username'], - 'display_id' => $row['display_id'], - 'display_name' => $row['username'] . '#' . $row['display_id'], - 'blocked_at' => $row['blocked_at'] - ]; - } - - echo json_encode(['success' => true, 'blocked' => $blocked]); - exit; - } - - // ═══════════════════════════════════════════════════════════ - // ADMIN ACTIONS - // ═══════════════════════════════════════════════════════════ - - if (!isAdmin()) { - echo json_encode(['success' => false, 'error' => 'Admin-Rechte erforderlich']); - exit; - } - - // ─────────────────────────────────────────────────────── - // GET ADMIN STATS - // ─────────────────────────────────────────────────────── - if ($action === 'admin_stats') { - $db = getDB(); - - // Total users - $result = $db->query('SELECT COUNT(*) as count FROM users WHERE is_banned = 0'); - $totalUsers = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // U18 users - $result = $db->query('SELECT COUNT(*) as count FROM users WHERE age_group = "U18" AND is_banned = 0'); - $u18Users = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // O18 users - $result = $db->query('SELECT COUNT(*) as count FROM users WHERE age_group = "O18" AND is_banned = 0'); - $o18Users = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // Total messages today - $result = $db->query('SELECT COUNT(*) as count FROM messages WHERE DATE(timestamp) = DATE("now")'); - $messagesToday = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // Pending reports - $result = $db->query('SELECT COUNT(*) as count FROM reports WHERE status = "pending"'); - $pendingReports = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // Flagged messages - $result = $db->query('SELECT COUNT(*) as count FROM messages WHERE is_flagged = 1'); - $flaggedMessages = $result->fetchArray(SQLITE3_ASSOC)['count']; - - // Banned users - $result = $db->query('SELECT COUNT(*) as count FROM users WHERE is_banned = 1'); - $bannedUsers = $result->fetchArray(SQLITE3_ASSOC)['count']; - - echo json_encode([ - 'success' => true, - 'stats' => [ - 'total_users' => $totalUsers, - 'u18_users' => $u18Users, - 'o18_users' => $o18Users, - 'messages_today' => $messagesToday, - 'pending_reports' => $pendingReports, - 'flagged_messages' => $flaggedMessages, - 'banned_users' => $bannedUsers - ] - ]); - exit; - } - - // ─────────────────────────────────────────────────────── - // GET REPORTS - // ─────────────────────────────────────────────────────── - if ($action === 'admin_get_reports') { - $db = getDB(); - - $query = ' - SELECT - r.id, - r.reason, - r.timestamp, - r.status, - r.message_id, - reporter.username as reporter_name, - reporter.user_id as reporter_display_id, - reported.username as reported_name, - reported.user_id as reported_display_id, - reported.id as reported_user_id, - m.message as message_content - FROM reports r - JOIN users reporter ON r.reporter_id = reporter.id - JOIN users reported ON r.reported_user_id = reported.id - LEFT JOIN messages m ON r.message_id = m.id - ORDER BY r.timestamp DESC - LIMIT 50 - '; - - $result = $db->query($query); - - $reports = []; - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $reports[] = [ - 'id' => $row['id'], - 'reason' => $row['reason'], - 'timestamp' => $row['timestamp'], - 'status' => $row['status'], - 'reporter_name' => $row['reporter_name'] . '#' . $row['reporter_display_id'], - 'reported_name' => $row['reported_name'] . '#' . $row['reported_display_id'], - 'reported_user_id' => $row['reported_user_id'], - 'message_content' => $row['message_content'] - ]; - } - - echo json_encode(['success' => true, 'reports' => $reports]); - exit; - } - - // ─────────────────────────────────────────────────────── - // BAN USER - // ─────────────────────────────────────────────────────── - if ($action === 'admin_ban_user') { - $userId = intval($_POST['user_id'] ?? 0); - $reason = trim($_POST['reason'] ?? 'Verstoß gegen Nutzungsbedingungen'); - - if ($userId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - $db = getDB(); - - $stmt = $db->prepare(' - UPDATE users - SET is_banned = 1, ban_reason = :reason - WHERE id = :user_id - '); - $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); - $stmt->bindValue(':reason', $reason, SQLITE3_TEXT); - $stmt->execute(); - - logSecurityEvent(null, 'ADMIN_BAN_USER', "User ID: $userId, Reason: $reason"); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // UNBAN USER - // ─────────────────────────────────────────────────────── - if ($action === 'admin_unban_user') { - $userId = intval($_POST['user_id'] ?? 0); - - if ($userId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); - exit; - } - - $db = getDB(); - - $stmt = $db->prepare(' - UPDATE users - SET is_banned = 0, ban_reason = NULL - WHERE id = :user_id - '); - $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); - $stmt->execute(); - - logSecurityEvent(null, 'ADMIN_UNBAN_USER', "User ID: $userId"); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // RESOLVE REPORT - // ─────────────────────────────────────────────────────── - if ($action === 'admin_resolve_report') { - $reportId = intval($_POST['report_id'] ?? 0); - $status = $_POST['status'] ?? 'resolved'; - - if ($reportId <= 0) { - echo json_encode(['success' => false, 'error' => 'Ungültige Report-ID']); - exit; - } - - $db = getDB(); - - $stmt = $db->prepare('UPDATE reports SET status = :status WHERE id = :report_id'); - $stmt->bindValue(':status', $status, SQLITE3_TEXT); - $stmt->bindValue(':report_id', $reportId, SQLITE3_INTEGER); - $stmt->execute(); - - logSecurityEvent(null, 'ADMIN_RESOLVE_REPORT', "Report ID: $reportId, Status: $status"); - - echo json_encode(['success' => true]); - exit; - } - - // ─────────────────────────────────────────────────────── - // GET SECURITY LOGS - // ─────────────────────────────────────────────────────── - if ($action === 'admin_get_logs') { - $db = getDB(); - - $query = ' - SELECT - l.id, - l.action, - l.details, - l.ip_address, - l.timestamp, - u.username, - u.user_id as display_id - FROM security_logs l - LEFT JOIN users u ON l.user_id = u.id - ORDER BY l.timestamp DESC - LIMIT 100 - '; - - $result = $db->query($query); - - $logs = []; - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $logs[] = [ - 'id' => $row['id'], - 'action' => $row['action'], - 'details' => $row['details'], - 'ip_address' => $row['ip_address'], - 'timestamp' => $row['timestamp'], - 'username' => $row['username'] ? $row['username'] . '#' . $row['display_id'] : 'System' - ]; - } - - echo json_encode(['success' => true, 'logs' => $logs]); - exit; - } - - echo json_encode(['success' => false, 'error' => 'Unbekannte Aktion']); - exit; -} - -// ═══════════════════════════════════════════════════════════ -// SSE STREAM (ECHTZEIT) -// ═══════════════════════════════════════════════════════════ - -if (isset($_GET['stream']) && $_GET['stream'] === 'events') { - if (!isLoggedIn()) { - exit; - } - - header('Content-Type: text/event-stream'); - header('Cache-Control: no-cache'); - header('Connection: keep-alive'); - header('X-Accel-Buffering: no'); - - $currentUserId = getCurrentUserId(); - $lastMessageId = intval($_GET['last_message_id'] ?? 0); - - set_time_limit(0); - ob_implicit_flush(true); - ob_end_flush(); - - $db = getDB(); - - $stmt = $db->prepare(' - SELECT - m.id, - m.from_user_id, - m.to_user_id, - m.message, - m.timestamp, - u.username as from_username, - u.user_id as from_display_id - FROM messages m - JOIN users u ON m.from_user_id = u.id - WHERE m.id > :last_message_id - AND (m.to_user_id = :current_user_id OR m.from_user_id = :current_user_id) - ORDER BY m.id ASC - '); - $stmt->bindValue(':last_message_id', $lastMessageId, SQLITE3_INTEGER); - $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); - $result = $stmt->execute(); - - $messages = []; - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $messages[] = [ - 'id' => $row['id'], - 'from_user_id' => $row['from_user_id'], - 'to_user_id' => $row['to_user_id'], - 'message' => $row['message'], - 'timestamp' => $row['timestamp'], - 'from_username' => $row['from_username'], - 'from_display_id' => $row['from_display_id'], - 'from_display_name' => $row['from_username'] . '#' . $row['from_display_id'] - ]; - } - - if (!empty($messages)) { - echo "data: " . json_encode(['type' => 'messages', 'messages' => $messages]) . "\n\n"; - flush(); - } else { - echo "data: " . json_encode(['type' => 'ping']) . "\n\n"; - flush(); - } - - exit; -} - -// ═══════════════════════════════════════════════════════════ -// HTML OUTPUT -// ═══════════════════════════════════════════════════════════ -?> - - - - - - 💬 Secure Private Chat - - - + +
+
+

🔐 Admin-Dashboard

+ +
+ +
+ +
+ +
+
+
+

🚨 Offene Meldungen

+
+
+
Lade Meldungen…
+
+
+ +
+

🚩 Markierte Nachrichten

+
+
Lade Nachrichten…
+
+
+ +
+

🚫 Gesperrte Nutzer

+
+
Lade Nutzer…
+
+
+
+
+ + +
+

🔐 Admin-Login

+

Zugriff nur für autorisierte Moderatoren.

+ +
+ +
+
+ + +
+ +
+ + +
+ + +
+ + +
+ + -
+

💬 Secure Private Chat

Sicherer Chat mit Altersverifikation

@@ -3294,25 +2469,335 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { // JAVASCRIPT // ═══════════════════════════════════════════════════════════ - + +// ADMIN DASHBOARD +const adminStatsGrid = document.getElementById('adminStatsGrid'); +const adminReportsContainer = document.getElementById('adminReportsContainer'); +const adminFlaggedContainer = document.getElementById('adminFlaggedContainer'); +const adminBannedContainer = document.getElementById('adminBannedContainer'); + +function adminEscapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text ?? ''; + return div.innerHTML; +} + +function adminFormatDate(value) { + if (!value) return '-'; + try { + return new Date(value).toLocaleString('de-DE'); + } catch (e) { + return value; + } +} + +async function adminFetch(action, payload = {}) { + const formData = new FormData(); + formData.append('action', action); + Object.entries(payload).forEach(([key, val]) => formData.append(key, val)); + + const response = await fetch('', { method: 'POST', body: formData }); + return response.json(); +} + +function renderAdminStats(stats) { + if (!stats) { + adminStatsGrid.innerHTML = '
Keine Statistiken verfügbar.
'; + return; + } + + const statItems = [ + ['Registrierte Nutzer', stats.total_users], + ['U18 Nutzer', stats.u18_users], + ['Ü18 Nutzer', stats.o18_users], + ['Aktiv online', stats.online_users], + ['Nachrichten gesamt', stats.total_messages], + ['Markierte Nachrichten', stats.flagged_messages], + ['Offene Meldungen', stats.pending_reports], + ['Gesperrte Nutzer', stats.banned_users] + ]; + + adminStatsGrid.innerHTML = statItems.map(([label, value]) => ` +
+ ${adminEscapeHtml(label)} + ${Number(value) || 0} +
+ `).join(''); +} + +function renderReports(reports) { + if (!reports || reports.length === 0) { + adminReportsContainer.innerHTML = '
Aktuell liegen keine offenen Meldungen vor.
'; + return; + } + + const rows = reports.map(report => ` + + ${adminEscapeHtml(report.reporter)} + ${adminEscapeHtml(report.reported)} + + ${adminEscapeHtml(report.reason)} + ${report.message ? `
${adminEscapeHtml(report.message)}
` : ''} + + ${adminEscapeHtml(adminFormatDate(report.timestamp))} + +
+ + +
+ + + `).join(''); + + adminReportsContainer.innerHTML = ` + + + + + + + + + + + ${rows} +
MelderGemeldeterGrund & NachrichtZeitpunktAktionen
+ `; + + adminReportsContainer.querySelectorAll('button[data-action="ban"]').forEach(btn => { + btn.addEventListener('click', () => adminBanUser(btn.dataset.user)); + }); + + adminReportsContainer.querySelectorAll('button[data-action="resolve"]').forEach(btn => { + btn.addEventListener('click', () => adminResolveReport(btn.dataset.report)); + }); +} + +function renderFlagged(flagged) { + if (!flagged || flagged.length === 0) { + adminFlaggedContainer.innerHTML = '
Keine markierten Nachrichten vorhanden.
'; + return; + } + + const rows = flagged.map(item => ` + + ${adminEscapeHtml(item.user)} + ${adminEscapeHtml(item.message)} + ${adminEscapeHtml(item.reason)} + ${adminEscapeHtml(adminFormatDate(item.timestamp))} + +
+ + +
+ + + `).join(''); + + adminFlaggedContainer.innerHTML = ` + + + + + + + + + + + ${rows} +
NutzerNachrichtGrundZeitpunktAktionen
+ `; + + adminFlaggedContainer.querySelectorAll('button[data-action="ban"]').forEach(btn => { + btn.addEventListener('click', () => adminBanUser(btn.dataset.user)); + }); + + adminFlaggedContainer.querySelectorAll('button[data-action="delete"]').forEach(btn => { + btn.addEventListener('click', () => adminDeleteMessage(btn.dataset.message)); + }); +} + +function renderBanned(banned) { + if (!banned || banned.length === 0) { + adminBannedContainer.innerHTML = '
Keine Nutzer gesperrt.
'; + return; + } + + const rows = banned.map(user => ` + + ${adminEscapeHtml(user.display_name)} + ${adminEscapeHtml(user.reason)} + ${adminEscapeHtml(adminFormatDate(user.last_seen))} + +
+ +
+ + + `).join(''); + + adminBannedContainer.innerHTML = ` + + + + + + + + + + ${rows} +
NutzerGrundZuletzt aktivAktionen
+ `; + + adminBannedContainer.querySelectorAll('button[data-action="unban"]').forEach(btn => { + btn.addEventListener('click', () => adminUnbanUser(btn.dataset.user)); + }); +} + +async function loadAdminStats() { + const response = await fetch('?action=admin_get_stats'); + const result = await response.json(); + if (result.success) { + renderAdminStats(result.stats); + } +} + +async function loadAdminReports() { + const response = await fetch('?action=admin_get_reports'); + const result = await response.json(); + if (result.success) { + renderReports(result.reports); + } +} + +async function loadAdminFlagged() { + const response = await fetch('?action=admin_get_flagged'); + const result = await response.json(); + if (result.success) { + renderFlagged(result.flagged); + } +} + +async function loadAdminBanned() { + const result = await fetch('?action=admin_get_banned_users'); + const data = await result.json(); + if (data.success) { + renderBanned(data.banned); + } +} + +async function adminBanUser(userId) { + const reason = prompt('Grund für die Sperre eingeben:', 'Verstoß gegen Nutzungsbedingungen'); + if (reason === null) return; + + const result = await adminFetch('admin_ban_user', { user_id: userId, reason }); + if (!result.success) { + alert(result.error || 'Aktion fehlgeschlagen'); + return; + } + await refreshAdminData(); +} + +async function adminResolveReport(reportId) { + const actionTaken = prompt('Status für Report festlegen (z.B. resolved, dismissed):', 'resolved'); + if (actionTaken === null) return; + + const result = await adminFetch('admin_resolve_report', { report_id: reportId, action_taken: actionTaken }); + if (!result.success) { + alert(result.error || 'Aktion fehlgeschlagen'); + return; + } + await refreshAdminData(); +} + +async function adminDeleteMessage(messageId) { + if (!confirm('Markierte Nachricht wirklich löschen?')) return; + + const result = await adminFetch('admin_delete_message', { message_id: messageId }); + if (!result.success) { + alert(result.error || 'Aktion fehlgeschlagen'); + return; + } + await refreshAdminData(); +} + +async function adminUnbanUser(userId) { + const result = await adminFetch('admin_unban_user', { user_id: userId }); + if (!result.success) { + alert(result.error || 'Aktion fehlgeschlagen'); + return; + } + await refreshAdminData(); +} + +async function refreshAdminData() { + await Promise.all([ + loadAdminStats(), + loadAdminReports(), + loadAdminFlagged(), + loadAdminBanned() + ]); +} + +document.getElementById('adminLogoutBtn').addEventListener('click', async () => { + await adminFetch('logout'); + window.location.href = '?'; +}); + +refreshAdminData(); +setInterval(refreshAdminData, 30000); + + +// ADMIN LOGIN +const adminLoginForm = document.getElementById('adminLoginForm'); +const adminError = document.getElementById('adminError'); + +adminLoginForm.addEventListener('submit', async (e) => { + e.preventDefault(); + adminError.style.display = 'none'; + + const formData = new FormData(); + formData.append('action', 'admin_login'); + formData.append('username', document.getElementById('adminUsername').value.trim()); + formData.append('password', document.getElementById('adminPassword').value); + + try { + const response = await fetch('', { method: 'POST', body: formData }); + const result = await response.json(); + + if (result.success) { + window.location.href = '?admin=1'; + } else { + adminError.textContent = result.error || 'Anmeldung fehlgeschlagen'; + adminError.style.display = 'block'; + } + } catch (error) { + adminError.textContent = 'Server nicht erreichbar'; + adminError.style.display = 'block'; + } +}); + + // REGISTRATION document.getElementById('registerForm').addEventListener('submit', async (e) => { e.preventDefault(); - + const username = document.getElementById('username').value.trim(); const birthdate = document.getElementById('birthdate').value; const agreedTerms = document.getElementById('agreeTerms').checked; - + const formData = new FormData(); formData.append('action', 'register'); formData.append('username', username); formData.append('birthdate', birthdate); formData.append('agreed_terms', agreedTerms); - + try { const response = await fetch('', { method: 'POST', body: formData }); const result = await response.json(); - + if (result.success) { window.location.reload(); } else { @@ -3337,79 +2822,140 @@ const state = { eventSource: null }; -// Load Users async function loadUsers() { const response = await fetch('?action=get_users'); const result = await response.json(); - + if (result.success) { state.users = result.users; renderUserList(); } } -// Render User List function renderUserList() { const userList = document.getElementById('userList'); - const searchTerm = document.getElementById('userSearch').value.toLowerCase(); - - const filtered = state.users.filter(u => u.display_name.toLowerCase().includes(searchTerm)); - - userList.innerHTML = filtered.map(user => ` -
-
- ${user.username.charAt(0).toUpperCase()} -
-
- - ${user.unread_count > 0 ? `
${user.unread_count}
` : ''} -
- `).join(''); + if (!userList) { + return; + } + + const searchInput = document.getElementById('userSearch'); + const searchTerm = (searchInput?.value || '').toLowerCase(); + + const filtered = state.users.filter(u => (u.display_name || '').toLowerCase().includes(searchTerm)); + + if (filtered.length === 0) { + userList.innerHTML = '
Keine Nutzer gefunden.
'; + return; + } + + userList.innerHTML = filtered.map(user => { + const displayNameRaw = (user.display_name || user.username || '').trim(); + const displayName = displayNameRaw.length > 0 ? displayNameRaw : 'Unbekannt'; + const initial = displayName.charAt(0).toUpperCase() || '?'; + + return ` + + `; + }).join(''); + + userList.querySelectorAll('.user-item').forEach(item => { + item.addEventListener('click', () => { + const userId = Number(item.dataset.userId); + const user = state.users.find(u => u.id === userId); + if (!user) { + return; + } + selectUser(user.id, user.display_name || user.username); + }); + }); } -// Select User function selectUser(userId, displayName) { state.selectedUserId = userId; - - document.getElementById('chatWelcome').style.display = 'none'; - document.getElementById('chatMessagesContainer').style.display = 'flex'; - - document.getElementById('chatMessagesHeader').innerHTML = ` -
${displayName.charAt(0).toUpperCase()}
-
${displayName}
- `; - + + const welcome = document.getElementById('chatWelcome'); + if (welcome) { + welcome.style.display = 'none'; + } + + const container = document.getElementById('chatMessagesContainer'); + if (container) { + container.style.display = 'flex'; + } + + const safeDisplayName = (displayName || '').trim() || 'Unbekannt'; + const initial = safeDisplayName.charAt(0).toUpperCase() || '?'; + + const header = document.getElementById('chatMessagesHeader'); + if (header) { + header.innerHTML = ` +
${escapeHtml(initial)}
+
${escapeHtml(safeDisplayName)}
+ `; + } + + const messageList = document.getElementById('chatMessages'); + if (messageList) { + messageList.innerHTML = '
Nachrichten werden geladen…
'; + } + loadMessages(userId); renderUserList(); } -// Load Messages async function loadMessages(userId) { - const response = await fetch(`?action=get_messages&user_id=${userId}`); - const result = await response.json(); - - if (result.success) { - state.messages = result.messages; - renderMessages(); - markAsRead(userId); - - if (result.messages.length > 0) { - state.lastMessageId = Math.max(...result.messages.map(m => m.id)); + const messageList = document.getElementById('chatMessages'); + + try { + const response = await fetch(`?action=get_messages&user_id=${userId}`); + const result = await response.json(); + + if (result.success) { + state.messages = result.messages; + renderMessages(); + markAsRead(userId); + + if (result.messages.length > 0) { + state.lastMessageId = Math.max(...result.messages.map(m => m.id)); + } + } else if (messageList) { + state.messages = []; + messageList.innerHTML = `
${escapeHtml(result.error || 'Nachrichten konnten nicht geladen werden.')}
`; + } + } catch (error) { + if (messageList) { + state.messages = []; + messageList.innerHTML = '
Verbindung fehlgeschlagen. Bitte versuche es erneut.
'; } } } -// Render Messages function renderMessages() { const container = document.getElementById('chatMessages'); - + + if (!container) { + return; + } + + if (!state.messages || state.messages.length === 0) { + container.innerHTML = '
Noch keine Nachrichten. Starte den Chat!
'; + return; + } + container.innerHTML = state.messages.map(msg => { const isSent = msg.from_user_id === state.currentUserId; const time = new Date(msg.timestamp).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); - + return `
${escapeHtml(msg.message)}
@@ -3417,25 +2963,24 @@ function renderMessages() {
`; }).join(''); - + container.scrollTop = container.scrollHeight; } -// Send Message async function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); - + if (!message || !state.selectedUserId) return; - + const formData = new FormData(); formData.append('action', 'send_message'); formData.append('to_user_id', state.selectedUserId); formData.append('message', message); - + const response = await fetch('', { method: 'POST', body: formData }); const result = await response.json(); - + if (result.success) { input.value = ''; } else { @@ -3443,36 +2988,34 @@ async function sendMessage() { } } -// Mark as Read async function markAsRead(userId) { const formData = new FormData(); formData.append('action', 'mark_read'); formData.append('user_id', userId); - + await fetch('', { method: 'POST', body: formData }); loadUsers(); } -// SSE function startSSE() { state.eventSource = new EventSource(`?stream=events&last_message_id=${state.lastMessageId}`); - + state.eventSource.onmessage = (event) => { const data = JSON.parse(event.data); - + if (data.type === 'messages' && data.messages) { data.messages.forEach(msg => { if (msg.id > state.lastMessageId) { state.lastMessageId = msg.id; - - if (state.selectedUserId && + + if (state.selectedUserId && ((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) || (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) { - + if (!state.messages.find(m => m.id === msg.id)) { state.messages.push(msg); renderMessages(); - + if (msg.to_user_id === state.currentUserId) { markAsRead(msg.from_user_id); } @@ -3480,20 +3023,18 @@ function startSSE() { } } }); - + loadUsers(); } }; } -// Utility function escapeHtml(text) { const div = document.createElement('div'); - div.textContent = text; + div.textContent = text ?? ''; return div.innerHTML; } -// Event Listeners document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -3509,20 +3050,17 @@ document.getElementById('logoutBtn').addEventListener('click', async () => { window.location.reload(); }); -// Auto-resize textarea document.getElementById('chatInput').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; }); -// Ping every 10s setInterval(async () => { const formData = new FormData(); formData.append('action', 'ping'); await fetch('', { method: 'POST', body: formData }); }, 10000); -// Init loadUsers(); startSSE(); setInterval(loadUsers, 30000);