Add comprehensive unit tests for Security, UserModel, and Validation utilities
- Implemented SecurityTest to validate token generation, CSRF protection, input sanitization, and rate limiting. - Created UserModelTest to ensure correct database operations for user management, including creation, updating, banning, and fetching active users. - Developed ValidationTest to verify input validation and sanitization for user IDs, nicknames, messages, and API requests. - Introduced Security and Validation utility classes with methods for secure token generation, input sanitization, and comprehensive validation rules.
This commit is contained in:
parent
5692874b10
commit
41cd7a4fd8
32 changed files with 5796 additions and 368 deletions
490
index.php
490
index.php
|
|
@ -1,26 +1,35 @@
|
|||
<?php
|
||||
session_start();
|
||||
/**
|
||||
* Dodgers Stream Theater
|
||||
* Main application entry point with secure authentication and API handling
|
||||
*/
|
||||
|
||||
// Admin configuration - Change this to a secure random string
|
||||
define('ADMIN_CODE', 'dodgers2024streamAdm1nC0d3!xyz789'); // Change this!
|
||||
// Initialize application with security framework
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
// Check if user is admin
|
||||
$isAdmin = false;
|
||||
if (isset($_GET['admin']) && $_GET['admin'] === ADMIN_CODE) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
}
|
||||
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
// Get configuration and security objects
|
||||
// $isAdmin is now set by bootstrap.php
|
||||
|
||||
// Generate or retrieve user ID
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
$_SESSION['user_id'] = substr(uniqid(), -6); // 6 character unique ID
|
||||
}
|
||||
// Load models and services
|
||||
$userModel = new UserModel();
|
||||
$chatMessageModel = new ChatMessageModel();
|
||||
$activeViewerModel = new ActiveViewerModel();
|
||||
|
||||
// Simple file-based storage
|
||||
// File-based storage (to be migrated to database later)
|
||||
$chatFile = 'chat_messages.json';
|
||||
$viewersFile = 'active_viewers.json';
|
||||
$bannedFile = 'banned_users.json';
|
||||
$maxMessages = 100; // Keep last 100 messages
|
||||
$maxMessages = Config::get('chat.max_messages', 100);
|
||||
|
||||
// Get stream base URL from configuration
|
||||
$streamBaseUrl = Config::get('stream.base_url', 'http://38.64.28.91:23456');
|
||||
|
||||
// Handle SSE (Server-Sent Events) connections for real-time chat
|
||||
if (isset($_GET['sse']) && $_GET['sse'] === '1' && isset($_GET['user_id'])) {
|
||||
$chatServer = new ChatServer();
|
||||
$chatServer->handleSSE($_GET['user_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Clean up old viewers (inactive for more than 10 seconds)
|
||||
function cleanupViewers() {
|
||||
|
|
@ -42,26 +51,38 @@ function cleanupViewers() {
|
|||
// Handle API requests for stream status
|
||||
if (isset($_GET['api']) && $_GET['api'] === 'stream_status') {
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: 0');
|
||||
|
||||
$streamUrl = 'http://38.64.28.91:23456/stream.m3u8';
|
||||
$corsOrigins = Config::get('cors.allowed_origins', []);
|
||||
if (in_array($_SERVER['HTTP_ORIGIN'] ?? '', $corsOrigins)) {
|
||||
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
|
||||
}
|
||||
|
||||
$streamUrl = $streamBaseUrl . '/stream.m3u8';
|
||||
$online = false;
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "User-Agent: Mozilla/5.0\r\n",
|
||||
'timeout' => 5 // Quick check
|
||||
]
|
||||
]);
|
||||
if (Security::checkRateLimit(Security::getClientIP(), 'stream_status', 10, 60)) {
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "User-Agent: Mozilla/5.0\r\n",
|
||||
'timeout' => 5 // Quick check
|
||||
]
|
||||
]);
|
||||
|
||||
$content = @file_get_contents($streamUrl, false, $context);
|
||||
$content = @file_get_contents($streamUrl, false, $context);
|
||||
Security::logSecurityEvent('stream_status_check', ['online' => $online]);
|
||||
|
||||
if ($content !== false && !empty($content)) {
|
||||
// Check if it looks like a valid m3u8
|
||||
if (str_starts_with($content, '#EXTM3U')) {
|
||||
$online = true;
|
||||
if ($content !== false && !empty($content)) {
|
||||
// Check if it looks like a valid m3u8
|
||||
if (str_starts_with($content, '#EXTM3U')) {
|
||||
$online = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Security::logSecurityEvent('stream_status_rate_limited');
|
||||
}
|
||||
|
||||
echo json_encode(['online' => $online]);
|
||||
|
|
@ -70,14 +91,29 @@ if (isset($_GET['api']) && $_GET['api'] === 'stream_status') {
|
|||
|
||||
// Handle proxy requests for the stream
|
||||
if (isset($_GET['proxy']) && $_GET['proxy'] === 'stream') {
|
||||
$streamUrl = 'http://38.64.28.91:23456/stream.m3u8';
|
||||
|
||||
// Check rate limiting
|
||||
if (!Security::checkRateLimit(Security::getClientIP(), 'proxy_stream')) {
|
||||
Security::logSecurityEvent('proxy_stream_rate_limited');
|
||||
http_response_code(429);
|
||||
echo json_encode(['error' => 'Too many requests. Please try again later.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$streamUrl = $streamBaseUrl . '/stream.m3u8';
|
||||
|
||||
// Set appropriate headers for m3u8 content
|
||||
header('Content-Type: application/vnd.apple.mpegurl');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Range');
|
||||
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: 0');
|
||||
|
||||
$corsOrigins = Config::get('cors.allowed_origins', []);
|
||||
if (in_array($_SERVER['HTTP_ORIGIN'] ?? '', $corsOrigins)) {
|
||||
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Range');
|
||||
}
|
||||
|
||||
// Fetch and output the m3u8 content
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
|
|
@ -86,33 +122,39 @@ if (isset($_GET['proxy']) && $_GET['proxy'] === 'stream') {
|
|||
'timeout' => 10
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
$content = @file_get_contents($streamUrl, false, $context);
|
||||
|
||||
|
||||
if ($content !== false) {
|
||||
// Parse and update the m3u8 content to use our proxy for .ts segments
|
||||
$lines = explode("\n", $content);
|
||||
$updatedContent = [];
|
||||
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (!empty($line) && !str_starts_with($line, '#')) {
|
||||
// This is a .ts segment URL
|
||||
if (strpos($line, 'http') === 0) {
|
||||
// Absolute URL
|
||||
$updatedContent[] = '?proxy=segment&url=' . urlencode($line);
|
||||
// Absolute URL - validate it first
|
||||
if (Security::isValidStreamUrl($line)) {
|
||||
$updatedContent[] = '?proxy=segment&url=' . urlencode($line);
|
||||
}
|
||||
} else {
|
||||
// Relative URL
|
||||
$baseUrl = 'http://38.64.28.91:23456/';
|
||||
$updatedContent[] = '?proxy=segment&url=' . urlencode($baseUrl . $line);
|
||||
$segmentUrl = $streamBaseUrl . '/' . $line;
|
||||
if (Security::isValidStreamUrl($segmentUrl)) {
|
||||
$updatedContent[] = '?proxy=segment&url=' . urlencode($segmentUrl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$updatedContent[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Security::logSecurityEvent('proxy_stream_success');
|
||||
echo implode("\n", $updatedContent);
|
||||
} else {
|
||||
Security::logSecurityEvent('proxy_stream_failed', ['url' => $streamUrl]);
|
||||
http_response_code(500);
|
||||
echo "Failed to fetch stream";
|
||||
}
|
||||
|
|
@ -121,17 +163,31 @@ if (isset($_GET['proxy']) && $_GET['proxy'] === 'stream') {
|
|||
|
||||
// Handle proxy requests for .ts segments
|
||||
if (isset($_GET['proxy']) && $_GET['proxy'] === 'segment' && isset($_GET['url'])) {
|
||||
$segmentUrl = urldecode($_GET['url']);
|
||||
|
||||
// Validate URL to prevent abuse
|
||||
if (strpos($segmentUrl, 'http://38.64.28.91:23456/') !== 0) {
|
||||
http_response_code(403);
|
||||
// Check rate limiting
|
||||
if (!Security::checkRateLimit(Security::getClientIP(), 'proxy_segment')) {
|
||||
Security::logSecurityEvent('proxy_segment_rate_limited');
|
||||
http_response_code(429);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$segmentUrl = Security::sanitizeInput($_GET['url'], 'url');
|
||||
|
||||
// Validate URL to prevent SSRF attacks
|
||||
if (!$segmentUrl || !Security::isValidStreamUrl($segmentUrl)) {
|
||||
Security::logSecurityEvent('proxy_segment_invalid_url', ['url' => $_GET['url'] ?? '']);
|
||||
http_response_code(403);
|
||||
echo "Invalid segment URL";
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: video/mp2t');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
header('Cache-Control: public, max-age=3600'); // Cache segments for 1 hour
|
||||
|
||||
$corsOrigins = Config::get('cors.allowed_origins', []);
|
||||
if (in_array($_SERVER['HTTP_ORIGIN'] ?? '', $corsOrigins)) {
|
||||
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
|
|
@ -139,13 +195,16 @@ if (isset($_GET['proxy']) && $_GET['proxy'] === 'segment' && isset($_GET['url'])
|
|||
'timeout' => 10
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
$content = @file_get_contents($segmentUrl, false, $context);
|
||||
|
||||
|
||||
if ($content !== false) {
|
||||
Security::logSecurityEvent('proxy_segment_success');
|
||||
echo $content;
|
||||
} else {
|
||||
Security::logSecurityEvent('proxy_segment_failed', ['url' => $segmentUrl]);
|
||||
http_response_code(500);
|
||||
echo "Failed to fetch segment";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
|
@ -153,151 +212,223 @@ if (isset($_GET['proxy']) && $_GET['proxy'] === 'segment' && isset($_GET['url'])
|
|||
// Handle chat actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Admin actions
|
||||
if ($_POST['action'] === 'delete_message' && $isAdmin && isset($_POST['message_id'])) {
|
||||
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
|
||||
$messages = array_filter($messages, function($msg) {
|
||||
return $msg['id'] !== $_POST['message_id'];
|
||||
});
|
||||
$messages = array_values($messages); // Re-index array
|
||||
file_put_contents($chatFile, json_encode($messages));
|
||||
echo json_encode(['success' => true]);
|
||||
|
||||
$action = $_POST['action'];
|
||||
|
||||
// Validate the action parameter
|
||||
if (!preg_match('/^[a-z_]+$/', $action)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid action parameter']);
|
||||
Security::logSecurityEvent('invalid_chat_action', ['action' => $action]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'clear_chat' && $isAdmin) {
|
||||
file_put_contents($chatFile, json_encode([]));
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'ban_user' && $isAdmin && isset($_POST['user_id'])) {
|
||||
$banned = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
|
||||
if (!in_array($_POST['user_id'], $banned)) {
|
||||
$banned[] = $_POST['user_id'];
|
||||
file_put_contents($bannedFile, json_encode($banned));
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'heartbeat') {
|
||||
$userId = $_SESSION['user_id'];
|
||||
$nickname = isset($_POST['nickname']) ? htmlspecialchars(substr($_POST['nickname'], 0, 20)) : 'Anonymous';
|
||||
|
||||
$viewers = file_exists($viewersFile) ? json_decode(file_get_contents($viewersFile), true) : [];
|
||||
|
||||
// Update or add viewer
|
||||
$found = false;
|
||||
foreach ($viewers as &$viewer) {
|
||||
if ($viewer['user_id'] === $userId) {
|
||||
$viewer['last_seen'] = time();
|
||||
$viewer['nickname'] = $nickname;
|
||||
$viewer['is_admin'] = $isAdmin;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$viewers[] = [
|
||||
'user_id' => $userId,
|
||||
'nickname' => $nickname,
|
||||
'last_seen' => time(),
|
||||
'is_admin' => $isAdmin
|
||||
];
|
||||
}
|
||||
|
||||
file_put_contents($viewersFile, json_encode($viewers));
|
||||
$viewerCount = cleanupViewers();
|
||||
|
||||
echo json_encode(['success' => true, 'viewer_count' => $viewerCount]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'send' && isset($_POST['message']) && isset($_POST['nickname'])) {
|
||||
$nickname = htmlspecialchars(substr($_POST['nickname'], 0, 20));
|
||||
$message = htmlspecialchars(substr($_POST['message'], 0, 1000));
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
// Check if user is banned
|
||||
$banned = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
|
||||
if (in_array($userId, $banned)) {
|
||||
echo json_encode(['success' => false, 'error' => 'You are banned from chat']);
|
||||
|
||||
// Admin-only actions
|
||||
if (in_array($action, ['delete_message', 'clear_chat', 'ban_user'])) {
|
||||
if (!$isAdmin) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'Admin access required']);
|
||||
Security::logSecurityEvent('unauthorized_admin_action', ['action' => $action, 'ip' => Security::getClientIP()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($nickname) && !empty($message)) {
|
||||
|
||||
// Rate limiting for admin actions
|
||||
if (!Security::checkRateLimit(Security::getClientIP(), 'admin_actions', 10, 60)) {
|
||||
http_response_code(429);
|
||||
echo json_encode(['success' => false, 'error' => 'Too many admin actions. Please wait.']);
|
||||
Security::logSecurityEvent('admin_rate_limited');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle different actions
|
||||
switch ($action) {
|
||||
case 'delete_message':
|
||||
if (!isset($_POST['message_id']) || !preg_match('/^[a-zA-Z0-9]+$/', $_POST['message_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid message ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$messageIdToDelete = $_POST['message_id'];
|
||||
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
|
||||
|
||||
$filteredMessages = array_filter($messages, function($msg) use ($messageIdToDelete) {
|
||||
return $msg['id'] !== $messageIdToDelete;
|
||||
});
|
||||
$filteredMessages = array_values($filteredMessages);
|
||||
file_put_contents($chatFile, json_encode($filteredMessages));
|
||||
|
||||
Security::logSecurityEvent('message_deleted', ['message_id' => $messageIdToDelete]);
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
case 'clear_chat':
|
||||
file_put_contents($chatFile, json_encode([]));
|
||||
Security::logSecurityEvent('chat_cleared');
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
case 'ban_user':
|
||||
if (!isset($_POST['user_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'User ID required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$bannedUsers = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
|
||||
if (!in_array($_POST['user_id'], $bannedUsers)) {
|
||||
$bannedUsers[] = $_POST['user_id'];
|
||||
file_put_contents($bannedFile, json_encode($bannedUsers));
|
||||
}
|
||||
|
||||
Security::logSecurityEvent('user_banned', ['user_id' => $_POST['user_id']]);
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
case 'heartbeat':
|
||||
// Validate heartbeat data
|
||||
$heartbeatValidation = Validation::validateHeartbeat($_POST);
|
||||
if (!$heartbeatValidation['valid']) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid heartbeat data']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$nickname = $heartbeatValidation['validated']['nickname'] ?? 'Anonymous';
|
||||
|
||||
$viewers = file_exists($viewersFile) ? json_decode(file_get_contents($viewersFile), true) : [];
|
||||
|
||||
// Update or add viewer
|
||||
$found = false;
|
||||
foreach ($viewers as &$viewer) {
|
||||
if ($viewer['user_id'] === $userId) {
|
||||
$viewer['last_seen'] = time();
|
||||
$viewer['nickname'] = $nickname;
|
||||
$viewer['is_admin'] = $isAdmin;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$viewers[] = [
|
||||
'user_id' => $userId,
|
||||
'nickname' => $nickname,
|
||||
'last_seen' => time(),
|
||||
'is_admin' => $isAdmin
|
||||
];
|
||||
}
|
||||
|
||||
file_put_contents($viewersFile, json_encode($viewers));
|
||||
$viewerCount = cleanupViewers();
|
||||
|
||||
echo json_encode(['success' => true, 'viewer_count' => $viewerCount]);
|
||||
exit;
|
||||
|
||||
case 'send':
|
||||
if (!isset($_POST['message']) || !isset($_POST['nickname'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Message and nickname required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Rate limiting for message sending
|
||||
if (!Security::checkRateLimit($_SESSION['user_id'], 'send_message', 5, 60)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Too many messages. Please wait before sending another.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate message data
|
||||
$messageValidation = Validation::validateMessageSend($_POST);
|
||||
if (!$messageValidation['valid']) {
|
||||
echo json_encode(['success' => false, 'error' => 'Validation failed: ' . implode(', ', array_values($messageValidation['errors']))]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$nickname = $messageValidation['validated']['nickname'];
|
||||
$message = $messageValidation['validated']['message'];
|
||||
|
||||
// Check if user is banned
|
||||
$bannedUsers = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
|
||||
if (in_array($userId, $bannedUsers)) {
|
||||
echo json_encode(['success' => false, 'error' => 'You are banned from chat']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
|
||||
|
||||
$newMessage = [
|
||||
'id' => uniqid(),
|
||||
'id' => Security::generateSecureToken(8), // More secure than uniqid()
|
||||
'user_id' => $userId,
|
||||
'nickname' => $nickname,
|
||||
'message' => $message,
|
||||
'nickname' => htmlspecialchars($nickname, ENT_QUOTES, 'UTF-8'),
|
||||
'message' => htmlspecialchars($message, ENT_QUOTES, 'UTF-8'),
|
||||
'timestamp' => time(),
|
||||
'time' => date('M j, H:i'),
|
||||
'is_admin' => $isAdmin
|
||||
];
|
||||
|
||||
array_push($messages, $newMessage);
|
||||
|
||||
|
||||
$messages[] = $newMessage;
|
||||
|
||||
// Keep only last N messages
|
||||
if (count($messages) > $maxMessages) {
|
||||
$messages = array_slice($messages, -$maxMessages);
|
||||
}
|
||||
|
||||
|
||||
file_put_contents($chatFile, json_encode($messages));
|
||||
|
||||
Security::logSecurityEvent('message_sent', ['message_id' => $newMessage['id']]);
|
||||
echo json_encode(['success' => true, 'message' => $newMessage]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid input']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'fetch') {
|
||||
$lastId = isset($_POST['last_id']) ? $_POST['last_id'] : '';
|
||||
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
|
||||
|
||||
// Find new messages only
|
||||
$newMessages = [];
|
||||
$foundLast = empty($lastId);
|
||||
|
||||
foreach ($messages as $msg) {
|
||||
if ($foundLast) {
|
||||
$newMessages[] = $msg;
|
||||
exit;
|
||||
|
||||
case 'fetch':
|
||||
$lastId = isset($_POST['last_id']) ? trim($_POST['last_id']) : '';
|
||||
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
|
||||
|
||||
// Find new messages only
|
||||
$newMessages = [];
|
||||
$foundLast = empty($lastId);
|
||||
|
||||
foreach ($messages as $msg) {
|
||||
if ($foundLast) {
|
||||
$newMessages[] = $msg;
|
||||
}
|
||||
if ($msg['id'] === $lastId) {
|
||||
$foundLast = true;
|
||||
}
|
||||
}
|
||||
if ($msg['id'] === $lastId) {
|
||||
$foundLast = true;
|
||||
|
||||
// If lastId wasn't found, return all messages (initial load or refresh)
|
||||
if (!$foundLast && !empty($lastId)) {
|
||||
$newMessages = $messages;
|
||||
}
|
||||
}
|
||||
|
||||
// If lastId wasn't found, return all messages (initial load or refresh)
|
||||
if (!$foundLast && !empty($lastId)) {
|
||||
$newMessages = $messages;
|
||||
}
|
||||
|
||||
// Get viewer count
|
||||
$viewerCount = cleanupViewers();
|
||||
|
||||
// Determine if we should send all messages (for initial load or after admin actions)
|
||||
$sendAllMessages = empty($lastId) || !$foundLast;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'messages' => $newMessages,
|
||||
'all_messages' => $sendAllMessages ? $messages : null,
|
||||
'message_count' => count($messages),
|
||||
'viewer_count' => $viewerCount,
|
||||
'is_admin' => $isAdmin
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'get_user_id') {
|
||||
echo json_encode(['success' => true, 'user_id' => $_SESSION['user_id'], 'is_admin' => $isAdmin]);
|
||||
exit;
|
||||
|
||||
// Get viewer count
|
||||
$viewerCount = cleanupViewers();
|
||||
|
||||
// Determine if we should send all messages (for initial load or after admin actions)
|
||||
$sendAllMessages = empty($lastId) || !$foundLast;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'messages' => $newMessages,
|
||||
'all_messages' => $sendAllMessages ? $messages : null,
|
||||
'message_count' => count($messages),
|
||||
'viewer_count' => $viewerCount,
|
||||
'is_admin' => $isAdmin
|
||||
]);
|
||||
exit;
|
||||
|
||||
case 'get_user_id':
|
||||
echo json_encode(['success' => true, 'user_id' => $_SESSION['user_id'], 'is_admin' => $isAdmin]);
|
||||
exit;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Unknown action']);
|
||||
Security::logSecurityEvent('unknown_chat_action', ['action' => $action]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -307,6 +438,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dodgers Stream Theater</title>
|
||||
<meta name="csrf-token" content="<?php echo htmlspecialchars(Security::generateCSRFToken()); ?>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue