feat: jury composition + banner image upload

- migration 004: thesis_supervisors.role + is_external; view adds jury_president/jury_promoteurs/jury_lecteurs
- migration 005: theses.banner_path; view exposes t.banner_path and t.license_id
- Database: getThesisJury(), setThesisJury(), setBannerPath()
- admin/add.php: jury fieldset (président/promoteur/lecteurs + externe checkboxes, JS add/remove rows); banner file input
- admin/edit.php: jury fieldset pre-populated from DB; banner preview + remove checkbox + upload; multipart form
- admin/actions/formulaire.php: parse jury fields → setThesisJury(); banner upload to banners/
- tfe.php: three conditional jury rows (président·e, promoteur·ice, lecteur·ices)
- schema.sql: updated thesis_supervisors, theses, v_theses_full, v_theses_public definitions
- admin.css: fieldset, jury-row, jury-entry, btn-remove styles
This commit is contained in:
Pontoporeia
2026-03-24 13:25:23 +01:00
parent d87348c388
commit cefceb046c
12 changed files with 569 additions and 138 deletions

View File

@@ -84,9 +84,22 @@ try {
$problematique = sanitize_string($_POST["problématique"] ?? '');
$durationInfo = sanitize_string($_POST["duration_info"] ?? '');
// Supervisor(s)
$promoteuriceRaw = sanitize_string($_POST["promoteurice"] ?? '');
$supervisorNames = !empty($promoteuriceRaw) ? array_map('trim', explode(',', $promoteuriceRaw)) : [];
// Jury members
$juryMembers = [];
if (!empty(trim($_POST['jury_president'] ?? ''))) {
$juryMembers[] = ['name' => trim($_POST['jury_president']), 'role' => 'president', 'is_external' => 0];
}
if (!empty(trim($_POST['jury_promoteur'] ?? ''))) {
$juryMembers[] = ['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 !== '') {
$juryMembers[] = ['name' => $name, 'role' => 'lecteur',
'is_external' => isset($_POST['jury_lecteurs_ext'][$i]) ? 1 : 0];
}
}
// Keywords (max 10)
$tagRaw = sanitize_string($_POST["tag"] ?? '');
@@ -119,6 +132,7 @@ try {
// File uploads
$couverture = $_FILES["couverture"] ?? null;
$bannerFile = $_FILES["banner"] ?? null;
$files = $_FILES["files"] ?? null;
// ===== CREATE OR FIND AUTHOR =====
@@ -164,14 +178,8 @@ try {
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)");
$stmt->execute([$thesisId, $authorId]);
// ===== LINK SUPERVISORS TO THESIS =====
foreach ($supervisorNames 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]);
}
}
// ===== LINK JURY TO THESIS =====
$db->setThesisJury($thesisId, $juryMembers);
// ===== LINK LANGUAGES TO THESIS =====
foreach ($languageIds as $languageId) {
@@ -201,7 +209,8 @@ try {
// Create necessary directories — outside the webroot (security items #3 & #4).
// Files are served through /media.php, never directly via a URL path.
$uploadBaseDir = STORAGE_ROOT . "/theses/{$annee}/{$identifier}/";
$coverDir = STORAGE_ROOT . "/covers/";
$coverDir = STORAGE_ROOT . "/covers/";
$bannerDir = STORAGE_ROOT . "/banners/";
if (!file_exists($uploadBaseDir)) {
mkdir($uploadBaseDir, 0755, true);
@@ -209,6 +218,9 @@ try {
if (!file_exists($coverDir)) {
mkdir($coverDir, 0755, true);
}
if (!file_exists($bannerDir)) {
mkdir($bannerDir, 0755, true);
}
// Define security constraints
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip'];
@@ -252,6 +264,30 @@ try {
}
}
// Process banner image
if ($bannerFile && isset($bannerFile["error"]) && $bannerFile["error"] === UPLOAD_ERR_OK) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($bannerFile["tmp_name"]);
$fileExtension = strtolower(pathinfo($bannerFile["name"], PATHINFO_EXTENSION));
$allowedBannerMimes = ['image/jpeg', 'image/png', 'image/webp'];
$allowedBannerExts = ['jpg', 'jpeg', 'png', 'webp'];
$maxBannerSize = 5 * 1024 * 1024; // 5 MB
if (in_array($mimeType, $allowedBannerMimes) && in_array($fileExtension, $allowedBannerExts)
&& $bannerFile["size"] <= $maxBannerSize) {
$randomName = bin2hex(random_bytes(16));
$safeFileName = $randomName . "." . $fileExtension;
$targetFile = $bannerDir . $safeFileName;
if (move_uploaded_file($bannerFile["tmp_name"], $targetFile)) {
chmod($targetFile, 0644);
$db->setBannerPath($thesisId, "banners/" . $safeFileName);
error_log("Banner image uploaded: " . $safeFileName);
}
} else {
error_log("Invalid or oversized banner image: " . $bannerFile["name"]);
}
}
// Process thesis files
if ($files && is_array($files["name"])) {
for ($i = 0; $i < count($files["name"]); $i++) {