Files
easystream-main/f_core/f_classes/class.contentmanager.php
SamiAhmed7777 0b7e2d0a5b 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
2025-10-21 00:39:45 -07:00

661 lines
22 KiB
PHP

<?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');
/**
* Content Metadata Management System
*/
class VContentManager
{
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;
}
/**
* Update content metadata
* @param string $contentType Content type (video, image, audio, document)
* @param string $contentId Content ID
* @param array $metadata Metadata to update
* @param int $userId User ID (for permission check)
* @return array Update result
*/
public function updateContentMetadata($contentType, $contentId, $metadata, $userId)
{
try {
// Validate content type
$table = $this->getContentTable($contentType);
if (!$table) {
return [
'success' => false,
'error' => 'Invalid content type'
];
}
// Get existing content
$content = $this->getContent($contentType, $contentId);
if (!$content) {
return [
'success' => false,
'error' => 'Content not found'
];
}
// Check permissions
if ($content['usr_id'] != $userId && !$this->isAdmin($userId)) {
return [
'success' => false,
'error' => 'Permission denied'
];
}
// Validate and sanitize metadata
$validatedMetadata = $this->validateMetadata($metadata);
if (!$validatedMetadata['valid']) {
return [
'success' => false,
'error' => $validatedMetadata['error'],
'details' => $validatedMetadata['details']
];
}
// Update content
$updateData = $validatedMetadata['data'];
$updateData['updated_at'] = date('Y-m-d H:i:s');
$this->db->doUpdate($table, 'file_key', $updateData, $contentId);
// Update tags if provided
if (isset($metadata['tags'])) {
$this->updateContentTags($contentType, $contentId, $metadata['tags']);
}
// Update categories if provided
if (isset($metadata['categories'])) {
$this->updateContentCategories($contentType, $contentId, $metadata['categories']);
}
$this->logger->info('Content metadata updated', [
'content_type' => $contentType,
'content_id' => $contentId,
'user_id' => $userId,
'updated_fields' => array_keys($updateData)
]);
return [
'success' => true,
'message' => 'Content metadata updated successfully',
'updated_fields' => array_keys($updateData)
];
} catch (Exception $e) {
$this->logger->error('Failed to update content metadata', [
'content_type' => $contentType,
'content_id' => $contentId,
'user_id' => $userId,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => 'Failed to update content metadata'
];
}
}
/**
* Bulk update content metadata
* @param string $contentType Content type
* @param array $contentIds Array of content IDs
* @param array $metadata Metadata to update
* @param int $userId User ID
* @return array Update result
*/
public function bulkUpdateMetadata($contentType, $contentIds, $metadata, $userId)
{
try {
$results = [
'success' => 0,
'failed' => 0,
'errors' => []
];
foreach ($contentIds as $contentId) {
$result = $this->updateContentMetadata($contentType, $contentId, $metadata, $userId);
if ($result['success']) {
$results['success']++;
} else {
$results['failed']++;
$results['errors'][$contentId] = $result['error'];
}
}
return [
'success' => true,
'results' => $results,
'message' => "Updated {$results['success']} items, {$results['failed']} failed"
];
} catch (Exception $e) {
$this->logger->error('Bulk metadata update failed', [
'content_type' => $contentType,
'content_count' => count($contentIds),
'user_id' => $userId,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => 'Bulk update failed'
];
}
}
/**
* Get content categories
* @param string $contentType Content type
* @return array Categories
*/
public function getContentCategories($contentType = null)
{
try {
$whereClause = $contentType ? "WHERE content_type = ?" : "";
$params = $contentType ? [$contentType] : [];
$query = "SELECT * FROM db_content_categories {$whereClause} ORDER BY name ASC";
$result = $this->db->doQuery($query, $params);
$categories = [];
while ($row = $this->db->doFetch($result)) {
$categories[] = [
'id' => $row['id'],
'name' => $row['name'],
'slug' => $row['slug'],
'description' => $row['description'],
'content_type' => $row['content_type'],
'parent_id' => $row['parent_id'],
'sort_order' => $row['sort_order']
];
}
return [
'success' => true,
'categories' => $categories
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Create content category
* @param array $categoryData Category data
* @return array Creation result
*/
public function createCategory($categoryData)
{
try {
$validatedData = $this->validateCategoryData($categoryData);
if (!$validatedData['valid']) {
return [
'success' => false,
'error' => $validatedData['error']
];
}
$categoryId = $this->db->doInsert('db_content_categories', $validatedData['data']);
if (!$categoryId) {
throw new Exception('Failed to create category');
}
return [
'success' => true,
'category_id' => $categoryId,
'message' => 'Category created successfully'
];
} catch (Exception $e) {
$this->logger->error('Failed to create category', [
'category_data' => $categoryData,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => 'Failed to create category'
];
}
}
/**
* Get popular tags
* @param string $contentType Content type filter
* @param int $limit Number of tags to return
* @return array Popular tags
*/
public function getPopularTags($contentType = null, $limit = 50)
{
try {
$whereClause = $contentType ? "WHERE content_type = ?" : "";
$params = $contentType ? [$contentType] : [];
$params[] = $limit;
$query = "SELECT tag_name, COUNT(*) as usage_count
FROM db_content_tags
{$whereClause}
GROUP BY tag_name
ORDER BY usage_count DESC, tag_name ASC
LIMIT ?";
$result = $this->db->doQuery($query, $params);
$tags = [];
while ($row = $this->db->doFetch($result)) {
$tags[] = [
'name' => $row['tag_name'],
'usage_count' => (int)$row['usage_count']
];
}
return [
'success' => true,
'tags' => $tags
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Search content by metadata
* @param array $searchParams Search parameters
* @param int $limit Limit
* @param int $offset Offset
* @return array Search results
*/
public function searchContent($searchParams, $limit = 20, $offset = 0)
{
try {
$contentType = $searchParams['content_type'] ?? 'video';
$table = $this->getContentTable($contentType);
if (!$table) {
return [
'success' => false,
'error' => 'Invalid content type'
];
}
$whereClause = "WHERE file_active = 1";
$params = [];
// Text search
if (!empty($searchParams['query'])) {
$whereClause .= " AND (file_title LIKE ? OR file_description LIKE ?)";
$searchTerm = '%' . $searchParams['query'] . '%';
$params[] = $searchTerm;
$params[] = $searchTerm;
}
// Category filter
if (!empty($searchParams['category'])) {
$whereClause .= " AND file_category = ?";
$params[] = $searchParams['category'];
}
// User filter
if (!empty($searchParams['user_id'])) {
$whereClause .= " AND usr_id = ?";
$params[] = $searchParams['user_id'];
}
// Date range filter
if (!empty($searchParams['date_from'])) {
$whereClause .= " AND file_date >= ?";
$params[] = $searchParams['date_from'];
}
if (!empty($searchParams['date_to'])) {
$whereClause .= " AND file_date <= ?";
$params[] = $searchParams['date_to'];
}
// Privacy filter
if (!empty($searchParams['privacy'])) {
$whereClause .= " AND privacy = ?";
$params[] = $searchParams['privacy'];
}
// Sort order
$sortOrder = $this->getSortOrder($searchParams['sort'] ?? 'newest');
$query = "SELECT f.*, u.usr_user
FROM {$table} f
JOIN db_accountuser u ON f.usr_id = u.usr_id
{$whereClause}
ORDER BY {$sortOrder}
LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$result = $this->db->doQuery($query, $params);
$content = [];
while ($row = $this->db->doFetch($result)) {
$content[] = $this->formatContentData($row, $contentType);
}
// Get total count
$countQuery = "SELECT COUNT(*) as total FROM {$table} f {$whereClause}";
$countParams = array_slice($params, 0, -2); // Remove limit and offset
$countResult = $this->db->doQuery($countQuery, $countParams);
$totalCount = $this->db->doFetch($countResult)['total'];
return [
'success' => true,
'content' => $content,
'pagination' => [
'total' => $totalCount,
'limit' => $limit,
'offset' => $offset,
'pages' => ceil($totalCount / $limit)
]
];
} catch (Exception $e) {
$this->logger->error('Content search failed', [
'search_params' => $searchParams,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => 'Search failed'
];
}
}
/**
* Private helper methods
*/
private function getContentTable($contentType)
{
$tables = [
'video' => 'db_videofiles',
'image' => 'db_imagefiles',
'audio' => 'db_audiofiles',
'document' => 'db_documentfiles'
];
return $tables[$contentType] ?? null;
}
private function getContent($contentType, $contentId)
{
$table = $this->getContentTable($contentType);
$query = "SELECT * FROM {$table} WHERE file_key = ?";
$result = $this->db->doQuery($query, [$contentId]);
return $this->db->doFetch($result);
}
private function validateMetadata($metadata)
{
$validatedData = [];
$errors = [];
// Title validation
if (isset($metadata['title'])) {
$title = trim($metadata['title']);
if (empty($title)) {
$errors[] = 'Title cannot be empty';
} elseif (strlen($title) > 255) {
$errors[] = 'Title too long (max 255 characters)';
} else {
$validatedData['file_title'] = $title;
}
}
// Description validation
if (isset($metadata['description'])) {
$description = trim($metadata['description']);
if (strlen($description) > 5000) {
$errors[] = 'Description too long (max 5000 characters)';
} else {
$validatedData['file_description'] = $description;
}
}
// Privacy validation
if (isset($metadata['privacy'])) {
$validPrivacy = ['public', 'unlisted', 'private'];
if (!in_array($metadata['privacy'], $validPrivacy)) {
$errors[] = 'Invalid privacy setting';
} else {
$validatedData['privacy'] = $metadata['privacy'];
}
}
// Category validation
if (isset($metadata['category'])) {
$category = trim($metadata['category']);
if (!empty($category)) {
$validatedData['file_category'] = $category;
}
}
return [
'valid' => empty($errors),
'data' => $validatedData,
'error' => empty($errors) ? null : 'Validation failed',
'details' => $errors
];
}
private function updateContentTags($contentType, $contentId, $tags)
{
try {
// Remove existing tags
$this->db->doQuery(
"DELETE FROM db_content_tags WHERE content_type = ? AND content_id = ?",
[$contentType, $contentId]
);
// Add new tags
if (!empty($tags)) {
$tagList = is_array($tags) ? $tags : explode(',', $tags);
foreach ($tagList as $tag) {
$tag = trim($tag);
if (!empty($tag)) {
$tagData = [
'content_type' => $contentType,
'content_id' => $contentId,
'tag_name' => $tag,
'created_at' => date('Y-m-d H:i:s')
];
$this->db->doInsert('db_content_tags', $tagData);
}
}
}
} catch (Exception $e) {
$this->logger->error('Failed to update content tags', [
'content_type' => $contentType,
'content_id' => $contentId,
'error' => $e->getMessage()
]);
}
}
private function updateContentCategories($contentType, $contentId, $categories)
{
try {
// Remove existing category associations
$this->db->doQuery(
"DELETE FROM db_content_category_relations WHERE content_type = ? AND content_id = ?",
[$contentType, $contentId]
);
// Add new category associations
if (!empty($categories)) {
$categoryList = is_array($categories) ? $categories : [$categories];
foreach ($categoryList as $categoryId) {
if (!empty($categoryId)) {
$relationData = [
'content_type' => $contentType,
'content_id' => $contentId,
'category_id' => $categoryId,
'created_at' => date('Y-m-d H:i:s')
];
$this->db->doInsert('db_content_category_relations', $relationData);
}
}
}
} catch (Exception $e) {
$this->logger->error('Failed to update content categories', [
'content_type' => $contentType,
'content_id' => $contentId,
'error' => $e->getMessage()
]);
}
}
private function validateCategoryData($categoryData)
{
$validatedData = [];
$errors = [];
// Name validation
if (empty($categoryData['name'])) {
$errors[] = 'Category name is required';
} else {
$validatedData['name'] = trim($categoryData['name']);
$validatedData['slug'] = $this->generateSlug($validatedData['name']);
}
// Content type validation
$validTypes = ['video', 'image', 'audio', 'document', 'all'];
if (empty($categoryData['content_type']) || !in_array($categoryData['content_type'], $validTypes)) {
$errors[] = 'Valid content type is required';
} else {
$validatedData['content_type'] = $categoryData['content_type'];
}
// Optional fields
$validatedData['description'] = trim($categoryData['description'] ?? '');
$validatedData['parent_id'] = $categoryData['parent_id'] ?? null;
$validatedData['sort_order'] = (int)($categoryData['sort_order'] ?? 0);
$validatedData['created_at'] = date('Y-m-d H:i:s');
return [
'valid' => empty($errors),
'data' => $validatedData,
'error' => empty($errors) ? null : implode(', ', $errors)
];
}
private function generateSlug($name)
{
$slug = strtolower(trim($name));
$slug = preg_replace('/[^a-z0-9-]/', '-', $slug);
$slug = preg_replace('/-+/', '-', $slug);
return trim($slug, '-');
}
private function getSortOrder($sort)
{
switch ($sort) {
case 'newest':
return 'f.file_date DESC';
case 'oldest':
return 'f.file_date ASC';
case 'popular':
return 'f.file_views DESC';
case 'title':
return 'f.file_title ASC';
case 'rating':
return 'f.file_likes DESC';
default:
return 'f.file_date DESC';
}
}
private function formatContentData($row, $contentType)
{
return [
'id' => $row['file_key'],
'type' => $contentType,
'title' => $row['file_title'],
'description' => $row['file_description'] ?? '',
'category' => $row['file_category'] ?? '',
'privacy' => $row['privacy'] ?? 'public',
'views' => (int)($row['file_views'] ?? 0),
'likes' => (int)($row['file_likes'] ?? 0),
'dislikes' => (int)($row['file_dislikes'] ?? 0),
'comments' => (int)($row['file_comments_count'] ?? 0),
'uploaded_at' => $row['file_date'],
'updated_at' => $row['updated_at'] ?? $row['file_date'],
'uploader' => [
'id' => $row['usr_id'],
'username' => $row['usr_user']
]
];
}
private function isAdmin($userId)
{
try {
$query = "SELECT usr_role FROM db_accountuser WHERE usr_id = ?";
$result = $this->db->doQuery($query, [$userId]);
$user = $this->db->doFetch($result);
return $user && in_array($user['usr_role'], ['admin', 'moderator']);
} catch (Exception $e) {
return false;
}
}
}