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

840 lines
43 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_errors.log');
ini_set('display_errors', 0);
set_time_limit(600);
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
]);
$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 ---
function getAuthToken() {
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;
}
}
return '';
}
function checkAuth($pdo) {
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) return true;
$token = getAuthToken();
if ($token !== md5(ENCRYPTION_KEY . 'session')) {
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 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;
}
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 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' => '',
'director' => '', 'actors' => '', 'poster' => '', 'description' => ''
];
}
// ── NOUVELLE FONCTION : SCRAPPING DVDFR.COM ──
function fetchFromDvdfr($ean) {
$empty = emptyPhysicalResult();
$url = "https://www.dvdfr.com/listeliv.php?base=dvd&mots_recherche=" . urlencode($ean);
$html = httpGet($url, 15);
if (!$html) return $empty;
// Recherche du premier produit trouvé dans la liste
if (preg_match('/<li>\s*<div class="col-xs-4 text-center mb-01 col-md-2">.*?<a href="\/dvd\/[^"]+\/([^"]+)\/">.*?<img[^>]*src="([^"]+)"[^>]*alt="([^"]+)".*?<\/a>.*?<h2 class="livre_titre">.*?<a[^>]*>([^<]+)<\/a>.*?<\/h2>.*?<h2 class="livre_auteur">.*?<a[^>]*>([^<]+)<\/a>.*?<\/h2>.*?<li class="editeur">.*?<span[^>]*>([^<]+)<\/span>.*?<\/li>.*?<span class="item_format">.*?<a[^>]*>([^<]+)<\/a>.*?<span class="item_prix[^>]*>([^<]+)<\/span>/is', $html, $m)) {
// Titre
$empty['title'] = trim($m[4]);
// Image (version haute qualité)
$imageUrl = trim($m[2]);
// Remplacer _v.jpg par _1.jpg pour avoir l'image en haute qualité
$imageUrl = str_replace('_v.jpg', '_1.jpg', $imageUrl);
$empty['poster'] = $imageUrl;
// Réalisateur
$empty['director'] = trim($m[5]);
// Éditeur
$empty['publisher'] = trim($m[6]);
// Format
$format = trim($m[7]);
$empty['format'] = detectFormat($format);
// Prix (optionnel)
$price = trim($m[8]);
} else {
// Fallback : recherche individuelle des éléments
if (preg_match('/<h2 class="livre_titre">.*?<a[^>]*>([^<]+)<\/a>/is', $html, $m)) {
$empty['title'] = trim($m[1]);
}
if (preg_match('/<h2 class="livre_auteur">.*?<a[^>]*>([^<]+)<\/a>/is', $html, $m)) {
$empty['director'] = trim($m[1]);
}
if (preg_match('/<li class="editeur">.*?<span[^>]*>([^<]+)<\/span>/is', $html, $m)) {
$empty['publisher'] = trim($m[1]);
}
if (preg_match('/<img[^>]*id="img' . preg_quote($ean, '/') . '"[^>]*src="([^"]+)"/i', $html, $m)) {
$imageUrl = trim($m[1]);
$imageUrl = str_replace('_v.jpg', '_1.jpg', $imageUrl);
$empty['poster'] = $imageUrl;
} elseif (preg_match('/<img[^>]*src="(https:\/\/images\.epagine\.fr\/[^"]+)"/i', $html, $m)) {
$imageUrl = trim($m[1]);
$imageUrl = str_replace('_v.jpg', '_1.jpg', $imageUrl);
$empty['poster'] = $imageUrl;
}
if (preg_match('/<span class="item_format">.*?<a[^>]*>([^<]+)<\/a>/is', $html, $m)) {
$format = trim($m[1]);
$empty['format'] = detectFormat($format);
}
}
// Nettoyage du titre
if (!empty($empty['title'])) {
$empty['title'] = html_entity_decode($empty['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$empty['title'] = cleanTitle($empty['title']);
$empty['title'] = trim($empty['title']);
}
return $empty;
}
// ── NOUVELLE FONCTION : SCRAPPING UPCINDEX.COM (MODIFIÉE AVEC MÉTADONNÉES) ──
function fetchFromUpcIndex($ean) {
$empty = emptyPhysicalResult();
$url = "https://www.upcindex.com/" . urlencode($ean);
$html = httpGet($url, 15);
if (!$html) return $empty;
// Recherche du titre
if (preg_match('/<h1[^>]*class="text-center"[^>]*>.*?<span[^>]*itemprop="name"[^>]*>([^<]+)<\/span>/is', $html, $m)) {
$empty['title'] = trim(strip_tags($m[1]));
} elseif (preg_match('/<title[^>]*>([^<]+)<\/title>/i', $html, $m)) {
$title = trim($m[1]);
$title = preg_replace('/\s*[-]\s*UPCindex.*$/i', '', $title);
$empty['title'] = $title;
}
// ✅ RÉCUPÉRATION DE L'IMAGE DEPUIS UPCINDEX (première image produit)
// Chercher la première image de produit (après le barcode)
if (preg_match_all('/<img[^>]*itemprop="http:\/\/schema\.org\/image"[^>]*src="([^"]+)"/i', $html, $matches)) {
// Prendre la première image de produit (pas le barcode)
foreach ($matches[1] as $imgSrc) {
if (strpos($imgSrc, 'barcode.png') === false && strpos($imgSrc, 'upcindex.com') !== false) {
$empty['poster'] = 'https:' . $imgSrc;
break;
}
}
}
// ✅ RÉCUPÉRATION DES MÉTADONNÉES PHYSIQUES
// Directeur
if (preg_match('/<dt[^>]*>director<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $m)) {
$empty['director'] = trim($m[1]);
}
// Acteurs
if (preg_match_all('/<dt[^>]*>actor<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $actorsMatches)) {
$empty['actors'] = implode(', ', array_map('trim', $actorsMatches[1]));
}
// Format
if (preg_match('/<dt[^>]*>format<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $m)) {
$format = trim($m[1]);
if (stripos($format, 'DVD') !== false) $empty['format'] = 'DVD';
elseif (stripos($format, 'Blu-ray') !== false) $empty['format'] = 'Blu-ray';
elseif (stripos($format, '4K') !== false) $empty['format'] = '4K Ultra HD';
}
// Aspect ratio
if (preg_match('/<dt[^>]*>aspect ratio<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $m)) {
$empty['aspect_ratio'] = trim($m[1]);
}
// Nombre de disques
if (preg_match('/<dt[^>]*>number of discs<\/dt>\s*<dd[^>]*>(\d+)<\/dd>/is', $html, $m)) {
$empty['number_of_discs'] = (int)$m[1];
}
// Brand/Publisher
if (preg_match('/<dt[^>]*>brand<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $m)) {
$empty['publisher'] = trim($m[1]);
}
// Region code
if (preg_match('/<dt[^>]*>region code<\/dt>\s*<dd[^>]*>([^<]+)<\/dd>/is', $html, $m)) {
$empty['description'] = 'Region ' . trim($m[1]);
}
// Nettoyage du titre
if (!empty($empty['title'])) {
$empty['title'] = html_entity_decode($empty['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$empty['title'] = trim($empty['title']);
}
if (!empty($empty['title'])) {
$empty['format'] = detectFormat($empty['title']);
}
return $empty;
}
// ── SCRAPPING FNAC ──
function fetchFromFnac($ean) {
$empty = emptyPhysicalResult();
$url = "https://www.fnac.com/SearchResult/ResultList.aspx?Search=" . urlencode($ean);
$html = httpGet($url, 15);
if (!$html) return $empty;
// GARDE-FOU : si l'EAN recherché n'apparaît nulle part dans le HTML
$eanDigits = preg_replace('/[^0-9]/', '', (string)$ean);
if ($eanDigits && strpos($html, $eanDigits) === false) {
error_log("fetchFromFnac: EAN $eanDigits absent du HTML retourné — page rejetée.");
return $empty;
}
// Chercher un lien avec un titre
if (preg_match('/<a[^>]*href="\/(?:[^"]*\/)?(?:tp\d+|A\d+|[^"]*-s\d+\.html)"[^>]*>([^<]+)<\/a>/i', $html, $m)) {
$empty['title'] = trim(strip_tags($m[1]));
}
elseif (preg_match('/<a[^>]*href="\/(?:[^"]*\/)?(?:tp\d+|A\d+|[^"]*-s\d+\.html)"[^>]*title="([^"]+)"[^>]*>/i', $html, $m)) {
$empty['title'] = trim($m[1]);
}
elseif (preg_match('/<h2[^>]*class="[^"]*f-product__name[^"]*"[^>]*>([^<]+)<\/h2>/i', $html, $m)) {
$empty['title'] = trim(strip_tags($m[1]));
}
elseif (preg_match('/<a[^>]*class="[^"]*js-ProductTitle[^"]*"[^>]*>([^<]+)<\/a>/i', $html, $m)) {
$empty['title'] = trim(strip_tags($m[1]));
}
if (!empty($empty['title'])) {
$empty['title'] = html_entity_decode($empty['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$empty['title'] = preg_replace('/\s*\|\s*Fnac.*$/i', '', $empty['title']);
$empty['title'] = trim($empty['title']);
}
if (preg_match('/<meta[^>]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) {
$empty['poster'] = trim($m[1]);
} elseif (preg_match('/<img[^>]*class="[^"]*js-ProductImage[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) {
$empty['poster'] = trim($m[1]);
}
if (!empty($empty['title'])) {
$empty['format'] = detectFormat($empty['title']);
}
return $empty;
}
// ── SCRAPPING BLU-RAY.COM (PAR TITRE) ──
function fetchFromBlurayComByTitle($title) {
static $lastRequest = 0;
$empty = emptyPhysicalResult();
if (empty($title)) return $empty;
$now = microtime(true);
if ($lastRequest > 0 && ($now - $lastRequest) < 3) {
usleep((int)((3 - ($now - $lastRequest)) * 1000000));
}
$lastRequest = microtime(true);
$cleanTitle = cleanTitle($title);
$searchUrl = "https://www.blu-ray.com/movies/search.php?keyword=" . urlencode($cleanTitle) . "&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);
if (!$searchHtml || $httpCode !== 200) return $empty;
if (!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('/<h3[^>]*>([^<]+)<\/h3>\s*(?:&nbsp;)?\((\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('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
$empty['poster'] = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]);
}
return $empty;
}
// ── SCRAPPING BLU-RAY.COM (PAR EAN - FALLBACK) ──
function fetchFromBlurayComByEan($ean) {
static $lastRequest = 0;
$empty = emptyPhysicalResult();
$ean = preg_replace('/[^0-9]/', '', (string)$ean);
if (strlen($ean) < 8) return $empty;
$now = microtime(true);
if ($lastRequest > 0 && ($now - $lastRequest) < 3) {
usleep((int)((3 - ($now - $lastRequest)) * 1000000));
}
$lastRequest = microtime(true);
$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);
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('/<div[^>]*id="movie_info"[^>]*>.*?<h3>([^<]+)<\/h3>/is', $movieHtml, $m)) {
$empty['title'] = trim($m[1]);
} elseif (preg_match('/<h1[^>]*>([^<]+)<\/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[^>]*>([^<]+)<\/h3>\s*(?:&nbsp;)?\((\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('/<img[^>]*class="coverfront"[^>]*src="([^"]+)"/i', $movieHtml, $m)) {
$empty['poster'] = preg_replace('/_large\.jpg/', '_front.jpg', $m[1]);
}
return $empty;
}
// ── 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;
$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) : [];
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;
$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;
}
// ── 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' => ''];
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]);
$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('/<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;
}
/ ── AGGREGATEUR PHYSIQUE (VERSION MODIFIÉE AVEC DVDFR) ──
function fetchPhysicalByEan($ean, $pdo = null) {
error_log("=== DEBUT fetchPhysicalByEan EAN=$ean ===");
$res = emptyPhysicalResult();
$title = '';
// 1. DVDFR.COM (Priorité 1 - pour titre, image et métadonnées)
$dvdfrData = fetchFromDvdfr($ean);
error_log("DVDFR -> title='" . ($dvdfrData['title'] ?? '') . "' poster='" . ($dvdfrData['poster'] ?? '') . "' director='" . ($dvdfrData['director'] ?? '') . "'");
if (!empty($dvdfrData['title'])) {
$title = $dvdfrData['title'];
$res = $dvdfrData;
}
// 2. UPCINDEX.COM (Priorité 2 - pour métadonnées physiques détaillées)
$upcIndexData = fetchFromUpcIndex($ean);
error_log("UPCINDEX -> title='" . ($upcIndexData['title'] ?? '') . "' director='" . ($upcIndexData['director'] ?? '') . "'");
if (!empty($upcIndexData['title']) && empty($title)) {
$title = $upcIndexData['title'];
}
// Fusionner les données UPCINDEX (métadonnées physiques)
if (!empty($upcIndexData)) {
if (!empty($upcIndexData['director']) && empty($res['director'])) {
$res['director'] = $upcIndexData['director'];
}
if (!empty($upcIndexData['actors'])) $res['actors'] = $upcIndexData['actors'];
if (!empty($upcIndexData['format']) && empty($res['format'])) {
$res['format'] = $upcIndexData['format'];
}
if (!empty($upcIndexData['aspect_ratio'])) $res['aspect_ratio'] = $upcIndexData['aspect_ratio'];
if (!empty($upcIndexData['number_of_discs'])) $res['number_of_discs'] = $upcIndexData['number_of_discs'];
if (!empty($upcIndexData['publisher']) && empty($res['publisher'])) {
$res['publisher'] = $upcIndexData['publisher'];
}
if (!empty($upcIndexData['description'])) $res['description'] = $upcIndexData['description'];
if (!empty($upcIndexData['year'])) $res['year'] = $upcIndexData['year'];
// Garder l'image de DVD.fr en priorité
if (empty($res['poster']) && !empty($upcIndexData['poster'])) {
$res['poster'] = $upcIndexData['poster'];
}
}
// 3. FNAC (Fallback si pas de titre)
if (empty($title)) {
$fnacData = fetchFromFnac($ean);
error_log("FNAC -> title='" . ($fnacData['title'] ?? '') . "'");
if (!empty($fnacData['title'])) {
$title = $fnacData['title'];
if (empty($res['title'])) $res = $fnacData;
}
}
// 4. MovieCovers (pour jaquette HD FR si pas d'image)
if (!empty($title) && empty($res['poster'])) {
$mc = fetchFromMovieCovers($title, $res['year'] ?? '');
error_log("MOVIECOVERS -> poster='" . ($mc['poster'] ?? '') . "'");
if (!empty($mc['poster'])) $res['poster'] = $mc['poster'];
}
error_log("=== FIN fetchPhysicalByEan -> title FINAL='" . ($res['title'] ?? '') . "' poster='" . ($res['poster'] ?? '') . "' ===");
return $res;
}
// ── 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;
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 ($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;
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);
$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();
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') {
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'];
}
$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 (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;
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;
// ✅ PRIORITÉ ABSOLUE À L'IMAGE DE GO-UPC/PHYSICAL
$poster = !empty($physicalData['poster']) ? $physicalData['poster'] :
(($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') ? $tmdbData['poster'] : 'assets/img/default_physical_media.jpg');
$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'] ?? '',
$physicalData['director'] ?: $tmdbData['director'], // ✅ Priorité aux données physiques
$physicalData['actors'] ?: $tmdbData['actors'], // ✅ Priorité aux données physiques
$ean, $physicalData['format'] ?? detectFormat($rawTitle),
$tmdbData['length'] ?: ($physicalData['length'] ?? ''),
$physicalData['publisher'] ?? '', $physicalData['number_of_discs'] ?? 1, $physicalData['aspect_ratio'] ?? ''
]);
echo json_encode(["success" => true, "title" => $finalTitle, "director" => $physicalData['director'] ?: $tmdbData['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)");
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'] ?? '';
// ✅ PRIORITÉ À L'IMAGE DE GO-UPC
$poster = !empty($physicalData['poster']) ? $physicalData['poster'] : 'assets/img/default_physical_media.jpg';
$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";
// ⚠️ NE PAS ÉCRASER L'IMAGE DE GO-UPC
if (empty($poster) && $tmdb['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 {
$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++;
}
}
$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()]);
}
break;
}