mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
feat: extract MediaController, wire into Dispatcher, delete media.php
This commit is contained in:
559
app/public/admin/index.php
Normal file
559
app/public/admin/index.php
Normal file
@@ -0,0 +1,559 @@
|
||||
<?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
|
||||
|
||||
$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',
|
||||
];
|
||||
|
||||
$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.");
|
||||
|
||||
$orientationName = $orientationMap[$orientationCode] ?? null;
|
||||
$orientationId = null;
|
||||
if ($orientationName) {
|
||||
$s = $importPdo->prepare("SELECT id FROM orientations WHERE name = ?");
|
||||
$s->execute([$orientationName]);
|
||||
$r = $s->fetch(); $orientationId = $r ? $r['id'] : null;
|
||||
}
|
||||
|
||||
$apProgramId = null;
|
||||
if (!empty($apCode)) {
|
||||
$s = $importPdo->prepare("SELECT id FROM ap_programs WHERE code = ?");
|
||||
$s->execute([$apCode]);
|
||||
$r = $s->fetch(); $apProgramId = $r ? $r['id'] : null;
|
||||
}
|
||||
|
||||
$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.");
|
||||
}
|
||||
?>
|
||||
<?php $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/header.php'; ?>
|
||||
|
||||
<script>
|
||||
function toggleAll(src) {
|
||||
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.checked = src.checked);
|
||||
updateBulk();
|
||||
}
|
||||
function updateBulk() {
|
||||
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||
const bulk = document.getElementById('bulk-actions');
|
||||
document.getElementById('selected-count').textContent = checked.length;
|
||||
bulk.style.display = checked.length > 0 ? 'flex' : 'none';
|
||||
}
|
||||
function bulkAction(action) {
|
||||
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||
if (!checked.length) { alert('Sélectionnez au moins un TFE.'); return; }
|
||||
let word, endpoint;
|
||||
if (action === 'publish') { word = 'publier'; endpoint = 'actions/publish.php'; }
|
||||
else if (action === 'unpublish') { word = 'dépublier'; endpoint = 'actions/publish.php'; }
|
||||
else if (action === 'delete') { word = 'supprimer'; endpoint = 'actions/delete.php'; }
|
||||
else return;
|
||||
if (action === 'delete') {
|
||||
if (!confirm(`Supprimer définitivement ${checked.length} TFE(s) ? Cette action est irréversible.`)) return;
|
||||
} else {
|
||||
if (!confirm(`${word.charAt(0).toUpperCase()+word.slice(1)} ${checked.length} TFE(s) ?`)) return;
|
||||
}
|
||||
document.getElementById('bulk-action-input').value = action;
|
||||
document.getElementById('bulk-form').action = endpoint;
|
||||
const container = document.getElementById('bulk-checkboxes');
|
||||
container.innerHTML = '';
|
||||
checked.forEach(cb => {
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'hidden'; inp.name = 'selected_theses[]'; inp.value = cb.value;
|
||||
container.appendChild(inp);
|
||||
});
|
||||
document.getElementById('bulk-form').submit();
|
||||
}
|
||||
function deleteThesis(id, title) {
|
||||
if (!confirm(`Supprimer « ${title} » ?\nCette action est irréversible.`)) return;
|
||||
const form = document.getElementById('delete-form-' + id);
|
||||
if (form) form.submit();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk));
|
||||
});
|
||||
</script>
|
||||
|
||||
<main id="main-content">
|
||||
<!-- Title + filters + stats + import all in one toolbar row -->
|
||||
<div class="admin-list-toolbar">
|
||||
<h1>Liste des TFE</h1>
|
||||
|
||||
<form class="admin-filters" method="get" action="/admin/">
|
||||
<input type="text" name="search" placeholder="Titre, auteur..."
|
||||
value="<?= htmlspecialchars($searchQuery) ?>">
|
||||
<select name="year">
|
||||
<option value="">Année</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= $y ?>" <?= $yearFilter == $y ? 'selected' : '' ?>><?= $y ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="orientation">
|
||||
<option value="">Orientation</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="ap">
|
||||
<option value="">AP</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= $ap['id'] ?>" <?= $apFilter == $ap['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="admin-filters-btn">Filtrer</button>
|
||||
<?php if ($searchQuery || $yearFilter || $orientationFilter || $apFilter): ?>
|
||||
<button type="button" class="admin-filters-reset"
|
||||
onclick="window.location='/admin/'">✕ Réinitialiser</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="admin-list-toolbar__right">
|
||||
<dl class="admin-stats">
|
||||
<div class="admin-stat">
|
||||
<dt class="admin-stat__label">Total</dt>
|
||||
<dd class="admin-stat__number"><?= $stats['total'] ?></dd>
|
||||
</div>
|
||||
<div class="admin-stat">
|
||||
<dt class="admin-stat__label">Publiés</dt>
|
||||
<dd class="admin-stat__number"><?= $stats['published'] ?></dd>
|
||||
</div>
|
||||
<div class="admin-stat">
|
||||
<dt class="admin-stat__label">Attente</dt>
|
||||
<dd class="admin-stat__number"><?= $stats['pending'] ?></dd>
|
||||
</div>
|
||||
</dl>
|
||||
<a href="/admin/add.php" class="admin-btn admin-btn--sm">Ajouter un TFE</a>
|
||||
<button type="button" class="admin-btn admin-btn--sm" id="import-dialog-btn"
|
||||
onclick="document.getElementById('import-dialog').showModal()">
|
||||
Importer un CSV
|
||||
</button>
|
||||
<a href="/admin/actions/export-csv.php" class="admin-btn admin-btn--sm">
|
||||
Exporter CSV
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bulk actions bar -->
|
||||
<div id="bulk-actions" class="admin-bulk-actions" role="toolbar" aria-label="Actions groupées">
|
||||
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
|
||||
<div class="admin-bulk-btns">
|
||||
<button type="button" class="admin-btn-sm admin-btn-publish" onclick="bulkAction('publish')">Publier</button>
|
||||
<button type="button" class="admin-btn-sm admin-btn-unpublish" onclick="bulkAction('unpublish')">Dépublier</button>
|
||||
<button type="button" class="admin-btn-sm admin-btn-delete" onclick="bulkAction('delete')">Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="bulk-form" method="post" action="actions/publish.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" id="bulk-action-input" name="action" value="">
|
||||
<input type="hidden" name="bulk" value="1">
|
||||
<div id="bulk-checkboxes"></div>
|
||||
</form>
|
||||
|
||||
<!-- Table -->
|
||||
<?php if (empty($theses)): ?>
|
||||
<p class="admin-empty">Aucun TFE trouvé.</p>
|
||||
<?php else: ?>
|
||||
<p class="admin-list-meta">
|
||||
<?php
|
||||
$from = $offset + 1;
|
||||
$to = min($offset + $perPage, $totalCount);
|
||||
if ($totalPages > 1) {
|
||||
echo "{$from}-{$to} sur {$totalCount} TFE";
|
||||
} else {
|
||||
echo "$totalCount TFE";
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
$sortParams = array_filter([
|
||||
'search' => $searchQuery,
|
||||
'year' => $yearFilter ?: '',
|
||||
'orientation' => $orientationFilter ?: '',
|
||||
'ap' => $apFilter ?: '',
|
||||
]);
|
||||
|
||||
$sortLink = function(string $col) use ($sortCol, $sortDir, $sortParams): string {
|
||||
$params = $sortParams;
|
||||
$params['sort'] = $col;
|
||||
$params['dir'] = ($sortCol === $col && $sortDir === 'desc') ? 'asc' : 'desc';
|
||||
return '/admin/?' . http_build_query($params);
|
||||
};
|
||||
|
||||
$sortArrow = function(string $col) use ($sortCol, $sortDir): string {
|
||||
if ($sortCol !== $col) return '';
|
||||
return $sortDir === 'asc' ? ' ↑' : ' ↓';
|
||||
};
|
||||
?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><input type="checkbox" onchange="toggleAll(this)"></th>
|
||||
<th scope="col"><a href="<?= $sortLink('identifier') ?>" class="admin-sort-link">ID<?= $sortArrow('identifier') ?></a></th>
|
||||
<th scope="col"><a href="<?= $sortLink('title') ?>" class="admin-sort-link">Titre<?= $sortArrow('title') ?></a></th>
|
||||
<th scope="col">Auteur(s)</th>
|
||||
<th scope="col"><a href="<?= $sortLink('year') ?>" class="admin-sort-link">Année<?= $sortArrow('year') ?></a></th>
|
||||
<th scope="col"><a href="<?= $sortLink('orientation') ?>" class="admin-sort-link">Orientation<?= $sortArrow('orientation') ?></a></th>
|
||||
<th scope="col"><a href="<?= $sortLink('ap_program') ?>" class="admin-sort-link">AP<?= $sortArrow('ap_program') ?></a></th>
|
||||
<th scope="col"><a href="<?= $sortLink('is_published') ?>" class="admin-sort-link">Statut<?= $sortArrow('is_published') ?></a></th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($theses as $thesis): ?>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="selected_theses[]" value="<?= $thesis['id'] ?>"></td>
|
||||
<td class="admin-table-id"><?= htmlspecialchars($thesis['identifier'] ?? $thesis['id']) ?></td>
|
||||
<td>
|
||||
<div class="thesis-title"><?= htmlspecialchars($thesis['title']) ?></div>
|
||||
<?php if ($thesis['subtitle']): ?>
|
||||
<div class="thesis-subtitle"><?= htmlspecialchars($thesis['subtitle']) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($thesis['authors'] ?? 'N/A') ?></td>
|
||||
<td><?= $thesis['year'] ?></td>
|
||||
<td><?= htmlspecialchars($thesis['orientation'] ?? 'N/A') ?></td>
|
||||
<td><?= htmlspecialchars($thesis['ap_program'] ?? 'N/A') ?></td>
|
||||
<td>
|
||||
<?php $badgeType = 'publish'; $badgeValue = $thesis['is_published']; include APP_ROOT . '/templates/partials/status-badge.php'; ?>
|
||||
<?php if (!empty($thesis['access_type'])): ?>
|
||||
<br><?php $badgeType = 'access'; $badgeValue = $thesis['access_type']; include APP_ROOT . '/templates/partials/status-badge.php'; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="admin-actions">
|
||||
<a href="/admin/thanks.php?id=<?= $thesis['id'] ?>" class="admin-btn-sm admin-btn-view">Voir</a>
|
||||
<a href="/admin/edit.php?id=<?= $thesis['id'] ?>" class="admin-btn-sm admin-btn-edit">Éditer</a>
|
||||
<form method="post" action="actions/publish.php" class="publish-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>">
|
||||
<?php if ($thesis['is_published']): ?>
|
||||
<input type="hidden" name="action" value="unpublish">
|
||||
<button type="submit" class="admin-btn-sm admin-btn-unpublish"
|
||||
onclick="return confirm('Retirer de la publication ?')">Dépublier</button>
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="action" value="publish">
|
||||
<button type="submit" class="admin-btn-sm admin-btn-publish">Publier</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<form method="post" action="actions/delete.php" id="delete-form-<?= $thesis['id'] ?>" class="publish-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>">
|
||||
<button type="button" class="admin-btn-sm admin-btn-delete"
|
||||
onclick="deleteThesis(<?= $thesis['id'] ?>, <?= htmlspecialchars(json_encode($thesis['title']), ENT_QUOTES) ?>)">Supprimer</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$baseParams = array_filter([
|
||||
'search' => $searchQuery,
|
||||
'year' => $yearFilter ?: '',
|
||||
'orientation' => $orientationFilter ?: '',
|
||||
'ap' => $apFilter ?: '',
|
||||
'sort' => $sortCol,
|
||||
'dir' => $sortDir,
|
||||
]);
|
||||
include APP_ROOT . '/templates/partials/pagination.php';
|
||||
?>
|
||||
</main>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
IMPORT DIALOG
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<dialog id="import-dialog" class="admin-dialog" aria-labelledby="import-dialog-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="import-dialog-title">Importer une liste de TFE</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="document.getElementById('import-dialog').close()">✕</button>
|
||||
</div>
|
||||
|
||||
<?php if ($importMessage || !empty($importErrors)): ?>
|
||||
<div class="admin-import-status-card">
|
||||
<?php if (!empty($importErrors)): ?>
|
||||
<div class="toast admin-import-status-card__errors" role="alert" data-type="error">
|
||||
<strong>⚠ Erreurs :</strong>
|
||||
<ul class="admin-error-list">
|
||||
<?php foreach ($importErrors as $err): ?>
|
||||
<li><?= htmlspecialchars($err) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($importMessage): ?>
|
||||
<p class="toast admin-import-status-card__success" role="status" data-type="success">✓ <?= htmlspecialchars($importMessage) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="admin-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
|
||||
<div>
|
||||
<label for="csv_file">Fichier CSV</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<small class="admin-file-hint">
|
||||
Colonnes : 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>
|
||||
Quatre premières lignes ignorées — Séparateur : virgule — UTF-8
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-footer">
|
||||
<button type="submit" class="admin-btn">Importer</button>
|
||||
<button type="button" class="admin-btn-secondary"
|
||||
onclick="document.getElementById('import-dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($importResults)): ?>
|
||||
<details class="admin-import-log-details">
|
||||
<summary>Logs d'importation (<?= count($importResults) ?> entrées)</summary>
|
||||
<ul class="admin-import-log">
|
||||
<?php foreach ($importResults as $r): ?>
|
||||
<li class="admin-import-log__item admin-import-log__item--<?= $r['type'] ?>"><?= htmlspecialchars($r['msg']) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</dialog>
|
||||
|
||||
<?php if ($importMessage || !empty($importErrors)): ?>
|
||||
<script>document.getElementById('import-dialog').showModal();</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
Reference in New Issue
Block a user