Actualiser api.php

This commit is contained in:
2026-06-24 13:01:28 +02:00
parent e6d0c99a07
commit b525ad6f76
+73 -122
View File
@@ -17,17 +17,18 @@ try {
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL)");
$pdo->exec("CREATE TABLE IF NOT EXISTS config (key_name VARCHAR(50) PRIMARY KEY, key_value TEXT NOT NULL)");
$pdo->exec("CREATE TABLE IF NOT EXISTS critiques (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, year VARCHAR(10), director VARCHAR(255), poster TEXT, rating DECIMAL(3,1) DEFAULT 3.0, review TEXT, streaming VARCHAR(255))");
$pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0;");
try { $pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0"); } catch (\Exception $e) {}
// 🔥 CRÉATION DE LA TABLE VIDÉOTHÈQUE AVEC LA COLONNE ACTORS
$pdo->exec("CREATE TABLE IF NOT EXISTS videotheque (id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, 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)");
try { $pdo->exec("ALTER TABLE videotheque ADD COLUMN actors TEXT AFTER description"); } catch (\Exception $e) {}
// 🔥 SUPPRESSION DE LA TABLE CACHE SI ELLE EXISTE ENCORE
try { $pdo->exec("DROP TABLE IF EXISTS cache_api"); } catch (\Exception $e) {}
} catch (\PDOException $e) { echo json_encode(["error" => "Erreur BDD : " . $e->getMessage()]); exit; }
// ── FONCTIONS UTILITAIRES ──
function makeStableId($type, $title, $year) {
// 🔥 Isolation stricte : le type est inclus dans le hash pour éviter les collisions entre les deux tables
return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000;
}
@@ -61,13 +62,19 @@ function getTmdbApiKey($pdo) {
return $row ? decryptData($row['key_value']) : null;
}
function httpGet($url, $timeout = 5) {
function httpGet($url, $timeout = 3) {
if (!function_exists('curl_init')) {
$ctx = stream_context_create(['http' => ['timeout' => $timeout, 'user_agent' => 'MonCinema/5.0']]);
return @file_get_contents($url, false, $ctx);
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
CURLOPT_SSL_VERIFYPEER => false
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) MonCinema/5.0',
CURLOPT_FOLLOWLOCATION => true
]);
$res = curl_exec($ch);
curl_close($ch);
@@ -96,15 +103,29 @@ function extractYear($dateStr) {
return '';
}
// ── 1. API UPCitemDB (SANS CACHE) ──
function fetchUPCitemdb($ean, $pdo) {
if (empty($ean) || strlen($ean) < 8) return null;
$url = "https://api.upcitemdb.com/prod/trial/lookup?upc=" . urlencode($ean);
$res = httpGet($url, 5);
if (!$res) return null;
$data = json_decode($res, true);
if (isset($data['code']) && $data['code'] === 'OK' && !empty($data['items'])) {
$item = $data['items'][0];
return [
'title' => $item['title'] ?? '',
'poster' => $item['images'][0] ?? '',
'publisher' => $item['brand'] ?? $item['publisher'] ?? '',
'format' => detectFormat($item['title'] ?? '', $item['description'] ?? '')
];
}
return null;
}
// ── 2. API DVDFr (SANS CACHE - Récupération complète) ──
function fetchDVDFr($ean, $pdo) {
if (empty($ean) || strlen($ean) < 8) return null;
$cacheKey = 'dvdfr_' . md5($ean);
$cached = getCache($pdo, $cacheKey);
if ($cached) return $cached;
// DVDFr exige un User-Agent propre
$ua = 'MonCinema/1.0 (collection privée; contact@moncineapp.fr)';
// Étape 1 : recherche par gencode → récupère l'id DVDFr
@@ -154,20 +175,10 @@ function fetchDVDFr($ean, $pdo) {
$dvd = $fiche->dvd[0];
// 🔥 Extraction de la jaquette (CORRECTION)
// Extraction de la jaquette
$poster = '';
// Méthode 1 : cover directe
if (isset($dvd->cover)) {
$poster = (string)$dvd->cover; // ✅ CORRECTION ICI
}
// Méthode 2 : covers->cover[0]
if (empty($poster) && isset($dvd->covers->cover[0])) {
$poster = (string)$dvd->covers->cover[0];
}
// Méthode 3 : image directe
if (empty($poster) && isset($dvd->image)) {
$poster = (string)$dvd->image;
}
if (isset($dvd->cover)) $poster = (string)$dvd->cover;
if (empty($poster) && isset($dvd->covers->cover[0])) $poster = (string)$dvd->covers->cover[0];
// Extraction des métadonnées techniques
$result = [
@@ -179,65 +190,49 @@ function fetchDVDFr($ean, $pdo) {
'discs' => isset($dvd->nbdisques) ? (string)$dvd->nbdisques : '',
];
// Ne met en cache que si on a au moins une affiche ou un éditeur
if (!empty($result['poster']) || !empty($result['publisher'])) {
setCache($pdo, $cacheKey, $result, 'dvdfr');
}
return !empty($result['poster']) || !empty($result['publisher']) ? $result : null;
return (!empty($result['poster']) || !empty($result['publisher'])) ? $result : null;
}
// ── 3. API TMDB (SANS CACHE - Full Extract) ──
function fetchTMDBFull($title, $year, $apiKey, $pdo) {
if (empty($apiKey) || empty($title)) return null;
$cleanTitle = cleanTitle($title);
// 1. Recherche avec l'année
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&year={$year}&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
// 2. Fallback sans l'année
if (empty($searchData['results'])) {
$searchUrl = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR";
$searchRes = httpGet($searchUrl, 5);
$searchData = $searchRes ? json_decode($searchRes, true) : [];
$searchUrl2 = "https://api.themoviedb.org/3/search/movie?api_key={$apiKey}&query=" . urlencode($cleanTitle) . "&language=fr-FR";
$searchRes2 = httpGet($searchUrl2, 5);
$searchData = $searchRes2 ? json_decode($searchRes2, true) : [];
}
if (empty($searchData['results'])) return null;
$movieId = $searchData['results'][0]['id'];
// 3. Détails complets
$detailsUrl = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$apiKey}&append_to_response=credits,watch/providers&language=fr-FR";
$detailsRes = httpGet($detailsUrl, 5);
if (!$detailsRes) return null;
$details = json_decode($detailsRes, true);
// Extraction Réalisateurs
$director = '';
if (!empty($details['credits']['crew'])) {
$directorsList = [];
foreach ($details['credits']['crew'] as $crew) {
if ($crew['job'] === 'Director') {
$directorsList[] = $crew['name'];
}
if ($crew['job'] === 'Director') $directorsList[] = $crew['name'];
}
$director = implode(', ', $directorsList);
}
// Extraction Acteurs (Top 4)
$cast = [];
if (!empty($details['credits']['cast'])) {
$topCast = array_slice($details['credits']['cast'], 0, 4);
foreach ($topCast as $actor) {
$cast[] = $actor['name'];
}
foreach ($topCast as $actor) $cast[] = $actor['name'];
}
// Synopsis
$overview = $details['overview'] ?? '';
// Streaming
$streaming = '';
$frProviders = $details['watch/providers']['results']['FR'] ?? [];
$platforms = [];
@@ -310,7 +305,6 @@ switch ($action) {
break;
case 'get_films':
// Fusion parfaite des deux tables avec des colonnes neutres (NULL) pour harmoniser le flux
$sql = "
SELECT id, title, year, director, poster, rating, review, NULL AS description, streaming, 'critique' AS type
FROM critiques
@@ -326,14 +320,13 @@ switch ($action) {
case 'search_ean_full':
$ean = $_GET['ean'] ?? '';
if (!$ean) { echo json_encode(['error' => 'EAN manquant']); exit; }
$result = [
'ean' => $ean, 'title' => '', 'director' => '', 'year' => '',
'poster' => '', 'publisher' => '', 'format' => '',
'length' => '', 'number_of_discs' => 1, 'aspect_ratio' => '', 'actors' => ''
];
// 🔥 UNIQUEMENT DVDFr
// DVDFr en priorité
$dvdfrData = fetchDVDFr($ean, $pdo);
if (!empty($dvdfrData)) {
if (!empty($dvdfrData['poster'])) $result['poster'] = $dvdfrData['poster'];
@@ -344,7 +337,7 @@ switch ($action) {
if (!empty($dvdfrData['discs'])) $result['number_of_discs'] = (int)$dvdfrData['discs'];
}
// TMDB pour le titre, réalisateur, année et acteurs
// TMDB pour le reste
$tmdbKey = getTmdbApiKey($pdo);
if ($tmdbKey && !empty($result['publisher'])) {
$tmdbData = fetchTMDBFull($result['publisher'], '', $tmdbKey, $pdo);
@@ -355,15 +348,14 @@ switch ($action) {
if (!empty($tmdbData['cast'])) $result['actors'] = implode(', ', $tmdbData['cast']);
}
}
echo json_encode(['success' => true, 'data' => $result]);
break;
case 'save_film':
checkAuth($pdo);
$type = $data['type'] ?? 'critique';
$id = !empty($data['id']) ? $data['id'] : makeStableId($type, $data['title'] ?? '', $data['year'] ?? '0000');
// 🔥 Récupération des données manquantes via TMDB uniquement (plus de UPC)
if (empty($data['director']) || empty($data['poster'])) {
$tmdbData = fetchTMDBFull($data['title'] ?? '', $data['year'] ?? '', getTmdbApiKey($pdo), $pdo);
if ($tmdbData) {
@@ -376,27 +368,11 @@ switch ($action) {
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 {
// Remplacez votre bloc SQL dans 'save_film' par celui-ci :
$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)";
$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'] ?? '']);
}
@@ -419,34 +395,28 @@ switch ($action) {
else { http_response_code(400); echo json_encode(["success" => false, "error" => "Aucun élément sélectionné."]); }
break;
// ── IMPORT PAR LOTS CSV (CROISEMENT UPC + TMDB) ──
case 'import_batch':
// ── IMPORT PAR LOTS CSV (DVDfr + TMDB, SANS CACHE) ──
case 'import_batch':
checkAuth($pdo);
set_time_limit(0);
$items = $data['items'] ?? [];
$type = $data['type'] ?? 'videotheque';
$tmdbApiKey = getTmdbApiKey($pdo);
$imported = 0;
$debugLog = []; // 🔥 LOG DE DÉBOGAGE
$debugLog = [];
try {
$pdo->beginTransaction();
$pdo->beginTransaction(); // 🔥 UNE SEULE TRANSACTION
foreach ($items as $index => $rowData) {
foreach ($items as $rowData) {
$title = $rowData['title'] ?? $rowData['Name'] ?? $rowData['Title'] ?? 'Sans titre';
$publishDate = $rowData['publish_date'] ?? $rowData['Year'] ?? $rowData['year'] ?? $rowData['Date'] ?? '';
$year = extractYear($publishDate);
$id = makeStableId($type, $title, $year);
if ($type === 'videotheque') {
// 🔥 EXTRACTION CORRECTE DES ACTEURS DEPUIS LE CSV
$csvActors = '';
if (!empty($rowData['ensemble'])) {
$csvActors = $rowData['ensemble'];
} elseif (!empty($rowData['creators'])) {
$csvActors = $rowData['creators'];
}
// Acteurs depuis CSV
$csvActors = $rowData['ensemble'] ?? $rowData['creators'] ?? '';
$actors = '';
if (!empty($csvActors)) {
$actorsArray = array_map('trim', explode(',', $csvActors));
@@ -478,43 +448,36 @@ case 'import_batch':
$poster = $rowData['poster'] ?? '';
$director = '';
// Remplacez votre bloc de gestion DVDFr actuel par ceci :
// 🔥 1. DVDFr (priorité pour les métadonnées physiques)
if (!empty($ean)) {
$dvdfrData = fetchDVDFr($ean, $pdo);
if (!empty($dvdfrData)) {
if (empty($poster) && !empty($dvdfrData['poster'])) $poster = $dvdfrData['poster'];
if (empty($format) && !empty($dvdfrData['format'])) $format = $dvdfrData['format'];
if (empty($publisher) && !empty($dvdfrData['publisher'])) $publisher = $dvdfrData['publisher'];
if (empty($length) && !empty($dvdfrData['length'])) $length = $dvdfrData['length'];
if (empty($aspect) && !empty($dvdfrData['aspect'])) $aspect = $dvdfrData['aspect'];
if (!empty($dvdfrData['poster'])) $poster = $dvdfrData['poster'];
if (!empty($dvdfrData['publisher'])) $publisher = $dvdfrData['publisher'];
if (!empty($dvdfrData['format'])) $format = $dvdfrData['format'];
if (!empty($dvdfrData['length'])) $length = $dvdfrData['length'];
if (!empty($dvdfrData['aspect'])) $aspect = $dvdfrData['aspect'];
if (!empty($dvdfrData['discs'])) $discs = (int)$dvdfrData['discs'];
}
}
// 2. TMDB (Données Officielles & Synopsis)
// 🔥 2. TMDB (réalisateur, acteurs, synopsis)
if ($tmdbApiKey && !empty($title)) {
// 🔥 CRÉER UNE VERSION PROPRE DU TITRE POUR TMDB
$tmdbTitle = $title;
// Supprimer les suffixes d'édition
$tmdbTitle = preg_replace('/\s*[\[\(].*?[\]\)]\s*/', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*-\s*(Édition|Edition|Collector|Simple|Spéciale|Digibook|Ultimate|Intégrale|Combo|SteelBook|Boîtier|Coffret).*$/i', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*(Blu-ray|Bluray|DVD|4K|Ultra HD|Combo|VHS|BDRip|\[.*\]).*$/i', '', $tmdbTitle);
$tmdbTitle = preg_replace('/\s*(Coffret|Trilogie|Quadrilogie|Collection|Anthologie).*$/i', '', $tmdbTitle);
// Séparer les titres multiples (ex: "Alien / Aliens")
$tmdbTitle = preg_split('/\s*(\/|\+|:)\s*/', $tmdbTitle)[0];
// Séparer les sous-titres après tiret
$tmdbTitle = explode(' - ', $tmdbTitle)[0];
$tmdbTitle = trim($tmdbTitle);
// 🔥 LOG POUR DÉBOGAGE
if ($tmdbTitle !== $title) {
$debugLog[] = "Titre original: '$title' -> Titre TMDB: '$tmdbTitle'";
$debugLog[] = "Titre nettoyé: '$title' -> '$tmdbTitle'";
}
$tmdbData = fetchTMDBFull($tmdbTitle, $year, $tmdbApiKey, $pdo);
// Si TMDB ne trouve rien, essayer avec le titre original
if (!$tmdbData && $tmdbTitle !== $title) {
$tmdbData = fetchTMDBFull($title, $year, $tmdbApiKey, $pdo);
}
@@ -525,19 +488,14 @@ case 'import_batch':
if (empty($year) && !empty($tmdbData['year'])) $year = $tmdbData['year'];
if (empty($length) && !empty($tmdbData['length'])) $length = $tmdbData['length'];
if (!empty($tmdbData['overview'])) $description = $tmdbData['overview'];
// 🔥 NE PAS ÉCRASER LES ACTEURS DU CSV SI TMDB N'A PAS D'ACTEURS
if (!empty($tmdbData['cast'])) {
$actors = implode(', ', $tmdbData['cast']);
}
if (!empty($tmdbData['cast'])) $actors = implode(', ', $tmdbData['cast']);
} else {
$debugLog[] = "TMDB n'a pas trouvé: '$tmdbTitle' (année: $year)";
$debugLog[] = "TMDB non trouvé pour: '$tmdbTitle'";
}
}
// 🔥 LOG FINAL
$debugLog[] = "Film #$id: '$title' | Réal: '$director' | Acteurs: '$actors' | Durée: '$length'";
$debugLog[] = "Film: '$title' | Réal: '$director' | Durée: '$length' | Éditeur: '$publisher'";
// INSERT SQL
$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
@@ -554,8 +512,9 @@ case 'import_batch':
actors=IF(VALUES(actors)!='',VALUES(actors),actors)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $title, $year, $director, $poster, $format, $length, $publisher, $ean, $discs, $aspect, $description, $actors]);
} else {
// Pour les critiques (code inchangé)
// Pour les critiques
$ratingRaw = $rowData['Rating'] ?? $rowData['rating'] ?? '';
$rating = ($ratingRaw !== '' && $ratingRaw !== null) ? (float)$ratingRaw : null;
$review = $rowData['Review'] ?? $rowData['review'] ?? '';
@@ -585,24 +544,16 @@ case 'import_batch':
}
$imported++;
}
$pdo->commit();
// 🔥 RENVOYER LES LOGS DE DÉBOGAGE
echo json_encode([
"success" => true,
"imported" => $imported,
"debug" => $debugLog
]);
$pdo->commit();
echo json_encode(["success" => true, "imported" => $imported, "debug" => $debugLog]);
} catch (\Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
http_response_code(500);
echo json_encode([
"success" => false,
"error" => "Erreur serveur : " . $e->getMessage(),
"debug" => $debugLog
]);
echo json_encode(["success" => false, "error" => "Erreur serveur : " . $e->getMessage(), "debug" => $debugLog]);
}
break;
}