From 5950b3a633e1bf857b13fa3aee1211a03cbc32bf Mon Sep 17 00:00:00 2001 From: Cedric Date: Thu, 2 Jul 2026 10:51:06 +0200 Subject: [PATCH] Actualiser api.php --- api.php | 1069 ++++++++++++++----------------------------------------- 1 file changed, 263 insertions(+), 806 deletions(-) diff --git a/api.php b/api.php index 744f033..c12b7a5 100644 --- a/api.php +++ b/api.php @@ -2,7 +2,7 @@ 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 +set_time_limit(600); error_reporting(E_ALL); header("Content-Type: application/json; charset=UTF-8"); @@ -19,7 +19,6 @@ try { 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))"); @@ -27,30 +26,12 @@ try { } 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 (!empty($_SERVER['HTTP_AUTHORIZATION'])) return $_SERVER['HTTP_AUTHORIZATION']; + if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) return $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 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; - } + if (strcasecmp($name, 'Authorization') === 0) return $value; } } return ''; @@ -60,7 +41,6 @@ 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; } } @@ -84,13 +64,6 @@ function getTmdbApiKey($pdo) { 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, [ @@ -106,26 +79,6 @@ function httpGet($url, $timeout = 10, $headers = []) { 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); @@ -134,7 +87,6 @@ function removeAccentsForUrl($str) { 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); @@ -152,276 +104,137 @@ function detectFormat($title, $desc = '') { 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 makeStableId($type, $title, $year) { + return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000; } function emptyPhysicalResult() { return [ 'title' => '', 'publisher' => '', 'format' => '', 'length' => '', - 'number_of_discs' => 1, 'aspect_ratio' => '', 'year' => '' + 'number_of_discs' => 1, 'aspect_ratio' => '', 'year' => '', + 'director' => '', 'actors' => '', 'poster' => '', 'description' => '' ]; } -function makeStableId($type, $title, $year) { - return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000; -} +// ── SCRAPPING FNAC ── +function fetchFromFnac($ean) { + $empty = emptyPhysicalResult(); + $url = "https://www.fnac.com/SearchResult/ResultList.aspx?Search=" . urlencode($ean); + $html = httpGet($url, 10); + if (!$html) return $empty; -// ── 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); - - if ($html) { - // 1. Priorité absolue : la balise meta twitter:title qui contient le vrai titre propre - // Exemple : - if (preg_match('/]*name="twitter:title"[^>]*content="([^"]+)"/i', $html, $m)) { - // html_entity_decode gère les apostrophes encodées (') - return html_entity_decode(trim($m[1]), ENT_QUOTES | ENT_HTML5, 'UTF-8'); - } - - // 2. Alternative : on cherche dans les données structurées JSON-LD Schema.org - // Exemple : "name":"L'Arme fatale - \u00c9dition Sp\u00e9ciale" - if (preg_match('/"@type":"Product"[^}]+?"name":"([^"]+)"/is', $html, $m)) { - // json_decode permet de transformer proprement les \u00c9 en é, etc. - return json_decode('"' . $m[1] . '"'); - } - - // 3. Fallback sur le h1 (qui contient souvent le titre sale) - if (preg_match('/]*>(.*?)<\/h1>/si', $html, $m)) { - $title = strip_tags($m[1]); - $title = preg_replace('/ - UPC \d+$/', '', $title); - return trim($title); - } + // Extraction du titre du premier résultat + if (preg_match('/]*class="[^"]*js-ProductTitle[^"]*"[^>]*>([^<]+)<\/a>/i', $html, $m)) { + $empty['title'] = trim($m[1]); + } elseif (preg_match('/]*class="[^"]*title[^"]*"[^>]*>([^<]+)<\/h2>/i', $html, $m)) { + $empty['title'] = trim($m[1]); } - return null; + + // Extraction de l'image + if (preg_match('/]*class="[^"]*js-ProductImage[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) { + $empty['poster'] = $m[1]; + } elseif (preg_match('/]*class="[^"]*product-image[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) { + $empty['poster'] = $m[1]; + } + + if (!empty($empty['title'])) { + $empty['format'] = detectFormat($empty['title']); + } + + return $empty; } -function fetchPhysicalFromUpcmdb($ean, $pdo) { - $empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>'']; - $apiKey = getUpcmdbApiKey($pdo); - if (!$apiKey) return $empty; +// ── SCRAPPING BLU-RAY.COM ── +function fetchFromBlurayCom($ean) { + static $lastRequest = 0; + $empty = emptyPhysicalResult(); $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; + $now = microtime(true); + if ($lastRequest > 0 && ($now - $lastRequest) < 3) { + usleep((int)((3 - ($now - $lastRequest)) * 1000000)); + } + $lastRequest = microtime(true); - $item = $data['data']; - $raw = trim($item['title'] ?? ''); - if (!$raw) return $empty; + $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); + curl_close($ch); - 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'] ?? '') - ]; + if (!$searchHtml || $httpCode !== 200 || !preg_match('/href="(https:\/\/www\.blu-ray\.com\/movies\/[^"]+\/(\d+)\/)"/i', $searchHtml, $matches)) { + return $empty; + } + + $movieUrl = $matches[1]; + sleep(2); + + $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) return $empty; + + if (preg_match('/]*id="movie_info"[^>]*>.*?

