mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
Remove obsolete test scripts and add new API controllers for dashboard and game management
- Deleted test scripts: test_jellyfin_execution.php, test_stash.php, test_xbvr.php, test_xbvr_sync.php, vite.config.js - Added DashboardController for fetching dashboard statistics and recent activity - Added GameController for managing games, including fetching all games, game details, and games by category - Introduced various check scripts to validate database structures and data integrity for adult videos, games, gender data, posters, and TV show actors
This commit is contained in:
@@ -8,6 +8,35 @@ use App\Controllers\Controller;
|
||||
|
||||
class BaseApiController extends Controller
|
||||
{
|
||||
protected function getPdo(): \PDO
|
||||
{
|
||||
// Get PDO from the container - this assumes PDO is registered in the DI container
|
||||
global $container;
|
||||
if ($container && $container->has('pdo')) {
|
||||
return $container->get('pdo');
|
||||
}
|
||||
|
||||
// Fallback to creating a new PDO connection
|
||||
$host = $_ENV['DB_HOST'] ?? 'localhost';
|
||||
$dbname = $_ENV['DB_NAME'] ?? 'medialib';
|
||||
$username = $_ENV['DB_USER'] ?? 'root';
|
||||
$password = $_ENV['DB_PASS'] ?? '';
|
||||
|
||||
try {
|
||||
return new \PDO(
|
||||
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
|
||||
$username,
|
||||
$password,
|
||||
[
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
||||
\PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Exception('Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
protected function success(Response $response, $data = null, int $status = 200): Response
|
||||
{
|
||||
$responseData = ['success' => true];
|
||||
|
||||
206
app/Controllers/Api/DashboardController.php
Normal file
206
app/Controllers/Api/DashboardController.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Api\ApiController;
|
||||
|
||||
class DashboardController extends ApiController
|
||||
{
|
||||
private \PDO $pdo;
|
||||
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard statistics
|
||||
*/
|
||||
public function getStats(Request $request, Response $response): Response
|
||||
{
|
||||
try {
|
||||
$stats = [];
|
||||
|
||||
// Get movies count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM movies");
|
||||
$stmt->execute();
|
||||
$moviesCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Total Movies',
|
||||
'value' => number_format($moviesCount),
|
||||
'icon' => 'FilmIcon',
|
||||
'color' => 'bg-blue-500',
|
||||
'href' => '/movies'
|
||||
];
|
||||
|
||||
// Get TV shows count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM tv_shows");
|
||||
$stmt->execute();
|
||||
$tvShowsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'TV Shows',
|
||||
'value' => number_format($tvShowsCount),
|
||||
'icon' => 'TvIcon',
|
||||
'color' => 'bg-purple-500',
|
||||
'href' => '/tvshows'
|
||||
];
|
||||
|
||||
// Get games count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM games");
|
||||
$stmt->execute();
|
||||
$gamesCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Games',
|
||||
'value' => number_format($gamesCount),
|
||||
'icon' => 'GamepadIcon',
|
||||
'color' => 'bg-green-500',
|
||||
'href' => '/games'
|
||||
];
|
||||
|
||||
// Get music albums count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM music_albums");
|
||||
$stmt->execute();
|
||||
$albumsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Music Albums',
|
||||
'value' => number_format($albumsCount),
|
||||
'icon' => 'MusicalNoteIcon',
|
||||
'color' => 'bg-pink-500',
|
||||
'href' => '/music'
|
||||
];
|
||||
|
||||
// Get adult videos count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM adult_videos");
|
||||
$stmt->execute();
|
||||
$adultCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Adult Videos',
|
||||
'value' => number_format($adultCount),
|
||||
'icon' => 'LockClosedIcon',
|
||||
'color' => 'bg-red-500',
|
||||
'href' => '/adult'
|
||||
];
|
||||
|
||||
// Get actors count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM actors");
|
||||
$stmt->execute();
|
||||
$actorsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Actors',
|
||||
'value' => number_format($actorsCount),
|
||||
'icon' => 'UserIcon',
|
||||
'color' => 'bg-indigo-500',
|
||||
'href' => '/actors'
|
||||
];
|
||||
|
||||
return $this->success($response, $stats);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($response, 'Failed to fetch dashboard stats', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent activity
|
||||
*/
|
||||
public function getRecentActivity(Request $request, Response $response): Response
|
||||
{
|
||||
try {
|
||||
$activities = [];
|
||||
|
||||
// Get recent movies (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added movie' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'movie' as type
|
||||
FROM movies
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentMovies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentMovies);
|
||||
|
||||
// Get recent TV shows (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added TV show' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'tvshow' as type
|
||||
FROM tv_shows
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentTvShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentTvShows);
|
||||
|
||||
// Get recent games (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added game' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'game' as type
|
||||
FROM games
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentGames = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentGames);
|
||||
|
||||
// Get recent music albums (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added album' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'music' as type
|
||||
FROM music_albums
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentAlbums = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentAlbums);
|
||||
|
||||
// Sort all activities by time (most recent first)
|
||||
usort($activities, function($a, $b) {
|
||||
return strtotime($b['time']) - strtotime($a['time']);
|
||||
});
|
||||
|
||||
// Take only the 10 most recent activities
|
||||
$activities = array_slice($activities, 0, 10);
|
||||
|
||||
// Format time to be more relative
|
||||
foreach ($activities as &$activity) {
|
||||
$activity['id'] = uniqid();
|
||||
$activity['time'] = $this->formatRelativeTime($activity['time']);
|
||||
}
|
||||
|
||||
return $this->success($response, $activities);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($response, 'Failed to fetch recent activity', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time to relative format (simplified version)
|
||||
*/
|
||||
private function formatRelativeTime($dateString): string
|
||||
{
|
||||
$date = strtotime($dateString);
|
||||
$now = time();
|
||||
$diff = $now - $date;
|
||||
|
||||
if ($diff < 3600) {
|
||||
$minutes = floor($diff / 60);
|
||||
return $minutes <= 1 ? 'Just now' : "$minutes minutes ago";
|
||||
} elseif ($diff < 86400) {
|
||||
$hours = floor($diff / 3600);
|
||||
return $hours <= 1 ? '1 hour ago' : "$hours hours ago";
|
||||
} elseif ($diff < 604800) {
|
||||
$days = floor($diff / 86400);
|
||||
return $days <= 1 ? '1 day ago' : "$days days ago";
|
||||
} else {
|
||||
return date('M j, Y', $date);
|
||||
}
|
||||
}
|
||||
}
|
||||
364
app/Controllers/Api/GameController.php
Normal file
364
app/Controllers/Api/GameController.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\Game;
|
||||
use App\Controllers\Api\BaseApiController;
|
||||
|
||||
class GameController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* Get all games grouped by completion status
|
||||
*/
|
||||
public function index(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
$search = $queryParams['search'] ?? '';
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
try {
|
||||
// Get games from database
|
||||
$games = $this->getAllGamesWithCategories($search, $sort);
|
||||
|
||||
// Group games by completion status
|
||||
$groupedGames = $this->groupGamesByCategory($games);
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $groupedGames
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch games: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get game by ID
|
||||
*/
|
||||
public function show(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$gameId = $args['id'] ?? null;
|
||||
|
||||
if (!$gameId) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Game ID is required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$game = $this->getGameDetails($gameId);
|
||||
|
||||
if (!$game) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Game not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $game
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch game: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get games by category (BEATEN, PLAYING, etc.)
|
||||
*/
|
||||
public function getByCategory(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$category = strtoupper($args['category'] ?? '');
|
||||
$queryParams = $request->getQueryParams();
|
||||
$search = $queryParams['search'] ?? '';
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
if (!in_array($category, ['BEATEN', 'PLAYING', 'COMPLETED', 'UNPLAYED'])) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Invalid category. Must be one of: BEATEN, PLAYING, COMPLETED, UNPLAYED'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$games = $this->getGamesByCategory($category, $search, $sort);
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'category' => $category,
|
||||
'games' => $games,
|
||||
'count' => count($games)
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch games by category: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all games from database
|
||||
*/
|
||||
private function getAllGamesWithCategories(string $search = '', string $sort = 'title_asc'): array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.backdrop_url,
|
||||
g.rating,
|
||||
g.release_date,
|
||||
g.platform,
|
||||
g.developer,
|
||||
g.genres,
|
||||
g.playtime_hours,
|
||||
g.completion_status,
|
||||
g.last_played,
|
||||
g.community_score,
|
||||
g.critic_score,
|
||||
g.source_name
|
||||
FROM games g
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
$params = [];
|
||||
|
||||
// Add search filter
|
||||
if (!empty($search)) {
|
||||
$sql .= " AND (g.title LIKE :search OR g.developer LIKE :search)";
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
// Add sorting
|
||||
switch ($sort) {
|
||||
case 'title_desc':
|
||||
$sql .= " ORDER BY g.title DESC";
|
||||
break;
|
||||
case 'year_asc':
|
||||
$sql .= " ORDER BY g.release_date ASC";
|
||||
break;
|
||||
case 'year_desc':
|
||||
$sql .= " ORDER BY g.release_date DESC";
|
||||
break;
|
||||
case 'playtime_desc':
|
||||
$sql .= " ORDER BY g.playtime_hours DESC";
|
||||
break;
|
||||
case 'rating_desc':
|
||||
$sql .= " ORDER BY g.rating DESC";
|
||||
break;
|
||||
case 'last_played_desc':
|
||||
$sql .= " ORDER BY g.last_played DESC";
|
||||
break;
|
||||
default:
|
||||
$sql .= " ORDER BY g.title ASC";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$games = [];
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
// Parse genres if stored as JSON
|
||||
if (!empty($row['genres'])) {
|
||||
$genres = json_decode($row['genres'], true);
|
||||
$row['genres'] = is_array($genres) ? $genres : [];
|
||||
} else {
|
||||
$row['genres'] = [];
|
||||
}
|
||||
|
||||
// Set default completion status
|
||||
if (empty($row['completion_status'])) {
|
||||
$row['completion_status'] = 'UNPLAYED';
|
||||
}
|
||||
|
||||
$games[] = $row;
|
||||
}
|
||||
|
||||
return $games;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group games by completion status
|
||||
*/
|
||||
private function groupGamesByCategory(array $games): array
|
||||
{
|
||||
$categories = [
|
||||
'BEATEN' => ['name' => 'BEATEN', 'count' => 0, 'games' => []],
|
||||
'PLAYING' => ['name' => 'PLAYING', 'count' => 0, 'games' => []],
|
||||
'COMPLETED' => ['name' => 'COMPLETED', 'count' => 0, 'games' => []],
|
||||
'UNPLAYED' => ['name' => 'UNPLAYED', 'count' => 0, 'games' => []]
|
||||
];
|
||||
|
||||
foreach ($games as $game) {
|
||||
$status = $game['completion_status'] ?? 'UNPLAYED';
|
||||
if (isset($categories[$status])) {
|
||||
$categories[$status]['games'][] = $game;
|
||||
$categories[$status]['count']++;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get games by specific category
|
||||
*/
|
||||
private function getGamesByCategory(string $category, string $search = '', string $sort = 'title_asc'): array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.backdrop_url,
|
||||
g.rating,
|
||||
g.release_date,
|
||||
g.platform,
|
||||
g.developer,
|
||||
g.genres,
|
||||
g.playtime_hours,
|
||||
g.completion_status,
|
||||
g.last_played,
|
||||
g.community_score,
|
||||
g.critic_score,
|
||||
g.source_name
|
||||
FROM games g
|
||||
WHERE g.completion_status = :category
|
||||
";
|
||||
|
||||
$params = [':category' => $category];
|
||||
|
||||
// Add search filter
|
||||
if (!empty($search)) {
|
||||
$sql .= " AND (g.title LIKE :search OR g.developer LIKE :search)";
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
// Add sorting
|
||||
switch ($sort) {
|
||||
case 'title_desc':
|
||||
$sql .= " ORDER BY g.title DESC";
|
||||
break;
|
||||
case 'year_asc':
|
||||
$sql .= " ORDER BY g.release_date ASC";
|
||||
break;
|
||||
case 'year_desc':
|
||||
$sql .= " ORDER BY g.release_date DESC";
|
||||
break;
|
||||
case 'playtime_desc':
|
||||
$sql .= " ORDER BY g.playtime_hours DESC";
|
||||
break;
|
||||
case 'rating_desc':
|
||||
$sql .= " ORDER BY g.rating DESC";
|
||||
break;
|
||||
case 'last_played_desc':
|
||||
$sql .= " ORDER BY g.last_played DESC";
|
||||
break;
|
||||
default:
|
||||
$sql .= " ORDER BY g.title ASC";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$games = [];
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
// Parse genres if stored as JSON
|
||||
if (!empty($row['genres'])) {
|
||||
$genres = json_decode($row['genres'], true);
|
||||
$row['genres'] = is_array($genres) ? $genres : [];
|
||||
} else {
|
||||
$row['genres'] = [];
|
||||
}
|
||||
|
||||
$games[] = $row;
|
||||
}
|
||||
|
||||
return $games;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed game information
|
||||
*/
|
||||
private function getGameDetails(int $gameId): ?array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.backdrop_url,
|
||||
g.rating,
|
||||
g.release_date,
|
||||
g.platform,
|
||||
g.developer,
|
||||
g.publisher,
|
||||
g.genres,
|
||||
g.playtime_hours,
|
||||
g.completion_status,
|
||||
g.last_played,
|
||||
g.community_score,
|
||||
g.critic_score,
|
||||
g.source_name,
|
||||
g.description,
|
||||
g.gameplay,
|
||||
g.synopsis,
|
||||
g.age_ratings,
|
||||
g.version,
|
||||
g.time_to_beat,
|
||||
g.controls,
|
||||
g.pacing,
|
||||
g.perspective,
|
||||
g.series
|
||||
FROM games g
|
||||
WHERE g.id = :id
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':id' => $gameId]);
|
||||
|
||||
$game = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$game) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse JSON fields
|
||||
$jsonFields = ['genres', 'age_ratings'];
|
||||
foreach ($jsonFields as $field) {
|
||||
if (!empty($game[$field])) {
|
||||
$decoded = json_decode($game[$field], true);
|
||||
$game[$field] = is_array($decoded) ? $decoded : [];
|
||||
} else {
|
||||
$game[$field] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Set default completion status
|
||||
if (empty($game['completion_status'])) {
|
||||
$game['completion_status'] = 'UNPLAYED';
|
||||
}
|
||||
|
||||
return $game;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,139 @@ class Actor extends Model
|
||||
'metadata' => 'array'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all actors with filtering and pagination
|
||||
*/
|
||||
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT a.*,
|
||||
COUNT(DISTINCT am.movie_id) as movie_count,
|
||||
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT aav.adult_video_id) as adult_video_count
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "a.name LIKE :search";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Gender filter
|
||||
if (!empty($filters['gender'])) {
|
||||
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
|
||||
$params['gender'] = strtoupper($filters['gender']);
|
||||
}
|
||||
|
||||
// Adult filter
|
||||
if (isset($filters['adult'])) {
|
||||
if ($filters['adult']) {
|
||||
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
|
||||
} else {
|
||||
$whereClauses[] = "aav.adult_video_id IS NULL";
|
||||
}
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY a.id";
|
||||
|
||||
// Add sorting
|
||||
$sortBy = $filters['sort'] ?? 'name';
|
||||
$sortOrder = $filters['order'] ?? 'asc';
|
||||
|
||||
switch ($sortBy) {
|
||||
case 'name':
|
||||
$sql .= " ORDER BY a.name {$sortOrder}";
|
||||
break;
|
||||
case 'age':
|
||||
$sql .= " ORDER BY JSON_EXTRACT(a.metadata, '$.age') {$sortOrder}";
|
||||
break;
|
||||
case 'media_count':
|
||||
$sql .= " ORDER BY (COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id)) {$sortOrder}";
|
||||
break;
|
||||
case 'movie_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT am.movie_id) {$sortOrder}";
|
||||
break;
|
||||
case 'tv_show_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT te.tv_show_id) {$sortOrder}";
|
||||
break;
|
||||
case 'adult_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT aav.adult_video_id) {$sortOrder}";
|
||||
break;
|
||||
default:
|
||||
$sql .= " ORDER BY a.name ASC";
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$sql .= " LIMIT :limit OFFSET :offset";
|
||||
$params['limit'] = $limit;
|
||||
$params['offset'] = $offset;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count actors with filters
|
||||
*/
|
||||
public function count(array $filters = []): int
|
||||
{
|
||||
$sql = "
|
||||
SELECT COUNT(DISTINCT a.id) as total
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "a.name LIKE :search";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Gender filter
|
||||
if (!empty($filters['gender'])) {
|
||||
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
|
||||
$params['gender'] = strtoupper($filters['gender']);
|
||||
}
|
||||
|
||||
// Adult filter
|
||||
if (isset($filters['adult'])) {
|
||||
if ($filters['adult']) {
|
||||
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
|
||||
} else {
|
||||
$whereClauses[] = "aav.adult_video_id IS NULL";
|
||||
}
|
||||
}
|
||||
|
||||
// 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 all movies this actor is associated with
|
||||
*/
|
||||
@@ -29,24 +162,83 @@ class Actor extends Model
|
||||
ORDER BY m.release_date DESC, m.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$movies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each movie
|
||||
foreach ($movies as &$movie) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($movie['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($movie['metadata'])) {
|
||||
$metadata = json_decode($movie['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$movie['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($movie['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$movie['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$movie['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $movies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all TV shows this actor is associated with
|
||||
* Get all TV shows this actor is associated with (via episodes)
|
||||
*/
|
||||
public function tvShows()
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ts.*, s.display_name as source_name
|
||||
SELECT DISTINCT ts.*, s.display_name as source_name
|
||||
FROM tv_shows ts
|
||||
JOIN sources s ON ts.source_id = s.id
|
||||
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
|
||||
WHERE ats.actor_id = :actor_id
|
||||
JOIN tv_episodes te ON ts.id = te.tv_show_id
|
||||
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
|
||||
WHERE ate.actor_id = :actor_id
|
||||
ORDER BY ts.first_air_date DESC, ts.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$tvShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each TV show
|
||||
foreach ($tvShows as &$tvShow) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($tvShow['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($tvShow['metadata'])) {
|
||||
$metadata = json_decode($tvShow['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$tvShow['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($tvShow['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$tvShow['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$tvShow['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tvShows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +255,41 @@ class Actor extends Model
|
||||
ORDER BY av.release_date DESC, av.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$adultVideos = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each adult video
|
||||
foreach ($adultVideos as &$adultVideo) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($adultVideo['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($adultVideo['metadata'])) {
|
||||
$metadata = json_decode($adultVideo['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$adultVideo['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($adultVideo['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$adultVideo['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$adultVideo['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
|
||||
// Add screenshot URL if available
|
||||
if (!empty($metadata['screenshot_url'])) {
|
||||
$adultVideo['screenshot_url'] = $metadata['screenshot_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $adultVideos;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,12 +300,13 @@ class Actor extends Model
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT
|
||||
COUNT(DISTINCT am.movie_id) as movie_count,
|
||||
COUNT(DISTINCT ats.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT aav.adult_video_id) as adult_video_count,
|
||||
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT ats.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
|
||||
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_show ats ON a.id = ats.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
WHERE a.id = :actor_id
|
||||
");
|
||||
@@ -196,8 +423,8 @@ class Actor extends Model
|
||||
// Build ORDER BY clause
|
||||
$orderBy = match ($sort) {
|
||||
'name_desc' => 'name DESC',
|
||||
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
|
||||
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
|
||||
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
|
||||
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
|
||||
default => 'name ASC'
|
||||
};
|
||||
|
||||
@@ -206,7 +433,7 @@ class Actor extends Model
|
||||
SELECT
|
||||
a.*,
|
||||
(SELECT COUNT(*) FROM actor_movie WHERE actor_id = a.id) as movie_count,
|
||||
(SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = a.id) as tv_show_count,
|
||||
(SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = a.id) as tv_show_count,
|
||||
(SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = a.id) as adult_video_count
|
||||
FROM actors a
|
||||
{$whereClause}
|
||||
@@ -279,10 +506,11 @@ class Actor extends Model
|
||||
private function getActorTvShows(int $actorId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ts.id, ts.title
|
||||
SELECT DISTINCT ts.id, ts.title
|
||||
FROM tv_shows ts
|
||||
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
|
||||
WHERE ats.actor_id = ?
|
||||
JOIN tv_episodes te ON ts.id = te.tv_show_id
|
||||
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
|
||||
WHERE ate.actor_id = ?
|
||||
ORDER BY ts.title
|
||||
");
|
||||
$stmt->execute([$actorId]);
|
||||
|
||||
@@ -230,7 +230,7 @@ class AdultVideo extends Model
|
||||
*/
|
||||
public function updateCastField(): bool
|
||||
{
|
||||
$actors = $this->actors();
|
||||
$actors = $this->actors($this->id);
|
||||
$actorNames = array_column($actors, 'name');
|
||||
$castString = implode(', ', $actorNames);
|
||||
|
||||
@@ -239,6 +239,194 @@ class AdultVideo extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -356,62 +356,93 @@ class TvShow extends Model
|
||||
|
||||
public function getSeasonsWithEpisodes(): array
|
||||
{
|
||||
// Get all episodes for this TV show, grouped by season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT season_number,
|
||||
COUNT(*) as episode_count,
|
||||
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
|
||||
FROM tv_episodes
|
||||
WHERE tv_show_id = :tv_show_id
|
||||
GROUP BY season_number
|
||||
ORDER BY season_number ASC
|
||||
");
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$seasons = [];
|
||||
|
||||
// For each season, get the episodes and create a season object
|
||||
foreach ($seasonStats as $stat) {
|
||||
$seasonNumber = $stat['season_number'];
|
||||
|
||||
// Get episodes for this season
|
||||
try {
|
||||
// Get all episodes for this TV show, grouped by season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT e.*
|
||||
FROM tv_episodes e
|
||||
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
|
||||
ORDER BY e.episode_number ASC
|
||||
SELECT season_number,
|
||||
COUNT(*) as episode_count,
|
||||
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
|
||||
FROM tv_episodes
|
||||
WHERE tv_show_id = :tv_show_id
|
||||
GROUP BY season_number
|
||||
ORDER BY season_number ASC
|
||||
");
|
||||
$stmt->execute([
|
||||
'tv_show_id' => $this->id,
|
||||
'season_number' => $seasonNumber
|
||||
]);
|
||||
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Add actors for each episode
|
||||
foreach ($episodes as &$episode) {
|
||||
$episodeStmt = $this->pdo->prepare("
|
||||
SELECT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
WHERE ate.tv_episode_id = :tv_episode_id
|
||||
ORDER BY a.name ASC
|
||||
$seasons = [];
|
||||
|
||||
// For each season, get the episodes and create a season object
|
||||
foreach ($seasonStats as $stat) {
|
||||
$seasonNumber = $stat['season_number'];
|
||||
|
||||
// Get episodes for this season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT e.*
|
||||
FROM tv_episodes e
|
||||
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
|
||||
ORDER BY e.episode_number ASC
|
||||
");
|
||||
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
|
||||
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$stmt->execute([
|
||||
'tv_show_id' => $this->id,
|
||||
'season_number' => $seasonNumber
|
||||
]);
|
||||
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Add actors for each episode
|
||||
foreach ($episodes as &$episode) {
|
||||
try {
|
||||
$episodeStmt = $this->pdo->prepare("
|
||||
SELECT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
WHERE ate.tv_episode_id = :tv_episode_id
|
||||
ORDER BY a.name ASC
|
||||
");
|
||||
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
|
||||
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
$episode['actors'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Create a season object (simulating the old seasons table structure)
|
||||
$seasons[] = [
|
||||
'id' => null, // No seasons table, so no ID
|
||||
'season_number' => $seasonNumber,
|
||||
'episode_count' => (int)$stat['episode_count'],
|
||||
'watched_episodes' => (int)$stat['watched_episodes'],
|
||||
'episodes' => $episodes
|
||||
];
|
||||
}
|
||||
|
||||
// Create a season object (simulating the old seasons table structure)
|
||||
$seasons[] = [
|
||||
'id' => null, // No seasons table, so no ID
|
||||
'season_number' => $seasonNumber,
|
||||
'episode_count' => (int)$stat['episode_count'],
|
||||
'watched_episodes' => (int)$stat['watched_episodes'],
|
||||
'episodes' => $episodes
|
||||
];
|
||||
return $seasons;
|
||||
} catch (\Exception $e) {
|
||||
// Return empty array if tables don't exist or query fails
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $seasons;
|
||||
/**
|
||||
* Get all actors for this TV show (from all episodes)
|
||||
*/
|
||||
public function getActors(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT DISTINCT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
WHERE te.tv_show_id = :tv_show_id
|
||||
ORDER BY a.name ASC
|
||||
");
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
// Return empty array if tables don't exist or query fails
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,6 +502,147 @@ public static function getAvailableGenres(\PDO $pdo): array
|
||||
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available sources from TV shows
|
||||
*/
|
||||
public static function getAvailableSources(\PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query("
|
||||
SELECT DISTINCT s.display_name
|
||||
FROM tv_shows t
|
||||
JOIN sources s ON t.source_id = s.id
|
||||
WHERE t.source_id IS NOT NULL
|
||||
ORDER BY s.display_name
|
||||
");
|
||||
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all TV shows with filtering and pagination
|
||||
*/
|
||||
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT t.*, s.display_name as source_name
|
||||
FROM tv_shows t
|
||||
LEFT JOIN sources s ON t.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "t.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(t.first_air_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 t.first_air_date DESC, t.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 &$tvShow) {
|
||||
if (!empty($tvShow['metadata'])) {
|
||||
$metadata = json_decode($tvShow['metadata'], true);
|
||||
|
||||
// Extract additional fields from metadata if available
|
||||
// This can be expanded based on your metadata structure
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count TV shows with filters
|
||||
*/
|
||||
public function count(array $filters = []): int
|
||||
{
|
||||
$sql = "
|
||||
SELECT COUNT(*) as total
|
||||
FROM tv_shows t
|
||||
LEFT JOIN sources s ON t.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "t.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(t.first_air_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 all available years from TV shows' first_air_date
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user