Files
xamxam/public/admin/import.php

353 lines
16 KiB
PHP

<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
require_once __DIR__ . '/../../src/AdminAuth.php';
// CSV Import page for Post-ERG thesis database
// This page allows importing thesis data from CSV files
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
AdminAuth::requireLogin();
// Generate CSRF token
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
require_once __DIR__ . '/../../src/Database.php';
$pageTitle = "Import";
$message = '';
$errors = [];
$importedCount = 0;
$skippedCount = 0;
$importResults = [];
// Handle CSV upload and import
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
// Verify CSRF token
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
$errors[] = "Erreur de sécurité : token invalide.";
} else {
try {
$db = new Database();
$pdo = $db->getPDO();
// Check file upload
if ($_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
throw new Exception("Erreur lors du téléversement du fichier.");
}
// Read CSV file
$csvFile = $_FILES['csv_file']['tmp_name'];
$handle = fopen($csvFile, 'r');
if (!$handle) {
throw new Exception("Impossible d'ouvrir le fichier CSV.");
}
// Skip first two rows (empty and headers)
fgetcsv($handle, 0, ',', '"', ''); // Empty row
$headers = fgetcsv($handle, 0, ',', '"', ''); // Header row
fgetcsv($handle, 0, ',', '"', ''); // Description row
$headers = fgetcsv($handle, 0, ',', '"', ''); // Actual column names
// Map CSV columns
$columnMap = [
0 => 'identifier', // Identifiant
1 => 'title', // Titre
2 => 'subtitle', // Sous-titre
3 => 'authors', // Auteur·ice(s)
4 => 'contact', // Contact
5 => 'supervisors', // Promoteur·ice(s)
6 => 'formats', // Format
7 => 'year', // Année
8 => 'ap', // AP
9 => 'orientation', // Orientation
10 => 'finality', // Finalité
11 => 'keywords', // Mots-clés
12 => 'synopsis', // Synopsis
13 => 'context', // Contexte
14 => 'remarks', // Remarques
15 => 'language', // Langue
16 => 'access', // Autorisation
17 => 'license', // License
18 => 'size_info', // taille
19 => 'jury_points', // Points sur 20
20 => 'baiu_link', // lien BAIU
];
// Orientation abbreviation mapping
$orientationMap = [
'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',
];
// Process each row
$lineNumber = 5; // Start after headers
while (($row = fgetcsv($handle, 0, ',', '"', '')) !== false) {
$lineNumber++;
// Skip empty rows
if (empty($row[0]) && empty($row[1])) {
continue;
}
try {
$db->beginTransaction();
// Extract data
$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] ?? '');
// Validate required fields
if (empty($title) || empty($year)) {
throw new Exception("Ligne $lineNumber: Titre et année requis.");
}
// Map orientation
$orientationName = isset($orientationMap[$orientationCode]) ? $orientationMap[$orientationCode] : null;
$orientationId = null;
if ($orientationName) {
$stmtOr = $pdo->prepare("SELECT id FROM orientations WHERE name = ?");
$stmtOr->execute([$orientationName]);
$rowOr = $stmtOr->fetch();
$orientationId = $rowOr ? $rowOr['id'] : null;
}
// Map AP program
$apProgramId = null;
if (!empty($apCode)) {
$stmt = $pdo->prepare("SELECT id FROM ap_programs WHERE code = ?");
$stmt->execute([$apCode]);
$result = $stmt->fetch();
if ($result) {
$apProgramId = $result['id'];
}
}
// Map finality
$finalityId = null;
if (!empty($finalityName)) {
$stmtFin = $pdo->prepare("SELECT id FROM finality_types WHERE name = ?");
$stmtFin->execute([$finalityName]);
$rowFin = $stmtFin->fetch();
$finalityId = $rowFin ? $rowFin['id'] : null;
}
// Skip if identifier already exists
if (!empty($identifier)) {
$stmtCheck = $pdo->prepare("SELECT id FROM theses WHERE identifier = ?");
$stmtCheck->execute([$identifier]);
if ($stmtCheck->fetch()) {
$db->rollback();
$skippedCount++;
$importResults[] = "⚠ Ligne $lineNumber: identifiant \"$identifier\" déjà présent, ignoré.";
continue;
}
}
// Insert thesis
$stmt = $pdo->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,
submitted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
");
$stmt->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
]);
$thesisId = $pdo->lastInsertId();
// Add authors
if (!empty($authorsRaw)) {
$authors = array_map('trim', explode(',', $authorsRaw));
foreach ($authors as $index => $authorName) {
if (!empty($authorName)) {
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? $contact : null);
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
$stmt->execute([$thesisId, $authorId, $index + 1]);
}
}
}
// Add supervisors
if (!empty($supervisorsRaw)) {
$supervisors = array_map('trim', explode(',', $supervisorsRaw));
foreach ($supervisors as $index => $supervisorName) {
if (!empty($supervisorName)) {
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
}
}
}
// Add keywords
if (!empty($keywordsRaw)) {
$keywords = array_map('trim', explode(',', $keywordsRaw));
$keywords = array_slice($keywords, 0, 10); // Max 10
foreach ($keywords as $keyword) {
if (!empty($keyword)) {
$tagId = $db->findOrCreateTag($keyword);
if ($tagId) {
$stmtTag = $pdo->prepare("INSERT INTO thesis_tags (thesis_id, tag_id) VALUES (?, ?)");
$stmtTag->execute([$thesisId, $tagId]);
}
}
}
}
// Add language
if (!empty($languageRaw)) {
$stmtLang = $pdo->prepare("SELECT id FROM languages WHERE name = ?");
$stmtLang->execute([ucfirst(strtolower($languageRaw))]);
$rowLang = $stmtLang->fetch();
$languageId = $rowLang ? $rowLang['id'] : null;
if ($languageId) {
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
$stmt->execute([$thesisId, $languageId]);
}
}
// Add formats
if (!empty($formatsRaw)) {
$formats = array_map('trim', explode(',', $formatsRaw));
foreach ($formats as $formatName) {
if (!empty($formatName)) {
$stmtFmt = $pdo->prepare("SELECT id FROM format_types WHERE name = ?");
$stmtFmt->execute([ucfirst(strtolower($formatName))]);
$rowFmt = $stmtFmt->fetch();
$formatId = $rowFmt ? $rowFmt['id'] : null;
if ($formatId) {
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
$stmt->execute([$thesisId, $formatId]);
}
}
}
}
$db->commit();
$importedCount++;
$importResults[] = " Ligne $lineNumber: \"$title\" importé (ID: $thesisId)";
} catch (Exception $e) {
$db->rollback();
$skippedCount++;
$importResults[] = " Ligne $lineNumber: " . $e->getMessage();
error_log("Import error on line $lineNumber: " . $e->getMessage());
}
}
fclose($handle);
$message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés.";
} catch (Exception $e) {
$errors[] = $e->getMessage();
error_log("CSV import error: " . $e->getMessage());
}
}
// Regenerate CSRF token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
<main class="admin-main" id="main-content">
<h1 class="admin-page-title">Importer une liste de TFE</h1>
<?php if (!empty($errors)): ?>
<div class="admin-alert admin-alert--error">
<strong>⚠ Erreurs :</strong>
<ul style="margin:.5rem 0 0;padding-left:1.2rem;">
<?php foreach ($errors as $err): ?>
<li><?= htmlspecialchars($err) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($message): ?>
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<form action="import.php" method="post" enctype="multipart/form-data" class="admin-import-area">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Fichier CSV :</label>
<div class="admin-file-input">
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
<div class="admin-hint" style="margin-top:.5rem;">
Colonnes attendues : Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU<br>
— Deux premières lignes ignorées (en-tête) — Séparateur : virgule — Encodage : UTF-8
</div>
</div>
</div>
<div style="margin-top:1rem;">
<button type="submit" class="admin-btn">Importer</button>
</div>
</form>
<?php if (!empty($importResults)): ?>
<div style="margin-top:2rem;">
<h2 style="font-size:1rem;font-weight:600;margin-bottom:.75rem;color:var(--admin-text-muted);text-transform:uppercase;letter-spacing:.06em;">Résultats de l'import</h2>
<div class="info-message">
<pre><?php foreach ($importResults as $r) echo htmlspecialchars($r) . "\n"; ?></pre>
</div>
</div>
<?php endif; ?>
</main>
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>