([^<]+)<\/h3>/is', $movieHtml, $m)) { + $empty['title'] = trim($m[1]); + } elseif (preg_match('/]*>([^<]+)<\/h1>/i', $movieHtml, $m)) { + $empty['title'] = trim(preg_replace('/\s*(Blu-ray|4K|3D|DVD|UHD).*$/i', '', trim(strip_tags($m[1])))); + } + + if (preg_match('/]*>([^<]+)<\/h3>\s*(?: )?\((\d{4})\)/i', $movieHtml, $m)) $empty['year'] = $m[2]; + if (preg_match('/href="[^"]*studioid=\d+[^"]*"[^>]*>([^<]+)<\/a>/i', $movieHtml, $m)) $empty['publisher'] = trim($m[1]); + if (preg_match('/(\d+)\s*min<\/span>/i', $movieHtml, $m)) $empty['length'] = $m[1] . ' min'; + if (preg_match('/Aspect[\s-]*ratio:\s*([\d\.]+:[\d\.]+)/i', $movieHtml, $m)) $empty['aspect_ratio'] = trim($m[1]); + + 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]; + $empty['number_of_discs'] = $wordToNum[strtolower($m[1])] ?? 1; + } elseif (preg_match('/(\d+)-disc\s+set/i', $movieHtml, $m)) { + $empty['number_of_discs'] = (int)$m[1]; + } + + 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'; + + if (preg_match('/src="(https:\/\/images\.static-bluray\.com\/movies\/covers\/\d+_front\.jpg[^"]*)"/i', $movieHtml, $m)) { + $empty['poster'] = $m[1]; + } elseif (preg_match('/]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) { + $empty['poster'] = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]); + } + + if (preg_match('/]*id="movie_info"[^>]*>(.*?)]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) { + $infoHtml = $infoBlock[1]; + if (preg_match('/]*>
<\/font>\s*(.*?)


Directors:/is', $infoHtml, $m)) { + $empty['description'] = trim(preg_replace('/\s+/', ' ', strip_tags($m[1]))); + } + if (preg_match('/Directors?:\s*(.*?)(?:
|<\/div>)/is', $infoHtml, $m)) { + preg_match_all('/]*>([^<]+)<\/a>/i', $m[1], $dirMatches); + if (!empty($dirMatches[1])) $empty['director'] = implode(', ', array_map('trim', array_slice($dirMatches[1], 0, 2))); + } + if (preg_match('/Starring:\s*(.*?)(?:
|<\/div>)/is', $infoHtml, $m)) { + preg_match_all('/]*>([^<]+)<\/a>/i', $m[1], $actorMatches); + if (!empty($actorMatches[1])) $empty['actors'] = implode(', ', array_map('trim', array_slice($actorMatches[1], 0, 6))); + } + } + + return $empty; } -function fetchPhysicalByEan($ean, $pdo = null) { - // 1. Tenter UPCIndex en tout premier pour choper le vrai titre français - $upcIndexTitle = fetchFromUpcIndex($ean); - - if (!empty($upcIndexTitle)) { - $res = emptyPhysicalResult(); - $res['title'] = $upcIndexTitle; - $res['format'] = detectFormat($upcIndexTitle); - - // Maintenant qu'on a un beau titre propre, on cherche sur MovieCovers - $mc = fetchFromMovieCovers($upcIndexTitle, ''); - 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; - } - - // 2. Si UPCIndex échoue, essayer UPCitemdb (Fallback 1) - $res = fetchPhysicalFromUpcitemdb($ean); - if (!empty($res['title'])) { - $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. Dernier 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 ?? emptyPhysicalResult(); -} - -// ── 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) ── +// ── FONCTION TMDB COMPLÈTE ── 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; @@ -436,7 +249,6 @@ function fetchTmdbPosterAndSynopsis($title, $year = '', $pdo = null) { $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) : []; @@ -450,13 +262,11 @@ function fetchTmdbPosterAndSynopsis($title, $year = '', $pdo = null) { $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))) : ''; @@ -468,185 +278,9 @@ function fetchTmdbPosterAndSynopsis($title, $year = '', $pdo = null) { 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

