$genre) { $placeholders[] = ":genre_{$index}"; $params["genre_{$index}"] = $genre; } $whereClause = !empty($search) ? " AND" : " WHERE"; $sql .= $whereClause . " av.genre IN (" . implode(',', $placeholders) . ")"; } if (!empty($directors)) { $placeholders = []; foreach ($directors as $index => $director) { $placeholders[] = ":director_{$index}"; $params["director_{$index}"] = $director; } $whereClause = (!empty($search) || !empty($genres)) ? " AND" : " WHERE"; $sql .= $whereClause . " av.director IN (" . implode(',', $placeholders) . ")"; } if (!empty($sources)) { $placeholders = []; foreach ($sources as $index => $source) { $placeholders[] = ":source_{$index}"; $params["source_{$index}"] = $source; } $whereClause = (!empty($search) || !empty($genres) || !empty($directors)) ? " AND" : " WHERE"; $sql .= $whereClause . " s.display_name IN (" . implode(',', $placeholders) . ")"; } // Add sorting $sortOptions = [ 'recent' => 'av.created_at DESC', 'oldest' => 'av.created_at ASC', 'title_asc' => 'av.title ASC', 'title_desc' => 'av.title DESC', 'year_asc' => 'av.release_date ASC', 'year_desc' => 'av.release_date DESC', 'rating_asc' => 'av.rating ASC', 'rating_desc' => 'av.rating DESC', 'views_asc' => 'av.watch_count ASC', 'views_desc' => 'av.watch_count DESC', 'runtime_asc' => 'av.runtime_minutes ASC', 'runtime_desc' => 'av.runtime_minutes DESC', ]; $sortOrder = $sortOptions[$sort] ?? $sortOptions['recent']; $sql .= " ORDER BY " . $sortOrder . " LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT); foreach ($params as $key => $value) { $stmt->bindValue(":{$key}", $value); } $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } public static function getTotalCount(\PDO $pdo, string $search = '', array $genres = [], array $directors = [], array $sources = []): int { $sql = "SELECT COUNT(*) as count FROM adult_videos av JOIN sources s ON av.source_id = s.id"; $params = []; if (!empty($search)) { $sql .= " WHERE (av.title LIKE :search OR av.overview LIKE :search)"; $params['search'] = "%{$search}%"; } if (!empty($genres)) { $placeholders = []; foreach ($genres as $index => $genre) { $placeholders[] = ":genre_{$index}"; $params["genre_{$index}"] = $genre; } $whereClause = !empty($search) ? " AND" : " WHERE"; $sql .= $whereClause . " av.genre IN (" . implode(',', $placeholders) . ")"; } if (!empty($directors)) { $placeholders = []; foreach ($directors as $index => $director) { $placeholders[] = ":director_{$index}"; $params["director_{$index}"] = $director; } $whereClause = (!empty($search) || !empty($genres)) ? " AND" : " WHERE"; $sql .= $whereClause . " av.director IN (" . implode(',', $placeholders) . ")"; } if (!empty($sources)) { $placeholders = []; foreach ($sources as $index => $source) { $placeholders[] = ":source_{$index}"; $params["source_{$index}"] = $source; } $whereClause = (!empty($search) || !empty($genres) || !empty($directors)) ? " AND" : " WHERE"; $sql .= $whereClause . " s.display_name IN (" . implode(',', $placeholders) . ")"; } $stmt = $pdo->prepare($sql); foreach ($params as $key => $value) { $stmt->bindValue(":{$key}", $value); } $stmt->execute(); return (int) $stmt->fetch()['count']; } public function markAsWatched(): bool { $stmt = $this->pdo->prepare("UPDATE adult_videos SET watched = 1, watch_count = watch_count + 1, updated_at = NOW() WHERE id = :id"); return $stmt->execute(['id' => $this->id]); } public function markAsUnwatched(): bool { $stmt = $this->pdo->prepare("UPDATE adult_videos SET watched = 0, updated_at = NOW() WHERE id = :id"); return $stmt->execute(['id' => $this->id]); } public function toggleFavorite(): bool { $stmt = $this->pdo->prepare("UPDATE adult_videos SET is_favorite = !is_favorite, updated_at = NOW() WHERE id = :id"); return $stmt->execute(['id' => $this->id]); } public function source(): ?Source { $stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id"); $stmt->execute(['source_id' => $this->source_id]); $sourceData = $stmt->fetch(\PDO::FETCH_ASSOC); return $sourceData ? new Source($this->pdo, $sourceData) : null; } /** * Get all actors associated with this adult video */ public function actors($args) { $stmt = $this->pdo->prepare(" SELECT a.* FROM actors a JOIN actor_adult_video aav ON a.id = aav.actor_id WHERE aav.adult_video_id = :adult_video_id ORDER BY a.name ASC "); $stmt->execute(['adult_video_id' => $args]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Add an actor to this adult video */ public function addActor(int $actorId): bool { $stmt = $this->pdo->prepare(" INSERT IGNORE INTO actor_adult_video (adult_video_id, actor_id) VALUES (:adult_video_id, :actor_id) "); return $stmt->execute([ 'adult_video_id' => $this->id, 'actor_id' => $actorId ]); } /** * Remove an actor from this adult video */ public function removeActor(int $actorId): bool { $stmt = $this->pdo->prepare(" DELETE FROM actor_adult_video WHERE adult_video_id = :adult_video_id AND actor_id = :actor_id "); return $stmt->execute([ 'adult_video_id' => $this->id, 'actor_id' => $actorId ]); } /** * Update the cast field with actor names */ public function updateCastField(): bool { $actors = $this->actors($this->id); $actorNames = array_column($actors, 'name'); $castString = implode(', ', $actorNames); return $this->update($this->id, [ 'cast' => $castString ]); } /** * Find single adult video by ID with metadata processing */ public function find(int $id): ?array { $stmt = $this->pdo->prepare(" SELECT av.*, s.display_name as source_name FROM adult_videos av JOIN sources s ON av.source_id = s.id WHERE av.id = :id "); $stmt->execute(['id' => $id]); $video = $stmt->fetch(\PDO::FETCH_ASSOC); if (!$video) { return null; } // Process metadata to extract additional fields if (!empty($video['metadata'])) { $metadata = json_decode($video['metadata'], true); // Extract poster aspect ratio from metadata if available if (!empty($metadata['poster_aspect_ratio'])) { $video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio']; } // Use local cover path if available, otherwise fall back to original URL if (!empty($metadata['local_cover_path'])) { $video['poster_url'] = $metadata['local_cover_path']; } elseif (!empty($metadata['cover_url'])) { $video['poster_url'] = $metadata['cover_url']; } // Add screenshot URL if available if (!empty($metadata['screenshot_url'])) { $video['screenshot_url'] = $metadata['screenshot_url']; } // Add actors data if available if (!empty($metadata['actors'])) { $video['actors'] = $metadata['actors']; } } return $video; } /** * Get all adult videos with filtering and pagination */ public function findAll(array $filters = [], int $limit = null, int $offset = 0): array { $sql = " SELECT av.*, s.display_name as source_name FROM adult_videos av JOIN sources s ON av.source_id = s.id "; $params = []; $whereClauses = []; // Search filter if (!empty($filters['search'])) { $whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)"; $params['search'] = "%{$filters['search']}%"; } // Genre filter if (!empty($filters['genre'])) { $whereClauses[] = "av.genre = :genre"; $params['genre'] = $filters['genre']; } // Year filter if (!empty($filters['year'])) { $whereClauses[] = "YEAR(av.release_date) = :year"; $params['year'] = $filters['year']; } // Source filter if (!empty($filters['sources'])) { $sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']]; $placeholders = []; foreach ($sources as $index => $source) { $placeholders[] = ":source_{$index}"; $params["source_{$index}"] = $source; } $whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")"; } // Combine WHERE clauses if (!empty($whereClauses)) { $sql .= " WHERE " . implode(' AND ', $whereClauses); } // Add ordering and pagination $sql .= " ORDER BY av.release_date DESC, av.title ASC"; if ($limit) { $sql .= " LIMIT :limit OFFSET :offset"; $params['limit'] = $limit; $params['offset'] = $offset; } $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $results = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Process metadata to extract additional fields foreach ($results as &$video) { if (!empty($video['metadata'])) { $metadata = json_decode($video['metadata'], true); // Extract poster aspect ratio from metadata if available if (!empty($metadata['poster_aspect_ratio'])) { $video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio']; } // Use local cover path if available, otherwise fall back to original URL if (!empty($metadata['local_cover_path'])) { $video['poster_url'] = $metadata['local_cover_path']; } elseif (!empty($metadata['cover_url'])) { $video['poster_url'] = $metadata['cover_url']; } // Add actors data if available if (!empty($metadata['actors'])) { $video['actors'] = $metadata['actors']; } } } return $results; } /** * Count adult videos with filters */ public function count(array $filters = []): int { $sql = " SELECT COUNT(*) as total FROM adult_videos av JOIN sources s ON av.source_id = s.id "; $params = []; $whereClauses = []; // Search filter if (!empty($filters['search'])) { $whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)"; $params['search'] = "%{$filters['search']}%"; } // Genre filter if (!empty($filters['genre'])) { $whereClauses[] = "av.genre = :genre"; $params['genre'] = $filters['genre']; } // Year filter if (!empty($filters['year'])) { $whereClauses[] = "YEAR(av.release_date) = :year"; $params['year'] = $filters['year']; } // Source filter if (!empty($filters['sources'])) { $sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']]; $placeholders = []; foreach ($sources as $index => $source) { $placeholders[] = ":source_{$index}"; $params["source_{$index}"] = $source; } $whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")"; } // Combine WHERE clauses if (!empty($whereClauses)) { $sql .= " WHERE " . implode(' AND ', $whereClauses); } $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $result = $stmt->fetch(\PDO::FETCH_ASSOC); return (int)$result['total']; } /** * Get available genres for filtering */ public static function getAvailableGenres(\PDO $pdo): array { $stmt = $pdo->query(" SELECT DISTINCT genre FROM adult_videos WHERE genre IS NOT NULL AND genre != '' ORDER BY genre "); return $stmt->fetchAll(\PDO::FETCH_COLUMN); } /** * Get available directors for filtering */ public static function getAvailableDirectors(\PDO $pdo): array { $stmt = $pdo->query(" SELECT DISTINCT director FROM adult_videos WHERE director IS NOT NULL AND director != '' ORDER BY director "); return $stmt->fetchAll(\PDO::FETCH_COLUMN); } /** * Get available sources for filtering */ public static function getAvailableSources(\PDO $pdo): array { $stmt = $pdo->query(" SELECT DISTINCT s.display_name FROM sources s JOIN adult_videos av ON s.id = av.source_id WHERE s.display_name IS NOT NULL AND s.display_name != '' ORDER BY s.display_name "); return $stmt->fetchAll(\PDO::FETCH_COLUMN); } /** * Get TV show statistics */ public static function getStats(\PDO $pdo): array { $stmt = $pdo->query(" SELECT COUNT(*) as total_adult_videos, COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_adult_videos, AVG(rating) as avg_rating FROM adult_videos "); return $stmt->fetch(\PDO::FETCH_ASSOC); } /** * Search actors by name */ public static function searchActors(\PDO $pdo, string $query): array { $stmt = $pdo->prepare(" SELECT a.* FROM actors a WHERE a.name LIKE :query ORDER BY a.name LIMIT 10 "); $stmt->execute(['query' => "%{$query}%"]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } }