Extract ThesisCreateController; add Database publish methods

Consolidate action handlers into controller methods (todo/02-php-components.md).

src/ThesisCreateController.php (new, 435 lines)
  Mirrors ThesisEditController for the add-thesis flow.

  make()           — factory; instantiates Database via new Database()
  loadFormData()   — returns all lookup tables needed by admin/add.php
                     (orientations, apPrograms, finalityTypes, languages,
                      formatTypes, licenseTypes)
  submit(post, files) — full new-thesis creation pipeline:
    1. validateAndSanitise() — trims/strips HTML, validates required fields,
       year range, orientation/ap/finality IDs, language selection, max-10
       keywords, URL format; throws named Exception on failure
    2. findOrCreateAuthor() — reuses existing DB method
    3. Transaction: createThesis + setThesisJury + setThesisLanguages +
       setThesisFormats + setThesisTags; rolls back on any failure
    4. File uploads outside transaction: cover image (JPG/PNG only, stored in
       storage/covers/), banner via handleBannerUpload(), thesis files
       (PDF/JPG/PNG/MP4/ZIP/VTT, stored in storage/theses/YEAR/IDENT/,
       file_type auto-detected: caption/annex/main/other)
  autofocusFieldForError() — static; maps exception messages to field names
    for WCAG 3.3.1 autofocus on re-render (same contract as
    ThesisEditController::autofocusFieldForError)

admin/actions/formulaire.php  346 → 45 lines
  Now: bootstrap + CSRF guard + ThesisCreateController::make()->submit() +
  flash/redirect on error. All validation, DB logic, and file handling removed.

admin/add.php
  Lookup-table block (new Database() + 6 individual DB calls) replaced with
  ThesisCreateController::make()->loadFormData() + extract().

src/Database.php — two new methods added
  setPublished(int , bool ): void
    UPDATE theses SET is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
  bulkSetPublished(int[] , bool ): void
    Same but with an IN (...) clause for multiple IDs

admin/actions/publish.php  100 → 65 lines
  Raw SQL (->prepare('UPDATE theses SET is_published = ?...')) replaced
  with ->setPublished() / ->bulkSetPublished(). No raw PDO calls remain
  in any action handler file.
This commit is contained in:
Pontoporeia
2026-04-06 14:37:56 +02:00
parent b1e70a2bf1
commit 2841e05716
7 changed files with 501 additions and 384 deletions

View File

@@ -1,100 +1,65 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../../config/bootstrap.php";
require_once __DIR__ . '/../../../config/bootstrap.php';
require_once __DIR__ . '/../../../src/AdminAuth.php';
/**
* Handle publish/unpublish actions for theses
*/
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
AdminAuth::requireLogin();
require_once __DIR__ . '/../../../src/Database.php';
// Verify CSRF token
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
App::flash('error', "Erreur de sécurité : token invalide.");
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
App::flash('error', 'Erreur de sécurité : token invalide.');
header('Location: ../index.php');
exit;
}
$action = isset($_POST['action']) ? $_POST['action'] : '';
$isBulk = isset($_POST['bulk']) && $_POST['bulk'] == '1';
$action = $_POST['action'] ?? '';
$isBulk = !empty($_POST['bulk']);
if (!in_array($action, ['publish', 'unpublish'])) {
App::flash('error', "Action invalide.");
if (!in_array($action, ['publish', 'unpublish'], true)) {
App::flash('error', 'Action invalide.');
header('Location: ../index.php');
exit;
}
$published = ($action === 'publish');
try {
$db = new Database();
$pdo = $db->getPDO();
$isPublished = ($action === 'publish') ? 1 : 0;
if ($isBulk) {
// Handle bulk action
$thesisIds = isset($_POST['selected_theses']) ? $_POST['selected_theses'] : [];
$ids = array_filter(array_map('intval', $_POST['selected_theses'] ?? []), fn($id) => $id > 0);
if (empty($thesisIds)) {
App::flash('error', "Aucun TFE sélectionné.");
if (empty($ids)) {
App::flash('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)) {
App::flash('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') {
App::flash('success', "$count TFE(s) publié(s) avec succès!");
} else {
App::flash('success', "$count TFE(s) retiré(s) de la publication.");
}
$db->bulkSetPublished($ids, $published);
$count = count($ids);
App::flash('success', $published
? "$count TFE(s) publié(s) avec succès."
: "$count TFE(s) retiré(s) de la publication.");
} else {
// Handle single action
$thesisId = isset($_POST['thesis_id']) ? intval($_POST['thesis_id']) : 0;
$thesisId = filter_var($_POST['thesis_id'] ?? '', FILTER_VALIDATE_INT);
if ($thesisId <= 0) {
App::flash('error', "ID invalide.");
if (!$thesisId || $thesisId <= 0) {
App::flash('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') {
App::flash('success', "TFE publié avec succès!");
} else {
App::flash('success', "TFE retiré de la publication.");
}
$db->setPublished($thesisId, $published);
App::flash('success', $published ? 'TFE publié avec succès.' : 'TFE retiré de la publication.');
}
} catch (Exception $e) {
error_log("Publish error: " . $e->getMessage());
App::flash('error', "Erreur lors de la modification: " . $e->getMessage());
error_log('publish.php error: ' . $e->getMessage());
App::flash('error', 'Erreur lors de la modification : ' . $e->getMessage());
}
// Regenerate CSRF token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
header('Location: ../index.php');
exit;