Files
MediaCollectorLibary/app/Controllers/ActorController.php
Lars Behrends 1ec6016b10 dont know ?
2025-11-03 23:34:36 +01:00

430 lines
18 KiB
PHP

<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use PDO;
use Slim\Views\Twig;
class ActorController extends Controller
{
private PDO $pdo;
public function __construct(PDO $pdo, Twig $view)
{
parent::__construct($view);
$this->pdo = $pdo;
}
public function show(Request $request, Response $response, $args)
{
$actorId = $args['id'];
// Get actor details with counts from all media types (including episode actors)
$stmt = $this->pdo->prepare("
SELECT a.*,
COUNT(DISTINCT am.movie_id) as movie_count,
COUNT(DISTINCT CASE WHEN ats.tv_show_id IS NOT NULL THEN ats.tv_show_id
WHEN ate.tv_episode_id IS NOT NULL THEN te.tv_show_id END) as tv_show_count,
COUNT(DISTINCT aav.adult_video_id) as adult_video_count,
(COUNT(DISTINCT am.movie_id) +
COUNT(DISTINCT CASE WHEN ats.tv_show_id IS NOT NULL THEN ats.tv_show_id
WHEN ate.tv_episode_id IS NOT NULL THEN te.tv_show_id END) +
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
GROUP BY a.id
");
$stmt->execute(['actor_id' => $actorId]);
$actor = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$actor) {
return $response->withStatus(404)->withHeader('Content-Type', 'text/html');
}
// Get actor's adult videos (scenes)
$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' => $actorId]);
$scenes = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get actor's 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' => $actorId]);
$movies = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get actor's TV shows (from main cast and episodes)
$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
LEFT JOIN actor_tv_show ats ON ts.id = ats.tv_show_id AND ats.actor_id = :actor_id
LEFT JOIN tv_episodes te ON ts.id = te.tv_show_id
LEFT JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id AND ate.actor_id = :actor_id2
WHERE ats.actor_id = :actor_id4 OR ate.actor_id = :actor_id3
ORDER BY ts.first_air_date DESC, ts.title ASC
");
$stmt->execute(['actor_id' => $actorId,'actor_id2' => $actorId,'actor_id3' => $actorId, 'actor_id4' => $actorId]);
$tvShows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($scenes as &$scene) {
if (!empty($scene['metadata'])) {
$metadata = json_decode($scene['metadata'], true);
// Use local cover path if available, otherwise fall back to original URL
if (!empty($metadata['local_cover_path'])) {
$scene['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$scene['poster_url'] = $metadata['cover_url'];
}
// Add actors data if available
if (!empty($metadata['actors'])) {
$scene['actors'] = $metadata['actors'];
}
}
}
return $this->view->render($response, 'actor/show.twig', [
'title' => $actor['name'],
'actor' => $actor,
'scenes' => $scenes,
'movies' => $movies,
'tv_shows' => $tvShows
]);
}
public function edit(Request $request, Response $response, $args)
{
$actorId = $args['id'];
// Get actor details
$stmt = $this->pdo->prepare("SELECT * FROM actors WHERE id = :id");
$stmt->execute(['id' => $actorId]);
$actor = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$actor) {
return $response->withStatus(404)->withHeader('Content-Type', 'text/html');
}
// Decode metadata for form population
$metadata = json_decode($actor['metadata'] ?? '{}', true);
// Handle POST request (form submission)
if ($request->getMethod() === 'POST') {
$data = $request->getParsedBody();
$uploadedFiles = $request->getUploadedFiles();
// Validate required fields
$name = trim($data['name'] ?? '');
if (empty($name)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Name is required'
]);
}
// Handle image upload
$thumbnailPath = $actor['thumbnail_path']; // Keep existing by default
if (!empty($uploadedFiles['thumbnail']) && $uploadedFiles['thumbnail']->getError() === UPLOAD_ERR_OK) {
$uploadedFile = $uploadedFiles['thumbnail'];
// Validate file type
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($uploadedFile->getClientMediaType(), $allowedTypes)) {
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata,
'error' => 'Invalid image type. Only JPEG, PNG, GIF, and WebP are allowed.'
]);
}
// Generate filename and move file
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$filename = 'actor_' . $actorId . '_' . time() . '.' . $extension;
$uploadPath = __DIR__ . '/../../public/images/actors/' . $filename;
// Create directory if it doesn't exist
$uploadDir = dirname($uploadPath);
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$uploadedFile->moveTo($uploadPath);
$thumbnailPath = '/images/actors/' . $filename;
}
// Prepare metadata
$actorMetadata = [
'biography' => trim($data['biography'] ?? ''),
'birth_date' => trim($data['birth_date'] ?? ''),
'death_date' => trim($data['death_date'] ?? ''),
'birth_place' => trim($data['birth_place'] ?? ''),
'nationality' => trim($data['nationality'] ?? ''),
'gender' => trim($data['gender'] ?? ''),
'ethnicity' => trim($data['ethnicity'] ?? ''),
'country' => trim($data['nationality'] ?? ''), // Map nationality to country for Stash compatibility
'height' => trim($data['height'] ?? ''),
'measurements' => trim($data['measurements'] ?? ''),
'cup_size' => trim($data['cup_size'] ?? ''),
'piercings' => trim($data['piercings'] ?? ''),
'tattoos' => trim($data['tattoos'] ?? ''),
'hair_color' => trim($data['hair_color'] ?? ''),
'eye_color' => trim($data['eye_color'] ?? ''),
'weight' => trim($data['weight'] ?? ''),
'fake_tits' => trim($data['fake_tits'] ?? ''),
'penis_length' => trim($data['penis_length'] ?? ''),
'circumcised' => trim($data['circumcised'] ?? ''),
'career_length' => trim($data['career_length'] ?? ''),
'aliases' => array_filter(array_map('trim', explode(',', $data['aliases'] ?? ''))),
'favorite' => isset($data['favorite']) ? true : false,
'ignore_auto_tag' => isset($data['ignore_auto_tag']) ? true : false,
'scene_count' => (int)($data['scene_count'] ?? 0),
'details' => trim($data['details'] ?? ''),
'social_media' => [
'twitter' => trim($data['twitter'] ?? ''),
'instagram' => trim($data['instagram'] ?? ''),
'onlyfans' => trim($data['onlyfans'] ?? ''),
'website' => trim($data['website'] ?? '')
],
'adult_specific' => [
'debut_year' => trim($data['debut_year'] ?? ''),
'retirement_year' => trim($data['retirement_year'] ?? ''),
'active' => isset($data['active']) ? true : false,
'genres' => array_filter(array_map('trim', explode(',', $data['adult_genres'] ?? ''))),
'specialties' => array_filter(array_map('trim', explode(',', $data['specialties'] ?? '')))
]
];
// Update actor
$stmt = $this->pdo->prepare("
UPDATE actors
SET name = :name, thumbnail_path = :thumbnail_path, metadata = :metadata, updated_at = NOW()
WHERE id = :id
");
$stmt->execute([
'id' => $actorId,
'name' => $name,
'thumbnail_path' => $thumbnailPath,
'metadata' => json_encode($actorMetadata)
]);
// Redirect back to actor show page
return $response->withHeader('Location', '/media/actors/' . $actorId)->withStatus(302);
}
// GET request - show edit form
return $this->view->render($response, 'actor/edit.twig', [
'title' => 'Edit Actor',
'actor' => $actor,
'metadata' => $metadata
]);
}
public function index(Request $request, Response $response, $args)
{
$queryParams = $request->getQueryParams();
// Get pagination parameters
$page = max(1, (int)($queryParams['page'] ?? 1));
$perPage = max(12, min(100, (int)($queryParams['per_page'] ?? 24)));
// Get search parameters
$search = trim($queryParams['search'] ?? '');
// Get filter parameters
$hasMovies = $queryParams['has_movies'] ?? null;
$hasTvShows = $queryParams['has_tv_shows'] ?? null;
$hasAdultVideos = $queryParams['has_adult_videos'] ?? null;
// Get sort parameter
$sort = $queryParams['sort'] ?? 'total_media_desc'; // total_media_desc, total_media_asc, name_asc, name_desc
// Build the base query - simplified to ensure all actors are found
$sql = "
SELECT a.id, a.name, a.thumbnail_path,
COALESCE(adult_counts.adult_video_count, 0) as adult_video_count,
COALESCE(movie_counts.movie_count, 0) as movie_count,
COALESCE(tv_counts.tv_show_count, 0) as tv_show_count,
(COALESCE(adult_counts.adult_video_count, 0) + COALESCE(movie_counts.movie_count, 0) + COALESCE(tv_counts.tv_show_count, 0)) as total_media_count,
GREATEST(
COALESCE(adult_dates.latest_adult, '1900-01-01'),
COALESCE(movie_dates.latest_movie, '1900-01-01'),
COALESCE(tv_dates.latest_tv, '1900-01-01')
) as latest_media_date
FROM actors a
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT adult_video_id) as adult_video_count
FROM actor_adult_video
GROUP BY actor_id
) adult_counts ON a.id = adult_counts.actor_id
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT movie_id) as movie_count
FROM actor_movie
GROUP BY actor_id
) movie_counts ON a.id = movie_counts.actor_id
LEFT JOIN (
SELECT actor_id, COUNT(DISTINCT tv_show_id) as tv_show_count
FROM (
SELECT actor_id, tv_show_id FROM actor_tv_show
UNION
SELECT ate.actor_id, te.tv_show_id
FROM actor_tv_episode ate
JOIN tv_episodes te ON ate.tv_episode_id = te.id
) combined_tv
GROUP BY actor_id
) tv_counts ON a.id = tv_counts.actor_id
LEFT JOIN (
SELECT aav.actor_id, MAX(av.release_date) as latest_adult
FROM actor_adult_video aav
JOIN adult_videos av ON aav.adult_video_id = av.id
GROUP BY aav.actor_id
) adult_dates ON a.id = adult_dates.actor_id
LEFT JOIN (
SELECT am.actor_id, MAX(m.release_date) as latest_movie
FROM actor_movie am
JOIN movies m ON am.movie_id = m.id
GROUP BY am.actor_id
) movie_dates ON a.id = movie_dates.actor_id
LEFT JOIN (
SELECT combined_tv.actor_id, MAX(ts.first_air_date) as latest_tv
FROM (
SELECT actor_id, tv_show_id FROM actor_tv_show
UNION
SELECT ate.actor_id, te.tv_show_id
FROM actor_tv_episode ate
JOIN tv_episodes te ON ate.tv_episode_id = te.id
) combined_tv
JOIN tv_shows ts ON combined_tv.tv_show_id = ts.id
GROUP BY combined_tv.actor_id
) tv_dates ON a.id = tv_dates.actor_id
";
$params = [];
$whereClauses = [];
// Add search filter
if (!empty($search)) {
$whereClauses[] = "a.name LIKE :search";
$params['search'] = "%{$search}%";
}
if (!empty($whereClauses)) {
$sql .= ' WHERE ' . implode(' AND ', $whereClauses);
}
$sql .= " GROUP BY a.id";
// Add HAVING clause for filters that require aggregation
$havingClauses = [];
if ($hasMovies === '1') {
$havingClauses[] = "movie_count > 0";
}
if ($hasTvShows === '1') {
$havingClauses[] = "tv_show_count > 0";
}
if ($hasAdultVideos === '1') {
$havingClauses[] = "adult_video_count > 0";
}
if (!empty($havingClauses)) {
$sql .= ' HAVING ' . implode(' AND ', $havingClauses);
}
// Add sorting
$sortMap = [
'total_media_desc' => 'total_media_count DESC, a.name ASC',
'total_media_asc' => 'total_media_count ASC, a.name ASC',
'name_asc' => 'a.name ASC',
'name_desc' => 'a.name DESC',
'latest_desc' => 'latest_media_date DESC NULLS LAST, a.name ASC',
];
$orderBy = $sortMap[$sort] ?? 'total_media_count DESC, a.name ASC';
$sql .= " ORDER BY {$orderBy}";
// Get total count for pagination
$countSql = str_replace('SELECT a.id, a.name, a.thumbnail_path,', 'SELECT COUNT(*) as count,', $sql);
$countSql = preg_replace('/ORDER BY.*$/', '', $countSql);
$countStmt = $this->pdo->prepare($countSql);
foreach ($params as $key => $value) {
$countStmt->bindValue($key, $value);
}
$countStmt->execute();
$countResult = $countStmt->fetch(PDO::FETCH_ASSOC);
$totalCount = (int) ($countResult['count'] ?? 0);
// Add pagination
$offset = ($page - 1) * $perPage;
$sql .= " LIMIT :limit OFFSET :offset";
// Execute main query
$stmt = $this->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();
$actors = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Calculate pagination info
$totalPages = ceil($totalCount / $perPage);
$hasNextPage = $page < $totalPages;
$hasPrevPage = $page > 1;
return $this->view->render($response, 'actor/index.twig', [
'title' => 'Actors & Performers',
'actors' => $actors,
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total_pages' => $totalPages,
'total_items' => $totalCount,
'has_next' => $hasNextPage,
'has_prev' => $hasPrevPage,
'next_page' => $page + 1,
'prev_page' => $page - 1
],
'search' => $search,
'sort' => $sort,
'sort_options' => [
'total_media_desc' => 'Most Media',
'total_media_asc' => 'Least Media',
'name_asc' => 'Name A-Z',
'name_desc' => 'Name Z-A',
'latest_desc' => 'Recently Active'
],
'filters' => [
'has_movies' => $hasMovies,
'has_tv_shows' => $hasTvShows,
'has_adult_videos' => $hasAdultVideos
]
]);
}
}