inside #movie_info) - if (preg_match('/]*id="movie_info"[^>]*>.*?

([^<]+)<\/h3>/is', $movieHtml, $m)) { - $empty['title'] = trim($m[1]); - } elseif (preg_match('/]*>([^<]+)<\/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

) - if (preg_match('/]*>([^<]+)<\/h3>\s*(?: )?\((\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('/]*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('/]*id="movie_info"[^>]*>(.*?)]*id="movie_review_intro"/is', $movieHtml, $infoBlock)) { - $infoHtml = $infoBlock[1]; - - // Synopsis (après la balise vide) - if (preg_match('/]*>
<\/font>\s*(.*?)


Directors:/is', $infoHtml, $m)) { - $synopsis = trim(strip_tags($m[1])); - $synopsis = preg_replace('/\s+/', ' ', $synopsis); - $empty['description'] = $synopsis; - } - - // Réalisateur (extrait depuis les balises ) - if (preg_match('/Directors?:\s*(.*?)(?:
|<\/div>)/is', $infoHtml, $m)) { - preg_match_all('/]*>([^<]+)<\/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
) - if (preg_match('/Starring:\s*(.*?)(?:
|<\/div>)/is', $infoHtml, $m)) { - preg_match_all('/]*>([^<]+)<\/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; -} - +// ── MOVIECOVERS (Jaquettes HD FR) ── function fetchFromMovieCovers($title, $year = '') { - $empty = [ - 'title' => '', 'year' => '', 'director' => '', 'actors' => '', - 'poster' => '', 'description' => '', 'length' => '', - 'publisher' => '', 'format' => 'DVD', 'number_of_discs' => 1, - 'aspect_ratio' => '' - ]; + $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); @@ -658,7 +292,6 @@ function fetchFromMovieCovers($title, $year = '') { if (preg_match('/([^<]+)<\/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]); @@ -668,10 +301,7 @@ function fetchFromMovieCovers($title, $year = '') { if ($idmc) { $idmc_encoded = urlencode($idmc); - $hd_urls = [ - "https://moviecovers.com/DATA/zipcache/{$idmc_encoded}.jpg", - "https://moviecovers.com/getjpg.html/{$idmc_encoded}.jpg" - ]; + $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) { @@ -683,7 +313,6 @@ function fetchFromMovieCovers($title, $year = '') { 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]; } @@ -696,79 +325,33 @@ function fetchFromMovieCovers($title, $year = '') { 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 +// ── AGGREGATEUR PHYSIQUE (FNAC -> BLU-RAY.COM) ── +function fetchPhysicalByEan($ean, $pdo = null) { + // 1. Tenter la FNAC (Titre français garanti) + $fnacData = fetchFromFnac($ean); + if (!empty($fnacData['title'])) { + $res = $fnacData; + $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; } - 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]); + // 2. Fallback sur Blu-ray.com + $blurayData = fetchFromBlurayCom($ean); + if (!empty($blurayData['title'])) { + $res = $blurayData; + $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; } - 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; + return emptyPhysicalResult(); } // ── ROUTEUR PRINCIPAL ── @@ -800,90 +383,60 @@ switch ($action) { 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 'get_config_keys': + $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'"); + $stmt->execute(); $row = $stmt->fetch(); + echo json_encode(['tmdb_api_key' => $row ? decryptData($row['key_value']) : '']); + 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': - // 1. Récupération des critiques - $critiques = $pdo->query("SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type, NULL AS format FROM critiques ORDER BY id DESC")->fetchAll(); + case 'save_config': + checkAuth($pdo); + $name = $data['key_name'] ?? ''; + $val = trim($data['key_value'] ?? ''); + if ($name !== 'tmdb_api_key' || empty($val)) { http_response_code(400); echo json_encode(["error" => "Clé invalide."]); break; } + $stmt = $pdo->prepare("REPLACE INTO config (key_name, key_value) VALUES (?, ?)"); + $stmt->execute([$name, encryptData($val)]); + echo json_encode(["success" => true]); + break; - // CORRECTION : Utilisation du & pour modifier le tableau original + case 'get_films': + $critiques = $pdo->query("SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type, NULL AS format FROM critiques ORDER BY id DESC")->fetchAll(); foreach ($critiques as $row) { if ($row['rating'] !== null) { $ratingVal = (float)$row['rating']; $row['rating'] = ($ratingVal == floor($ratingVal)) ? (int)$ratingVal : $ratingVal; } } - unset($row); // Sécurité pour libérer la référence - - // 2. Récupération de la vidéothèque + unset($row); $videotheque = $pdo->query("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")->fetchAll(); - - // LOGGING : Utile pour voir dans php_errors.log si les tables sont vides - error_log("API get_films - Critiques trouvées: " . count($critiques) . " | Vidéothèque: " . count($videotheque)); - - echo json_encode([ - 'critique' => $critiques, - 'videotheque' => $videotheque - ]); + echo json_encode(['critique' => $critiques, 'videotheque' => $videotheque]); 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 ($type === 'critique') { + if (empty($data['director']) || empty($data['poster'])) { + $tmdbData = fetchTmdbPosterAndSynopsis($data['title'] ?? '', $data['year'] ?? '', $pdo); 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'; + $streaming = $data['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 ($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'] ?? '']); @@ -906,203 +459,107 @@ case 'get_films': 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; - - case 'add_item_by_ean': - $data = json_decode(file_get_contents("php://input"), true); - $ean = preg_replace('/[^0-9]/', '', $data['ean'] ?? ''); - - if (strlen($ean) < 8) { - echo json_encode(["success" => false, "error" => "EAN invalide"]); - exit; - } - - error_log("--- DÉBUT RECHERCHE EAN : $ean ---"); + + case 'add_item_by_ean': + $ean = preg_replace('/[^0-9]/', '', $data['ean'] ?? ''); + if (strlen($ean) < 8) { echo json_encode(["success" => false, "error" => "EAN invalide"]); exit; } + + $physicalData = fetchPhysicalByEan($ean, $pdo); + $rawTitle = $physicalData['title'] ?? ''; + + $tmdbData = fetchTmdbPosterAndSynopsis($rawTitle, $physicalData['year'] ?? '', $pdo); + + $finalTitle = !empty($tmdbData['title']) ? $tmdbData['title'] : $rawTitle; + $poster = ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') ? $tmdbData['poster'] : ($physicalData['poster'] ?? ''); + + $year = $tmdbData['year'] ?? $physicalData['year'] ?? ''; + $id = makeStableId('videotheque', $finalTitle, $year); + + $stmt = $pdo->prepare("INSERT INTO videotheque (id, title, year, poster, description, director, actors, ean_isbn13, format, length, publisher, number_of_discs, aspect_ratio) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), poster=IF(VALUES(poster)!='', VALUES(poster), poster), description=IF(VALUES(description)!='', VALUES(description), description), director=IF(VALUES(director)!='', VALUES(director), director), actors=IF(VALUES(actors)!='', VALUES(actors), actors)"); + $stmt->execute([ + $id, $finalTitle, $year, $poster, + $tmdbData['description'] ?? $physicalData['description'] ?? '', + $tmdbData['director'] ?: ($physicalData['director'] ?? ''), + $tmdbData['actors'] ?: ($physicalData['actors'] ?? ''), + $ean, $physicalData['format'] ?? detectFormat($rawTitle), + $tmdbData['length'] ?: ($physicalData['length'] ?? ''), + $physicalData['publisher'] ?? '', $physicalData['number_of_discs'] ?? 1, $physicalData['aspect_ratio'] ?? '' + ]); - // 1. Récupérer les données physiques - $physicalData = fetchPhysicalByEan($ean, $pdo); - $rawTitle = $physicalData['title'] ?? ''; - $directorFromEan = $physicalData['director'] ?? ''; - - error_log("UPCitemdb titre brut : '$rawTitle'"); - if ($directorFromEan) error_log("UPCitemdb réalisateur : '$directorFromEan'"); - - // 2. Chercher sur TMDB avec validation par réalisateur - $tmdbData = smartSearchTmdbWithDirector( - $rawTitle, - $directorFromEan, - $physicalData['year'] ?? '', - $pdo - ); - - // 3. Fusionner les données - $finalTitle = !empty($tmdbData['title']) ? $tmdbData['title'] : $rawTitle; - $poster = ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') - ? $tmdbData['poster'] - : ($physicalData['poster'] ?? ''); - - error_log("=== RÉSULTAT FINAL ==="); - error_log("Titre FR : $finalTitle"); - error_log("Réalisateur : " . ($tmdbData['director'] ?: $directorFromEan)); - - // 4. Insertion en base - $year = $tmdbData['year'] ?? $physicalData['year'] ?? ''; - $id = makeStableId('videotheque', $finalTitle, $year); - - $stmt = $pdo->prepare("INSERT INTO videotheque - (id, title, year, poster, description, director, actors, ean_isbn13, format, length, publisher, number_of_discs, aspect_ratio) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - title=VALUES(title), year=VALUES(year), poster=IF(VALUES(poster)!='', VALUES(poster), poster), - description=IF(VALUES(description)!='', VALUES(description), description), - director=IF(VALUES(director)!='', VALUES(director), director), - actors=IF(VALUES(actors)!='', VALUES(actors), actors)"); - - $stmt->execute([ - $id, $finalTitle, $year, $poster, - $tmdbData['description'] ?? '', - $tmdbData['director'] ?: $directorFromEan, - $tmdbData['actors'] ?? '', - $ean, - $physicalData['format'] ?? detectFormat($rawTitle), - $tmdbData['length'] ?? '', - $physicalData['publisher'] ?? '', - $physicalData['number_of_discs'] ?? 1, - $physicalData['aspect_ratio'] ?? '' - ]); - - echo json_encode([ - "success" => true, - "title" => $finalTitle, - "director" => $tmdbData['director'] ?: $directorFromEan, - "year" => $year - ]); - 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'] ?? '')); + echo json_encode(["success" => true, "title" => $finalTitle, "director" => $tmdbData['director'] ?: ($physicalData['director'] ?? ''), "year" => $year]); + break; + + case 'import_batch': + checkAuth($pdo); + $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)"); - if (strlen($ean) < 8) { - $skipped++; - continue; + foreach ($items as $item) { + $ean = preg_replace('/[^0-9]/', '', (string)($item['ean'] ?? '')); + if (strlen($ean) < 8) { $skipped++; continue; } + + $physicalData = fetchPhysicalByEan($ean, $pdo); + $title = $physicalData['title'] ?? ''; + $year = $physicalData['year'] ?? ''; + $format = $physicalData['format'] ?: detectFormat($title); + $publisher = $physicalData['publisher'] ?? ''; + $discs = $physicalData['number_of_discs'] ?: 1; + $aspect = $physicalData['aspect_ratio'] ?? ''; + $length = $physicalData['length'] ?? ''; + $poster = $physicalData['poster'] ?? ''; + $desc = $physicalData['description'] ?? ''; + $director = $physicalData['director'] ?? ''; + $actors = $physicalData['actors'] ?? ''; + + 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++; } - - // 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']; + } else { + $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 = !empty($rowData['publish_date']) ? substr($rowData['publish_date'], 0, 4) : ($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++; } - - // 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(); + http_response_code(500); + echo json_encode(["success" => false, "error" => $e->getMessage()]); } - $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; + break; } \ No newline at end of file