mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
430 lines
18 KiB
PHP
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
|
|
]
|
|
]);
|
|
}
|
|
}
|