mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
All admin pages refactored to thin controllers + pure view templates, mirroring the public-page pattern: Controllers (public/admin/*.php): auth, data loading, include template Views (templates/admin/*.php): pure HTML/PHP output Fragment partials (templates/admin/partials/): toast, system-log-panel, system-nginx-config-panel Pages migrated: login, tags, contenus, contenus-edit, account, acces-etudiante, thanks, add, edit, parametres, system, index Fragment endpoints refactored: system-fragment.php, toast-fragment.php Skipped (pure redirects): logout, logs, status, import
333 lines
16 KiB
PHP
333 lines
16 KiB
PHP
<?php
|
|
require_once __DIR__ . "/../../bootstrap.php";
|
|
require_once __DIR__ . '/../../src/AdminAuth.php';
|
|
AdminAuth::requireLogin();
|
|
|
|
if (empty($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
$pageTitle = "Liste des TFE";
|
|
require_once __DIR__ . '/../../src/Database.php';
|
|
|
|
// ── CSV Import (inline, submitted to this same page) ─────────────────────────
|
|
$importMessage = '';
|
|
$importErrors = [];
|
|
$importResults = [];
|
|
$importDone = false;
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
|
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
|
$importErrors[] = "Erreur de sécurité : token invalide.";
|
|
} else {
|
|
$importedCount = 0;
|
|
$skippedCount = 0;
|
|
try {
|
|
$importDb = new Database();
|
|
$importPdo = $importDb->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 = mb_strtolower($raw, 'UTF-8');
|
|
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 = mb_strtolower($raw, 'UTF-8');
|
|
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';
|
|
|