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; } } }