false, 'data' => null, 'error' => null]; try { // Get action from query parameter $action = isset($_GET['action']) ? $_GET['action'] : null; $method = $_SERVER['REQUEST_METHOD']; // Get authenticated user (supports both session and JWT) $userId = null; // Try JWT authentication first $authHeader = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null); if ($authHeader && preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) { $token = $matches[1]; $tokenData = VAuth::verifyToken($token); if ($tokenData && isset($tokenData['user_id'])) { $userId = $tokenData['user_id']; } } // Fall back to session authentication if (!$userId && isset($_SESSION['USER_ID'])) { $userId = $_SESSION['USER_ID']; } elseif (!$userId && isset($_SESSION['usr_id'])) { $userId = $_SESSION['usr_id']; } // Route based on method and action switch ($method) { case 'GET': if (isset($_GET['id'])) { // Get single comment handleGetComment($_GET['id'], $userId); } else { // List comments handleListComments($userId); } break; case 'POST': if (!$userId) { throw new Exception('Authentication required', 401); } switch ($action) { case 'create': case null: handleCreateComment($userId); break; case 'like': handleLikeComment($userId); break; case 'report': handleReportComment($userId); break; default: throw new Exception('Invalid action', 400); } break; case 'PUT': if (!$userId) { throw new Exception('Authentication required', 401); } handleUpdateComment($userId); break; case 'DELETE': if (!$userId) { throw new Exception('Authentication required', 401); } handleDeleteComment($userId); break; default: throw new Exception('Method not allowed', 405); } } catch (Exception $e) { http_response_code($e->getCode() >= 400 && $e->getCode() < 600 ? $e->getCode() : 500); $response['error'] = $e->getMessage(); echo json_encode($response); exit; } /** * List comments for a video */ function handleListComments($userId) { global $class_database, $response; $fileKey = isset($_GET['file_key']) ? $_GET['file_key'] : null; if (!$fileKey) { throw new Exception('file_key parameter is required', 400); } $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 50; $offset = ($page - 1) * $limit; $sort = isset($_GET['sort']) ? $_GET['sort'] : 'recent'; // recent, top, oldest // Determine sort order $orderBy = match($sort) { 'top' => 'c.comment_likes DESC, c.comment_date DESC', 'oldest' => 'c.comment_date ASC', default => 'c.comment_date DESC' }; // Get total count $countSql = "SELECT COUNT(*) FROM db_comments WHERE file_key = ? AND parent_id IS NULL"; $total = (int)$class_database->singleFieldValue($countSql, [$fileKey]); // Get top-level comments (not replies) $sql = "SELECT c.comment_id, c.usr_id, c.comment_text, c.comment_date, c.comment_likes, c.parent_id, u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified, (SELECT COUNT(*) FROM db_comments WHERE parent_id = c.comment_id) as reply_count FROM db_comments c LEFT JOIN db_users u ON c.usr_id = u.usr_id WHERE c.file_key = ? AND c.parent_id IS NULL ORDER BY $orderBy LIMIT ? OFFSET ?"; $comments = $class_database->execute($sql, [$fileKey, $limit, $offset]); $commentList = $comments ? $comments->GetArray() : []; // If user is logged in, check which comments they've liked if ($userId && !empty($commentList)) { $commentIds = array_column($commentList, 'comment_id'); $placeholders = implode(',', array_fill(0, count($commentIds), '?')); $likeSql = "SELECT comment_id FROM db_comment_likes WHERE comment_id IN ($placeholders) AND usr_id = ?"; $likedComments = $class_database->execute($likeSql, array_merge($commentIds, [$userId])); $likedIds = []; if ($likedComments) { while (!$likedComments->EOF) { $likedIds[] = $likedComments->fields['comment_id']; $likedComments->MoveNext(); } } // Add liked status to comments foreach ($commentList as &$comment) { $comment['user_liked'] = in_array($comment['comment_id'], $likedIds); } } $response['success'] = true; $response['data'] = [ 'comments' => $commentList, 'pagination' => [ 'page' => $page, 'limit' => $limit, 'total' => $total, 'pages' => ceil($total / $limit) ] ]; echo json_encode($response); } /** * Get single comment with replies */ function handleGetComment($commentId, $userId) { global $class_database, $response; // Get the comment $sql = "SELECT c.comment_id, c.usr_id, c.file_key, c.comment_text, c.comment_date, c.comment_likes, c.parent_id, u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified FROM db_comments c LEFT JOIN db_users u ON c.usr_id = u.usr_id WHERE c.comment_id = ?"; $result = $class_database->execute($sql, [$commentId]); if (!$result || $result->RecordCount() === 0) { throw new Exception('Comment not found', 404); } $comment = $result->fields; // Get replies $repliesSql = "SELECT c.comment_id, c.usr_id, c.comment_text, c.comment_date, c.comment_likes, c.parent_id, u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified FROM db_comments c LEFT JOIN db_users u ON c.usr_id = u.usr_id WHERE c.parent_id = ? ORDER BY c.comment_date ASC"; $replies = $class_database->execute($repliesSql, [$commentId]); $comment['replies'] = $replies ? $replies->GetArray() : []; // Check if user liked this comment if ($userId) { $likeSql = "SELECT 1 FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?"; $liked = $class_database->execute($likeSql, [$commentId, $userId]); $comment['user_liked'] = $liked && $liked->RecordCount() > 0; } $response['success'] = true; $response['data'] = $comment; echo json_encode($response); } /** * Create new comment */ function handleCreateComment($userId) { global $class_database, $response; // Get JSON input $input = json_decode(file_get_contents('php://input'), true); if (!$input) { throw new Exception('Invalid JSON input', 400); } $fileKey = isset($input['file_key']) ? $input['file_key'] : null; $commentText = isset($input['comment_text']) ? trim($input['comment_text']) : ''; $parentId = isset($input['parent_id']) ? (int)$input['parent_id'] : null; // Validate required fields if (!$fileKey) { throw new Exception('file_key is required', 400); } if (empty($commentText)) { throw new Exception('Comment text cannot be empty', 400); } if (strlen($commentText) > 5000) { throw new Exception('Comment text too long (max 5000 characters)', 400); } // Sanitize comment text $commentText = VSecurity::sanitize($commentText); // Check if video exists $videoCheckSql = "SELECT 1 FROM db_videofiles WHERE file_key = ?"; $videoExists = $class_database->execute($videoCheckSql, [$fileKey]); if (!$videoExists || $videoExists->RecordCount() === 0) { throw new Exception('Video not found', 404); } // If this is a reply, check if parent comment exists if ($parentId) { $parentCheckSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?"; $parentExists = $class_database->execute($parentCheckSql, [$parentId]); if (!$parentExists || $parentExists->RecordCount() === 0) { throw new Exception('Parent comment not found', 404); } $parentUserId = $parentExists->fields['usr_id']; } // Insert comment $sql = "INSERT INTO db_comments (file_key, usr_id, comment_text, comment_date, parent_id, comment_likes) VALUES (?, ?, ?, NOW(), ?, 0)"; $result = $class_database->execute($sql, [$fileKey, $userId, $commentText, $parentId]); if (!$result) { throw new Exception('Failed to create comment', 500); } // Get the inserted comment ID $commentId = $class_database->Insert_ID(); // If this is a reply, create a notification for the parent comment author if ($parentId && $parentUserId != $userId) { $notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date) VALUES (?, 'comment_reply', ?, ?, NOW())"; $class_database->execute($notifSql, [$parentUserId, $userId, $commentId]); } // Get video owner and create notification $videoOwnerSql = "SELECT usr_id FROM db_videofiles WHERE file_key = ?"; $videoOwner = $class_database->execute($videoOwnerSql, [$fileKey]); if ($videoOwner && $videoOwner->fields['usr_id'] != $userId) { $notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date) VALUES (?, 'comment', ?, ?, NOW())"; $class_database->execute($notifSql, [$videoOwner->fields['usr_id'], $userId, $commentId]); } // Return the created comment $getCommentSql = "SELECT c.comment_id, c.usr_id, c.file_key, c.comment_text, c.comment_date, c.comment_likes, c.parent_id, u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified FROM db_comments c LEFT JOIN db_users u ON c.usr_id = u.usr_id WHERE c.comment_id = ?"; $newComment = $class_database->execute($getCommentSql, [$commentId]); $response['success'] = true; $response['data'] = $newComment->fields; echo json_encode($response); } /** * Update comment */ function handleUpdateComment($userId) { global $class_database, $response; $commentId = isset($_GET['id']) ? (int)$_GET['id'] : null; if (!$commentId) { throw new Exception('Comment ID is required', 400); } // Verify ownership $checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?"; $checkResult = $class_database->execute($checkSql, [$commentId]); if (!$checkResult || $checkResult->RecordCount() === 0) { throw new Exception('Comment not found', 404); } if ($checkResult->fields['usr_id'] != $userId) { throw new Exception('You do not have permission to edit this comment', 403); } // Get JSON input $input = json_decode(file_get_contents('php://input'), true); if (!$input || !isset($input['comment_text'])) { throw new Exception('comment_text is required', 400); } $commentText = trim($input['comment_text']); if (empty($commentText)) { throw new Exception('Comment text cannot be empty', 400); } if (strlen($commentText) > 5000) { throw new Exception('Comment text too long (max 5000 characters)', 400); } $commentText = VSecurity::sanitize($commentText); // Update comment $sql = "UPDATE db_comments SET comment_text = ?, comment_edited = 1 WHERE comment_id = ?"; $result = $class_database->execute($sql, [$commentText, $commentId]); if (!$result) { throw new Exception('Failed to update comment', 500); } $response['success'] = true; $response['data'] = ['message' => 'Comment updated successfully']; echo json_encode($response); } /** * Delete comment */ function handleDeleteComment($userId) { global $class_database, $response; $commentId = isset($_GET['id']) ? (int)$_GET['id'] : null; if (!$commentId) { throw new Exception('Comment ID is required', 400); } // Verify ownership or admin status $checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?"; $checkResult = $class_database->execute($checkSql, [$commentId]); if (!$checkResult || $checkResult->RecordCount() === 0) { throw new Exception('Comment not found', 404); } // Check if user is the comment owner or has admin role $isOwner = $checkResult->fields['usr_id'] == $userId; $isAdmin = VAuth::hasPermission('comments.delete.any'); if (!$isOwner && !$isAdmin) { throw new Exception('You do not have permission to delete this comment', 403); } // Delete comment and its replies $sql = "DELETE FROM db_comments WHERE comment_id = ? OR parent_id = ?"; $result = $class_database->execute($sql, [$commentId, $commentId]); if (!$result) { throw new Exception('Failed to delete comment', 500); } $response['success'] = true; $response['data'] = ['message' => 'Comment deleted successfully']; echo json_encode($response); } /** * Like/unlike comment */ function handleLikeComment($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $commentId = isset($input['comment_id']) ? (int)$input['comment_id'] : null; if (!$commentId) { throw new Exception('comment_id is required', 400); } // Check if comment exists $checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?"; $commentExists = $class_database->execute($checkSql, [$commentId]); if (!$commentExists || $commentExists->RecordCount() === 0) { throw new Exception('Comment not found', 404); } $commentOwnerId = $commentExists->fields['usr_id']; // Check if already liked $likeSql = "SELECT 1 FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?"; $existing = $class_database->execute($likeSql, [$commentId, $userId]); if ($existing && $existing->RecordCount() > 0) { // Remove like $deleteSql = "DELETE FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?"; $class_database->execute($deleteSql, [$commentId, $userId]); // Decrement like count $updateSql = "UPDATE db_comments SET comment_likes = comment_likes - 1 WHERE comment_id = ?"; $class_database->execute($updateSql, [$commentId]); $action = 'unliked'; } else { // Add like $insertSql = "INSERT INTO db_comment_likes (comment_id, usr_id, like_date) VALUES (?, ?, NOW())"; $class_database->execute($insertSql, [$commentId, $userId]); // Increment like count $updateSql = "UPDATE db_comments SET comment_likes = comment_likes + 1 WHERE comment_id = ?"; $class_database->execute($updateSql, [$commentId]); // Create notification for comment owner if ($commentOwnerId != $userId) { $notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date) VALUES (?, 'comment_like', ?, ?, NOW())"; $class_database->execute($notifSql, [$commentOwnerId, $userId, $commentId]); } $action = 'liked'; } // Get updated like count $countSql = "SELECT comment_likes FROM db_comments WHERE comment_id = ?"; $countResult = $class_database->execute($countSql, [$commentId]); $response['success'] = true; $response['data'] = [ 'action' => $action, 'like_count' => $countResult->fields['comment_likes'] ]; echo json_encode($response); } /** * Report comment */ function handleReportComment($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $commentId = isset($input['comment_id']) ? (int)$input['comment_id'] : null; $reason = isset($input['reason']) ? VSecurity::sanitize($input['reason']) : ''; if (!$commentId) { throw new Exception('comment_id is required', 400); } if (empty($reason)) { throw new Exception('Reason is required', 400); } // Check if comment exists $checkSql = "SELECT 1 FROM db_comments WHERE comment_id = ?"; $commentExists = $class_database->execute($checkSql, [$commentId]); if (!$commentExists || $commentExists->RecordCount() === 0) { throw new Exception('Comment not found', 404); } // Check if already reported by this user $reportCheckSql = "SELECT 1 FROM db_reports WHERE comment_id = ? AND usr_id = ?"; $existing = $class_database->execute($reportCheckSql, [$commentId, $userId]); if ($existing && $existing->RecordCount() > 0) { throw new Exception('You have already reported this comment', 400); } // Insert report $sql = "INSERT INTO db_reports (comment_id, usr_id, report_reason, report_date, report_status) VALUES (?, ?, ?, NOW(), 'pending')"; $result = $class_database->execute($sql, [$commentId, $userId, $reason]); if (!$result) { throw new Exception('Failed to submit report', 500); } $response['success'] = true; $response['data'] = ['message' => 'Comment reported successfully']; echo json_encode($response); }