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) 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))");
try { $pdo->exec("ALTER TABLE critiques MODIFY COLUMN rating DECIMAL(3,1) DEFAULT 3.0"); } catch (\Exception $e) {}
$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) {}
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) {
return (abs(crc32(strtolower(trim($type ?? '')) . '|' . strtolower(trim($title ?? '')) . '|' . trim($year ?? ''))) % 2000000000) + 100000000;
}
function checkAuth($pdo) {
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) return true;
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (empty($token) && function_exists('apache_request_headers')) {
$headers = apache_request_headers();
$token = $headers['Authorization'] ?? $headers['authorization'] ?? '';
}
if ($token !== md5(ENCRYPTION_KEY . 'session')) { http_response_code(403); echo json_encode(["error" => "Accès interdit."]); exit; }
}
function encryptData($data) {
$iv = openssl_random_pseudo_bytes(16);
$key = hash('sha256', ENCRYPTION_KEY, true);
return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv) . '::' . $iv);
}
function decryptData($str) {
$decoded = base64_decode($str);
if (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 = 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_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);
return $res ?: null;
}
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 extractYear($dateStr) {
if (preg_match('/(\d{4})/', $dateStr, $m)) return $m[1];
return '';
}
// ── API DVDFr (réécriture complète) ──
function fetchDVDFr($ean, $pdo) {
if (empty($ean) || strlen((string)$ean) < 8) return null;
$ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
$baseHeaders = [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Encoding: gzip, deflate, br',
'Connection: keep-alive',
];
// ── Helpers internes ──
$curlGet = function(string $url) use ($ua, $baseHeaders): ?string {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => $ua,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_ENCODING => '', // décompression auto (gzip, br…)
CURLOPT_HTTPHEADER => $baseHeaders,
CURLOPT_COOKIEFILE => '', // active le jar de cookies en mémoire
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (!$body || $code !== 200) {
error_log("DVDFr curlGet: HTTP $code pour $url");
return null;
}
return $body;
};
$absoluteUrl = function(string $src): string {
if (strpos($src, 'http') === 0) return $src;
if (strpos($src, '//') === 0) return 'https:' . $src;
if (strpos($src, '/') === 0) return 'https://www.dvdfr.com' . $src;
return 'https://www.dvdfr.com/' . $src;
};
// ── ÉTAPE 1 : trouver l'URL de la fiche via la recherche ──
$searchHtml = $curlGet('https://www.dvdfr.com/search/?q=' . urlencode($ean));
if (!$searchHtml) {
error_log("DVDFr: échec de la page de recherche pour EAN $ean");
return null;
}
// Patterns par ordre de priorité
$ficheUrl = null;
$patterns = [
// lien direct vers une fiche /dvd/ ou /blu-ray/ contenant l'EAN dans l'URL
'@href=["\']([^"\']*(?:dvd|blu-ray|4k|vhs|cd|coffret)[^"\']+\.html)["\']@i',
// toute fiche .html dans le domaine dvdfr.com
'@href=["\'](?:https?://(?:www\.)?dvdfr\.com)?(/[^"\']+\.html)["\']@i',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $searchHtml, $m)) {
$ficheUrl = $absoluteUrl($m[1]);
break;
}
}
if (!$ficheUrl) {
error_log("DVDFr: aucune fiche trouvée pour EAN $ean");
return null;
}
error_log("DVDFr: fiche → $ficheUrl");
// ── ÉTAPE 2 : charger la fiche ──
$html = $curlGet($ficheUrl);
if (!$html) {
error_log("DVDFr: impossible de charger la fiche $ficheUrl");
return null;
}
// ── ÉTAPE 3 : extraction des données ──
$result = [
'poster' => '',
'title' => '',
'publisher' => '',
'format' => '',
'length' => '',
'aspect' => '',
'discs' => '',
];
// --- 3a. AFFICHE ---
// Priorité 1 : og:image (le plus fiable)
if (preg_match('/]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i', $html, $m) ||
preg_match('/]+content=["\']([^"\']+)["\'][^>]+property=["\']og:image["\']/i', $html, $m)) {
$result['poster'] = $m[1];
error_log("DVDFr: affiche via og:image → " . $result['poster']);
}
// Priorité 2 : JSON-LD
if (empty($result['poster'])) {
preg_match_all('/