feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

View File

@@ -0,0 +1,749 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
defined('_ISVALID') or header('Location: /error');
/**
* Social Features and User Interactions System
*/
class VSocial
{
private $logger;
private $db;
private static $instance = null;
public function __construct()
{
$this->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...
}