mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
463 lines
14 KiB
PHP
463 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
class TvShow extends Model
|
|
{
|
|
protected string $table = 'tv_shows';
|
|
protected array $fillable = [
|
|
'title',
|
|
'overview',
|
|
'creator',
|
|
'genre',
|
|
'cast',
|
|
'first_air_date',
|
|
'last_air_date',
|
|
'number_of_seasons',
|
|
'number_of_episodes',
|
|
'rating',
|
|
'imdb_id',
|
|
'tmdb_id',
|
|
'tvdb_id',
|
|
'poster_url',
|
|
'backdrop_url',
|
|
'is_favorite',
|
|
'metadata',
|
|
'source_id'
|
|
];
|
|
|
|
protected array $casts = [
|
|
'number_of_seasons' => 'int',
|
|
'number_of_episodes' => 'int',
|
|
'rating' => 'float',
|
|
'is_favorite' => 'bool',
|
|
'first_air_date' => 'date',
|
|
'last_air_date' => 'date',
|
|
'metadata' => 'array'
|
|
];
|
|
|
|
/**
|
|
* Remove an actor from this TV show
|
|
*/
|
|
public function removeActor(int $actorId): bool
|
|
{
|
|
$stmt = $this->pdo->prepare("
|
|
DELETE FROM actor_tv_show
|
|
WHERE tv_show_id = :tv_show_id AND actor_id = :actor_id
|
|
");
|
|
return $stmt->execute([
|
|
'tv_show_id' => $this->id,
|
|
'actor_id' => $actorId
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get TV show statistics
|
|
*/
|
|
public static function getStats(\PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
COUNT(*) as total_tv_shows,
|
|
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_tv_shows,
|
|
AVG(rating) as avg_rating
|
|
FROM tv_shows
|
|
");
|
|
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Get total count with optional search and filters
|
|
*/
|
|
public static function getTotalCount(
|
|
\PDO $pdo,
|
|
string $search = '',
|
|
array $genres = [],
|
|
array $networks = [],
|
|
array $statuses = []
|
|
): int {
|
|
$sql = "SELECT COUNT(*) as count FROM tv_shows t JOIN sources s ON t.source_id = s.id";
|
|
$params = [];
|
|
$whereClauses = [];
|
|
|
|
if (!empty($search)) {
|
|
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
|
$params['search'] = "%{$search}%";
|
|
}
|
|
|
|
if (!empty($genres)) {
|
|
$genreConditions = [];
|
|
foreach ($genres as $i => $genre) {
|
|
$param = ":genre_{$i}";
|
|
$genreConditions[] = "FIND_IN_SET({$param}, t.genre) > 0";
|
|
$params["genre_{$i}"] = $genre;
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $genreConditions) . ')';
|
|
}
|
|
|
|
if (!empty($networks)) {
|
|
$networkConditions = [];
|
|
foreach ($networks as $i => $network) {
|
|
$param = ":network_{$i}";
|
|
$networkConditions[] = "t.networks LIKE {$param}";
|
|
$params["network_{$i}"] = "%\"name\":\"{$network}\"%";
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $networkConditions) . ')';
|
|
}
|
|
|
|
if (!empty($statuses)) {
|
|
$statusConditions = [];
|
|
foreach ($statuses as $i => $status) {
|
|
$param = ":status_{$i}";
|
|
$statusConditions[] = "t.status = {$param}";
|
|
$params["status_{$i}"] = $status;
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $statusConditions) . ')';
|
|
}
|
|
|
|
if (!empty($whereClauses)) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $whereClauses);
|
|
}
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue($key, $value);
|
|
}
|
|
$stmt->execute();
|
|
return (int) $stmt->fetch(\PDO::FETCH_COLUMN);
|
|
}
|
|
|
|
/**
|
|
* Get all TV shows with pagination and optional search
|
|
*/
|
|
public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = '', array $genres = [], array $years = []): array
|
|
{
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$sql = "
|
|
SELECT t.*, s.display_name as source_name
|
|
FROM tv_shows t
|
|
JOIN sources s ON t.source_id = s.id
|
|
";
|
|
$params = [];
|
|
|
|
if (!empty($search)) {
|
|
$sql .= " WHERE t.title 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 . " t.genre IN (" . implode(',', $placeholders) . ")";
|
|
}
|
|
|
|
if (!empty($years)) {
|
|
$placeholders = [];
|
|
foreach ($years as $index => $year) {
|
|
$placeholders[] = ":year_{$index}";
|
|
$params["year_{$index}"] = $year;
|
|
}
|
|
$whereClause = (!empty($search) || !empty($genres)) ? " AND" : " WHERE";
|
|
$sql .= $whereClause . " YEAR(first_air_date) IN (" . implode(',', $placeholders) . ")";
|
|
}
|
|
|
|
$sql .= " ORDER BY t.title ASC 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);
|
|
}
|
|
|
|
/**
|
|
* Toggle favorite status
|
|
*/
|
|
public function toggleFavorite(): bool
|
|
{
|
|
return $this->update($this->id, [
|
|
'is_favorite' => !$this->is_favorite
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Update rating
|
|
*/
|
|
public function updateRating(float $rating): bool
|
|
{
|
|
return $this->update($this->id, [
|
|
'rating' => min(10.0, max(0.0, $rating))
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get the source relationship
|
|
*/
|
|
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 paginated TV shows with filters
|
|
*/
|
|
public static function getPaginated(
|
|
\PDO $pdo,
|
|
int $page,
|
|
int $perPage,
|
|
string $search = '',
|
|
array $genres = [],
|
|
array $networks = [],
|
|
array $statuses = [],
|
|
string $sort = 'title_asc'
|
|
): array {
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$sql = "
|
|
SELECT t.*, s.display_name as source_name
|
|
FROM tv_shows t
|
|
JOIN sources s ON t.source_id = s.id
|
|
";
|
|
$params = [];
|
|
$whereClauses = [];
|
|
|
|
if (!empty($search)) {
|
|
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
|
$params['search'] = "%{$search}%";
|
|
}
|
|
|
|
if (!empty($genres)) {
|
|
$genreConditions = [];
|
|
foreach ($genres as $i => $genre) {
|
|
$param = ":genre_{$i}";
|
|
$genreConditions[] = "FIND_IN_SET({$param}, t.genre) > 0";
|
|
$params["genre_{$i}"] = $genre;
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $genreConditions) . ')';
|
|
}
|
|
|
|
if (!empty($networks)) {
|
|
$networkConditions = [];
|
|
foreach ($networks as $i => $network) {
|
|
$param = ":network_{$i}";
|
|
$networkConditions[] = "t.networks LIKE {$param}";
|
|
$params["network_{$i}"] = "%\"name\":\"{$network}\"%";
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $networkConditions) . ')';
|
|
}
|
|
|
|
if (!empty($statuses)) {
|
|
$statusConditions = [];
|
|
foreach ($statuses as $i => $status) {
|
|
$param = ":status_{$i}";
|
|
$statusConditions[] = "t.status = {$param}";
|
|
$params["status_{$i}"] = $status;
|
|
}
|
|
$whereClauses[] = '(' . implode(' OR ', $statusConditions) . ')';
|
|
}
|
|
|
|
if (!empty($whereClauses)) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $whereClauses);
|
|
}
|
|
|
|
// Add sorting
|
|
$sortMap = [
|
|
'title_asc' => 't.title ASC',
|
|
'title_desc' => 't.title DESC',
|
|
'rating_desc' => 't.vote_average DESC NULLS LAST',
|
|
'rating_asc' => 't.vote_average ASC NULLS LAST',
|
|
'newest' => 't.first_air_date DESC NULLS LAST',
|
|
'oldest' => 't.first_air_date ASC NULLS LAST',
|
|
];
|
|
|
|
$sortClause = $sortMap[$sort] ?? 't.title ASC';
|
|
$sql .= " ORDER BY {$sortClause} 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);
|
|
}
|
|
|
|
/**
|
|
* Get all available genres from TV shows
|
|
*/
|
|
public static function getGenres(\PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("
|
|
SELECT DISTINCT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(t.genre, ',', n.n), ',', -1)) as genre
|
|
FROM tv_shows t
|
|
JOIN (
|
|
SELECT 1 as n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
|
|
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
|
|
) n
|
|
WHERE n.n <= LENGTH(t.genre) - LENGTH(REPLACE(t.genre, ',', '')) + 1
|
|
AND t.genre IS NOT NULL AND t.genre != ''
|
|
ORDER BY genre
|
|
");
|
|
|
|
$genres = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
|
return array_values(array_filter(array_unique($genres)));
|
|
}
|
|
|
|
/**
|
|
* Get all available networks from TV shows
|
|
*/
|
|
public static function getNetworks(\PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("SELECT DISTINCT networks FROM tv_shows WHERE networks IS NOT NULL AND networks != ''");
|
|
$networks = [];
|
|
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
|
$showNetworks = json_decode($row['networks'], true) ?: [];
|
|
foreach ($showNetworks as $network) {
|
|
if (isset($network['name'])) {
|
|
$networks[$network['name']] = $network['name'];
|
|
}
|
|
}
|
|
}
|
|
|
|
sort($networks);
|
|
return array_values($networks);
|
|
}
|
|
|
|
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
|
|
$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
|
|
");
|
|
$stmt->execute([
|
|
'tv_show_id' => $this->id,
|
|
'season_number' => $seasonNumber
|
|
]);
|
|
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* Get similar TV shows based on genres
|
|
*/
|
|
public function getSimilarShows(int $limit = 6): array
|
|
{
|
|
$genres = $this->genre ? explode(',', $this->genre) : [];
|
|
$placeholders = str_repeat('?,', count($genres) - 1) . '?';
|
|
|
|
$sql = "
|
|
SELECT t.*,
|
|
COUNT(DISTINCT g.genre) as matching_genres
|
|
FROM tv_shows t
|
|
CROSS JOIN (SELECT TRIM(value) as genre
|
|
FROM json_each('[\"" . str_replace(',', '","', str_replace('"', '\\"', $this->genre)) . "\"]')
|
|
WHERE value != '') g
|
|
WHERE t.id != ?
|
|
AND t.genre LIKE '%' || g.genre || '%'
|
|
GROUP BY t.id
|
|
HAVING matching_genres > 0
|
|
ORDER BY matching_genres DESC, t.rating DESC
|
|
LIMIT ?
|
|
";
|
|
|
|
$params = array_merge([$this->id, $limit]);
|
|
$stmt = $this->pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
|
|
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Record that this TV show was viewed
|
|
*/
|
|
public function recordView(): bool
|
|
{
|
|
$stmt = $this->pdo->prepare("
|
|
INSERT OR REPLACE INTO tv_show_views
|
|
(tv_show_id, view_count, last_viewed_at)
|
|
VALUES (?, COALESCE((SELECT view_count FROM tv_show_views WHERE tv_show_id = ?), 0) + 1, CURRENT_TIMESTAMP)
|
|
");
|
|
return $stmt->execute([$this->id, $this->id]);
|
|
}
|
|
|
|
/**
|
|
* Get all available genres from TV shows
|
|
*/
|
|
public static function getAvailableGenres(\PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("
|
|
SELECT DISTINCT genre
|
|
FROM tv_shows
|
|
WHERE genre IS NOT NULL AND genre != ''
|
|
ORDER BY genre
|
|
");
|
|
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
|
}
|
|
|
|
/**
|
|
* Get all available years from TV shows' first_air_date
|
|
*/
|
|
public static function getAvailableYears(\PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("
|
|
SELECT DISTINCT YEAR(first_air_date) as year
|
|
FROM tv_shows
|
|
WHERE first_air_date IS NOT NULL
|
|
ORDER BY year DESC
|
|
");
|
|
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
|
}
|
|
|
|
}
|