getPDO(); if ($_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) { throw new Exception("Erreur lors du téléversement du fichier."); } $handle = fopen($_FILES['csv_file']['tmp_name'], 'r'); if (!$handle) throw new Exception("Impossible d'ouvrir le fichier CSV."); fgetcsv($handle, 0, ',', '"', ''); fgetcsv($handle, 0, ',', '"', ''); fgetcsv($handle, 0, ',', '"', ''); fgetcsv($handle, 0, ',', '"', ''); // skip 4 header rows // Code → canonical name (legacy short-code CSV format) $orientationCodeMap = [ 'SC'=>'Sculpture','VI'=>'Vidéographie','CA'=>"Cinéma d'animation", 'IP'=>'Installation-Performance','PE'=>'Peinture','PH'=>'Photographie', 'DE'=>'Dessin','AN'=>'Arts Numériques','GR'=>'Graphisme', 'TY'=>'Typographie','DN'=>'Design Numérique','IL'=>'Illustration', 'BD'=>'Bande-Dessinée','SE'=>'Sérigraphie','GV'=>'Gravure', ]; // Alias map: normalise known variant spellings → canonical DB name. // Keys are lowercased+stripped for comparison. $orientationAliases = [ 'arts numériques' => 'Arts Numériques', 'design numérique' => 'Design Numérique', 'installation/performance' => 'Installation-Performance', 'installation performance' => 'Installation-Performance', 'cinema d\'animation' => "Cinéma d'animation", 'cinéma d\'animation' => "Cinéma d'animation", 'bande dessinée' => 'Bande-Dessinée', ]; // Resolve an orientation string (code or full name) → canonical DB name. $resolveOrientation = function(string $raw) use ($orientationCodeMap, $orientationAliases, $importPdo): ?int { $raw = trim($raw); if ($raw === '') return null; // 1. Try legacy short code if (isset($orientationCodeMap[$raw])) { $raw = $orientationCodeMap[$raw]; } // 2. Try alias map (lowercase key) $key = strtolower($raw); if (isset($orientationAliases[$key])) { $raw = $orientationAliases[$key]; } // 3. Exact DB match $s = $importPdo->prepare("SELECT id FROM orientations WHERE name = ?"); $s->execute([$raw]); $r = $s->fetch(); if ($r) return (int)$r['id']; // 4. Case-insensitive DB match $s = $importPdo->prepare("SELECT id FROM orientations WHERE LOWER(name) = LOWER(?)"); $s->execute([$raw]); $r = $s->fetch(); return $r ? (int)$r['id'] : null; }; // AP alias map: variant spellings → canonical DB name. $apAliases = [ 'l.i.e.n.s.' => 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'liens' => 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'lieux, interdisciplinarités, écologie, nécessité, systèmes' => 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'récits et expérimentation' => 'Récits et expérimentation', 'recits et experimentation' => 'Récits et expérimentation', 'atelier pratiques situées' => 'Atelier Pratiques Situées', 'design et politique du multiple' => 'Design et Politique du Multiple', 'narration spéculative' => 'Narration Spéculative', 'pacs' => 'PACS', ]; // Resolve an AP string (code or full name) → ap_program id. $resolveAP = function(string $raw) use ($apAliases, $importPdo): ?int { $raw = trim($raw); if ($raw === '') return null; // 1. Try alias map (lowercase key) $key = strtolower($raw); if (isset($apAliases[$key])) { $raw = $apAliases[$key]; } // 2. Exact name match $s = $importPdo->prepare("SELECT id FROM ap_programs WHERE name = ?"); $s->execute([$raw]); $r = $s->fetch(); if ($r) return (int)$r['id']; // 3. Code match (legacy) $s = $importPdo->prepare("SELECT id FROM ap_programs WHERE code = ?"); $s->execute([$raw]); $r = $s->fetch(); if ($r) return (int)$r['id']; // 4. Case-insensitive name match $s = $importPdo->prepare("SELECT id FROM ap_programs WHERE LOWER(name) = LOWER(?)"); $s->execute([$raw]); $r = $s->fetch(); return $r ? (int)$r['id'] : null; }; $lineNumber = 5; while (($row = fgetcsv($handle, 0, ',', '"', '')) !== false) { $lineNumber++; if (empty($row[0]) && empty($row[1])) continue; try { $importDb->beginTransaction(); $identifier = trim($row[0] ?? ''); $title = trim($row[1] ?? ''); $subtitle = trim($row[2] ?? ''); $authorsRaw = trim($row[3] ?? ''); $contact = trim($row[4] ?? ''); $supervisorsRaw = trim($row[5] ?? ''); $formatsRaw = trim($row[6] ?? ''); $year = intval($row[7] ?? 0); $apCode = trim($row[8] ?? ''); $orientationCode = trim($row[9] ?? ''); $finalityName = trim($row[10] ?? ''); $keywordsRaw = trim($row[11] ?? ''); $synopsis = trim($row[12] ?? ''); $context = trim($row[13] ?? ''); $remarks = trim($row[14] ?? ''); $languageRaw = trim($row[15] ?? ''); $access = trim($row[16] ?? ''); $license = trim($row[17] ?? ''); $sizeInfo = trim($row[18] ?? ''); $juryPoints = !empty($row[19]) ? floatval($row[19]) : null; $baiuLink = trim($row[20] ?? ''); if (empty($title) || empty($year)) throw new Exception("Titre et année requis."); $orientationId = $resolveOrientation($orientationCode); $apProgramId = $resolveAP($apCode); $finalityId = null; if (!empty($finalityName)) { $s = $importPdo->prepare("SELECT id FROM finality_types WHERE name = ?"); $s->execute([$finalityName]); $r = $s->fetch(); $finalityId = $r ? $r['id'] : null; } $accessTypeId = null; if (!empty($access)) { $s = $importPdo->prepare("SELECT id FROM access_types WHERE name = ?"); $s->execute([ucfirst(strtolower($access))]); $r = $s->fetch(); $accessTypeId = $r ? $r['id'] : null; } if ($accessTypeId === null) $accessTypeId = 1; if (!empty($identifier)) { $s = $importPdo->prepare("SELECT id FROM theses WHERE identifier = ?"); $s->execute([$identifier]); if ($s->fetch()) { $importDb->rollback(); $skippedCount++; $importResults[] = ['type'=>'skip', 'msg'=>"Ligne $lineNumber: identifiant \"$identifier\" déjà présent, ignoré."]; continue; } } $s = $importPdo->prepare(" INSERT INTO theses ( identifier, title, subtitle, year, orientation_id, ap_program_id, finality_id, synopsis, context_note, remarks, file_size_info, jury_points, baiu_link, access_type_id, is_published, submitted_at ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,CURRENT_TIMESTAMP) "); $s->execute([ !empty($identifier) ? $identifier : null, $title, !empty($subtitle) ? $subtitle : null, $year, $orientationId, $apProgramId, $finalityId, !empty($synopsis) ? $synopsis : null, !empty($context) ? $context : null, !empty($remarks) ? $remarks : null, !empty($sizeInfo) ? $sizeInfo : null, $juryPoints, !empty($baiuLink) ? $baiuLink : null, $accessTypeId, ]); $thesisId = $importPdo->lastInsertId(); if (!empty($authorsRaw)) { foreach (array_map('trim', explode(',', $authorsRaw)) as $idx => $name) { if ($name) { $aId = $importDb->findOrCreateAuthor($name, $idx === 0 ? $contact : null); $s = $importPdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?,?,?)"); $s->execute([$thesisId, $aId, $idx + 1]); } } } if (!empty($supervisorsRaw)) { foreach (array_map('trim', explode(',', $supervisorsRaw)) as $idx => $name) { if ($name) { $sId = $importDb->findOrCreateSupervisor($name); $s = $importPdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?,?,?)"); $s->execute([$thesisId, $sId, $idx + 1]); } } } if (!empty($keywordsRaw)) { foreach (array_slice(array_map('trim', explode(',', $keywordsRaw)), 0, 10) as $kw) { if ($kw) { $tId = $importDb->findOrCreateTag($kw); if ($tId) { $s = $importPdo->prepare("INSERT INTO thesis_tags (thesis_id, tag_id) VALUES (?,?)"); $s->execute([$thesisId, $tId]); } } } } if (!empty($languageRaw)) { $s = $importPdo->prepare("SELECT id FROM languages WHERE name = ?"); $s->execute([ucfirst(strtolower($languageRaw))]); $r = $s->fetch(); if ($r) { $s2 = $importPdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?,?)"); $s2->execute([$thesisId, $r['id']]); } } if (!empty($formatsRaw)) { foreach (array_map('trim', explode(',', $formatsRaw)) as $fmt) { if ($fmt) { $s = $importPdo->prepare("SELECT id FROM format_types WHERE name = ?"); $s->execute([ucfirst(strtolower($fmt))]); $r = $s->fetch(); if ($r) { $s2 = $importPdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?,?)"); $s2->execute([$thesisId, $r['id']]); } } } } $importDb->commit(); $importedCount++; $importResults[] = ['type'=>'ok', 'msg'=>"\"$title\" (ID: $thesisId)"]; } catch (Exception $e) { $importDb->rollback(); $skippedCount++; $importResults[] = ['type'=>'error', 'msg'=>"Ligne $lineNumber: " . $e->getMessage()]; error_log("Import error on line $lineNumber: " . $e->getMessage()); } } fclose($handle); $importMessage = "Import terminé : $importedCount TFE importés, $skippedCount ignorés."; $importDone = true; } catch (Exception $e) { $importErrors[] = $e->getMessage(); error_log("CSV import error: " . $e->getMessage()); } } $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } try { $db = new Database(); $searchQuery = isset($_GET['search']) ? trim($_GET['search']) : ''; $yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null; $orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null; $apFilter = isset($_GET['ap']) ? intval($_GET['ap']) : null; $sortCol = isset($_GET['sort']) ? trim($_GET['sort']) : 'submitted_at'; $sortDir = isset($_GET['dir']) ? trim($_GET['dir']) : 'desc'; $filters = []; if ($searchQuery) $filters['search'] = $searchQuery; if ($yearFilter) $filters['year'] = $yearFilter; if ($orientationFilter) $filters['orientation'] = $orientationFilter; if ($apFilter) $filters['ap'] = $apFilter; $filters['sort'] = $sortCol; $filters['dir'] = $sortDir; $perPage = 25; $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1; $totalCount = $db->getThesesListCount($filters); $totalPages = $totalCount > 0 ? (int) ceil($totalCount / $perPage) : 1; $page = min($page, $totalPages); $offset = ($page - 1) * $perPage; $theses = $db->getThesesList($filters, $perPage, $offset); $stats = $db->getThesesStats(); $years = $db->getAllYears(); $orientations = $db->getAllOrientations(); $apPrograms = $db->getAllAPPrograms(); } catch (Exception $e) { error_log("Error loading theses list: " . $e->getMessage()); die("Erreur lors du chargement de la liste."); } $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; include APP_ROOT . '/templates/header.php'; include APP_ROOT . '/templates/admin/index.php'; require_once APP_ROOT . '/templates/admin/footer.php';