From 4f5ff5a22c60135ba328ccfbc1fc786491f8e979 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Sat, 28 Mar 2026 18:08:23 +0100 Subject: [PATCH] refactor: extract edit.php POST handler to actions/edit.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit edit.php was a 530-line file mixing form display, POST handling, file uploads, and reference-data loading. This refactor splits it along the same action-file pattern already used by formulaire.php, tag.php, and page.php. Changes: - public/admin/actions/edit.php (new): standalone POST handler; auth guard, CSRF check, transaction, redirect with session flash messages - public/admin/edit.php: display-only; reads edit_success/edit_error flash keys from session; form action points to actions/edit.php via a hidden thesis_id field instead of a query-string self-post - src/Database.php: four new methods to remove all raw PDO from both files: - updateThesis(int, array): void — UPDATE theses core fields - setThesisAuthors(int, array): void — delete-then-reinsert authors - getThesisLanguageIds(int): array — SELECT language_id for form - getThesisFormatIds(int): array — SELECT format_id for form --- TODO.md | 9 +- public/admin/actions/edit.php | 141 ++++++++++++++ public/admin/edit.php | 172 +++--------------- src/Database.php | 83 +++++++++ .../ad921d60486366258809553a3db49a4a.json | 2 +- storage/test.db | Bin 237568 -> 237568 bytes 6 files changed, 253 insertions(+), 154 deletions(-) create mode 100644 public/admin/actions/edit.php diff --git a/TODO.md b/TODO.md index 3c6066c..5532111 100644 --- a/TODO.md +++ b/TODO.md @@ -423,10 +423,11 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis ### C — Code organisation / maintainability -- [ ] **`edit.php` does too much** — 530 lines combining form display, POST handling, file upload, - and all reference-data loading in one file. Extract the POST handler to - `public/admin/actions/edit.php` (matching the pattern already used by `formulaire.php`, - `tag.php`, `page.php`, etc.). +- [x] **`edit.php` does too much** — POST handler extracted to `public/admin/actions/edit.php`; + `edit.php` is now display-only (loads data, renders form, reads flash messages from session). + Added `Database::updateThesis()`, `Database::setThesisAuthors()`, + `Database::getThesisLanguageIds()`, `Database::getThesisFormatIds()` to remove all raw PDO + from both files. Matches the pattern of `formulaire.php`, `tag.php`, `page.php`. - [x] **`formulaire.php` duplicates banner-upload logic verbatim from `edit.php`** — extracted to `Database::handleBannerUpload(int $thesisId, ?array $uploadedFile): ?string`; both action diff --git a/public/admin/actions/edit.php b/public/admin/actions/edit.php new file mode 100644 index 0000000..5a4dab9 --- /dev/null +++ b/public/admin/actions/edit.php @@ -0,0 +1,141 @@ +beginTransaction(); + + // Thesis metadata + $db->updateThesis($thesisId, [ + 'title' => trim($_POST['titre']), + 'subtitle' => trim($_POST['subtitle'] ?? ''), + 'year' => intval($_POST['année']), + 'orientation_id' => intval($_POST['orientation']), + 'ap_program_id' => intval($_POST['ap']), + 'finality_id' => intval($_POST['finality']), + 'synopsis' => trim($_POST['synopsis']), + 'context_note' => trim($_POST['context_note'] ?? ''), + 'file_size_info' => trim($_POST['duration_info'] ?? ''), + 'baiu_link' => trim($_POST['lien'] ?? ''), + 'license_id' => filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null, + 'access_type_id' => filter_var($_POST['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null, + 'is_published' => isset($_POST['is_published']), + ]); + + // Authors + $authorsRaw = trim($_POST['auteurice'] ?? ''); + $authorEntries = []; + if (!empty($authorsRaw)) { + $names = array_map('trim', explode(',', $authorsRaw)); + foreach ($names as $index => $name) { + if ($name !== '') { + $authorEntries[] = [ + 'name' => $name, + 'email' => $index === 0 ? ($_POST['mail'] ?? null) : null, + ]; + } + } + } + $db->setThesisAuthors($thesisId, $authorEntries); + + // Jury + $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, + ]; + } + } + $db->setThesisJury($thesisId, $juryMembers); + + // Languages + $db->setThesisLanguages( + $thesisId, + isset($_POST['languages']) && is_array($_POST['languages']) ? $_POST['languages'] : [] + ); + + // Formats + $db->setThesisFormats( + $thesisId, + isset($_POST['formats']) && is_array($_POST['formats']) ? $_POST['formats'] : [] + ); + + // Tags + $keywordsRaw = trim($_POST['tag'] ?? ''); + $editKeywords = !empty($keywordsRaw) ? array_map('trim', explode(',', $keywordsRaw)) : []; + $db->setThesisTags($thesisId, $editKeywords); + + $db->commit(); + + // 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); + } + + // Regenerate CSRF token + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + + // Flash success and redirect back to edit form + $_SESSION['edit_success'] = "TFE mis à jour avec succès!"; + header('Location: ../edit.php?id=' . $thesisId); + exit(); + +} catch (Exception $e) { + if (isset($db)) { + $db->rollback(); + } + error_log("Edit action error: " . $e->getMessage()); + + $_SESSION['edit_error'] = $e->getMessage(); + header('Location: ../edit.php?id=' . $thesisId); + exit(); +} diff --git a/public/admin/edit.php b/public/admin/edit.php index a1c6b74..870dbe9 100644 --- a/public/admin/edit.php +++ b/public/admin/edit.php @@ -14,167 +14,41 @@ if (empty($_SESSION['csrf_token'])) { require_once __DIR__ . '/../../src/Database.php'; $thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0; -$error = null; -$success = null; if ($thesisId <= 0) { die("ID invalide"); } +// Consume flash messages from the edit action +$error = $_SESSION['edit_error'] ?? null; +$success = $_SESSION['edit_success'] ?? null; +unset($_SESSION['edit_error'], $_SESSION['edit_success']); + 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()); - } - } + $db = new Database(); // 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); + // Load current relationships via dedicated DB methods (no raw PDO) + $currentLanguages = $db->getThesisLanguageIds($thesisId); + $currentFormats = $db->getThesisFormatIds($thesisId); + $jury = $db->getThesisJury($thesisId); - $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(); + // Reference / lookup data + $orientations = $db->getAllOrientations(); + $apPrograms = $db->getAllAPPrograms(); $finalityTypes = $db->getAllFinalityTypes(); - $languages = $db->getAllLanguages(); - $formatTypes = $db->getAllFormatTypes(); - $licenseTypes = $db->getAllLicenseTypes(); - $accessTypes = $db->getAccessTypes(); + $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); + $rawRow = $db->getThesisRawFields($thesisId); $currentLicenseId = $rawRow['license_id'] ?? null; $currentAccessTypeId = $rawRow['access_type_id'] ?? null; $currentContextNote = $rawRow['context_note'] ?? ''; @@ -199,8 +73,9 @@ try {
-
+ +
@@ -257,11 +132,10 @@ try { pdo->prepare( + "SELECT language_id FROM thesis_languages WHERE thesis_id = ?" + ); + $stmt->execute([$thesisId]); + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + + /** + * Return the list of format IDs currently linked to a thesis. + * @return int[] + */ + public function getThesisFormatIds(int $thesisId): array { + $stmt = $this->pdo->prepare( + "SELECT format_id FROM thesis_formats WHERE thesis_id = ?" + ); + $stmt->execute([$thesisId]); + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + /** * Replace all tag associations for a thesis. * Tags are identified by name (findOrCreateTag is called for each). @@ -1128,6 +1152,65 @@ class Database { * * @return int The new thesis ID. */ + /** + * Update core thesis fields. + * All columns except identifier, submitted_at, and file-related fields. + */ + public function updateThesis(int $thesisId, array $data): void { + $stmt = $this->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([ + $data['title'], + !empty($data['subtitle']) ? $data['subtitle'] : null, + (int)$data['year'], + (int)$data['orientation_id'], + (int)$data['ap_program_id'], + (int)$data['finality_id'], + $data['synopsis'], + !empty($data['context_note']) ? $data['context_note'] : null, + !empty($data['file_size_info']) ? $data['file_size_info'] : null, + !empty($data['baiu_link']) ? $data['baiu_link'] : null, + isset($data['license_id']) ? $data['license_id'] : null, + isset($data['access_type_id']) ? $data['access_type_id'] : null, + $data['is_published'] ? 1 : 0, + $thesisId, + ]); + } + + /** + * Replace all author associations for a thesis (delete-then-reinsert). + * $authors is an array of ['name' => string, 'email' => string|null]. + * The first entry is considered the primary author (author_order = 1). + */ + public function setThesisAuthors(int $thesisId, array $authors): void { + $this->pdo->prepare("DELETE FROM thesis_authors WHERE thesis_id = ?")->execute([$thesisId]); + $stmt = $this->pdo->prepare( + "INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)" + ); + foreach ($authors as $index => $author) { + $name = trim($author['name'] ?? ''); + if ($name === '') continue; + $authorId = $this->findOrCreateAuthor($name, $author['email'] ?? null); + $stmt->execute([$thesisId, $authorId, $index + 1]); + } + } + public function createThesis(array $data): int { $identifier = $this->generateThesisIdentifier((int)$data['year']); diff --git a/src/cache/rate_limit/ad921d60486366258809553a3db49a4a.json b/src/cache/rate_limit/ad921d60486366258809553a3db49a4a.json index 52d28aa..1b5d283 100644 --- a/src/cache/rate_limit/ad921d60486366258809553a3db49a4a.json +++ b/src/cache/rate_limit/ad921d60486366258809553a3db49a4a.json @@ -1 +1 @@ -[1774712930] \ No newline at end of file +[1774717688] \ No newline at end of file diff --git a/storage/test.db b/storage/test.db index ae942556d666e2e72d994fbe8d71123b13bda5d1..004266c9690d47058715f46a08ac180287fe4316 100644 GIT binary patch delta 28 icmZoTz}IkqZ$pqiV|sJ2etWP!BM>uf57uX1TL1u+3<;?K delta 28 icmZoTz}IkqZ$pqiV_I{tetWP!BM>uf57uX1TL1u*`U$82