mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
The file had accumulated severe corruption in its lower half (garbled selector text, variable names spliced into property values, orphaned declarations, broken nesting) alongside hardcoded hex colours throughout. Rewrote the entire file cleanly: - Every colour is now a var() referencing a token defined in variables.css: --accent-primary/secondary/foreground, --accent-blue/green/yellow/red, --bg-secondary/tertiary, --border-primary, --text-primary/secondary/tertiary, --error, --warning, --success, --accent-muted. - Zero raw hex values remain in admin.css. - Removed the corrupted/dead CSS from the bottom half and reconstructed all selectors from what the templates actually use (audited via grep). - Fixed structural issues: broken border shorthand, nested rules that were not valid CSS, orphaned declaration blocks. - New/restored rules: .admin-maintenance-bar (was corrupted), .status-access variants (was corrupted), .admin-section-title--danger, .admin-danger-zone, .admin-account-status (all reconstructed cleanly). - .admin-btn--warning and .admin-btn--danger now use var(--accent-yellow) and var(--accent-red) instead of hardcoded dark hex values. - .admin-btn-remove hover now uses var(--error) instead of #e55. - .admin-btn-unpublish now uses var(--bg-secondary)/var(--text-tertiary) instead of hardcoded grey hex values. - select option background colours removed (browser chrome, not styleable cross-platform). Templates: replace 4 inline var(--admin-text-muted) with var(--text-secondary) in index.php, thanks.php, import.php.
370 lines
16 KiB
PHP
370 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;
|
|
}
|
|
|
|
// Map access type (Autorisation column)
|
|
// CSV values are expected to match access_types.name: "Libre", "Interne", "Interdit"
|
|
$accessTypeId = null;
|
|
if (!empty($access)) {
|
|
$stmtAcc = $pdo->prepare("SELECT id FROM access_types WHERE name = ?");
|
|
$stmtAcc->execute([ucfirst(strtolower($access))]);
|
|
$rowAcc = $stmtAcc->fetch();
|
|
$accessTypeId = $rowAcc ? $rowAcc['id'] : null;
|
|
}
|
|
// Default to Libre (id=1) when not specified so imported theses are visible
|
|
if ($accessTypeId === null) {
|
|
$accessTypeId = 1;
|
|
}
|
|
|
|
// 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,
|
|
access_type_id, is_published,
|
|
submitted_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 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,
|
|
$accessTypeId
|
|
]);
|
|
|
|
$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 $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; ?>
|
|
<?php include APP_ROOT . '/templates/header.php'; ?>
|
|
|
|
<main id="main-content">
|
|
<h1>Importer une liste de TFE</h1>
|
|
|
|
<?php if (!empty($errors)): ?>
|
|
<div role="alert" data-type="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): ?>
|
|
<p role="status" data-type="success">✓ <?= htmlspecialchars($message) ?></p>
|
|
<?php endif; ?>
|
|
|
|
<form action="import.php" method="post" enctype="multipart/form-data" class="admin-import-area admin-form">
|
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
|
|
|
<div>
|
|
<label>Fichier CSV :</label>
|
|
<div class="admin-file-input">
|
|
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
|
<small 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
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-submit-wrap">
|
|
<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(--text-secondary);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'; ?>
|