Files
mon-petit-cinema/api.php
T
2026-07-01 16:31:28 +02:00

1089 lines
48 KiB
PHP

<?php
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_errors.log');
ini_set('display_errors', 0);
set_time_limit(600); // Protection timeout pour les gros imports
error_reporting(E_ALL);
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
define('ENCRYPTION_KEY', 'MaCleSecreteSuperRobuste123!');
try {
$pdo = new PDO("mysql:host=localhost;dbname=mon_cinema;charset=utf8mb4", "root", "", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
// Initialisation BDD simplifiée
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username VARCHAR(50), password_hash VARCHAR(255))");
$pdo->exec("CREATE TABLE IF NOT EXISTS config (key_name VARCHAR(50) PRIMARY KEY, key_value TEXT)");
$pdo->exec("CREATE TABLE IF NOT EXISTS critiques (id BIGINT PRIMARY KEY, title VARCHAR(255), year VARCHAR(10), director VARCHAR(255), poster TEXT, rating DECIMAL(3,1), review TEXT, streaming VARCHAR(255))");
$pdo->exec("CREATE TABLE IF NOT EXISTS videotheque (id BIGINT PRIMARY KEY, title VARCHAR(255), year VARCHAR(10), director VARCHAR(255), poster TEXT, format VARCHAR(50), length VARCHAR(50), publisher VARCHAR(255), ean_isbn13 VARCHAR(50), number_of_discs INT DEFAULT 1, aspect_ratio VARCHAR(50), description TEXT, actors TEXT)");
} catch (PDOException $e) { die(json_encode(["error" => "Connexion BDD échouée"])); }
// --- Fonctions Utilitaires ---
// Récupère le token d'authentification envoyé par le client, en tolérant les
// configurations Apache/WAMP qui ne transmettent pas HTTP_AUTHORIZATION à PHP
// par défaut (l'en-tête est bien envoyé par le navigateur, mais Apache le
// "mange" avant qu'il n'atteigne $_SERVER, sauf si CGIPassAuth est activé).
function getAuthToken() {
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
return $_SERVER['HTTP_AUTHORIZATION'];
}
// Cas fréquent avec RewriteRule / certains proxys : le header est déplacé ici
if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
return $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
}
// Filet de sécurité générique : relire les en-têtes bruts de la requête
if (function_exists('getallheaders')) {
foreach (getallheaders() as $name => $value) {
if (strcasecmp($name, 'Authorization') === 0) {
return $value;
}
}
} elseif (function_exists('apache_request_headers')) {
foreach (apache_request_headers() as $name => $value) {
if (strcasecmp($name, 'Authorization') === 0) {
return $value;
}
}
}
return '';
}
function checkAuth($pdo) {
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) return true;
$token = getAuthToken();
if ($token !== md5(ENCRYPTION_KEY . 'session')) {
error_log("Auth: ❌ Token invalide ou absent (HTTP_AUTHORIZATION reçu: " . (empty($token) ? "VIDE — vérifier CGIPassAuth/config Apache" : "présent mais différent") . ")");
http_response_code(403); exit;
}
}
function encryptData($data) {
$iv = openssl_random_pseudo_bytes(16);
return base64_encode(openssl_encrypt($data, 'AES-256-CBC', hash('sha256', ENCRYPTION_KEY, true), OPENSSL_RAW_DATA, $iv) . '::' . $iv);
}
function decryptData($str) {
$decoded = base64_decode($str);
if ($decoded === false || strpos($decoded, '::') === false) return null;
list($enc, $iv) = explode('::', $decoded, 2);
return openssl_decrypt($enc, 'AES-256-CBC', hash('sha256', ENCRYPTION_KEY, true), OPENSSL_RAW_DATA, substr($iv, 0, 16));
}
function getTmdbApiKey($pdo) {
$stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'");
$stmt->execute();
$row = $stmt->fetch();
return $row ? decryptData($row['key_value']) : null;
}
function getUpcmdbApiKey($pdo) {
$stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'upcmdb_api_key'");
$stmt->execute();
$row = $stmt->fetch();
return $row ? decryptData($row['key_value']) : null;
}
function httpGet($url, $timeout = 10, $headers = []) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0',
CURLOPT_HTTPHEADER => $headers
]);
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ($code === 200) ? $res : null;
}
// Vérifie qu'une URL d'image répond bien en 200 (utilisé pour valider les URLs
// de couverture "devinées" à partir d'un ID, sans avoir à parser du HTML fragile)
function urlExists($url) {
if (empty($url)) return false;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true, // requête HEAD
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0',
]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code === 200;
}
function removeAccentsForUrl($str) {
$str = str_replace(['œ', 'Œ'], ['oe', 'OE'], $str);
$str = str_replace(['æ', 'Æ'], ['ae', 'AE'], $str);
$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
if ($transliterated !== false && $transliterated !== '') $str = $transliterated;
return $str;
}
function cleanTitle($title) {
$clean = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $title);
$clean = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Digibook|Ultimate|Intégrale|Combo|SteelBook|Boîtier).*$/i', '', $clean);
$clean = preg_replace('/(blu-ray|bluray|dvd|4k|ultra hd|combo|vhs|bdrip).*$/i', '', $clean);
return trim(preg_replace('/\s{2,}/', ' ', $clean));
}
function detectFormat($title, $desc = '') {
$t = strtoupper($title . ' ' . $desc);
if (strpos($t, '4K') !== false || strpos($t, 'UHD') !== false) return '4K Ultra HD';
if (strpos($t, 'BLU-RAY') !== false || strpos($t, 'BLURAY') !== false) return 'Blu-ray';
if (strpos($t, 'DVD') !== false) return 'DVD';
if (strpos($t, 'VHS') !== false) return 'VHS';
if (strpos($t, 'COFFRET') !== false || strpos($t, 'TRILOGIE') !== false) return 'Coffret';
return 'Blu-ray';
}
function parseDiscCountFromTitle($title) {
if (preg_match('/(\d+)\s*(?:dvd|blu-?ray|bluray|bd|disc|disque)/i', $title, $m)) {
return max(1, (int)$m[1]);
}
if (preg_match('/(?:coffret|pack|collection|trilogie|anthologie).*?(\d+)/i', $title, $m)) {
return max(1, (int)$m[1]);
}
if (preg_match('/\btrilogie\b/i', $title)) return 3;
return 1;
}
function cleanUpcTitle($title) {
// Recherche la position du premier guillemet (")
$firstQuote = strpos($title, '"');
// Recherche la position du dernier guillemet (")
$lastQuote = strrpos($title, '"');
// Si on a trouvé au moins deux guillemets différents
if ($firstQuote !== false && $lastQuote !== false && $firstQuote !== $lastQuote) {
// On extrait et nettoie ce qu'il y a strictement entre les deux
return trim(substr($title, $firstQuote + 1, $lastQuote - $firstQuote - 1));
}
// Si pas de guillemets trouvés, on retourne le titre original (fallback de sécurité)
return trim($title);
}
function emptyPhysicalResult() {
return [
'title' => '', 'publisher' => '', 'format' => '', 'length' => '',
'number_of_discs' => 1, 'aspect_ratio' => '', 'year' => ''
];
}
function makeStableId($type, $title, $year) {
return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000;
}
// ── FONCTIONS API PHYSIQUE (UPCitemdb → UPCMDB fallback) ──
function throttleUpcLookup() {
static $last = 0;
$elapsed = microtime(true) - $last;
if ($last > 0 && $elapsed < 2) usleep((int)((2 - $elapsed) * 1000000));
$last = microtime(true);
}
function fetchPhysicalFromUpcitemdb($ean) {
$empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>''];
$ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) return $empty;
throttleUpcLookup();
$res = httpGet("https://api.upcitemdb.com/prod/trial/lookup?upc=" . urlencode($ean), 10);
if (!$res) return $empty;
$data = json_decode($res, true);
if (empty($data['items'][0])) return $empty;
$item = $data['items'][0];
$raw = $item['title'] ?? '';
$clean = cleanUpcTitle($raw) ?: $raw;
return [
'title' => $clean,
'publisher' => trim($item['brand'] ?? $item['manufacturer'] ?? ''),
'format' => detectFormat($raw),
'number_of_discs' => parseDiscCountFromTitle($raw),
'aspect_ratio' => '',
'year' => '',
'length' => ''
];
}
function fetchFromUpcIndex($ean) {
$url = "https://www.upcindex.com/" . urlencode($ean);
$html = httpGet($url, 15); // Utilise votre fonction curl existante
if ($html && preg_match('/<h1[^>]*>(.*?)<\/h1>/si', $html, $m)) {
$title = strip_tags($m[1]);
// Nettoyage spécifique : retire le suffixe "- UPC [chiffres]" souvent présent
$title = preg_replace('/ - UPC \d+$/', '', $title);
return trim($title);
}
return null;
}
function fetchPhysicalFromUpcmdb($ean, $pdo) {
$empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>''];
$apiKey = getUpcmdbApiKey($pdo);
if (!$apiKey) return $empty;
$ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) return $empty;
$url = "https://upcmdb.com/api/v1/lookup/" . urlencode($ean);
$res = httpGet($url, 10, 'MonPetitCinema/1.0', ['Accept: application/json', 'X-API-Key: ' . $apiKey]);
if (!$res || $res[0] === '<') return $empty;
$data = json_decode($res, true);
if (($data['status'] ?? '') !== 'success' || empty($data['data'])) return $empty;
$item = $data['data'];
$raw = trim($item['title'] ?? '');
if (!$raw) return $empty;
return [
'title' => cleanUpcTitle($raw) ?: $raw,
'publisher' => trim($item['studio'] ?? $item['publisher'] ?? ''),
'format' => trim($item['format'] ?: detectFormat($raw)),
'length' => trim($item['runtime'] ?? ''),
'number_of_discs' => (int)($item['discs'] ?? parseDiscCountFromTitle($raw)) ?: 1,
'aspect_ratio' => trim($item['aspect_ratio'] ?? ''),
'year' => trim($item['year'] ?? '')
];
}
function fetchPhysicalByEan($ean, $pdo = null) {
// 1. Essayer UPCitemdb
$res = fetchPhysicalFromUpcitemdb($ean);
if (!empty($res['title'])) {
// 2. Chercher sur MovieCovers avec le titre trouvé
$mc = fetchFromMovieCovers($res['title'], $res['year']);
if (!empty($mc['poster'])) {
$res['poster'] = $mc['poster'];
}
if (!empty($mc['director'])) {
$res['director'] = $mc['director'];
}
if (!empty($mc['actors'])) {
$res['actors'] = $mc['actors'];
}
if (!empty($mc['description'])) {
$res['description'] = $mc['description'];
}
return $res;
}
// 3. Fallback UPCMDB
if ($pdo) {
$fb = fetchPhysicalFromUpcmdb($ean, $pdo);
if (!empty($fb['title'])) {
$mc = fetchFromMovieCovers($fb['title'], $fb['year']);
if (!empty($mc['poster'])) {
$fb['poster'] = $mc['poster'];
}
return $fb;
}
}
return $res;
}
// ── FONCTION POUR RÉCUPÉRER LES AFFICHES DEPUIS TMDB ──
function fetchPosterTMDB($title, $year = '', $pdo = null) {
$defaultPoster = 'assets/img/default_physical_media.jpg';
$cleanTitle = cleanTitle($title);
if (empty($cleanTitle)) {
return ['poster' => $defaultPoster, 'title' => $cleanTitle, 'format' => 'Blu-ray'];
}
$tmdbKey = getTmdbApiKey($pdo);
if (!$tmdbKey) {
error_log("TMDB: ❌ Clé API non configurée");
return ['poster' => $defaultPoster, 'title' => $cleanTitle, 'format' => 'Blu-ray'];
}
// ÉTAPE 1 : Recherche du film
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($cleanTitle);
if (!empty($year)) {
$searchUrl .= "&year={$year}";
}
$searchUrl .= "&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
// Si pas de résultat avec l'année, on réessaie sans
if (empty($searchData['results']) && !empty($year)) {
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
}
if (empty($searchData['results'])) {
error_log("TMDB: ❌ Film non trouvé pour '{$cleanTitle}'");
return ['poster' => $defaultPoster, 'title' => $cleanTitle, 'format' => 'Blu-ray'];
}
// ÉTAPE 2 : Récupérer le poster du premier résultat
$posterPath = $searchData['results'][0]['poster_path'] ?? '';
if (!empty($posterPath)) {
$posterUrl = "https://image.tmdb.org/t/p/w500" . $posterPath;
error_log("TMDB: ✅ Affiche trouvée pour '{$cleanTitle}' → {$posterUrl}");
return [
'poster' => $posterUrl,
'title' => $cleanTitle,
'format' => 'Blu-ray'
];
}
error_log("TMDB: ❌ Film trouvé mais pas d'affiche pour '{$cleanTitle}'");
return ['poster' => $defaultPoster, 'title' => $cleanTitle, 'format' => 'Blu-ray'];
}
// ── FONCTION POUR RÉCUPÉRER LE SYNOPSIS DEPUIS TMDB ──
function fetchTmdbSynopsis($title, $year = '', $pdo = null) {
$tmdbKey = getTmdbApiKey($pdo);
if (!$tmdbKey || empty($title)) return '';
$cleanTitle = cleanTitle($title);
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($cleanTitle);
if (!empty($year)) $searchUrl .= "&year={$year}";
$searchUrl .= "&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
if (empty($searchData['results'])) {
// Retry sans l'année
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
}
if (!empty($searchData['results'][0]['overview'])) {
return $searchData['results'][0]['overview'];
}
return '';
}
// ── FONCTION TMDB COMPLÈTE (Affiche + Métadonnées) ──
function fetchTmdbPosterAndSynopsis($title, $year = '', $pdo = null) {
$default = ['poster'=>'assets/img/default_physical_media.jpg','title'=>'','description'=>'','director'=>'','actors'=>'','length'=>'','year'=>''];
if (empty($title)) return $default;
$tmdbKey = getTmdbApiKey($pdo);
if (!$tmdbKey) return $default;
$clean = cleanTitle($title);
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($clean) . "&language=fr-FR";
if ($year) $searchUrl .= "&year={$year}";
$res = httpGet($searchUrl, 5);
$data = $res ? json_decode($res, true) : [];
// Retry sans année si échec
if (empty($data['results']) && $year) {
$res = httpGet("https://api.themoviedb.org/3/search/movie?api_key={$tmdbKey}&query=" . urlencode($clean) . "&language=fr-FR", 5);
$data = $res ? json_decode($res, true) : [];
}
if (empty($data['results'])) return $default;
$movie = $data['results'][0];
$movieId = $movie['id'];
$default['poster'] = !empty($movie['poster_path']) ? "https://image.tmdb.org/t/p/w500{$movie['poster_path']}" : $default['poster'];
$default['year'] = !empty($movie['release_date']) ? substr($movie['release_date'], 0, 4) : $year;
$default['description'] = $movie['overview'] ?? '';
$default['title'] = $clean;
// Détails supplémentaires (réalisateur, acteurs, durée)
$detailsUrl = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$tmdbKey}&append_to_response=credits&language=fr-FR";
$detRes = httpGet($detailsUrl, 5);
if ($detRes) {
$det = json_decode($detRes, true);
$default['length'] = !empty($det['runtime']) ? "{$det['runtime']} min" : '';
if (!empty($det['credits']['crew'])) {
$dirs = array_filter($det['credits']['crew'], fn($c) => $c['job'] === 'Director');
$default['director'] = $dirs ? implode(', ', array_map(fn($c) => $c['name'], array_slice($dirs, 0, 2))) : '';
}
if (!empty($det['credits']['cast'])) {
$default['actors'] = implode(', ', array_map(fn($c) => $c['name'], array_slice($det['credits']['cast'], 0, 5)));
}
}
return $default;
}
function fetchFromBlurayCom($ean) {
static $lastRequest = 0;
$empty = [
'title' => '', 'year' => '', 'director' => '', 'actors' => '',
'poster' => '', 'description' => '', 'length' => '',
'publisher' => '', 'format' => 'Blu-ray', 'number_of_discs' => 1,
'aspect_ratio' => ''
];
$ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) {
error_log("Blu-ray.com: ❌ EAN invalide: $ean");
return $empty;
}
// Throttle: 3 secondes entre chaque requête
$now = microtime(true);
if ($lastRequest > 0 && ($now - $lastRequest) < 3) {
$sleepTime = 3 - ($now - $lastRequest);
error_log("Blu-ray.com: ⏱️ Attente de " . round($sleepTime, 2) . "s");
usleep((int)($sleepTime * 1000000));
}
$lastRequest = microtime(true);
error_log("Blu-ray.com: 🔍 Recherche EAN $ean");
$searchUrl = "https://www.blu-ray.com/movies/search.php?ean=" . urlencode($ean) . "&action=search";
$ch = curl_init($searchUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: fr-FR,fr;q=0.9',
'Referer: https://www.blu-ray.com/'
]
]);
$searchHtml = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if (!$searchHtml || $httpCode !== 200) {
error_log("Blu-ray.com: ❌ Échec recherche EAN $ean (HTTP $httpCode) - Erreur: $curlError");
return $empty;
}
// Extraire l'URL du film
if (!preg_match('/href="(https:\/\/www\.blu-ray\.com\/movies\/[^"]+\/(\d+)\/)"/i', $searchHtml, $matches)) {
error_log("Blu-ray.com: ❌ Film non trouvé pour EAN $ean");
return $empty;
}
$movieUrl = $matches[1];
$movieId = $matches[2];
error_log("Blu-ray.com: ✅ Film trouvé → $movieUrl");
sleep(2); // Délai avant la 2ème requête
$ch2 = curl_init($movieUrl);
curl_setopt_array($ch2, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Referer: https://www.blu-ray.com/'
]
]);
$movieHtml = curl_exec($ch2);
curl_close($ch2);
if (!$movieHtml) {
error_log("Blu-ray.com: ❌ Impossible de charger la page du film");
return $empty;
}
// ── EXTRACTION AVEC LES NOUVELLES REGEX ──
// Titre (dans <h3> inside #movie_info)
if (preg_match('/<div[^>]*id="movie_info"[^>]*>.*?<h3>([^<]+)<\/h3>/is', $movieHtml, $m)) {
$empty['title'] = trim($m[1]);
} elseif (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $movieHtml, $m)) {
$rawTitle = trim(strip_tags($m[1]));
$empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', $rawTitle));
}
// Année (juste après le <h3>)
if (preg_match('/<h3[^>]*>([^<]+)<\/h3>\s*(?:&nbsp;)?\((\d{4})\)/i', $movieHtml, $m)) {
$empty['year'] = $m[2];
}
// Studio/Éditeur
if (preg_match('/href="[^"]*studioid=\d+[^"]*"[^>]*>([^<]+)<\/a>/i', $movieHtml, $m)) {
$empty['publisher'] = trim($m[1]);
}
// Durée (dans un span avant "Rated")
if (preg_match('/(\d+)\s*min<\/span>/i', $movieHtml, $m)) {
$empty['length'] = $m[1] . ' min';
} elseif (preg_match('/(\d+)\s*min\s*\|\s*Rated/i', $movieHtml, $m)) {
$empty['length'] = $m[1] . ' min';
}
// Aspect ratio
if (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) {
$empty['aspect_ratio'] = trim($m[1]);
}
// Nombre de disques (gère "Three-disc set" en plus des chiffres)
if (preg_match('/(\w+)-disc\s+set/i', $movieHtml, $m)) {
$wordToNum = ['single' => 1, 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6];
$word = strtolower($m[1]);
$empty['number_of_discs'] = $wordToNum[$word] ?? 1;
} elseif (preg_match('/(\d+)-disc\s+set/i', $movieHtml, $m)) {
$empty['number_of_discs'] = (int)$m[1];
}
// Format
if (strpos($movieUrl, '/4k/') !== false || stripos($movieHtml, '4K Ultra HD') !== false) {
$empty['format'] = '4K Ultra HD';
} elseif (strpos($movieUrl, '/3d/') !== false) {
$empty['format'] = '3D Blu-ray';
} else {
$empty['format'] = 'Blu-ray';
}
// Affiche HD
if (preg_match('/src="(https:\/\/images\.static-bluray\.com\/movies\/covers\/\d+_front\.jpg[^"]*)"/i', $movieHtml, $m)) {
$empty['poster'] = $m[1];
} elseif (preg_match('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
$posterUrl = $m[1];
$posterUrl = preg_replace('/_large\.jpg/', '_front.jpg', $posterUrl);
$empty['poster'] = $posterUrl;
}
// Synopsis, Réalisateur, Acteurs (dans #movie_info)
if (preg_match('/<div[^>]*id="movie_info"[^>]*>(.*?)<div[^>]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) {
$infoHtml = $infoBlock[1];
// Synopsis (après la balise <font> vide)
if (preg_match('/<font[^>]*><br><\/font>\s*(.*?)<br><br><br>Directors:/is', $infoHtml, $m)) {
$synopsis = trim(strip_tags($m[1]));
$synopsis = preg_replace('/\s+/', ' ', $synopsis);
$empty['description'] = $synopsis;
}
// Réalisateur (extrait depuis les balises <a>)
if (preg_match('/Directors?:\s*(.*?)(?:<br>|<\/div>)/is', $infoHtml, $m)) {
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $m[1], $dirMatches);
if (!empty($dirMatches[1])) {
$empty['director'] = implode(', ', array_map('trim', array_slice($dirMatches[1], 0, 2)));
}
}
// Acteurs (extrait depuis les balises <a>)
if (preg_match('/Starring:\s*(.*?)(?:<br>|<\/div>)/is', $infoHtml, $m)) {
preg_match_all('/<a[^>]*>([^<]+)<\/a>/i', $m[1], $actorMatches);
if (!empty($actorMatches[1])) {
$empty['actors'] = implode(', ', array_map('trim', array_slice($actorMatches[1], 0, 6)));
}
}
}
error_log("Blu-ray.com: ✅ Données récupérées pour EAN $ean" . $empty['title']);
return $empty;
}
function fetchFromMovieCovers($title, $year = '') {
$empty = [
'title' => '', 'year' => '', 'director' => '', 'actors' => '',
'poster' => '', 'description' => '', 'length' => '',
'publisher' => '', 'format' => 'DVD', 'number_of_discs' => 1,
'aspect_ratio' => ''
];
if (empty($title)) return $empty;
$cleanTitle = removeAccentsForUrl($title);
$urlTitle = strtoupper(str_replace(' ', '+', $cleanTitle));
$url = "https://moviecovers.com/film/titre_{$urlTitle}.html";
$html = httpGet($url, 10);
if (!$html) return $empty;
if (preg_match('/<TITLE>([^<]+)<\/TITLE>/i', $html, $m)) $empty['title'] = trim($m[1]);
// Récupération IDMC pour HD
$idmc = '';
if (preg_match('/IDMC.*?<PRE>([^<]+)<\/PRE>/is', $html, $m)) {
$idmc = trim($m[1]);
} elseif (preg_match('/<meta property="og:image" content="[^"]*\/getjpg\.html\/([^"\']+)\.jpg"/i', $html, $m)) {
$idmc = urldecode($m[1]);
}
if ($idmc) {
$idmc_encoded = urlencode($idmc);
$hd_urls = [
"https://moviecovers.com/DATA/zipcache/{$idmc_encoded}.jpg",
"https://moviecovers.com/getjpg.html/{$idmc_encoded}.jpg"
];
foreach ($hd_urls as $hd_url) {
$img_data = httpGet($hd_url, 5);
if ($img_data && strlen($img_data) > 5000) {
$empty['poster'] = $hd_url;
break;
}
}
}
if (empty($empty['poster'])) {
if (preg_match('/<img[^>]*title="Recto[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) $empty['poster'] = $m[1];
elseif (preg_match('/<img[^>]*src="([^"]+)"[^>]*title="Recto/i', $html, $m)) $empty['poster'] = $m[1];
elseif (preg_match('/<meta[^>]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) $empty['poster'] = $m[1];
}
if (preg_match('/R&eacute;alisateur<\/TH>\s*<TD[^>]*>.*?<a[^>]*>([^<]+)<\/a>/is', $html, $m)) $empty['director'] = trim($m[1]);
if (preg_match('/Ann&eacute;e<\/TH>\s*<TD[^>]*>.*?<a[^>]*>(\d{4})<\/a>/is', $html, $m)) $empty['year'] = $m[1];
if (preg_match('/Dur&eacute;e<\/TH>\s*<TD[^>]*>([\dH]+[\dmin]*)/is', $html, $m)) $empty['length'] = trim($m[1]);
if (preg_match_all('/<a href="\/multicrit\.html\?acteur=[^"]*">([^<]+)<\/a>/i', $html, $matches)) $empty['actors'] = implode(', ', array_slice($matches[1], 0, 5));
if (preg_match('/<meta[^>]*property="og:description"[^>]*content="([^"]+)"/i', $html, $m)) $empty['description'] = html_entity_decode($m[1]);
return $empty;
}
function removeAccents($string) {
$string = htmlentities($string, ENT_NOQUOTES, 'utf-8');
$string = preg_replace('#&([A-za-z])(?:acute|cedil|caron|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $string);
$string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string);
$string = preg_replace('#&[^;]+;#', '', $string);
return $string;
}
function fetchAndDownloadMovieCovers($title, $ean) {
if (empty($title) || empty($ean)) return null;
// Création du dossier d'images s'il n'existe pas
$dir = __DIR__ . '/../assets/img/covers';
if (!is_dir($dir)) mkdir($dir, 0777, true);
$localPath = "assets/img/covers/{$ean}.jpg";
$fullPath = __DIR__ . '/../' . $localPath;
// Si la jaquette a déjà été téléchargée lors d'un précédent import, on la réutilise direct
if (file_exists($fullPath)) return $localPath;
// Nettoyage du titre pour la recherche MovieCovers
$cleanTitle = removeAccents(trim(preg_replace('/(blu-ray|bluray|dvd|4k|ultra hd).*$/i', '', $title)));
$searchUrl = "https://www.moviecovers.com/multicrit.html?titre=" . urlencode(utf8_decode($cleanTitle));
$html = file_get_contents($searchUrl, false, stream_context_create([
'http' => ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 10]
]));
if (!$html) return null;
$filmHtml = null;
if (preg_match('/href=["\']?\/?(film\/titre_[^"\']+)\.html["\']?/i', $html, $m)) {
$filmUrl = "https://www.moviecovers.com/" . $m[1] . ".html";
usleep(500000); // Pause de 0.5s pour ne pas spammer le serveur
$filmHtml = file_get_contents($filmUrl, false, stream_context_create([
'http' => ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 10]
]));
} else if (stripos($html, 'IDMC') !== false) {
$filmHtml = $html; // Accès direct
}
if (!$filmHtml) return null;
// Extraction de l'identifiant IDMC
$idmc = null;
if (preg_match('/IDMC.*?<PRE>([^<]+)<\/PRE>/is', $filmHtml, $m)) {
$idmc = trim($m[1]);
} elseif (preg_match('/<meta property="og:image" content="[^"]*\/getjpg\.html\/([^"\']+)\.jpg"/i', $filmHtml, $m)) {
$idmc = urldecode($m[1]);
}
if ($idmc) {
$idmc_encoded = rawurlencode($idmc);
$coverUrls = [
"https://www.moviecovers.com/DATA/zipcache/{$idmc_encoded}.jpg",
"https://www.moviecovers.com/getjpg.html/{$idmc_encoded}.jpg"
];
foreach ($coverUrls as $imgUrl) {
usleep(1000000); // Pause de 1s avant de télécharger l'image (comme dans le script Python)
$imgData = @file_get_contents($imgUrl, false, stream_context_create([
'http' => ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 15]
]));
// Si l'image est valide et fait plus de 5Ko
if ($imgData && strlen($imgData) > 5000) {
file_put_contents($fullPath, $imgData);
return $localPath;
}
}
}
return null;
}
// ── ROUTEUR PRINCIPAL ──
$action = $_GET['action'] ?? '';
$data = json_decode(file_get_contents('php://input'), true) ?? [];
switch ($action) {
case 'check_security_status':
echo json_encode(["is_blank" => ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0)]);
break;
case 'login':
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) {
echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => true]);
} else {
$stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username = 'admin'");
$stmt->execute(); $user = $stmt->fetch();
if ($user && password_verify($data['password'] ?? '', $user['password_hash'])) {
echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => false]);
} else { http_response_code(401); echo json_encode(["error" => "Mot de passe incorrect."]); }
}
break;
case 'setup_admin': case 'update_password':
checkAuth($pdo);
$pwd = $data['password'] ?? $data['new_password'] ?? '';
$stmt = $pdo->prepare("REPLACE INTO users (id, username, password_hash) VALUES (1, 'admin', :pass)");
$stmt->execute([':pass' => password_hash($pwd, PASSWORD_BCRYPT)]);
echo json_encode(["success" => true]);
break;
// ── CONFIGURATION ──
case 'get_config_keys':
$keys = ['tmdb_api_key', 'upcmdb_api_key'];
$config = [];
foreach ($keys as $k) {
$stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = ?");
$stmt->execute([$k]);
$row = $stmt->fetch();
$config[$k] = $row ? decryptData($row['key_value']) : '';
}
echo json_encode($config);
break;
case 'save_config':
checkAuth($pdo);
$name = $data['key_name'] ?? '';
$val = trim($data['key_value'] ?? '');
if (!in_array($name, ['tmdb_api_key', 'upcmdb_api_key'])) {
http_response_code(400); echo json_encode(["error" => "Clé invalide."]); break;
}
if (empty($val)) break; // Ne rien écraser si vide
$stmt = $pdo->prepare("REPLACE INTO config (key_name, key_value) VALUES (?, ?)");
$stmt->execute([$name, encryptData($val)]);
echo json_encode(["success" => true]);
break;
case 'get_films':
$sql = "
SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type, NULL AS format
FROM critiques
UNION ALL
SELECT id, title, year, director, poster, NULL AS rating, NULL AS review, description, NULL AS streaming, 'videotheque' AS type, format
FROM videotheque
ORDER BY id DESC
";
$result = $pdo->query($sql)->fetchAll();
// IMPORTANT : Utilisation du pointeur `&` pour que la modification soit effective dans $result
foreach ($result as $row) {
if ($row['rating'] !== null) {
$ratingVal = (float)$row['rating'];
$row['rating'] = ($ratingVal == floor($ratingVal)) ? (int)$ratingVal : $ratingVal;
}
}
unset($row);
echo json_encode($result);
break;
case 'save_film':
checkAuth($pdo);
$type = $data['type'] ?? 'critique';
$id = !empty($data['id']) ? $data['id'] : makeStableId($type, $data['title'] ?? '', $data['year'] ?? '0000');
if ($type === 'critique' && (empty($data['director']) || empty($data['poster']))) {
$tmdbData = fetchTmdbPosterAndSynopsis($data['title'] ?? '', $data['year'] ?? '', $pdo);
if ($tmdbData) {
if (empty($data['director'])) $data['director'] = $tmdbData['director'];
if (empty($data['poster'])) $data['poster'] = $tmdbData['poster'];
}
}
if ($type === 'critique') {
$streaming = $data['streaming'] ?? '';
if (empty($streaming)) $streaming = 'Support physique / Cinéma';
$sql = "INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=VALUES(director), poster=VALUES(poster), rating=VALUES(rating), review=VALUES(review), streaming=VALUES(streaming)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]);
} else {
if (empty($data['poster']) && !empty($data['title'])) {
$tmdbData = fetchTmdbPosterAndSynopsis($data['title'], $data['year'] ?? '', $pdo);
if ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') {
$data['poster'] = $tmdbData['poster'];
}
if (empty($data['description'])) $data['description'] = $tmdbData['description'];
if (empty($data['director'])) $data['director'] = $tmdbData['director'];
if (empty($data['actors'])) $data['actors'] = $tmdbData['actors'];
if (empty($data['length'])) $data['length'] = $tmdbData['length'];
if (empty($data['format'])) $data['format'] = detectFormat($data['title'], $data['description'] ?? '');
}
$sql = "INSERT INTO videotheque (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description, actors) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=IF(VALUES(director)!='', VALUES(director), director), poster=IF(VALUES(poster)!='', VALUES(poster), poster), format=IF(VALUES(format)!='', VALUES(format), format), length=IF(VALUES(length)!='', VALUES(length), length), publisher=IF(VALUES(publisher)!='', VALUES(publisher), publisher), ean_isbn13=IF(VALUES(ean_isbn13)!='', VALUES(ean_isbn13), ean_isbn13), number_of_discs=IF(VALUES(number_of_discs)!=1, VALUES(number_of_discs), number_of_discs), aspect_ratio=IF(VALUES(aspect_ratio)!='', VALUES(aspect_ratio), aspect_ratio), description=IF(VALUES(description)!='', VALUES(description), description), actors=IF(VALUES(actors)!='', VALUES(actors), actors)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['format'] ?? '', $data['length'] ?? '', $data['publisher'] ?? '', $data['ean_isbn13'] ?? '', $data['number_of_discs'] ?? 1, $data['aspect_ratio'] ?? '', $data['description'] ?? '', $data['actors'] ?? '']);
}
echo json_encode(["success" => true]);
break;
case 'delete_film':
checkAuth($pdo);
$type = $_GET['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
$id = $_GET['id'] ?? null;
if (!$id) { http_response_code(400); echo json_encode(["error" => "ID manquant."]); break; }
$stmt = $pdo->prepare("DELETE FROM $table WHERE id = ?"); $stmt->execute([$id]);
echo json_encode(["success" => true]);
break;
case 'bulk_delete':
checkAuth($pdo);
$ids = $data['ids'] ?? []; $type = $data['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
if (!empty($ids)) { $placeholders = implode(',', array_fill(0, count($ids), '?')); $stmt = $pdo->prepare("DELETE FROM $table WHERE id IN ($placeholders)"); $stmt->execute($ids); echo json_encode(["success" => true]); }
else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); }
break;
// ── NOUVELLE ACTION : Recherche par EAN ──
case 'search_by_ean':
checkAuth($pdo);
$ean = $_GET['ean'] ?? '';
$ean = preg_replace('/[^0-9]/', '', $ean);
if (strlen($ean) < 8) {
echo json_encode(["success" => false, "error" => "EAN invalide"]);
break;
}
// 1. Blu-ray.com
$blurayData = fetchFromBlurayCom($ean);
// 2. MovieCovers pour l'affiche HD
$mcData = [];
if (!empty($blurayData['title'])) {
$mcData = fetchFromMovieCovers($blurayData['title'], $blurayData['year'] ?? '');
}
// 3. Fallback TMDB
$tmdbData = [];
$title = !empty($blurayData['title']) ? $blurayData['title'] : '';
if ($title) {
$tmdbData = fetchTmdbPosterAndSynopsis($title, $blurayData['year'] ?? '', $pdo);
}
// Fusion des données
$result = [
'success' => true,
'title' => $blurayData['title'] ?? '',
'year' => $blurayData['year'] ?? ($tmdbData['year'] ?? ''),
'director' => $mcData['director'] ?? ($blurayData['director'] ?? ($tmdbData['director'] ?? '')),
'poster' => $mcData['poster'] ?? ($blurayData['poster'] ?? ($tmdbData['poster'] ?? '')),
'format' => $blurayData['format'] ?? 'Blu-ray',
'length' => $blurayData['length'] ?? ($tmdbData['length'] ?? ''),
'publisher' => $blurayData['publisher'] ?? '',
'number_of_discs' => $blurayData['number_of_discs'] ?? 1,
'aspect_ratio' => $blurayData['aspect_ratio'] ?? '',
'actors' => $mcData['actors'] ?? ($blurayData['actors'] ?? ($tmdbData['actors'] ?? '')),
'description' => $mcData['description'] ?? ($blurayData['description'] ?? ($tmdbData['description'] ?? ''))
];
echo json_encode($result);
break;
case 'add_item_by_ean':
$data = json_decode(file_get_contents("php://input"), true);
$ean = $data['ean'] ?? '';
// 1. Récupération via UpcIndex (Priorité 1)
$title = fetchFromUpcIndex($ean);
// Fallback si UpcIndex échoue
if (!$title) {
$blurayData = fetchFromBlurayCom($ean);
$title = $blurayData['title'] ?? '';
}
if (empty($title)) {
echo json_encode(["success" => false, "error" => "EAN non trouvé sur UpcIndex ni Blu-ray.com"]);
exit;
}
// 2. Recherche sur TMDB (Source de vérité pour le titre FR et infos)
// On utilise fetchTmdbPosterAndSynopsis pour récupérer le titre français, poster, etc.
$tmdbData = fetchTmdbPosterAndSynopsis($title, '', $pdo);
// Si TMDB ne trouve rien avec le titre trouvé, on utilise le titre original
$finalTitle = (!empty($tmdbData['title']) && $tmdbData['title'] !== 'assets/img/default_physical_media.jpg')
? $tmdbData['title']
: $title;
$poster = ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') ? $tmdbData['poster'] : '';
// 3. Insertion
$id = makeStableId('videotheque', $finalTitle, date('Y'));
$stmt = $pdo->prepare("INSERT INTO videotheque
(id, title, poster, description, director, actors, ean_isbn13)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE title=VALUES(title)");
$stmt->execute([
$id,
$finalTitle,
$poster,
$tmdbData['description'] ?? '',
$tmdbData['director'] ?? '',
$tmdbData['actors'] ?? '',
$ean
]);
echo json_encode(["success" => true, "title" => $finalTitle]);
break;
case 'import_batch':
checkAuth($pdo);
$data = json_decode(file_get_contents("php://input"), true);
$type = $data['type'] ?? '';
$items = $data['items'] ?? [];
$pdo->beginTransaction();
$imported = 0; $skipped = 0;
try {
if ($type === 'videotheque') {
$stmt = $pdo->prepare("INSERT INTO videotheque (id, title, year, format, poster, ean_isbn13, description, length, number_of_discs, aspect_ratio, actors, publisher, director)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE
title=VALUES(title), year=VALUES(year), format=VALUES(format),
poster=IF(VALUES(poster)!='assets/img/default_physical_media.jpg', VALUES(poster), poster),
ean_isbn13=IF(VALUES(ean_isbn13)!='', VALUES(ean_isbn13), ean_isbn13),
description=IF(VALUES(description)!='', VALUES(description), description),
length=IF(VALUES(length)!='', VALUES(length), length),
number_of_discs=IF(VALUES(number_of_discs)!=1, VALUES(number_of_discs), number_of_discs),
aspect_ratio=IF(VALUES(aspect_ratio)!='', VALUES(aspect_ratio), aspect_ratio),
actors=IF(VALUES(actors)!='', VALUES(actors), actors),
publisher=IF(VALUES(publisher)!='', VALUES(publisher), publisher),
director=IF(VALUES(director)!='', VALUES(director), director)");
foreach ($items as $item) {
// ✅ ON NE RÉCUPÈRE QUE L'EAN
$ean = preg_replace('/[^0-9]/', '', (string)($item['ean'] ?? ''));
if (strlen($ean) < 8) {
$skipped++;
continue;
}
// 1. BLU-RAY.COM (Métadonnées physiques et affiche de base)
$blurayData = fetchFromBlurayCom($ean);
if (!is_array($blurayData)) $blurayData = [];
$title = $blurayData['title'] ?? '';
$year = $blurayData['year'] ?? '';
$format = $blurayData['format'] ?: detectFormat($title);
$publisher = $blurayData['publisher'] ?? '';
$discs = $blurayData['number_of_discs'] ?: 1;
$aspect = $blurayData['aspect_ratio'] ?? '';
$length = $blurayData['length'] ?? '';
$poster = $blurayData['poster'] ?? '';
$desc = $blurayData['description'] ?? '';
$director = $blurayData['director'] ?? '';
$actors = $blurayData['actors'] ?? '';
// 2. MOVIECOVERS (Pour l'affiche HD physique et complément d'infos)
if (!empty($title)) {
$mcData = fetchFromMovieCovers($title, $year);
if (!is_array($mcData)) $mcData = [];
// Priorité à l'affiche HD de MovieCovers
if (!empty($mcData['poster'])) $poster = $mcData['poster'];
if (empty($director) && !empty($mcData['director'])) $director = $mcData['director'];
if (empty($actors) && !empty($mcData['actors'])) $actors = $mcData['actors'];
if (empty($desc) && !empty($mcData['description'])) $desc = $mcData['description'];
if (empty($length) && !empty($mcData['length'])) $length = $mcData['length'];
if (empty($year) && !empty($mcData['year'])) $year = $mcData['year'];
}
// 3. FALLBACK TMDB (Si des données manquent encore)
if (empty($poster) || empty($director) || empty($actors) || empty($desc) || empty($title)) {
$tmdb = fetchTmdbPosterAndSynopsis($title, $year, $pdo);
if (empty($title)) $title = $tmdb['title'] ?? "EAN: $ean";
if (empty($poster) || $poster === 'assets/img/default_physical_media.jpg') $poster = $tmdb['poster'];
if (empty($director)) $director = $tmdb['director'] ?? '';
if (empty($actors)) $actors = $tmdb['actors'] ?? '';
if (empty($desc)) $desc = $tmdb['description'] ?? '';
if (empty($length) && !empty($tmdb['length'])) $length = $tmdb['length'];
if (empty($year) && !empty($tmdb['year'])) $year = $tmdb['year'];
}
if (empty($title)) {
$skipped++;
continue;
}
$id = makeStableId('videotheque', $title, $year);
$stmt->execute([$id, $title, $year, $format, $poster, $ean, $desc, $length, $discs, $aspect, $actors, $publisher, $director]);
$imported++;
}
} else {
// Import critiques (inchangé)
$stmtCritiques = $pdo->prepare("INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=VALUES(director),
poster=VALUES(poster), rating=VALUES(rating), review=VALUES(review), streaming=VALUES(streaming)");
foreach ($items as $rowData) {
$title = $rowData['Title'] ?? $rowData['title'] ?? '';
if (empty($title)) continue;
$year = '';
if (!empty($rowData['publish_date'])) $year = substr($rowData['publish_date'], 0, 4);
else $year = $rowData['Year'] ?? $rowData['year'] ?? '';
$id = makeStableId('critique', $title, $year);
$ratingRaw = $rowData['Rating'] ?? $rowData['rating'] ?? '';
$rating = ($ratingRaw !== '' && $ratingRaw !== null) ? (float)$ratingRaw : null;
$review = $rowData['Review'] ?? $rowData['review'] ?? $rowData['description'] ?? '';
$director = '';
$poster = 'assets/img/default_physical_media.jpg';
$streaming = 'Support physique / Cinéma';
$tmdbData = fetchTmdbPosterAndSynopsis($title, $year, $pdo);
if ($tmdbData) {
if (!empty($tmdbData['director'])) $director = $tmdbData['director'];
if ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') $poster = $tmdbData['poster'];
if (empty($year) && !empty($tmdbData['year'])) $year = $tmdbData['year'];
}
$stmtCritiques->execute([$id, $title, $year, $director, $poster, $rating, $review, $streaming]);
$imported++;
}
}
$pdo->commit();
echo json_encode(["success" => true, "imported" => $imported, "skipped" => $skipped]);
} catch (Throwable $e) {
$pdo->rollBack();
error_log("import_batch error: " . $e->getMessage());
http_response_code(500);
echo json_encode(["success" => false, "error" => $e->getMessage(), "line" => $e->getLine()]);
}
break;
}