Files
xamxam/public/admin/edit.php

486 lines
22 KiB
PHP

<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
require_once __DIR__ . '/../../src/AdminAuth.php';
// 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';
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
$error = null;
$success = null;
if ($thesisId <= 0) {
die("ID invalide");
}
try {
$db = new Database();
$pdo = $db->getPDO();
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csrf_token'])) {
// Verify CSRF token
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
throw new Exception("Erreur de sécurité : token invalide.");
}
try {
$db->beginTransaction();
// Update thesis basic info
$editLicenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
$editAccessTypeId = filter_var($_POST['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
$editContextNote = trim($_POST['context_note'] ?? '');
$stmt = $pdo->prepare("
UPDATE theses SET
title = ?,
subtitle = ?,
year = ?,
orientation_id = ?,
ap_program_id = ?,
finality_id = ?,
synopsis = ?,
context_note = ?,
file_size_info = ?,
baiu_link = ?,
license_id = ?,
access_type_id = ?,
is_published = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
");
$stmt->execute([
trim($_POST['titre']),
!empty($_POST['subtitle']) ? trim($_POST['subtitle']) : null,
intval($_POST['année']),
intval($_POST['orientation']),
intval($_POST['ap']),
intval($_POST['finality']),
trim($_POST['synopsis']),
!empty($editContextNote) ? $editContextNote : null,
!empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null,
!empty($_POST['lien']) ? trim($_POST['lien']) : null,
$editLicenseId,
$editAccessTypeId,
isset($_POST['is_published']) ? 1 : 0,
$thesisId
]);
// Update authors
$pdo->prepare("DELETE FROM thesis_authors WHERE thesis_id = ?")->execute([$thesisId]);
$authorsRaw = trim($_POST['auteurice'] ?? '');
if (!empty($authorsRaw)) {
$authors = array_map('trim', explode(',', $authorsRaw));
foreach ($authors as $index => $authorName) {
if (!empty($authorName)) {
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? ($_POST['mail'] ?? null) : null);
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
$stmt->execute([$thesisId, $authorId, $index + 1]);
}
}
}
// Update jury
$editJuryMembers = [];
if (!empty(trim($_POST['jury_president'] ?? ''))) {
$editJuryMembers[] = ['name' => trim($_POST['jury_president']), 'role' => 'president', 'is_external' => 0];
}
if (!empty(trim($_POST['jury_promoteur'] ?? ''))) {
$editJuryMembers[] = ['name' => trim($_POST['jury_promoteur']), 'role' => 'promoteur',
'is_external' => isset($_POST['jury_promoteur_ext']) ? 1 : 0];
}
foreach ($_POST['jury_lecteurs'] ?? [] as $i => $name) {
$name = trim($name);
if ($name !== '') {
$editJuryMembers[] = ['name' => $name, 'role' => 'lecteur',
'is_external' => isset($_POST['jury_lecteurs_ext'][$i]) ? 1 : 0];
}
}
$db->setThesisJury($thesisId, $editJuryMembers);
// Update languages
$db->setThesisLanguages($thesisId, isset($_POST['languages']) && is_array($_POST['languages']) ? $_POST['languages'] : []);
// Update formats
$db->setThesisFormats($thesisId, isset($_POST['formats']) && is_array($_POST['formats']) ? $_POST['formats'] : []);
// Update tags
$keywordsRaw = trim($_POST['tag'] ?? '');
$editKeywords = !empty($keywordsRaw) ? array_map('trim', explode(',', $keywordsRaw)) : [];
$db->setThesisTags($thesisId, $editKeywords);
$db->commit();
// Handle banner upload/removal (after commit, outside transaction)
if (isset($_POST['remove_banner'])) {
$currentBannerPath = $db->getThesisBannerPath($thesisId);
if ($currentBannerPath && defined('STORAGE_ROOT')) {
$absPath = STORAGE_ROOT . '/' . $currentBannerPath;
if (file_exists($absPath)) unlink($absPath);
}
$db->setBannerPath($thesisId, null);
} else {
$db->handleBannerUpload($thesisId, $_FILES['banner'] ?? null);
}
$success = "TFE mis à jour avec succès!";
// Regenerate CSRF token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} catch (Exception $e) {
$db->rollback();
$error = $e->getMessage();
error_log("Edit error: " . $e->getMessage());
}
}
// Load thesis data
$thesis = $db->getThesis($thesisId);
if (!$thesis) {
die("TFE non trouvé");
}
// Load current relationships
$stmt = $pdo->prepare("SELECT language_id FROM thesis_languages WHERE thesis_id = ?");
$stmt->execute([$thesisId]);
$currentLanguages = $stmt->fetchAll(PDO::FETCH_COLUMN);
$stmt = $pdo->prepare("SELECT format_id FROM thesis_formats WHERE thesis_id = ?");
$stmt->execute([$thesisId]);
$currentFormats = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Load jury
$jury = $db->getThesisJury($thesisId);
// Load reference data
$orientations = $db->getAllOrientations();
$apPrograms = $db->getAllAPPrograms();
$finalityTypes = $db->getAllFinalityTypes();
$languages = $db->getAllLanguages();
$formatTypes = $db->getAllFormatTypes();
$licenseTypes = $db->getAllLicenseTypes();
$accessTypes = $db->getAccessTypes();
// Fetch raw FK IDs (view only exposes name strings)
$rawRow = $db->getThesisRawFields($thesisId);
$currentLicenseId = $rawRow['license_id'] ?? null;
$currentAccessTypeId = $rawRow['access_type_id'] ?? null;
$currentContextNote = $rawRow['context_note'] ?? '';
// Set page title for header
$pageTitle = "Éditer TFE - " . htmlspecialchars($thesis['title']);
} catch (Exception $e) {
error_log("Error loading edit page: " . $e->getMessage());
die("Erreur lors du chargement: " . $e->getMessage());
}
?>
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
<main class="admin-main" id="main-content">
<h1 class="admin-page-title">Modifier un TFE</h1>
<?php if ($error): ?>
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($success) ?></div>
<?php endif; ?>
<form method="post" action="edit.php?id=<?= $thesisId ?>" class="admin-form" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div class="admin-form-row">
<label class="admin-label" for="auteurice">Auteur·ice(s) :</label>
<input class="admin-input" type="text" id="auteurice" name="auteurice"
value="<?= htmlspecialchars($thesis['authors']) ?>" required>
</div>
<div class="admin-form-row">
<label class="admin-label" for="mail">Contact :</label>
<input class="admin-input" type="text" id="mail" name="mail" value="">
</div>
<div class="admin-form-row">
<label class="admin-label" for="année">Année :</label>
<input class="admin-input" type="number" id="année" name="année"
value="<?= $thesis['year'] ?>" required>
</div>
<div class="admin-form-row">
<label class="admin-label" for="orientation">Orientation :</label>
<select class="admin-select" id="orientation" name="orientation" required>
<?php foreach ($orientations as $o): ?>
<option value="<?= $o['id'] ?>"
<?= ($thesis['orientation'] == $o['name']) ? 'selected' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-form-row">
<label class="admin-label" for="ap">Atelier pluridisciplinaire :</label>
<select class="admin-select" id="ap" name="ap" required>
<?php foreach ($apPrograms as $ap): ?>
<option value="<?= $ap['id'] ?>"
<?= ($thesis['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
<?= htmlspecialchars($ap['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-form-row">
<label class="admin-label" for="finality">Finalité du master :</label>
<select class="admin-select" id="finality" name="finality" required>
<?php foreach ($finalityTypes as $f): ?>
<option value="<?= $f['id'] ?>"
<?= ($thesis['finality_type'] == $f['name']) ? 'selected' : '' ?>>
<?= htmlspecialchars($f['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Composition du jury -->
<?php
// Pre-split jury by role for easy pre-population
$juryPresident = null;
$juryPromoteur = null;
$juryPromoteurExt = 0;
$juryLecteurs = [];
foreach ($jury as $jm) {
if ($jm['role'] === 'president') {
$juryPresident = $jm['name'];
} elseif ($jm['role'] === 'promoteur') {
$juryPromoteur = $jm['name'];
$juryPromoteurExt = (int)$jm['is_external'];
} elseif ($jm['role'] === 'lecteur') {
$juryLecteurs[] = $jm;
}
}
?>
<fieldset class="admin-fieldset">
<legend class="admin-fieldset-legend">Composition du jury</legend>
<div class="admin-form-row">
<label class="admin-label" for="jury_president">Président·e :</label>
<input class="admin-input" type="text" id="jury_president" name="jury_president"
value="<?= htmlspecialchars($juryPresident ?? '') ?>"
placeholder="Nom (interne)">
</div>
<div class="admin-form-row">
<label class="admin-label" for="jury_promoteur">Promoteur·ice :</label>
<div class="admin-jury-row">
<input class="admin-input" type="text" id="jury_promoteur" name="jury_promoteur"
value="<?= htmlspecialchars($juryPromoteur ?? '') ?>" placeholder="Nom">
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_promoteur_ext" value="1"
<?= $juryPromoteurExt ? 'checked' : '' ?>> Externe
</label>
</div>
</div>
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Lecteur·ices :</label>
<div>
<div id="jury-lecteurs-list" class="admin-jury-list">
<?php if (empty($juryLecteurs)): ?>
<div class="admin-jury-entry">
<input class="admin-input" type="text" name="jury_lecteurs[]" placeholder="Nom">
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
</div>
<?php else: ?>
<?php foreach ($juryLecteurs as $li => $lm): ?>
<div class="admin-jury-entry">
<input class="admin-input" type="text" name="jury_lecteurs[]"
value="<?= htmlspecialchars($lm['name']) ?>" placeholder="Nom">
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[<?= $li ?>]" value="1"
<?= $lm['is_external'] ? 'checked' : '' ?>> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<button type="button" class="admin-btn-secondary" style="margin-top:.5rem;"
onclick="addJuryRow()">+ Ajouter un·e lecteur·ice</button>
</div>
</div>
</fieldset>
<script>
var juryIdx = <?= max(count($juryLecteurs), 1) ?>;
function addJuryRow() {
var list = document.getElementById('jury-lecteurs-list');
var div = document.createElement('div');
div.className = 'admin-jury-entry';
div.innerHTML = '<input class="admin-input" type="text" name="jury_lecteurs[]" placeholder="Nom">'
+ '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>'
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>';
list.appendChild(div);
juryIdx++;
}
function removeJuryRow(btn) {
btn.closest('.admin-jury-entry').remove();
}
</script>
<div class="admin-form-row">
<label class="admin-label" for="access_type_id">Visibilité / Accès :</label>
<select class="admin-select" id="access_type_id" name="access_type_id">
<option value="">— Non défini —</option>
<?php foreach ($accessTypes as $at): ?>
<option value="<?= (int)$at['id'] ?>"
<?= ($currentAccessTypeId == $at['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($at['name']) ?>
<?php if (!empty($at['description'])): ?>
— <?= htmlspecialchars(mb_strimwidth($at['description'], 0, 60, '…')) ?>
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label" for="context_note">Note contextuelle :</label>
<div>
<textarea class="admin-textarea" id="context_note" name="context_note"
rows="4" maxlength="1500"><?= htmlspecialchars($currentContextNote ?? '') ?></textarea>
<p class="admin-hint">Visible publiquement pour les TFE Interne ou Interdit. Max 1 500 caractères.</p>
</div>
</div>
<div class="admin-form-row">
<label class="admin-label" for="license_id">Licence :</label>
<select class="admin-select" id="license_id" name="license_id">
<option value="">— Inconnue —</option>
<?php foreach ($licenseTypes as $lt): ?>
<option value="<?= $lt['id'] ?>"
<?= ($currentLicenseId == $lt['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($lt['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-form-row">
<label class="admin-label" for="titre">Titre :</label>
<input class="admin-input" type="text" id="titre" name="titre"
value="<?= htmlspecialchars($thesis['title']) ?>" required>
</div>
<div class="admin-form-row">
<label class="admin-label" for="subtitle">Sous-titre :</label>
<input class="admin-input" type="text" id="subtitle" name="subtitle"
value="<?= htmlspecialchars($thesis['subtitle'] ?? '') ?>">
</div>
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label" for="synopsis">Synopsis :</label>
<textarea class="admin-textarea" id="synopsis" name="synopsis" rows="7" required><?= htmlspecialchars($thesis['synopsis'] ?? '') ?></textarea>
</div>
<div class="admin-form-row">
<label class="admin-label">Langue(s) :</label>
<div class="admin-checkbox-list">
<?php foreach ($languages as $lang): ?>
<label class="admin-checkbox-label">
<input type="checkbox" name="languages[]" value="<?= $lang['id'] ?>"
<?= in_array($lang['id'], $currentLanguages) ? 'checked' : '' ?>>
<?= htmlspecialchars($lang['name']) ?>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="admin-form-row">
<label class="admin-label">Format(s) :</label>
<div class="admin-checkbox-list">
<?php foreach ($formatTypes as $fmt): ?>
<label class="admin-checkbox-label">
<input type="checkbox" name="formats[]" value="<?= $fmt['id'] ?>"
<?= in_array($fmt['id'], $currentFormats) ? 'checked' : '' ?>>
<?= htmlspecialchars($fmt['name']) ?>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="admin-form-row">
<label class="admin-label" for="tag">Mots-clés :</label>
<div>
<input class="admin-input" type="text" id="tag" name="tag"
value="<?= htmlspecialchars($thesis['keywords'] ?? '') ?>">
<p class="admin-hint">Séparer par des virgules. Max 10.</p>
</div>
</div>
<div class="admin-form-row">
<label class="admin-label" for="duration_info">Durée / Taille :</label>
<input class="admin-input" type="text" id="duration_info" name="duration_info"
value="<?= htmlspecialchars($thesis['file_size_info'] ?? '') ?>">
</div>
<div class="admin-form-row">
<label class="admin-label" for="lien">Lien externe :</label>
<input class="admin-input" type="url" id="lien" name="lien"
value="<?= htmlspecialchars($thesis['baiu_link'] ?? '') ?>">
</div>
<!-- Image bannière -->
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Image bannière (accueil) :</label>
<div>
<?php if (!empty($thesis['banner_path'])): ?>
<div style="margin-bottom:.5rem;">
<img src="/media.php?path=<?= urlencode($thesis['banner_path']) ?>"
alt="Bannière actuelle"
style="max-width:320px;max-height:100px;object-fit:cover;border:1px solid #444;">
<label class="admin-checkbox-label" style="margin-top:.35rem;display:block;">
<input type="checkbox" name="remove_banner" value="1"> Supprimer la bannière
</label>
</div>
<?php endif; ?>
<input type="file" name="banner" accept="image/jpeg,image/png,image/webp">
<p class="admin-hint">JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 5 MB.</p>
</div>
</div>
<div class="admin-form-row">
<label class="admin-label">Publication :</label>
<label class="admin-checkbox-label">
<input type="checkbox" name="is_published" value="1"
<?= $thesis['is_published'] ? 'checked' : '' ?>>
Publier ce TFE sur le site public
</label>
</div>
<div class="admin-submit-wrap">
<button type="submit" class="admin-btn">Enregistrer</button>
<a href="/admin/thanks.php?id=<?= $thesisId ?>" class="admin-btn-secondary" style="margin-left:.75rem;">Annuler</a>
</div>
</form>
</main>
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>