logger = VLogger::getInstance(); $this->db = VDatabase::getInstance(); } public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Handle like/dislike action on content * @param string $contentType Content type (video, comment, etc.) * @param string $contentId Content ID * @param string $userId User ID * @param string $action Action (like, dislike, remove) * @return array Result */ public function handleVote($contentType, $contentId, $userId, $action) { try { if (!$userId) { return [ 'success' => false, 'error' => 'User must be logged in to vote' ]; } // Validate content exists if (!$this->validateContent($contentType, $contentId)) { return [ 'success' => false, 'error' => 'Content not found' ]; } // Check existing vote $existingVote = $this->getExistingVote($contentType, $contentId, $userId); // Handle vote logic $result = $this->processVote($contentType, $contentId, $userId, $action, $existingVote); // Update content vote counts $this->updateVoteCounts($contentType, $contentId); // Log the action $this->logVoteAction($contentType, $contentId, $userId, $action, $result); return [ 'success' => true, 'action' => $result['action'], 'vote_counts' => $this->getVoteCounts($contentType, $contentId), 'user_vote' => $result['user_vote'] ]; } catch (Exception $e) { $this->logger->error('Vote handling failed', [ 'content_type' => $contentType, 'content_id' => $contentId, 'user_id' => $userId, 'action' => $action, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to process vote' ]; } } /** * Add comment to content * @param string $contentType Content type * @param string $contentId Content ID * @param string $userId User ID * @param string $comment Comment text * @param string $parentId Parent comment ID (for replies) * @return array Result */ public function addComment($contentType, $contentId, $userId, $comment, $parentId = null) { try { if (!$userId) { return [ 'success' => false, 'error' => 'User must be logged in to comment' ]; } // Validate and sanitize comment $comment = trim($comment); if (empty($comment)) { return [ 'success' => false, 'error' => 'Comment cannot be empty' ]; } if (strlen($comment) > 2000) { return [ 'success' => false, 'error' => 'Comment too long (max 2000 characters)' ]; } // Validate content exists if (!$this->validateContent($contentType, $contentId)) { return [ 'success' => false, 'error' => 'Content not found' ]; } // Check rate limiting if (!$this->checkCommentRateLimit($userId)) { return [ 'success' => false, 'error' => 'Too many comments. Please wait before commenting again.' ]; } // Validate parent comment if replying if ($parentId && !$this->validateParentComment($parentId, $contentType, $contentId)) { return [ 'success' => false, 'error' => 'Parent comment not found' ]; } // Filter comment content $filteredComment = $this->filterCommentContent($comment); // Create comment $commentData = [ 'content_type' => $contentType, 'content_id' => $contentId, 'user_id' => $userId, 'parent_id' => $parentId, 'comment_text' => $filteredComment, 'comment_status' => 'approved', // Could be 'pending' for moderation 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'likes_count' => 0, 'dislikes_count' => 0, 'replies_count' => 0 ]; $commentId = $this->db->doInsert('db_comments', $commentData); if (!$commentId) { throw new Exception('Failed to create comment'); } // Update parent comment reply count if ($parentId) { $this->db->doQuery( "UPDATE db_comments SET replies_count = replies_count + 1 WHERE id = ?", [$parentId] ); } // Update content comment count $this->updateCommentCount($contentType, $contentId); // Get comment with user info $commentWithUser = $this->getCommentWithUser($commentId); // Send notifications $this->sendCommentNotifications($contentType, $contentId, $userId, $parentId); $this->logger->info('Comment added successfully', [ 'comment_id' => $commentId, 'content_type' => $contentType, 'content_id' => $contentId, 'user_id' => $userId ]); return [ 'success' => true, 'comment_id' => $commentId, 'comment' => $commentWithUser ]; } catch (Exception $e) { $this->logger->error('Comment creation failed', [ 'content_type' => $contentType, 'content_id' => $contentId, 'user_id' => $userId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to add comment' ]; } } /** * Get comments for content * @param string $contentType Content type * @param string $contentId Content ID * @param int $page Page number * @param int $limit Comments per page * @param string $sort Sort order (newest, oldest, popular) * @return array Comments data */ public function getComments($contentType, $contentId, $page = 1, $limit = 20, $sort = 'newest') { try { $offset = ($page - 1) * $limit; // Build sort clause $sortClause = $this->buildCommentSortClause($sort); // Get top-level comments $query = "SELECT c.*, u.usr_user, u.usr_avatar, u.usr_verified FROM db_comments c JOIN db_accountuser u ON c.user_id = u.usr_id WHERE c.content_type = ? AND c.content_id = ? AND c.parent_id IS NULL AND c.comment_status = 'approved' ORDER BY {$sortClause} LIMIT ? OFFSET ?"; $result = $this->db->doQuery($query, [$contentType, $contentId, $limit, $offset]); $comments = []; while ($row = $this->db->doFetch($result)) { $comment = $this->formatComment($row); // Get replies if any if ($row['replies_count'] > 0) { $comment['replies'] = $this->getCommentReplies($row['id'], 3); // Get first 3 replies $comment['has_more_replies'] = $row['replies_count'] > 3; } $comments[] = $comment; } // Get total count $countQuery = "SELECT COUNT(*) as total FROM db_comments WHERE content_type = ? AND content_id = ? AND parent_id IS NULL AND comment_status = 'approved'"; $countResult = $this->db->doQuery($countQuery, [$contentType, $contentId]); $totalCount = $this->db->doFetch($countResult)['total']; return [ 'success' => true, 'comments' => $comments, 'pagination' => [ 'current_page' => $page, 'per_page' => $limit, 'total' => $totalCount, 'total_pages' => ceil($totalCount / $limit), 'has_more' => $page < ceil($totalCount / $limit) ] ]; } catch (Exception $e) { $this->logger->error('Failed to get comments', [ 'content_type' => $contentType, 'content_id' => $contentId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to load comments' ]; } } /** * Get comment replies * @param string $parentId Parent comment ID * @param int $limit Number of replies to get * @return array Replies */ public function getCommentReplies($parentId, $limit = 10) { try { $query = "SELECT c.*, u.usr_user, u.usr_avatar, u.usr_verified FROM db_comments c JOIN db_accountuser u ON c.user_id = u.usr_id WHERE c.parent_id = ? AND c.comment_status = 'approved' ORDER BY c.created_at ASC LIMIT ?"; $result = $this->db->doQuery($query, [$parentId, $limit]); $replies = []; while ($row = $this->db->doFetch($result)) { $replies[] = $this->formatComment($row); } return $replies; } catch (Exception $e) { $this->logger->error('Failed to get comment replies', [ 'parent_id' => $parentId, 'error' => $e->getMessage() ]); return []; } } /** * Delete comment * @param string $commentId Comment ID * @param string $userId User ID * @return array Result */ public function deleteComment($commentId, $userId) { try { // Get comment details $comment = $this->getComment($commentId); if (!$comment) { return [ 'success' => false, 'error' => 'Comment not found' ]; } // Check permissions (owner or admin) if ($comment['user_id'] != $userId && !$this->isAdmin($userId)) { return [ 'success' => false, 'error' => 'Permission denied' ]; } // Soft delete comment $this->db->doUpdate('db_comments', 'id', [ 'comment_status' => 'deleted', 'updated_at' => date('Y-m-d H:i:s') ], $commentId); // Update parent reply count if this is a reply if ($comment['parent_id']) { $this->db->doQuery( "UPDATE db_comments SET replies_count = replies_count - 1 WHERE id = ?", [$comment['parent_id']] ); } // Update content comment count $this->updateCommentCount($comment['content_type'], $comment['content_id']); $this->logger->info('Comment deleted', [ 'comment_id' => $commentId, 'user_id' => $userId ]); return [ 'success' => true, 'message' => 'Comment deleted successfully' ]; } catch (Exception $e) { $this->logger->error('Comment deletion failed', [ 'comment_id' => $commentId, 'user_id' => $userId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to delete comment' ]; } } /** * Generate social sharing URLs * @param string $contentType Content type * @param string $contentId Content ID * @return array Sharing URLs */ public function generateSharingUrls($contentType, $contentId) { try { // Get content details $content = $this->getContentDetails($contentType, $contentId); if (!$content) { return [ 'success' => false, 'error' => 'Content not found' ]; } $baseUrl = $this->getBaseUrl(); $contentUrl = $baseUrl . $this->getContentUrl($contentType, $contentId); $title = urlencode($content['title']); $description = urlencode(substr($content['description'], 0, 200)); $sharingUrls = [ 'facebook' => "https://www.facebook.com/sharer/sharer.php?u=" . urlencode($contentUrl), 'twitter' => "https://twitter.com/intent/tweet?url=" . urlencode($contentUrl) . "&text=" . $title, 'linkedin' => "https://www.linkedin.com/sharing/share-offsite/?url=" . urlencode($contentUrl), 'reddit' => "https://reddit.com/submit?url=" . urlencode($contentUrl) . "&title=" . $title, 'whatsapp' => "https://wa.me/?text=" . $title . "%20" . urlencode($contentUrl), 'telegram' => "https://t.me/share/url?url=" . urlencode($contentUrl) . "&text=" . $title, 'email' => "mailto:?subject=" . $title . "&body=" . $description . "%0A%0A" . urlencode($contentUrl), 'copy_link' => $contentUrl ]; return [ 'success' => true, 'sharing_urls' => $sharingUrls, 'content_url' => $contentUrl, 'title' => $content['title'] ]; } catch (Exception $e) { $this->logger->error('Failed to generate sharing URLs', [ 'content_type' => $contentType, 'content_id' => $contentId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to generate sharing URLs' ]; } } /** * Track social sharing action * @param string $contentType Content type * @param string $contentId Content ID * @param string $platform Sharing platform * @param string $userId User ID (optional) */ public function trackShare($contentType, $contentId, $platform, $userId = null) { try { $shareData = [ 'content_type' => $contentType, 'content_id' => $contentId, 'platform' => $platform, 'user_id' => $userId, 'ip_address' => $this->getRealIpAddress(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; $this->db->doInsert('db_social_shares', $shareData); // Update content share count $this->updateShareCount($contentType, $contentId); $this->logger->info('Social share tracked', [ 'content_type' => $contentType, 'content_id' => $contentId, 'platform' => $platform, 'user_id' => $userId ]); } catch (Exception $e) { $this->logger->error('Failed to track share', [ 'content_type' => $contentType, 'content_id' => $contentId, 'platform' => $platform, 'error' => $e->getMessage() ]); } } /** * Get social statistics for content * @param string $contentType Content type * @param string $contentId Content ID * @return array Social stats */ public function getSocialStats($contentType, $contentId) { try { // Get vote counts $voteCounts = $this->getVoteCounts($contentType, $contentId); // Get comment count $commentCount = $this->getCommentCount($contentType, $contentId); // Get share count $shareCount = $this->getShareCount($contentType, $contentId); return [ 'success' => true, 'stats' => [ 'likes' => $voteCounts['likes'], 'dislikes' => $voteCounts['dislikes'], 'comments' => $commentCount, 'shares' => $shareCount, 'engagement_score' => $this->calculateEngagementScore($voteCounts, $commentCount, $shareCount) ] ]; } catch (Exception $e) { $this->logger->error('Failed to get social stats', [ 'content_type' => $contentType, 'content_id' => $contentId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => 'Failed to get social statistics' ]; } } /** * Private helper methods */ private function validateContent($contentType, $contentId) { $table = $this->getContentTable($contentType); $idField = $this->getContentIdField($contentType); $query = "SELECT 1 FROM {$table} WHERE {$idField} = ? AND file_active = 1 LIMIT 1"; $result = $this->db->doQuery($query, [$contentId]); return $this->db->doFetch($result) !== false; } private function getExistingVote($contentType, $contentId, $userId) { $query = "SELECT vote_type FROM db_votes WHERE content_type = ? AND content_id = ? AND user_id = ?"; $result = $this->db->doQuery($query, [$contentType, $contentId, $userId]); $vote = $this->db->doFetch($result); return $vote ? $vote['vote_type'] : null; } private function processVote($contentType, $contentId, $userId, $action, $existingVote) { if ($action === 'remove' || $action === $existingVote) { // Remove existing vote $this->db->doQuery( "DELETE FROM db_votes WHERE content_type = ? AND content_id = ? AND user_id = ?", [$contentType, $contentId, $userId] ); return [ 'action' => 'removed', 'user_vote' => null ]; } else { // Add or update vote $voteData = [ 'content_type' => $contentType, 'content_id' => $contentId, 'user_id' => $userId, 'vote_type' => $action, 'created_at' => date('Y-m-d H:i:s') ]; if ($existingVote) { // Update existing vote $this->db->doQuery( "UPDATE db_votes SET vote_type = ?, created_at = ? WHERE content_type = ? AND content_id = ? AND user_id = ?", [$action, date('Y-m-d H:i:s'), $contentType, $contentId, $userId] ); } else { // Insert new vote $this->db->doInsert('db_votes', $voteData); } return [ 'action' => $existingVote ? 'updated' : 'added', 'user_vote' => $action ]; } } private function updateVoteCounts($contentType, $contentId) { $table = $this->getContentTable($contentType); $idField = $this->getContentIdField($contentType); // Get current vote counts $query = "SELECT SUM(CASE WHEN vote_type = 'like' THEN 1 ELSE 0 END) as likes, SUM(CASE WHEN vote_type = 'dislike' THEN 1 ELSE 0 END) as dislikes FROM db_votes WHERE content_type = ? AND content_id = ?"; $result = $this->db->doQuery($query, [$contentType, $contentId]); $counts = $this->db->doFetch($result); // Update content table $updateQuery = "UPDATE {$table} SET file_likes = ?, file_dislikes = ? WHERE {$idField} = ?"; $this->db->doQuery($updateQuery, [ $counts['likes'] ?? 0, $counts['dislikes'] ?? 0, $contentId ]); } private function getVoteCounts($contentType, $contentId) { $query = "SELECT SUM(CASE WHEN vote_type = 'like' THEN 1 ELSE 0 END) as likes, SUM(CASE WHEN vote_type = 'dislike' THEN 1 ELSE 0 END) as dislikes FROM db_votes WHERE content_type = ? AND content_id = ?"; $result = $this->db->doQuery($query, [$contentType, $contentId]); $counts = $this->db->doFetch($result); return [ 'likes' => (int)($counts['likes'] ?? 0), 'dislikes' => (int)($counts['dislikes'] ?? 0) ]; } private function checkCommentRateLimit($userId) { // Check if user has posted more than 10 comments in the last hour $query = "SELECT COUNT(*) as count FROM db_comments WHERE user_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"; $result = $this->db->doQuery($query, [$userId]); $count = $this->db->doFetch($result)['count']; return $count < 10; } private function filterCommentContent($comment) { // Basic content filtering $comment = strip_tags($comment); $comment = htmlspecialchars($comment, ENT_QUOTES, 'UTF-8'); // Remove excessive whitespace $comment = preg_replace('/\s+/', ' ', $comment); return trim($comment); } private function formatComment($row) { return [ 'id' => $row['id'], 'content_type' => $row['content_type'], 'content_id' => $row['content_id'], 'parent_id' => $row['parent_id'], 'comment_text' => $row['comment_text'], 'created_at' => $row['created_at'], 'likes_count' => (int)$row['likes_count'], 'dislikes_count' => (int)$row['dislikes_count'], 'replies_count' => (int)$row['replies_count'], 'user' => [ 'id' => $row['user_id'], 'username' => $row['usr_user'], 'avatar' => $row['usr_avatar'], 'verified' => (bool)$row['usr_verified'] ], 'replies' => [] ]; } private function getContentTable($contentType) { $tables = [ 'video' => 'db_videofiles', 'image' => 'db_imagefiles', 'audio' => 'db_audiofiles', 'document' => 'db_documentfiles' ]; return $tables[$contentType] ?? 'db_videofiles'; } private function getContentIdField($contentType) { return 'file_key'; } private function getBaseUrl() { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; return $protocol . '://' . $host; } private function getRealIpAddress() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { return $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { return $_SERVER['HTTP_X_FORWARDED_FOR']; } else { return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } } private function calculateEngagementScore($voteCounts, $commentCount, $shareCount) { // Simple engagement score calculation $likes = $voteCounts['likes']; $dislikes = $voteCounts['dislikes']; return ($likes * 1) + ($commentCount * 2) + ($shareCount * 3) - ($dislikes * 0.5); } // Additional helper methods would be implemented here... }