Files
Lars Behrends eb1ec1153d 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
2026-01-18 01:42:03 +01:00

536 lines
18 KiB
PHP

<?php
namespace App\Models;
class Actor extends Model
{
protected string $table = 'actors';
protected array $fillable = [
'name',
'thumbnail_path',
'metadata'
];
protected array $casts = [
'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
*/
public function movies()
{
$stmt = $this->pdo->prepare("
SELECT m.*, s.display_name as source_name
FROM movies m
JOIN sources s ON m.source_id = s.id
JOIN actor_movie am ON m.id = am.movie_id
WHERE am.actor_id = :actor_id
ORDER BY m.release_date DESC, m.title ASC
");
$stmt->execute(['actor_id' => $this->id]);
$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 (via episodes)
*/
public function tvShows()
{
$stmt = $this->pdo->prepare("
SELECT DISTINCT ts.*, s.display_name as source_name
FROM tv_shows ts
JOIN sources s ON ts.source_id = s.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]);
$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;
}
/**
* Get all adult videos this actor is associated with
*/
public function adultVideos()
{
$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
JOIN actor_adult_video aav ON av.id = aav.adult_video_id
WHERE aav.actor_id = :actor_id
ORDER BY av.release_date DESC, av.title ASC
");
$stmt->execute(['actor_id' => $this->id]);
$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;
}
/**
* Get actor statistics
*/
public function getStats(): array
{
$stmt = $this->pdo->prepare("
SELECT
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,
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_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
");
$stmt->execute(['actor_id' => $this->id]);
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
/**
* Link actor to a movie
*/
public function linkToMovie(int $movieId): bool
{
$stmt = $this->pdo->prepare("
INSERT IGNORE INTO actor_movie (actor_id, movie_id)
VALUES (:actor_id, :movie_id)
");
return $stmt->execute([
'actor_id' => $this->id,
'movie_id' => $movieId
]);
}
/**
* Link actor to a TV show
*/
public function linkToTvShow(int $tvShowId): bool
{
$stmt = $this->pdo->prepare("
INSERT IGNORE INTO actor_tv_show (actor_id, tv_show_id)
VALUES (:actor_id, :tv_show_id)
");
return $stmt->execute([
'actor_id' => $this->id,
'tv_show_id' => $tvShowId
]);
}
/**
* Link actor to an adult video
*/
public function linkToAdultVideo(int $adultVideoId): bool
{
$stmt = $this->pdo->prepare("
INSERT IGNORE INTO actor_adult_video (actor_id, adult_video_id)
VALUES (:actor_id, :adult_video_id)
");
return $stmt->execute([
'actor_id' => $this->id,
'adult_video_id' => $adultVideoId
]);
}
/**
* Unlink actor from a movie
*/
public function unlinkFromMovie(int $movieId): bool
{
$stmt = $this->pdo->prepare("
DELETE FROM actor_movie
WHERE actor_id = :actor_id AND movie_id = :movie_id
");
return $stmt->execute([
'actor_id' => $this->id,
'movie_id' => $movieId
]);
}
/**
* Unlink actor from a TV show
*/
public function unlinkFromTvShow(int $tvShowId): bool
{
$stmt = $this->pdo->prepare("
DELETE FROM actor_tv_show
WHERE actor_id = :actor_id AND tv_show_id = :tv_show_id
");
return $stmt->execute([
'actor_id' => $this->id,
'tv_show_id' => $tvShowId
]);
}
/**
* Unlink actor from an adult video
*/
public function unlinkFromAdultVideo(int $adultVideoId): bool
{
$stmt = $this->pdo->prepare("
DELETE FROM actor_adult_video
WHERE actor_id = :actor_id AND adult_video_id = :adult_video_id
");
return $stmt->execute([
'actor_id' => $this->id,
'adult_video_id' => $adultVideoId
]);
}
/**
* Get paginated actors with optional search and sorting
*/
public function getPaginated(\PDO $pdo, int $page = 1, int $perPage = 20, string $search = '', string $sort = 'name_asc'): array
{
$offset = ($page - 1) * $perPage;
// Build WHERE clause for search
$whereClause = '';
$params = [];
if (!empty($search)) {
$whereClause = 'WHERE name LIKE :search';
$params['search'] = '%' . $search . '%';
}
// 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(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'
};
// Get actors with their media counts
$stmt = $pdo->prepare("
SELECT
a.*,
(SELECT COUNT(*) FROM actor_movie WHERE actor_id = a.id) as movie_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}
ORDER BY {$orderBy}
LIMIT :limit OFFSET :offset
");
$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();
$actors = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Add relationships data for each actor
foreach ($actors as &$actor) {
$actor['movies'] = $this->getActorMovies($actor['id']);
$actor['tvShows'] = $this->getActorTvShows($actor['id']);
$actor['adultVideos'] = $this->getActorAdultVideos($actor['id']);
}
return $actors;
}
/**
* Get total count of actors with optional search
*/
public function getTotalCount(\PDO $pdo, string $search = ''): int
{
$whereClause = '';
$params = [];
if (!empty($search)) {
$whereClause = 'WHERE name LIKE :search';
$params['search'] = '%' . $search . '%';
}
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM actors {$whereClause}");
foreach ($params as $key => $value) {
$stmt->bindValue(':' . $key, $value);
}
$stmt->execute();
return (int)$stmt->fetch(\PDO::FETCH_ASSOC)['count'];
}
/**
* Get movies for a specific actor (helper method)
*/
private function getActorMovies(int $actorId): array
{
$stmt = $this->pdo->prepare("
SELECT m.id, m.title
FROM movies m
JOIN actor_movie am ON m.id = am.movie_id
WHERE am.actor_id = ?
ORDER BY m.title
");
$stmt->execute([$actorId]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get TV shows for a specific actor (helper method)
*/
private function getActorTvShows(int $actorId): array
{
$stmt = $this->pdo->prepare("
SELECT DISTINCT ts.id, ts.title
FROM tv_shows ts
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]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get adult videos for a specific actor (helper method)
*/
private function getActorAdultVideos(int $actorId): array
{
$stmt = $this->pdo->prepare("
SELECT av.id, av.title
FROM adult_videos av
JOIN actor_adult_video aav ON av.id = aav.adult_video_id
WHERE aav.actor_id = ?
ORDER BY av.title
");
$stmt->execute([$actorId]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}