PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); // Initialisation BDD simplifiée $pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username VARCHAR(50), password_hash VARCHAR(255))"); $pdo->exec("CREATE TABLE IF NOT EXISTS config (key_name VARCHAR(50) PRIMARY KEY, key_value TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS critiques (id BIGINT PRIMARY KEY, title VARCHAR(255), year VARCHAR(10), director VARCHAR(255), poster TEXT, rating DECIMAL(3,1), review TEXT, streaming VARCHAR(255))"); $pdo->exec("CREATE TABLE IF NOT EXISTS videotheque (id BIGINT PRIMARY KEY, title VARCHAR(255), year VARCHAR(10), director VARCHAR(255), poster TEXT, format VARCHAR(50), length VARCHAR(50), publisher VARCHAR(255), ean_isbn13 VARCHAR(50), number_of_discs INT DEFAULT 1, aspect_ratio VARCHAR(50), description TEXT, actors TEXT)"); } catch (PDOException $e) { die(json_encode(["error" => "Connexion BDD échouée"])); } // --- Fonctions Utilitaires --- // Récupère le token d'authentification envoyé par le client, en tolérant les // configurations Apache/WAMP qui ne transmettent pas HTTP_AUTHORIZATION à PHP // par défaut (l'en-tête est bien envoyé par le navigateur, mais Apache le // "mange" avant qu'il n'atteigne $_SERVER, sauf si CGIPassAuth est activé). function getAuthToken() { if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { return $_SERVER['HTTP_AUTHORIZATION']; } // Cas fréquent avec RewriteRule / certains proxys : le header est déplacé ici if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { return $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; } // Filet de sécurité générique : relire les en-têtes bruts de la requête if (function_exists('getallheaders')) { foreach (getallheaders() as $name => $value) { if (strcasecmp($name, 'Authorization') === 0) { return $value; } } } elseif (function_exists('apache_request_headers')) { foreach (apache_request_headers() as $name => $value) { if (strcasecmp($name, 'Authorization') === 0) { return $value; } } } return ''; } function checkAuth($pdo) { if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) return true; $token = getAuthToken(); if ($token !== md5(ENCRYPTION_KEY . 'session')) { error_log("Auth: ❌ Token invalide ou absent (HTTP_AUTHORIZATION reçu: " . (empty($token) ? "VIDE — vérifier CGIPassAuth/config Apache" : "présent mais différent") . ")"); http_response_code(403); exit; } } function encryptData($data) { $iv = openssl_random_pseudo_bytes(16); return base64_encode(openssl_encrypt($data, 'AES-256-CBC', hash('sha256', ENCRYPTION_KEY, true), OPENSSL_RAW_DATA, $iv) . '::' . $iv); } function decryptData($str) { $decoded = base64_decode($str); if ($decoded === false || strpos($decoded, '::') === false) return null; list($enc, $iv) = explode('::', $decoded, 2); return openssl_decrypt($enc, 'AES-256-CBC', hash('sha256', ENCRYPTION_KEY, true), OPENSSL_RAW_DATA, substr($iv, 0, 16)); } function getTmdbApiKey($pdo) { $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'"); $stmt->execute(); $row = $stmt->fetch(); return $row ? decryptData($row['key_value']) : null; } function getUpcmdbApiKey($pdo) { $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'upcmdb_api_key'"); $stmt->execute(); $row = $stmt->fetch(); return $row ? decryptData($row['key_value']) : null; } function httpGet($url, $timeout = 10, $headers = []) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $timeout, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0', CURLOPT_HTTPHEADER => $headers ]); $res = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ($code === 200) ? $res : null; } // Vérifie qu'une URL d'image répond bien en 200 (utilisé pour valider les URLs // de couverture "devinées" à partir d'un ID, sans avoir à parser du HTML fragile) function urlExists($url) { if (empty($url)) return false; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_NOBODY => true, // requête HEAD CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0', ]); curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $code === 200; } function removeAccentsForUrl($str) { $str = str_replace(['œ', 'Œ'], ['oe', 'OE'], $str); $str = str_replace(['æ', 'Æ'], ['ae', 'AE'], $str); $transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str); if ($transliterated !== false && $transliterated !== '') $str = $transliterated; return $str; } function cleanTitle($title) { $clean = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $title); $clean = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Digibook|Ultimate|Intégrale|Combo|SteelBook|Boîtier).*$/i', '', $clean); $clean = preg_replace('/(blu-ray|bluray|dvd|4k|ultra hd|combo|vhs|bdrip).*$/i', '', $clean); return trim(preg_replace('/\s{2,}/', ' ', $clean)); } function detectFormat($title, $desc = '') { $t = strtoupper($title . ' ' . $desc); if (strpos($t, '4K') !== false || strpos($t, 'UHD') !== false) return '4K Ultra HD'; if (strpos($t, 'BLU-RAY') !== false || strpos($t, 'BLURAY') !== false) return 'Blu-ray'; if (strpos($t, 'DVD') !== false) return 'DVD'; if (strpos($t, 'VHS') !== false) return 'VHS'; if (strpos($t, 'COFFRET') !== false || strpos($t, 'TRILOGIE') !== false) return 'Coffret'; return 'Blu-ray'; } function parseDiscCountFromTitle($title) { if (preg_match('/(\d+)\s*(?:dvd|blu-?ray|bluray|bd|disc|disque)/i', $title, $m)) { return max(1, (int)$m[1]); } if (preg_match('/(?:coffret|pack|collection|trilogie|anthologie).*?(\d+)/i', $title, $m)) { return max(1, (int)$m[1]); } if (preg_match('/\btrilogie\b/i', $title)) return 3; return 1; } function cleanUpcTitle($title) { // Recherche la position du premier guillemet (") $firstQuote = strpos($title, '"'); // Recherche la position du dernier guillemet (") $lastQuote = strrpos($title, '"'); // Si on a trouvé au moins deux guillemets différents if ($firstQuote !== false && $lastQuote !== false && $firstQuote !== $lastQuote) { // On extrait et nettoie ce qu'il y a strictement entre les deux return trim(substr($title, $firstQuote + 1, $lastQuote - $firstQuote - 1)); } // Si pas de guillemets trouvés, on retourne le titre original (fallback de sécurité) return trim($title); } function emptyPhysicalResult() { return [ 'title' => '', 'publisher' => '', 'format' => '', 'length' => '', 'number_of_discs' => 1, 'aspect_ratio' => '', 'year' => '' ]; } function makeStableId($type, $title, $year) { return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000; } // ── FONCTIONS API PHYSIQUE (UPCitemdb → UPCMDB fallback) ── function throttleUpcLookup() { static $last = 0; $elapsed = microtime(true) - $last; if ($last > 0 && $elapsed < 2) usleep((int)((2 - $elapsed) * 1000000)); $last = microtime(true); } function fetchPhysicalFromUpcitemdb($ean) { $empty = ['title'=>'','publisher'=>'','format'=>'','length'=>'','number_of_discs'=>1,'aspect_ratio'=>'','year'=>'']; $ean = preg_replace('/[^0-9]/', '', (string)$ean); if (strlen($ean) < 8) return $empty; throttleUpcLookup(); $res = httpGet("https://api.upcitemdb.com/prod/trial/lookup?upc=" . urlencode($ean), 10); if (!$res) return $empty; $data = json_decode($res, true); if (empty($data['items'][0])) return $empty; $item = $data['items'][0]; $raw = $item['title'] ?? ''; $clean = cleanUpcTitle($raw) ?: $raw; return [ 'title' => $clean, 'publisher' => trim($item['brand'] ?? $item['manufacturer'] ?? ''), 'format' => detectFormat($raw), 'number_of_discs' => parseDiscCountFromTitle($raw), 'aspect_ratio' => '', 'year' => '', 'length' => '' ]; } function fetchFromUpcIndex($ean) { $url = "https://www.upcindex.com/" . urlencode($ean); $html = httpGet($url, 15); // Utilise votre fonction curl existante if ($html && preg_match('/
([^<]+)<\/PRE>/is', $html, $m)) {
$idmc = trim($m[1]);
} elseif (preg_match('/ 5000) {
$empty['poster'] = $hd_url;
break;
}
}
}
if (empty($empty['poster'])) {
if (preg_match('/
]*title="Recto[^"]*"[^>]*src="([^"]+)"/i', $html, $m)) $empty['poster'] = $m[1];
elseif (preg_match('/
]*src="([^"]+)"[^>]*title="Recto/i', $html, $m)) $empty['poster'] = $m[1];
elseif (preg_match('/]*property="og:image"[^>]*content="([^"]+)"/i', $html, $m)) $empty['poster'] = $m[1];
}
if (preg_match('/Réalisateur<\/TH>\s*]*>.*?]*>([^<]+)<\/a>/is', $html, $m)) $empty['director'] = trim($m[1]);
if (preg_match('/Année<\/TH>\s* ]*>.*?]*>(\d{4})<\/a>/is', $html, $m)) $empty['year'] = $m[1];
if (preg_match('/Durée<\/TH>\s* ]*>([\dH]+[\dmin]*)/is', $html, $m)) $empty['length'] = trim($m[1]);
if (preg_match_all('/([^<]+)<\/a>/i', $html, $matches)) $empty['actors'] = implode(', ', array_slice($matches[1], 0, 5));
if (preg_match('/]*property="og:description"[^>]*content="([^"]+)"/i', $html, $m)) $empty['description'] = html_entity_decode($m[1]);
return $empty;
}
function removeAccents($string) {
$string = htmlentities($string, ENT_NOQUOTES, 'utf-8');
$string = preg_replace('#&([A-za-z])(?:acute|cedil|caron|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $string);
$string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string);
$string = preg_replace('#&[^;]+;#', '', $string);
return $string;
}
function fetchAndDownloadMovieCovers($title, $ean) {
if (empty($title) || empty($ean)) return null;
// Création du dossier d'images s'il n'existe pas
$dir = __DIR__ . '/../assets/img/covers';
if (!is_dir($dir)) mkdir($dir, 0777, true);
$localPath = "assets/img/covers/{$ean}.jpg";
$fullPath = __DIR__ . '/../' . $localPath;
// Si la jaquette a déjà été téléchargée lors d'un précédent import, on la réutilise direct
if (file_exists($fullPath)) return $localPath;
// Nettoyage du titre pour la recherche MovieCovers
$cleanTitle = removeAccents(trim(preg_replace('/(blu-ray|bluray|dvd|4k|ultra hd).*$/i', '', $title)));
$searchUrl = "https://www.moviecovers.com/multicrit.html?titre=" . urlencode(utf8_decode($cleanTitle));
$html = file_get_contents($searchUrl, false, stream_context_create([
'http' => ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 10]
]));
if (!$html) return null;
$filmHtml = null;
if (preg_match('/href=["\']?\/?(film\/titre_[^"\']+)\.html["\']?/i', $html, $m)) {
$filmUrl = "https://www.moviecovers.com/" . $m[1] . ".html";
usleep(500000); // Pause de 0.5s pour ne pas spammer le serveur
$filmHtml = file_get_contents($filmUrl, false, stream_context_create([
'http' => ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 10]
]));
} else if (stripos($html, 'IDMC') !== false) {
$filmHtml = $html; // Accès direct
}
if (!$filmHtml) return null;
// Extraction de l'identifiant IDMC
$idmc = null;
if (preg_match('/IDMC.*?([^<]+)<\/PRE>/is', $filmHtml, $m)) {
$idmc = trim($m[1]);
} elseif (preg_match('/ ['method' => 'GET', 'header' => "Referer: https://www.moviecovers.com/\r\nUser-Agent: Mozilla/5.0\r\n", 'timeout' => 15]
]));
// Si l'image est valide et fait plus de 5Ko
if ($imgData && strlen($imgData) > 5000) {
file_put_contents($fullPath, $imgData);
return $localPath;
}
}
}
return null;
}
// ── ROUTEUR PRINCIPAL ──
$action = $_GET['action'] ?? '';
$data = json_decode(file_get_contents('php://input'), true) ?? [];
switch ($action) {
case 'check_security_status':
echo json_encode(["is_blank" => ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0)]);
break;
case 'login':
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) {
echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => true]);
} else {
$stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username = 'admin'");
$stmt->execute(); $user = $stmt->fetch();
if ($user && password_verify($data['password'] ?? '', $user['password_hash'])) {
echo json_encode(["success" => true, "token" => md5(ENCRYPTION_KEY . 'session'), "blank" => false]);
} else { http_response_code(401); echo json_encode(["error" => "Mot de passe incorrect."]); }
}
break;
case 'setup_admin': case 'update_password':
checkAuth($pdo);
$pwd = $data['password'] ?? $data['new_password'] ?? '';
$stmt = $pdo->prepare("REPLACE INTO users (id, username, password_hash) VALUES (1, 'admin', :pass)");
$stmt->execute([':pass' => password_hash($pwd, PASSWORD_BCRYPT)]);
echo json_encode(["success" => true]);
break;
// ── CONFIGURATION ──
case 'get_config_keys':
$keys = ['tmdb_api_key', 'upcmdb_api_key'];
$config = [];
foreach ($keys as $k) {
$stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = ?");
$stmt->execute([$k]);
$row = $stmt->fetch();
$config[$k] = $row ? decryptData($row['key_value']) : '';
}
echo json_encode($config);
break;
case 'save_config':
checkAuth($pdo);
$name = $data['key_name'] ?? '';
$val = trim($data['key_value'] ?? '');
if (!in_array($name, ['tmdb_api_key', 'upcmdb_api_key'])) {
http_response_code(400); echo json_encode(["error" => "Clé invalide."]); break;
}
if (empty($val)) break; // Ne rien écraser si vide
$stmt = $pdo->prepare("REPLACE INTO config (key_name, key_value) VALUES (?, ?)");
$stmt->execute([$name, encryptData($val)]);
echo json_encode(["success" => true]);
break;
case 'get_films':
$sql = "
SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type, NULL AS format
FROM critiques
UNION ALL
SELECT id, title, year, director, poster, NULL AS rating, NULL AS review, description, NULL AS streaming, 'videotheque' AS type, format
FROM videotheque
ORDER BY id DESC
";
$result = $pdo->query($sql)->fetchAll();
// IMPORTANT : Utilisation du pointeur `&` pour que la modification soit effective dans $result
foreach ($result as $row) {
if ($row['rating'] !== null) {
$ratingVal = (float)$row['rating'];
$row['rating'] = ($ratingVal == floor($ratingVal)) ? (int)$ratingVal : $ratingVal;
}
}
unset($row);
echo json_encode($result);
break;
case 'save_film':
checkAuth($pdo);
$type = $data['type'] ?? 'critique';
$id = !empty($data['id']) ? $data['id'] : makeStableId($type, $data['title'] ?? '', $data['year'] ?? '0000');
if ($type === 'critique' && (empty($data['director']) || empty($data['poster']))) {
$tmdbData = fetchTmdbPosterAndSynopsis($data['title'] ?? '', $data['year'] ?? '', $pdo);
if ($tmdbData) {
if (empty($data['director'])) $data['director'] = $tmdbData['director'];
if (empty($data['poster'])) $data['poster'] = $tmdbData['poster'];
}
}
if ($type === 'critique') {
$streaming = $data['streaming'] ?? '';
if (empty($streaming)) $streaming = 'Support physique / Cinéma';
$sql = "INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=VALUES(director), poster=VALUES(poster), rating=VALUES(rating), review=VALUES(review), streaming=VALUES(streaming)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['rating'] ?? 3.0, $data['review'] ?? '', $streaming]);
} else {
if (empty($data['poster']) && !empty($data['title'])) {
$tmdbData = fetchTmdbPosterAndSynopsis($data['title'], $data['year'] ?? '', $pdo);
if ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') {
$data['poster'] = $tmdbData['poster'];
}
if (empty($data['description'])) $data['description'] = $tmdbData['description'];
if (empty($data['director'])) $data['director'] = $tmdbData['director'];
if (empty($data['actors'])) $data['actors'] = $tmdbData['actors'];
if (empty($data['length'])) $data['length'] = $tmdbData['length'];
if (empty($data['format'])) $data['format'] = detectFormat($data['title'], $data['description'] ?? '');
}
$sql = "INSERT INTO videotheque (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description, actors) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=IF(VALUES(director)!='', VALUES(director), director), poster=IF(VALUES(poster)!='', VALUES(poster), poster), format=IF(VALUES(format)!='', VALUES(format), format), length=IF(VALUES(length)!='', VALUES(length), length), publisher=IF(VALUES(publisher)!='', VALUES(publisher), publisher), ean_isbn13=IF(VALUES(ean_isbn13)!='', VALUES(ean_isbn13), ean_isbn13), number_of_discs=IF(VALUES(number_of_discs)!=1, VALUES(number_of_discs), number_of_discs), aspect_ratio=IF(VALUES(aspect_ratio)!='', VALUES(aspect_ratio), aspect_ratio), description=IF(VALUES(description)!='', VALUES(description), description), actors=IF(VALUES(actors)!='', VALUES(actors), actors)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $data['title'] ?? '', $data['year'] ?? '', $data['director'] ?? '', $data['poster'] ?? '', $data['format'] ?? '', $data['length'] ?? '', $data['publisher'] ?? '', $data['ean_isbn13'] ?? '', $data['number_of_discs'] ?? 1, $data['aspect_ratio'] ?? '', $data['description'] ?? '', $data['actors'] ?? '']);
}
echo json_encode(["success" => true]);
break;
case 'delete_film':
checkAuth($pdo);
$type = $_GET['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
$id = $_GET['id'] ?? null;
if (!$id) { http_response_code(400); echo json_encode(["error" => "ID manquant."]); break; }
$stmt = $pdo->prepare("DELETE FROM $table WHERE id = ?"); $stmt->execute([$id]);
echo json_encode(["success" => true]);
break;
case 'bulk_delete':
checkAuth($pdo);
$ids = $data['ids'] ?? []; $type = $data['type'] ?? 'critique'; $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
if (!empty($ids)) { $placeholders = implode(',', array_fill(0, count($ids), '?')); $stmt = $pdo->prepare("DELETE FROM $table WHERE id IN ($placeholders)"); $stmt->execute($ids); echo json_encode(["success" => true]); }
else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); }
break;
// ── NOUVELLE ACTION : Recherche par EAN ──
case 'search_by_ean':
checkAuth($pdo);
$ean = $_GET['ean'] ?? '';
$ean = preg_replace('/[^0-9]/', '', $ean);
if (strlen($ean) < 8) {
echo json_encode(["success" => false, "error" => "EAN invalide"]);
break;
}
// 1. Blu-ray.com
$blurayData = fetchFromBlurayCom($ean);
// 2. MovieCovers pour l'affiche HD
$mcData = [];
if (!empty($blurayData['title'])) {
$mcData = fetchFromMovieCovers($blurayData['title'], $blurayData['year'] ?? '');
}
// 3. Fallback TMDB
$tmdbData = [];
$title = !empty($blurayData['title']) ? $blurayData['title'] : '';
if ($title) {
$tmdbData = fetchTmdbPosterAndSynopsis($title, $blurayData['year'] ?? '', $pdo);
}
// Fusion des données
$result = [
'success' => true,
'title' => $blurayData['title'] ?? '',
'year' => $blurayData['year'] ?? ($tmdbData['year'] ?? ''),
'director' => $mcData['director'] ?? ($blurayData['director'] ?? ($tmdbData['director'] ?? '')),
'poster' => $mcData['poster'] ?? ($blurayData['poster'] ?? ($tmdbData['poster'] ?? '')),
'format' => $blurayData['format'] ?? 'Blu-ray',
'length' => $blurayData['length'] ?? ($tmdbData['length'] ?? ''),
'publisher' => $blurayData['publisher'] ?? '',
'number_of_discs' => $blurayData['number_of_discs'] ?? 1,
'aspect_ratio' => $blurayData['aspect_ratio'] ?? '',
'actors' => $mcData['actors'] ?? ($blurayData['actors'] ?? ($tmdbData['actors'] ?? '')),
'description' => $mcData['description'] ?? ($blurayData['description'] ?? ($tmdbData['description'] ?? ''))
];
echo json_encode($result);
break;
case 'add_item_by_ean':
$data = json_decode(file_get_contents("php://input"), true);
$ean = $data['ean'] ?? '';
// 1. Récupération via UpcIndex (Priorité 1)
$title = fetchFromUpcIndex($ean);
// Fallback si UpcIndex échoue
if (!$title) {
$blurayData = fetchFromBlurayCom($ean);
$title = $blurayData['title'] ?? '';
}
if (empty($title)) {
echo json_encode(["success" => false, "error" => "EAN non trouvé sur UpcIndex ni Blu-ray.com"]);
exit;
}
// 2. Recherche sur TMDB (Source de vérité pour le titre FR et infos)
// On utilise fetchTmdbPosterAndSynopsis pour récupérer le titre français, poster, etc.
$tmdbData = fetchTmdbPosterAndSynopsis($title, '', $pdo);
// Si TMDB ne trouve rien avec le titre trouvé, on utilise le titre original
$finalTitle = (!empty($tmdbData['title']) && $tmdbData['title'] !== 'assets/img/default_physical_media.jpg')
? $tmdbData['title']
: $title;
$poster = ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') ? $tmdbData['poster'] : '';
// 3. Insertion
$id = makeStableId('videotheque', $finalTitle, date('Y'));
$stmt = $pdo->prepare("INSERT INTO videotheque
(id, title, poster, description, director, actors, ean_isbn13)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE title=VALUES(title)");
$stmt->execute([
$id,
$finalTitle,
$poster,
$tmdbData['description'] ?? '',
$tmdbData['director'] ?? '',
$tmdbData['actors'] ?? '',
$ean
]);
echo json_encode(["success" => true, "title" => $finalTitle]);
break;
case 'import_batch':
checkAuth($pdo);
$data = json_decode(file_get_contents("php://input"), true);
$type = $data['type'] ?? '';
$items = $data['items'] ?? [];
$pdo->beginTransaction();
$imported = 0; $skipped = 0;
try {
if ($type === 'videotheque') {
$stmt = $pdo->prepare("INSERT INTO videotheque (id, title, year, format, poster, ean_isbn13, description, length, number_of_discs, aspect_ratio, actors, publisher, director)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE
title=VALUES(title), year=VALUES(year), format=VALUES(format),
poster=IF(VALUES(poster)!='assets/img/default_physical_media.jpg', VALUES(poster), poster),
ean_isbn13=IF(VALUES(ean_isbn13)!='', VALUES(ean_isbn13), ean_isbn13),
description=IF(VALUES(description)!='', VALUES(description), description),
length=IF(VALUES(length)!='', VALUES(length), length),
number_of_discs=IF(VALUES(number_of_discs)!=1, VALUES(number_of_discs), number_of_discs),
aspect_ratio=IF(VALUES(aspect_ratio)!='', VALUES(aspect_ratio), aspect_ratio),
actors=IF(VALUES(actors)!='', VALUES(actors), actors),
publisher=IF(VALUES(publisher)!='', VALUES(publisher), publisher),
director=IF(VALUES(director)!='', VALUES(director), director)");
foreach ($items as $item) {
// ✅ ON NE RÉCUPÈRE QUE L'EAN
$ean = preg_replace('/[^0-9]/', '', (string)($item['ean'] ?? ''));
if (strlen($ean) < 8) {
$skipped++;
continue;
}
// 1. BLU-RAY.COM (Métadonnées physiques et affiche de base)
$blurayData = fetchFromBlurayCom($ean);
if (!is_array($blurayData)) $blurayData = [];
$title = $blurayData['title'] ?? '';
$year = $blurayData['year'] ?? '';
$format = $blurayData['format'] ?: detectFormat($title);
$publisher = $blurayData['publisher'] ?? '';
$discs = $blurayData['number_of_discs'] ?: 1;
$aspect = $blurayData['aspect_ratio'] ?? '';
$length = $blurayData['length'] ?? '';
$poster = $blurayData['poster'] ?? '';
$desc = $blurayData['description'] ?? '';
$director = $blurayData['director'] ?? '';
$actors = $blurayData['actors'] ?? '';
// 2. MOVIECOVERS (Pour l'affiche HD physique et complément d'infos)
if (!empty($title)) {
$mcData = fetchFromMovieCovers($title, $year);
if (!is_array($mcData)) $mcData = [];
// Priorité à l'affiche HD de MovieCovers
if (!empty($mcData['poster'])) $poster = $mcData['poster'];
if (empty($director) && !empty($mcData['director'])) $director = $mcData['director'];
if (empty($actors) && !empty($mcData['actors'])) $actors = $mcData['actors'];
if (empty($desc) && !empty($mcData['description'])) $desc = $mcData['description'];
if (empty($length) && !empty($mcData['length'])) $length = $mcData['length'];
if (empty($year) && !empty($mcData['year'])) $year = $mcData['year'];
}
// 3. FALLBACK TMDB (Si des données manquent encore)
if (empty($poster) || empty($director) || empty($actors) || empty($desc) || empty($title)) {
$tmdb = fetchTmdbPosterAndSynopsis($title, $year, $pdo);
if (empty($title)) $title = $tmdb['title'] ?? "EAN: $ean";
if (empty($poster) || $poster === 'assets/img/default_physical_media.jpg') $poster = $tmdb['poster'];
if (empty($director)) $director = $tmdb['director'] ?? '';
if (empty($actors)) $actors = $tmdb['actors'] ?? '';
if (empty($desc)) $desc = $tmdb['description'] ?? '';
if (empty($length) && !empty($tmdb['length'])) $length = $tmdb['length'];
if (empty($year) && !empty($tmdb['year'])) $year = $tmdb['year'];
}
if (empty($title)) {
$skipped++;
continue;
}
$id = makeStableId('videotheque', $title, $year);
$stmt->execute([$id, $title, $year, $format, $poster, $ean, $desc, $length, $discs, $aspect, $actors, $publisher, $director]);
$imported++;
}
} else {
// Import critiques (inchangé)
$stmtCritiques = $pdo->prepare("INSERT INTO critiques (id, title, year, director, poster, rating, review, streaming)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE title=VALUES(title), year=VALUES(year), director=VALUES(director),
poster=VALUES(poster), rating=VALUES(rating), review=VALUES(review), streaming=VALUES(streaming)");
foreach ($items as $rowData) {
$title = $rowData['Title'] ?? $rowData['title'] ?? '';
if (empty($title)) continue;
$year = '';
if (!empty($rowData['publish_date'])) $year = substr($rowData['publish_date'], 0, 4);
else $year = $rowData['Year'] ?? $rowData['year'] ?? '';
$id = makeStableId('critique', $title, $year);
$ratingRaw = $rowData['Rating'] ?? $rowData['rating'] ?? '';
$rating = ($ratingRaw !== '' && $ratingRaw !== null) ? (float)$ratingRaw : null;
$review = $rowData['Review'] ?? $rowData['review'] ?? $rowData['description'] ?? '';
$director = '';
$poster = 'assets/img/default_physical_media.jpg';
$streaming = 'Support physique / Cinéma';
$tmdbData = fetchTmdbPosterAndSynopsis($title, $year, $pdo);
if ($tmdbData) {
if (!empty($tmdbData['director'])) $director = $tmdbData['director'];
if ($tmdbData['poster'] !== 'assets/img/default_physical_media.jpg') $poster = $tmdbData['poster'];
if (empty($year) && !empty($tmdbData['year'])) $year = $tmdbData['year'];
}
$stmtCritiques->execute([$id, $title, $year, $director, $poster, $rating, $review, $streaming]);
$imported++;
}
}
$pdo->commit();
echo json_encode(["success" => true, "imported" => $imported, "skipped" => $skipped]);
} catch (Throwable $e) {
$pdo->rollBack();
error_log("import_batch error: " . $e->getMessage());
http_response_code(500);
echo json_encode(["success" => false, "error" => $e->getMessage(), "line" => $e->getLine()]);
}
break;
}