diff --git a/chat1.php b/chat1.php deleted file mode 100644 index cba271d..0000000 --- a/chat1.php +++ /dev/null @@ -1,2984 +0,0 @@ -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 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(' - 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(); -$isAdminPage = isset($_GET['admin']); - -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 - '; - - 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); - - if ($currentAgeGroup === 'U18') { - $stmt->bindValue(':allowed_group', 'U18', SQLITE3_TEXT); - } else { - $stmt->bindValue(':blocked_group', 'U18', 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(); - $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 - 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(); - - $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']) { - 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 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(); - $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 - 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; - } - - 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 - // ─────────────────────────────────────────────────────── - 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(); - $currentAgeGroup = getCurrentAgeGroup(); - $lastMessageId = intval($_GET['last_message_id'] ?? 0); - - set_time_limit(0); - ob_implicit_flush(true); - 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 - m.id, - m.from_user_id, - m.to_user_id, - m.message, - m.timestamp, - 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 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 - 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)) { - $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'], - '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 -// ═══════════════════════════════════════════════════════════ -?> - - - - - - 💬 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

- -
- -
-
- - -
- -
- - -
- -
-

⚠️ 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
-
- -
-
- -
- -
- -
- -
- - -
-
-
-
- - - - -