From 52f8e267e5a6a46288bd885b48997c55916b0d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Gervreau-Mercier?= Date: Fri, 6 Feb 2026 14:31:23 +0100 Subject: [PATCH] admin: restructure action scripts to actions/ subdirectory --- public/admin/actions/formulaire.php | 331 ++++++++++++++++++++++++++++ public/admin/actions/publish.php | 98 ++++++++ 2 files changed, 429 insertions(+) create mode 100644 public/admin/actions/formulaire.php create mode 100644 public/admin/actions/publish.php diff --git a/public/admin/actions/formulaire.php b/public/admin/actions/formulaire.php new file mode 100644 index 0000000..2696089 --- /dev/null +++ b/public/admin/actions/formulaire.php @@ -0,0 +1,331 @@ +getPDO(); + + // Begin transaction - all or nothing + $db->beginTransaction(); + + // ===== VALIDATE AND SANITIZE INPUT DATA ===== + + // Author information + $auteurName = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo"); + + $mail = $_POST["mail"] ?? ''; + if (!empty($mail)) { + // Could be email or social media handle + $mail = sanitize_string($mail); + } + + // Year validation + $annee = filter_var($_POST["année"] ?? '', FILTER_VALIDATE_INT); + if ($annee === false || $annee < 2000 || $annee > (int)date('Y') + 1) { + throw new Exception("Année invalide. Veuillez entrer une année valide."); + } + + // Academic details + $orientationId = filter_var($_POST["orientation"] ?? '', FILTER_VALIDATE_INT); + if ($orientationId === false) { + throw new Exception("Veuillez sélectionner une orientation."); + } + + $apProgramId = filter_var($_POST["ap"] ?? '', FILTER_VALIDATE_INT); + if ($apProgramId === false) { + throw new Exception("Veuillez sélectionner un Atelier Pratique."); + } + + $finalityId = filter_var($_POST["finality"] ?? '', FILTER_VALIDATE_INT); + if ($finalityId === false) { + throw new Exception("Veuillez sélectionner une finalité."); + } + + // Thesis content + $titre = validate_required(sanitize_string($_POST["titre"] ?? ''), "Titre du mémoire"); + $subtitle = sanitize_string($_POST["subtitle"] ?? ''); + $synopsis = validate_required(sanitize_string($_POST["synopsis"] ?? ''), "Synopsis"); + $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)) : []; + + // Keywords (max 10) + $tagRaw = sanitize_string($_POST["tag"] ?? ''); + $keywords = !empty($tagRaw) ? array_map('trim', explode(',', $tagRaw)) : []; + if (count($keywords) > 10) { + throw new Exception("Maximum 10 mots-clés autorisés."); + } + + // Languages (at least one required) + $languageIds = $_POST["languages"] ?? []; + if (empty($languageIds)) { + throw new Exception("Veuillez sélectionner au moins une langue."); + } + $languageIds = array_map('intval', $languageIds); + + // Formats (optional, multiple selection) + $formatIds = isset($_POST["formats"]) ? array_map('intval', $_POST["formats"]) : []; + + // External link + $lien = $_POST["lien"] ?? ''; + if (!empty($lien)) { + $lien = filter_var($lien, FILTER_VALIDATE_URL); + if ($lien === false) { + throw new Exception("Lien URL invalide."); + } + } + + // File uploads + $couverture = $_FILES["couverture"] ?? null; + $files = $_FILES["files"] ?? null; + + // ===== CREATE OR FIND AUTHOR ===== + $authorId = $db->findOrCreateAuthor($auteurName, $mail); + error_log("Author ID: $authorId"); + + // ===== INSERT THESIS RECORD ===== + + // Generate unique identifier (YYYY-NNN format) + $stmt = $pdo->prepare("SELECT COUNT(*) as count FROM theses WHERE year = ?"); + $stmt->execute([$annee]); + $count = $stmt->fetch()['count'] + 1; + $identifier = sprintf("%d-%03d", $annee, $count); + + $stmt = $pdo->prepare(" + INSERT INTO theses ( + identifier, title, subtitle, year, + orientation_id, ap_program_id, finality_id, + synopsis, file_size_info, + baiu_link, + submitted_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + "); + + $stmt->execute([ + $identifier, + $titre, + !empty($subtitle) ? $subtitle : null, + $annee, + $orientationId, + $apProgramId, + $finalityId, + $synopsis, + !empty($durationInfo) ? $durationInfo : null, + !empty($lien) ? $lien : null + ]); + + $thesisId = $pdo->lastInsertId(); + error_log("Thesis ID: $thesisId"); + + // ===== LINK AUTHOR TO THESIS ===== + $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 LANGUAGES TO THESIS ===== + foreach ($languageIds as $languageId) { + $stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $languageId]); + } + + // ===== LINK FORMATS TO THESIS ===== + foreach ($formatIds as $formatId) { + $stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $formatId]); + } + + // ===== LINK KEYWORDS TO THESIS ===== + foreach ($keywords as $keyword) { + if (!empty($keyword)) { + $keywordId = $db->findOrCreateKeyword($keyword); + if ($keywordId) { + $stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $keywordId]); + } + } + } + + // ===== HANDLE FILE UPLOADS ===== + + // Create necessary directories + $uploadBaseDir = __DIR__ . "/data/theses/{$annee}/{$identifier}/"; + $coverDir = __DIR__ . "/data/covers/"; + + if (!file_exists($uploadBaseDir)) { + mkdir($uploadBaseDir, 0755, true); + } + if (!file_exists($coverDir)) { + mkdir($coverDir, 0755, true); + } + + // Define security constraints + $allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip']; + $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip']; + $maxFileSize = 50 * 1024 * 1024; // 50 MB + + // Process cover image + $coverPath = null; + if ($couverture && isset($couverture["error"]) && $couverture["error"] === UPLOAD_ERR_OK) { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file($couverture["tmp_name"]); + $fileExtension = strtolower(pathinfo($couverture["name"], PATHINFO_EXTENSION)); + + // Only allow image files for cover + if (in_array($mimeType, ['image/jpeg', 'image/png']) && + in_array($fileExtension, ['jpg', 'jpeg', 'png'])) { + + // Generate random filename + $randomName = bin2hex(random_bytes(16)); + $safeFileName = $randomName . "." . $fileExtension; + $targetFile = $coverDir . $safeFileName; + + if (move_uploaded_file($couverture["tmp_name"], $targetFile)) { + chmod($targetFile, 0644); + $coverPath = "data/covers/" . $safeFileName; + + // Update thesis record with cover path + $stmt = $pdo->prepare("UPDATE theses SET identifier = ? WHERE id = ?"); + // Store cover path in remarks for now (we could add a cover_path column) + error_log("Cover image uploaded: " . $safeFileName); + } + } else { + error_log("Invalid cover image type: " . $mimeType); + } + } + + // Process thesis files + if ($files && is_array($files["name"])) { + for ($i = 0; $i < count($files["name"]); $i++) { + // Skip if no file was uploaded for this slot + if ($files["error"][$i] === UPLOAD_ERR_NO_FILE) { + continue; + } + + if ($files["error"][$i] !== UPLOAD_ERR_OK) { + error_log("File upload error code " . $files["error"][$i] . ": " . $files["name"][$i]); + continue; + } + + // Validate file + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file($files["tmp_name"][$i]); + $fileExtension = strtolower(pathinfo($files["name"][$i], PATHINFO_EXTENSION)); + + if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) { + error_log("Invalid file type: " . $files["name"][$i] . " (MIME: $mimeType)"); + continue; + } + + if ($files["size"][$i] > $maxFileSize) { + error_log("File too large: " . $files["name"][$i]); + continue; + } + + // Generate random filename + $randomName = bin2hex(random_bytes(16)); + $safeFileName = $randomName . "." . $fileExtension; + $targetFile = $uploadBaseDir . $safeFileName; + + if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) { + chmod($targetFile, 0644); + + // Determine file type (simplified - could be enhanced) + $fileType = 'other'; + if (strpos(strtolower($files["name"][$i]), 'annex') !== false) { + $fileType = 'annex'; + } else if ($fileExtension === 'pdf') { + $fileType = 'main'; + } + + // Insert file record + $db->insertThesisFile( + $thesisId, + $fileType, + "data/theses/{$annee}/{$identifier}/" . $safeFileName, + basename($files["name"][$i]), + $files["size"][$i], + $mimeType + ); + + error_log("File uploaded: " . $safeFileName); + } else { + error_log("Failed to move file: " . $files["name"][$i]); + } + } + } + + // ===== COMMIT TRANSACTION ===== + $db->commit(); + + error_log("Thesis submission completed successfully: $identifier"); + + // Clear CSRF token + unset($_SESSION['csrf_token']); + + // Redirect to thank you page + header('Location: ../thanks.php?id=' . urlencode($thesisId)); + exit(); + +} catch (Exception $e) { + // Rollback transaction on error + if (isset($db)) { + $db->rollback(); + } + + error_log("Form processing error: " . $e->getMessage()); + + // Save error message and form data to session + $_SESSION['form_error'] = $e->getMessage(); + $_SESSION['form_data'] = $_POST; + + // Redirect back to form with preserved data + header('Location: ../add.php'); + exit(); +} diff --git a/public/admin/actions/publish.php b/public/admin/actions/publish.php new file mode 100644 index 0000000..f3a5860 --- /dev/null +++ b/public/admin/actions/publish.php @@ -0,0 +1,98 @@ +getPDO(); + + $isPublished = ($action === 'publish') ? 1 : 0; + + if ($isBulk) { + // Handle bulk action + $thesisIds = isset($_POST['selected_theses']) ? $_POST['selected_theses'] : []; + + if (empty($thesisIds)) { + $_SESSION['error'] = "Aucun TFE sélectionné."; + header('Location: ../index.php'); + exit; + } + + // Validate all IDs are integers + $thesisIds = array_map('intval', $thesisIds); + $thesisIds = array_filter($thesisIds, fn($id) => $id > 0); + + if (empty($thesisIds)) { + $_SESSION['error'] = "IDs invalides."; + header('Location: ../index.php'); + exit; + } + + // Prepare placeholders for IN clause + $placeholders = str_repeat('?,', count($thesisIds) - 1) . '?'; + $sql = "UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN ($placeholders)"; + + $stmt = $pdo->prepare($sql); + $params = array_merge([$isPublished], $thesisIds); + $stmt->execute($params); + + $count = count($thesisIds); + if ($action === 'publish') { + $_SESSION['success'] = "$count TFE(s) publié(s) avec succès!"; + } else { + $_SESSION['success'] = "$count TFE(s) retiré(s) de la publication."; + } + + } else { + // Handle single action + $thesisId = isset($_POST['thesis_id']) ? intval($_POST['thesis_id']) : 0; + + if ($thesisId <= 0) { + $_SESSION['error'] = "ID invalide."; + header('Location: ../index.php'); + exit; + } + + $stmt = $pdo->prepare("UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"); + $stmt->execute([$isPublished, $thesisId]); + + if ($action === 'publish') { + $_SESSION['success'] = "TFE publié avec succès!"; + } else { + $_SESSION['success'] = "TFE retiré de la publication."; + } + } + +} catch (Exception $e) { + error_log("Publish error: " . $e->getMessage()); + $_SESSION['error'] = "Erreur lors de la modification: " . $e->getMessage(); +} + +// Regenerate CSRF token +$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + +header('Location: ../index.php'); +exit;