From 0c7509ce3318e7a388075a60158e059095829eeb Mon Sep 17 00:00:00 2001 From: Metacube Date: Sun, 2 Nov 2025 17:53:33 +0100 Subject: [PATCH] Create chat1.php --- chat1.php | 3533 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3533 insertions(+) create mode 100644 chat1.php diff --git a/chat1.php b/chat1.php new file mode 100644 index 0000000..dece074 --- /dev/null +++ b/chat1.php @@ -0,0 +1,3533 @@ +busyTimeout(5000); + + // Users Table (mit Geburtsdatum und User-ID) + $db->exec(' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL, + user_id TEXT UNIQUE NOT NULL, + birthdate DATE NOT NULL, + age_group TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_seen DATETIME DEFAULT CURRENT_TIMESTAMP, + is_banned INTEGER DEFAULT 0, + ban_reason TEXT + ) + '); + + // Messages Table + $db->exec(' + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_user_id INTEGER NOT NULL, + to_user_id INTEGER NOT NULL, + message TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + is_read INTEGER DEFAULT 0, + is_flagged INTEGER DEFAULT 0, + flag_reason TEXT, + FOREIGN KEY (from_user_id) REFERENCES users(id), + FOREIGN KEY (to_user_id) REFERENCES users(id) + ) + '); + + // Online Status Table + $db->exec(' + CREATE TABLE IF NOT EXISTS online_status ( + user_id INTEGER PRIMARY KEY, + last_ping DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + '); + + // Reports Table + $db->exec(' + CREATE TABLE IF NOT EXISTS reports ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + reporter_id INTEGER NOT NULL, + reported_user_id INTEGER NOT NULL, + reason TEXT NOT NULL, + message_id INTEGER, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT DEFAULT "pending", + FOREIGN KEY (reporter_id) REFERENCES users(id), + FOREIGN KEY (reported_user_id) REFERENCES users(id), + FOREIGN KEY (message_id) REFERENCES messages(id) + ) + '); + + // Blocks Table + $db->exec(' + CREATE TABLE IF NOT EXISTS blocks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + blocker_id INTEGER NOT NULL, + blocked_id INTEGER NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (blocker_id) REFERENCES users(id), + FOREIGN KEY (blocked_id) REFERENCES users(id), + UNIQUE(blocker_id, blocked_id) + ) + '); + + // Rate Limiting Table + $db->exec(' + CREATE TABLE IF NOT EXISTS rate_limits ( + user_id INTEGER NOT NULL, + action_type TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + '); + + // Logs Table (für Behörden) + $db->exec(' + CREATE TABLE IF NOT EXISTS security_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + action TEXT NOT NULL, + details TEXT, + ip_address TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + '); + + // Admin Table + $db->exec(' + CREATE TABLE IF NOT EXISTS admins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + '); + + // Create default admin if not exists + $stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username'); + $stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] == 0) { + $stmt = $db->prepare('INSERT INTO admins (username, password_hash) VALUES (:username, :password)'); + $stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT); + $stmt->bindValue(':password', ADMIN_PASSWORD, SQLITE3_TEXT); + $stmt->execute(); + } + + // Create indexes + $db->exec('CREATE INDEX IF NOT EXISTS idx_messages_users ON messages(from_user_id, to_user_id)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_users_age_group ON users(age_group)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_blocks ON blocks(blocker_id, blocked_id)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_reports_status ON reports(status)'); + + return $db; +} + +// ═══════════════════════════════════════════════════════════ +// SECURITY FUNCTIONS +// ═══════════════════════════════════════════════════════════ + +function generateUserId() { + return str_pad(mt_rand(1000, 9999), 4, '0', STR_PAD_LEFT); +} + +function calculateAge($birthdate) { + $birth = new DateTime($birthdate); + $today = new DateTime(); + return $birth->diff($today)->y; +} + +function getAgeGroup($birthdate) { + $age = calculateAge($birthdate); + return $age < 18 ? 'U18' : 'O18'; +} + +function checkKeywordBlacklist($message) { + global $KEYWORD_BLACKLIST; + + $message_lower = mb_strtolower($message, 'UTF-8'); + + foreach ($KEYWORD_BLACKLIST as $keyword) { + if (stripos($message_lower, $keyword) !== false) { + return ['blocked' => true, 'keyword' => $keyword]; + } + } + + return ['blocked' => false]; +} + +function checkProfanityFilter($message) { + global $PROFANITY_FILTER; + + $message_lower = mb_strtolower($message, 'UTF-8'); + + foreach ($PROFANITY_FILTER as $word) { + if (stripos($message_lower, $word) !== false) { + return ['blocked' => true, 'word' => $word]; + } + } + + return ['blocked' => false]; +} + +function checkLinkFilter($message) { + $patterns = [ + '/https?:\/\//i', + '/www\./i', + '/[a-z0-9-]+\.(com|de|ch|net|org|info)/i' + ]; + + foreach ($patterns as $pattern) { + if (preg_match($pattern, $message)) { + return ['blocked' => true]; + } + } + + return ['blocked' => false]; +} + +function checkRateLimit($userId, $ageGroup) { + $db = getDB(); + + // Check messages per minute + $stmt = $db->prepare(" + SELECT COUNT(*) as count + FROM rate_limits + WHERE user_id = :user_id + AND action_type = 'message' + AND timestamp > datetime('now', '-1 minute') + "); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] >= MAX_MESSAGES_PER_MINUTE) { + return ['allowed' => false, 'reason' => 'Zu viele Nachrichten pro Minute (max ' . MAX_MESSAGES_PER_MINUTE . ')']; + } + + // Check messages per hour + $stmt = $db->prepare(" + SELECT COUNT(*) as count + FROM rate_limits + WHERE user_id = :user_id + AND action_type = 'message' + AND timestamp > datetime('now', '-1 hour') + "); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] >= MAX_MESSAGES_PER_HOUR) { + return ['allowed' => false, 'reason' => 'Zu viele Nachrichten pro Stunde (max ' . MAX_MESSAGES_PER_HOUR . ')']; + } + + // Check messages per day for U18 + if ($ageGroup === 'U18') { + $stmt = $db->prepare(" + SELECT COUNT(*) as count + FROM rate_limits + WHERE user_id = :user_id + AND action_type = 'message' + AND timestamp > datetime('now', '-1 day') + "); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] >= MAX_MESSAGES_PER_DAY_U18) { + return ['allowed' => false, 'reason' => 'Tages-Limit erreicht (max ' . MAX_MESSAGES_PER_DAY_U18 . ' für U18)']; + } + } + + return ['allowed' => true]; +} + +function logRateLimit($userId) { + $db = getDB(); + $stmt = $db->prepare("INSERT INTO rate_limits (user_id, action_type) VALUES (:user_id, 'message')"); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); +} + +function logSecurityEvent($userId, $action, $details = '') { + $db = getDB(); + $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; + + $stmt = $db->prepare(' + INSERT INTO security_logs (user_id, action, details, ip_address) + VALUES (:user_id, :action, :details, :ip) + '); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->bindValue(':action', $action, SQLITE3_TEXT); + $stmt->bindValue(':details', $details, SQLITE3_TEXT); + $stmt->bindValue(':ip', $ip, SQLITE3_TEXT); + $stmt->execute(); +} + +function isBlocked($userId, $otherUserId) { + $db = getDB(); + $stmt = $db->prepare(' + SELECT COUNT(*) as count + FROM blocks + WHERE (blocker_id = :user1 AND blocked_id = :user2) + OR (blocker_id = :user2 AND blocked_id = :user1) + '); + $stmt->bindValue(':user1', $userId, SQLITE3_INTEGER); + $stmt->bindValue(':user2', $otherUserId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + return $row['count'] > 0; +} + +function cleanupOldData() { + $db = getDB(); + + // Delete old messages + $hours = MESSAGE_RETENTION_HOURS; + $db->exec("DELETE FROM messages WHERE timestamp < datetime('now', '-{$hours} hours')"); + + // Delete old rate limits + $db->exec("DELETE FROM rate_limits WHERE timestamp < datetime('now', '-1 day')"); + + // Delete old logs (keep 6 months) + $months = LOG_RETENTION_MONTHS; + $db->exec("DELETE FROM security_logs WHERE timestamp < datetime('now', '-{$months} months')"); +} + +// ═══════════════════════════════════════════════════════════ +// SESSION & AUTH +// ═══════════════════════════════════════════════════════════ + +session_start(); + +function isLoggedIn() { + return isset($_SESSION['user_id']) && isset($_SESSION['username']); +} + +function isAdmin() { + return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true; +} + +function getCurrentUserId() { + return $_SESSION['user_id'] ?? null; +} + +function getCurrentUsername() { + return $_SESSION['username'] ?? null; +} + +function getCurrentUserDisplayName() { + if (!isLoggedIn()) return null; + return $_SESSION['username'] . '#' . $_SESSION['user_display_id']; +} + +function getCurrentAgeGroup() { + return $_SESSION['age_group'] ?? null; +} + +function updateOnlineStatus($userId) { + $db = getDB(); + $stmt = $db->prepare(' + INSERT OR REPLACE INTO online_status (user_id, last_ping) + VALUES (:user_id, CURRENT_TIMESTAMP) + '); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + + $stmt = $db->prepare('UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE id = :user_id'); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); +} + +// ═══════════════════════════════════════════════════════════ +// AJAX API HANDLER +// ═══════════════════════════════════════════════════════════ + +if (isset($_POST['action']) || isset($_GET['action'])) { + header('Content-Type: application/json'); + + $action = $_POST['action'] ?? $_GET['action']; + + // ─────────────────────────────────────────────────────── + // REGISTER + // ─────────────────────────────────────────────────────── + if ($action === 'register') { + $username = trim($_POST['username'] ?? ''); + $birthdate = trim($_POST['birthdate'] ?? ''); + $agreed_terms = isset($_POST['agreed_terms']) && $_POST['agreed_terms'] === 'true'; + + // Validierung + if (empty($username) || empty($birthdate)) { + echo json_encode(['success' => false, 'error' => 'Username und Geburtsdatum erforderlich']); + exit; + } + + if (!$agreed_terms) { + echo json_encode(['success' => false, 'error' => 'Bitte akzeptiere die Nutzungsbedingungen']); + exit; + } + + if (strlen($username) < 3 || strlen($username) > 15) { + echo json_encode(['success' => false, 'error' => 'Username muss 3-15 Zeichen lang sein']); + exit; + } + + if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) { + echo json_encode(['success' => false, 'error' => 'Nur Buchstaben, Zahlen und _ erlaubt']); + exit; + } + + // Verbotene Usernamen + $forbidden = ['admin', 'moderator', 'support', 'system', 'root']; + if (in_array(strtolower($username), $forbidden)) { + echo json_encode(['success' => false, 'error' => 'Dieser Username ist nicht erlaubt']); + exit; + } + + // Alter prüfen + $age = calculateAge($birthdate); + if ($age < 13) { + echo json_encode(['success' => false, 'error' => 'Du musst mindestens 13 Jahre alt sein']); + logSecurityEvent(null, 'REGISTER_UNDERAGE', "Username: $username, Age: $age"); + exit; + } + + $ageGroup = getAgeGroup($birthdate); + + $db = getDB(); + + // Generate unique user_id + $userId = generateUserId(); + $attempts = 0; + while ($attempts < 10) { + $stmt = $db->prepare('SELECT COUNT(*) as count FROM users WHERE user_id = :user_id'); + $stmt->bindValue(':user_id', $userId, SQLITE3_TEXT); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] == 0) break; + + $userId = generateUserId(); + $attempts++; + } + + // Create user + $stmt = $db->prepare(' + INSERT INTO users (username, user_id, birthdate, age_group) + VALUES (:username, :user_id, :birthdate, :age_group) + '); + $stmt->bindValue(':username', $username, SQLITE3_TEXT); + $stmt->bindValue(':user_id', $userId, SQLITE3_TEXT); + $stmt->bindValue(':birthdate', $birthdate, SQLITE3_TEXT); + $stmt->bindValue(':age_group', $ageGroup, SQLITE3_TEXT); + $stmt->execute(); + + $dbUserId = $db->lastInsertRowID(); + + $_SESSION['user_id'] = $dbUserId; + $_SESSION['username'] = $username; + $_SESSION['user_display_id'] = $userId; + $_SESSION['age_group'] = $ageGroup; + $_SESSION['birthdate'] = $birthdate; + + updateOnlineStatus($dbUserId); + logSecurityEvent($dbUserId, 'REGISTER', "Age group: $ageGroup"); + + echo json_encode([ + 'success' => true, + 'user_id' => $dbUserId, + 'username' => $username, + 'display_name' => $username . '#' . $userId, + 'age_group' => $ageGroup + ]); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN LOGIN + // ─────────────────────────────────────────────────────── + if ($action === 'admin_login') { + $username = trim($_POST['username'] ?? ''); + $password = trim($_POST['password'] ?? ''); + + $db = getDB(); + $stmt = $db->prepare('SELECT * FROM admins WHERE username = :username'); + $stmt->bindValue(':username', $username, SQLITE3_TEXT); + $result = $stmt->execute(); + $admin = $result->fetchArray(SQLITE3_ASSOC); + + if ($admin && password_verify($password, $admin['password_hash'])) { + $_SESSION['is_admin'] = true; + $_SESSION['admin_id'] = $admin['id']; + $_SESSION['admin_username'] = $admin['username']; + + logSecurityEvent(null, 'ADMIN_LOGIN', "Admin: $username"); + + echo json_encode(['success' => true]); + } else { + logSecurityEvent(null, 'ADMIN_LOGIN_FAILED', "Username: $username"); + echo json_encode(['success' => false, 'error' => 'Ungültige Zugangsdaten']); + } + exit; + } + + // ─────────────────────────────────────────────────────── + // LOGOUT + // ─────────────────────────────────────────────────────── + if ($action === 'logout') { + if (isLoggedIn()) { + logSecurityEvent(getCurrentUserId(), 'LOGOUT', ''); + } + session_destroy(); + echo json_encode(['success' => true]); + exit; + } + + // All other actions require login + if (!isLoggedIn() && !isAdmin()) { + echo json_encode(['success' => false, 'error' => 'Nicht eingeloggt']); + exit; + } + + // ─────────────────────────────────────────────────────── + // PING (UPDATE ONLINE STATUS) + // ─────────────────────────────────────────────────────── + if ($action === 'ping') { + updateOnlineStatus(getCurrentUserId()); + cleanupOldData(); + echo json_encode(['success' => true]); + exit; + } + + // ─────────────────────────────────────────────────────── + // GET USERS + // ─────────────────────────────────────────────────────── + if ($action === 'get_users') { + $db = getDB(); + $currentUserId = getCurrentUserId(); + $currentAgeGroup = getCurrentAgeGroup(); + + $query = ' + 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 + AND (julianday("now") - julianday(os.last_ping)) * 86400 < ' . ONLINE_TIMEOUT_SECONDS . ' + 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 + AND is_read = 0 + ) as unread_count, + ( + 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 + 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 + '; + + $stmt = $db->prepare($query); + $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); + $stmt->bindValue(':age_group', $currentAgeGroup, SQLITE3_TEXT); + $result = $stmt->execute(); + + $users = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + // Don't show users who blocked me or I blocked + if ($row['is_blocked_by_me'] > 0 || $row['has_blocked_me'] > 0) { + continue; + } + + $users[] = [ + 'id' => $row['id'], + 'username' => $row['username'], + 'display_id' => $row['display_id'], + 'display_name' => $row['username'] . '#' . $row['display_id'], + 'age_group' => $row['age_group'], + 'is_online' => $row['is_online'], + 'unread_count' => $row['unread_count'] + ]; + } + + echo json_encode(['success' => true, 'users' => $users]); + exit; + } + + // ─────────────────────────────────────────────────────── + // GET MESSAGES + // ─────────────────────────────────────────────────────── + 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(); + + $query = ' + SELECT + m.id, + m.from_user_id, + m.to_user_id, + m.message, + m.timestamp, + m.is_read, + m.is_flagged, + 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.from_user_id = :current_user_id AND m.to_user_id = :other_user_id) + OR + (m.from_user_id = :other_user_id AND m.to_user_id = :current_user_id) + ORDER BY m.timestamp ASC + '; + + $stmt = $db->prepare($query); + $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); + $stmt->bindValue(':other_user_id', $otherUserId, 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'], + 'is_read' => $row['is_read'], + 'is_flagged' => $row['is_flagged'], + 'from_username' => $row['from_username'], + 'from_display_name' => $row['from_username'] . '#' . $row['from_display_id'] + ]; + } + + echo json_encode(['success' => true, 'messages' => $messages]); + exit; + } + + // ─────────────────────────────────────────────────────── + // SEND MESSAGE + // ─────────────────────────────────────────────────────── + if ($action === 'send_message') { + $toUserId = intval($_POST['to_user_id'] ?? 0); + $message = trim($_POST['message'] ?? ''); + + if ($toUserId <= 0) { + echo json_encode(['success' => false, 'error' => 'Ungültige User-ID']); + exit; + } + + if (empty($message)) { + echo json_encode(['success' => false, 'error' => 'Nachricht darf nicht leer sein']); + exit; + } + + if (strlen($message) > 1000) { + echo json_encode(['success' => false, 'error' => 'Nachricht zu lang (max 1000 Zeichen)']); + exit; + } + + // Check if blocked + if (isBlocked(getCurrentUserId(), $toUserId)) { + echo json_encode(['success' => false, 'error' => 'Nachricht kann nicht gesendet werden']); + exit; + } + + $db = getDB(); + $currentUserId = getCurrentUserId(); + $currentAgeGroup = getCurrentAgeGroup(); + + // Rate Limiting + $rateLimitCheck = checkRateLimit($currentUserId, $currentAgeGroup); + if (!$rateLimitCheck['allowed']) { + logSecurityEvent($currentUserId, 'RATE_LIMIT_EXCEEDED', $rateLimitCheck['reason']); + echo json_encode(['success' => false, 'error' => $rateLimitCheck['reason']]); + exit; + } + + // Keyword Blacklist + $keywordCheck = checkKeywordBlacklist($message); + if ($keywordCheck['blocked']) { + logSecurityEvent($currentUserId, 'KEYWORD_BLOCKED', "Keyword: {$keywordCheck['keyword']}"); + echo + echo json_encode([ + 'success' => false, + 'error' => 'Deine Nachricht enthält nicht erlaubte Inhalte', + 'details' => 'Verbotenes Wort erkannt: ' . $keywordCheck['keyword'] + ]); + exit; + } + + // Profanity Filter + $profanityCheck = checkProfanityFilter($message); + if ($profanityCheck['blocked']) { + logSecurityEvent($currentUserId, 'PROFANITY_BLOCKED', "Word: {$profanityCheck['word']}"); + echo json_encode([ + 'success' => false, + 'error' => 'Deine Nachricht enthält Schimpfwörter', + 'details' => 'Bitte verwende eine angemessene Sprache' + ]); + 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', + 'details' => 'Aus Sicherheitsgründen können keine URLs gesendet werden' + ]); + exit; + } + + // Auto-Flagging (verdächtige Muster) + $isFlagged = 0; + $flagReason = ''; + + // Check for repeated characters (AAAAAAA) + if (preg_match('/(.)\1{5,}/', $message)) { + $isFlagged = 1; + $flagReason = 'Repeated characters'; + } + + // Check for all caps (min 20 chars) + if (strlen($message) > 20 && $message === strtoupper($message)) { + $isFlagged = 1; + $flagReason = 'All caps'; + } + + // Check for excessive emojis + $emojiCount = preg_match_all('/[\x{1F600}-\x{1F64F}]/u', $message); + if ($emojiCount > 10) { + $isFlagged = 1; + $flagReason = 'Excessive emojis'; + } + + // Insert message + $stmt = $db->prepare(' + INSERT INTO messages (from_user_id, to_user_id, message, is_flagged, flag_reason) + VALUES (:from_user_id, :to_user_id, :message, :is_flagged, :flag_reason) + '); + $stmt->bindValue(':from_user_id', $currentUserId, SQLITE3_INTEGER); + $stmt->bindValue(':to_user_id', $toUserId, SQLITE3_INTEGER); + $stmt->bindValue(':message', $message, SQLITE3_TEXT); + $stmt->bindValue(':is_flagged', $isFlagged, SQLITE3_INTEGER); + $stmt->bindValue(':flag_reason', $flagReason, SQLITE3_TEXT); + $stmt->execute(); + + $messageId = $db->lastInsertRowID(); + + // Log rate limit + logRateLimit($currentUserId); + + if ($isFlagged) { + logSecurityEvent($currentUserId, 'MESSAGE_FLAGGED', "Reason: $flagReason, Message ID: $messageId"); + } + + 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') { + $blockedUserId = intval($_POST['user_id'] ?? 0); + + if ($blockedUserId <= 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', $blockedUserId, SQLITE3_INTEGER); + $stmt->execute(); + + logSecurityEvent($currentUserId, 'USER_BLOCKED', "Blocked user ID: $blockedUserId"); + + echo json_encode(['success' => true]); + exit; + } + + // ─────────────────────────────────────────────────────── + // UNBLOCK USER + // ─────────────────────────────────────────────────────── + if ($action === 'unblock_user') { + $blockedUserId = intval($_POST['user_id'] ?? 0); + + if ($blockedUserId <= 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', $blockedUserId, SQLITE3_INTEGER); + $stmt->execute(); + + logSecurityEvent($currentUserId, 'USER_UNBLOCKED', "Unblocked user ID: $blockedUserId"); + + 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"); + + echo json_encode(['success' => true, 'message' => 'Meldung wurde erfolgreich gesendet']); + 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(); + + $blockedUsers = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $blockedUsers[] = [ + 'id' => $row['id'], + 'display_name' => $row['username'] . '#' . $row['display_id'], + 'blocked_at' => $row['blocked_at'] + ]; + } + + echo json_encode(['success' => true, 'blocked_users' => $blockedUsers]); + exit; + } + + // ═══════════════════════════════════════════════════════════ + // ADMIN ACTIONS + // ═══════════════════════════════════════════════════════════ + + if (!isAdmin()) { + echo json_encode(['success' => false, 'error' => 'Admin-Rechte erforderlich']); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: 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 + WHERE r.status = "pending" + ORDER BY r.timestamp DESC + LIMIT 50 + '; + + $result = $db->query($query); + + $reports = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $reports[] = [ + 'id' => $row['id'], + 'reporter' => $row['reporter_name'] . '#' . $row['reporter_display_id'], + 'reported' => $row['reported_name'] . '#' . $row['reported_display_id'], + 'reported_user_id' => $row['reported_user_id'], + 'reason' => $row['reason'], + 'message' => $row['message_content'], + 'timestamp' => $row['timestamp'], + 'status' => $row['status'] + ]; + } + + echo json_encode(['success' => true, 'reports' => $reports]); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: GET FLAGGED MESSAGES + // ─────────────────────────────────────────────────────── + if ($action === 'admin_get_flagged') { + $db = getDB(); + + $query = ' + SELECT + m.id, + m.message, + m.flag_reason, + m.timestamp, + u.username, + u.user_id as display_id, + u.id as user_db_id + FROM messages m + JOIN users u ON m.from_user_id = u.id + WHERE m.is_flagged = 1 + ORDER BY m.timestamp DESC + LIMIT 50 + '; + + $result = $db->query($query); + + $flagged = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $flagged[] = [ + 'id' => $row['id'], + 'user' => $row['username'] . '#' . $row['display_id'], + 'user_id' => $row['user_db_id'], + 'message' => $row['message'], + 'reason' => $row['flag_reason'], + 'timestamp' => $row['timestamp'] + ]; + } + + echo json_encode(['success' => true, 'flagged' => $flagged]); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: GET STATISTICS + // ─────────────────────────────────────────────────────── + if ($action === 'admin_get_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']; + + // Online users + $result = $db->query(' + SELECT COUNT(*) as count + FROM online_status + WHERE (julianday("now") - julianday(last_ping)) * 86400 < ' . ONLINE_TIMEOUT_SECONDS + ); + $onlineUsers = $result->fetchArray(SQLITE3_ASSOC)['count']; + + // Total messages (24h) + $result = $db->query('SELECT COUNT(*) as count FROM messages'); + $totalMessages = $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']; + + // Pending reports + $result = $db->query('SELECT COUNT(*) as count FROM reports WHERE status = "pending"'); + $pendingReports = $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, + 'online_users' => $onlineUsers, + 'total_messages' => $totalMessages, + 'flagged_messages' => $flaggedMessages, + 'pending_reports' => $pendingReports, + 'banned_users' => $bannedUsers + ] + ]); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: 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, 'message' => 'User wurde gesperrt']); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: 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, 'message' => 'Sperre wurde aufgehoben']); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: RESOLVE REPORT + // ─────────────────────────────────────────────────────── + if ($action === 'admin_resolve_report') { + $reportId = intval($_POST['report_id'] ?? 0); + $action_taken = trim($_POST['action_taken'] ?? '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(':report_id', $reportId, SQLITE3_INTEGER); + $stmt->bindValue(':status', $action_taken, SQLITE3_TEXT); + $stmt->execute(); + + logSecurityEvent(null, 'ADMIN_RESOLVE_REPORT', "Report ID: $reportId, Action: $action_taken"); + + echo json_encode(['success' => true, 'message' => 'Report wurde bearbeitet']); + exit; + } + + // ─────────────────────────────────────────────────────── + // ADMIN: DELETE MESSAGE + // ─────────────────────────────────────────────────────── + if ($action === 'admin_delete_message') { + $messageId = intval($_POST['message_id'] ?? 0); + + if ($messageId <= 0) { + echo json_encode(['success' => false, 'error' => 'Ungültige Message-ID']); + exit; + } + + $db = getDB(); + $stmt = $db->prepare('DELETE FROM messages WHERE id = :message_id'); + $stmt->bindValue(':message_id', $messageId, SQLITE3_INTEGER); + $stmt->execute(); + + logSecurityEvent(null, 'ADMIN_DELETE_MESSAGE', "Message ID: $messageId"); + + echo json_encode(['success' => true, 'message' => 'Nachricht wurde gelöscht']); + 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) + AND NOT EXISTS ( + 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) + ) + 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_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 +// ═══════════════════════════════════════════════════════════ +?> + + + + + + 🔒 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 + + + + + + + +
+

💬 Secure Private Chat

+

Sicherer Chat mit Altersverifikation

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

⚠️ Wichtige Regeln

+
    +
  • Gib NIEMALS persönliche Daten weiter (Adresse, Telefon, etc.)
  • +
  • Treffe dich NICHT mit Fremden
  • +
  • Bleibe respektvoll und freundlich
  • +
  • Keine Links oder externe Kontakte teilen
  • +
  • Bei verdächtigem Verhalten: Melde den User!
  • +
+
+ +
+ + +
+ + +
+ + +
+ + + +
+ +
+

💬 Secure Private Chat

+ +
+ + + + + +
+
+
💬
+
Wähle einen Benutzer aus der Liste
+
+ +
+
+ +
+ +
+ +
+ +
+ + +
+
+
+
+ + + + +