mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Extract ThesisEditController from admin/edit.php and actions/edit.php
src/ThesisEditController.php (285 lines) centralises all data-fetching and
mutation logic for the thesis-edit workflow:
load(int $thesisId): array
Fetches the thesis row, current language/format/jury selections, and all
lookup tables (orientations, AP programmes, finality types, languages,
formats, licences, access types) in one call. Returns a flat view-variable
array that the dispatcher extracts directly.
save(int $thesisId, array $post, array $files): void
Runs the full edit inside a transaction: thesis metadata, authors, jury,
languages, formats, tags. Banner upload/removal is handled outside the
transaction (filesystem op). Rolls back and re-throws on any failure.
static autofocusFieldForError(string $msg): ?string
Centralises the WCAG 3.3.1 exception-message → field-name mapping that
was previously duplicated inline in actions/edit.php.
Dispatcher changes:
admin/edit.php 191 → 162 lines (pure view + ThesisEditController::create() + load())
actions/edit.php 153 → 53 lines (CSRF guard + ThesisEditController::save() call)
Follows the same pattern as SearchController and SystemController.
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -11,6 +11,8 @@ Pending tasks have been split into topic files under [`todo/`](todo/README.md):
|
||||
|
||||
## Recently completed (this session)
|
||||
|
||||
- [x] `src/ThesisEditController.php` — extracted all data-fetching and mutation logic from `admin/edit.php` and `admin/actions/edit.php` into a dedicated controller class; `load(int $thesisId): array` fetches the thesis row, current language/format/jury selections, and all lookup tables for the view; `save(int $thesisId, array $post, array $files): void` validates and persists thesis metadata, authors, jury, languages, formats, tags, and banner in a transaction with proper rollback on error; static `autofocusFieldForError(string $msg): ?string` centralises WCAG 3.3.1 field-name mapping; `admin/edit.php` reduced 191→162 lines (pure dispatcher + view template); `actions/edit.php` reduced 153→53 lines (CSRF guard + one controller call)
|
||||
|
||||
- [x] `src/SystemController.php` — extracted all data-fetching logic from `admin/system.php` and `admin/system-fragment.php` into a dedicated controller class; centralises: system status checks (nginx, php-fpm, HTTP ping, SQLite DB, storage dir, maintenance flag) with 2-min TTL caching, PHP environment info (1-hour TTL), disk usage (5-min TTL), log file reading (`readLogTail`), nginx config reading, and the shared CSS-class classifier methods (`logLineClass`, `nginxLineClass`, `statusLabel`, `statusClass`, `humanBytes`, `diskColor`); `system.php` reduced 582→282 lines; `system-fragment.php` reduced 213→137 lines with all `frag_*`-prefixed duplicated helpers removed; both files now purely dispatch to the controller and render view templates
|
||||
|
||||
|
||||
|
||||
@@ -24,126 +24,26 @@ if ($thesisId <= 0) {
|
||||
die("ID de TFE invalide.");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../../src/Database.php';
|
||||
require_once APP_ROOT . '/src/ThesisEditController.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$ctrl = ThesisEditController::create();
|
||||
$ctrl->save($thesisId, $_POST, $_FILES);
|
||||
|
||||
$db->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
|
||||
// Regenerate CSRF token after successful save
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
|
||||
// Flash success and redirect back to edit form
|
||||
App::flash('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());
|
||||
|
||||
App::flash('error', $e->getMessage());
|
||||
|
||||
// WCAG 3.3.1 — map error to the field that caused it so the form can autofocus it.
|
||||
$msg = $e->getMessage();
|
||||
$autofocusField = null;
|
||||
if (str_contains($msg, 'titre') || str_contains($msg, 'Titre')) $autofocusField = 'titre';
|
||||
elseif (str_contains($msg, 'année') || str_contains($msg, 'année')) $autofocusField = 'année';
|
||||
elseif (str_contains($msg, 'synopsis') || str_contains($msg, 'Synopsis')) $autofocusField = 'synopsis';
|
||||
elseif (str_contains($msg, 'auteur') || str_contains($msg, 'Auteur')) $autofocusField = 'auteurice';
|
||||
// WCAG 3.3.1 — map error message to field name for autofocus on re-render.
|
||||
$autofocusField = ThesisEditController::autofocusFieldForError($e->getMessage());
|
||||
if ($autofocusField !== null) {
|
||||
App::flashAutofocus($autofocusField);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
require_once APP_ROOT . '/src/ThesisEditController.php';
|
||||
|
||||
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
|
||||
@@ -19,43 +19,14 @@ if ($thesisId <= 0) {
|
||||
die("ID invalide");
|
||||
}
|
||||
|
||||
// Flash messages are consumed by the flash-messages partial below.
|
||||
// WCAG 3.3.1 — consume the autofocus hint stored by the edit action on
|
||||
// validation failure.
|
||||
$autofocusField = App::consumeAutofocus();
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
|
||||
// Load thesis data
|
||||
$thesis = $db->getThesis($thesisId);
|
||||
if (!$thesis) {
|
||||
die("TFE non trouvé");
|
||||
}
|
||||
|
||||
// Load current relationships via dedicated DB methods (no raw PDO)
|
||||
$currentLanguages = $db->getThesisLanguageIds($thesisId);
|
||||
$currentFormats = $db->getThesisFormatIds($thesisId);
|
||||
$jury = $db->getThesisJury($thesisId);
|
||||
|
||||
// Reference / lookup 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']);
|
||||
|
||||
// WCAG 3.3.1 — consume the autofocus hint stored by the edit action on validation failure.
|
||||
$autofocusField = App::consumeAutofocus();
|
||||
|
||||
$ctrl = ThesisEditController::create();
|
||||
$view = $ctrl->load($thesisId);
|
||||
extract($view); // thesis, currentLanguages, currentFormats, jury, lookup tables, pageTitle …
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading edit page: " . $e->getMessage());
|
||||
die("Erreur lors du chargement: " . $e->getMessage());
|
||||
|
||||
285
src/ThesisEditController.php
Normal file
285
src/ThesisEditController.php
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* ThesisEditController
|
||||
*
|
||||
* Centralises all data-fetching and mutation logic for the admin thesis-edit
|
||||
* workflow (admin/edit.php + admin/actions/edit.php).
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Loading thesis data and lookup tables for the edit form view
|
||||
* - Validating and persisting POST submissions (thesis metadata, authors,
|
||||
* jury, languages, formats, tags, banner)
|
||||
* - WCAG 3.3.1: mapping validation exceptions to autofocus field hints
|
||||
*
|
||||
* The class has NO output side-effects; all redirects, flash writes, and
|
||||
* template rendering stay in the thin dispatcher files so the view layer
|
||||
* remains easy to inspect and modify.
|
||||
*/
|
||||
class ThesisEditController
|
||||
{
|
||||
private Database $db;
|
||||
|
||||
public function __construct(Database $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// ── Factory ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Convenience factory — instantiates Database and returns a ready
|
||||
* controller. Accepts an optional existing Database instance so callers
|
||||
* that already hold one (e.g. during testing) can avoid a second
|
||||
* connection.
|
||||
*/
|
||||
public static function create(?Database $db = null): self
|
||||
{
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
|
||||
return new self($db ?? Database::getInstance());
|
||||
}
|
||||
|
||||
// ── Read / view data ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Load all data required to render the edit form.
|
||||
*
|
||||
* Returns a flat array of view variables:
|
||||
* - 'thesis' – thesis row (from getThesis)
|
||||
* - 'currentLanguages' – int[]
|
||||
* - 'currentFormats' – int[]
|
||||
* - 'jury' – jury rows
|
||||
* - 'orientations' – lookup rows
|
||||
* - 'apPrograms' – lookup rows
|
||||
* - 'finalityTypes' – lookup rows
|
||||
* - 'languages' – lookup rows
|
||||
* - 'formatTypes' – lookup rows
|
||||
* - 'licenseTypes' – lookup rows
|
||||
* - 'accessTypes' – lookup rows
|
||||
* - 'currentLicenseId' – int|null
|
||||
* - 'currentAccessTypeId'– int|null
|
||||
* - 'currentContextNote' – string
|
||||
* - 'pageTitle' – string
|
||||
*
|
||||
* @throws Exception if the thesis is not found or a DB error occurs.
|
||||
*/
|
||||
public function load(int $thesisId): array
|
||||
{
|
||||
if ($thesisId <= 0) {
|
||||
throw new InvalidArgumentException("ID invalide");
|
||||
}
|
||||
|
||||
$thesis = $this->db->getThesis($thesisId);
|
||||
if (!$thesis) {
|
||||
throw new RuntimeException("TFE non trouvé");
|
||||
}
|
||||
|
||||
$currentLanguages = $this->db->getThesisLanguageIds($thesisId);
|
||||
$currentFormats = $this->db->getThesisFormatIds($thesisId);
|
||||
$jury = $this->db->getThesisJury($thesisId);
|
||||
|
||||
$orientations = $this->db->getAllOrientations();
|
||||
$apPrograms = $this->db->getAllAPPrograms();
|
||||
$finalityTypes = $this->db->getAllFinalityTypes();
|
||||
$languages = $this->db->getAllLanguages();
|
||||
$formatTypes = $this->db->getAllFormatTypes();
|
||||
$licenseTypes = $this->db->getAllLicenseTypes();
|
||||
$accessTypes = $this->db->getAccessTypes();
|
||||
|
||||
$rawRow = $this->db->getThesisRawFields($thesisId);
|
||||
$currentLicenseId = $rawRow['license_id'] ?? null;
|
||||
$currentAccessTypeId = $rawRow['access_type_id'] ?? null;
|
||||
$currentContextNote = $rawRow['context_note'] ?? '';
|
||||
|
||||
return [
|
||||
'thesis' => $thesis,
|
||||
'currentLanguages' => $currentLanguages,
|
||||
'currentFormats' => $currentFormats,
|
||||
'jury' => $jury,
|
||||
'orientations' => $orientations,
|
||||
'apPrograms' => $apPrograms,
|
||||
'finalityTypes' => $finalityTypes,
|
||||
'languages' => $languages,
|
||||
'formatTypes' => $formatTypes,
|
||||
'licenseTypes' => $licenseTypes,
|
||||
'accessTypes' => $accessTypes,
|
||||
'currentLicenseId' => $currentLicenseId,
|
||||
'currentAccessTypeId' => $currentAccessTypeId,
|
||||
'currentContextNote' => $currentContextNote,
|
||||
'pageTitle' => 'Éditer TFE - ' . htmlspecialchars($thesis['title']),
|
||||
];
|
||||
}
|
||||
|
||||
// ── Write / action ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Validate and persist a thesis-edit POST submission.
|
||||
*
|
||||
* Runs the full update inside a transaction:
|
||||
* 1. Thesis metadata (title, subtitle, year, orientation, ap, finality,
|
||||
* synopsis, context_note, file_size_info, baiu_link, license_id,
|
||||
* access_type_id, is_published)
|
||||
* 2. Authors (setThesisAuthors)
|
||||
* 3. Jury (setThesisJury)
|
||||
* 4. Languages (setThesisLanguages)
|
||||
* 5. Formats (setThesisFormats)
|
||||
* 6. Tags (setThesisTags)
|
||||
* Then handles banner upload/removal outside the transaction.
|
||||
*
|
||||
* @param int $thesisId Validated thesis ID (> 0).
|
||||
* @param array $post Sanitised $_POST array.
|
||||
* @param array $files $_FILES array (expects 'banner' key).
|
||||
*
|
||||
* @throws Exception on validation or DB error (caller must rollback if
|
||||
* the transaction is still open, but this method rolls
|
||||
* back internally before re-throwing).
|
||||
*/
|
||||
public function save(int $thesisId, array $post, array $files): void
|
||||
{
|
||||
if ($thesisId <= 0) {
|
||||
throw new InvalidArgumentException("ID de TFE invalide.");
|
||||
}
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
try {
|
||||
// ── 1. Thesis metadata ────────────────────────────────────────────
|
||||
$this->db->updateThesis($thesisId, [
|
||||
'title' => trim($post['titre'] ?? ''),
|
||||
'subtitle' => trim($post['subtitle'] ?? ''),
|
||||
'year' => intval($post['année'] ?? 0),
|
||||
'orientation_id' => intval($post['orientation'] ?? 0),
|
||||
'ap_program_id' => intval($post['ap'] ?? 0),
|
||||
'finality_id' => intval($post['finality'] ?? 0),
|
||||
'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']),
|
||||
]);
|
||||
|
||||
// ── 2. Authors ────────────────────────────────────────────────────
|
||||
$authorsRaw = trim($post['auteurice'] ?? '');
|
||||
$authorEntries = [];
|
||||
if ($authorsRaw !== '') {
|
||||
foreach (array_map('trim', explode(',', $authorsRaw)) as $i => $name) {
|
||||
if ($name !== '') {
|
||||
$authorEntries[] = [
|
||||
'name' => $name,
|
||||
'email' => $i === 0 ? ($post['mail'] ?? null) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->db->setThesisAuthors($thesisId, $authorEntries);
|
||||
|
||||
// ── 3. Jury ───────────────────────────────────────────────────────
|
||||
$juryMembers = $this->collectJuryMembers($post);
|
||||
$this->db->setThesisJury($thesisId, $juryMembers);
|
||||
|
||||
// ── 4. Languages ──────────────────────────────────────────────────
|
||||
$this->db->setThesisLanguages(
|
||||
$thesisId,
|
||||
isset($post['languages']) && is_array($post['languages'])
|
||||
? $post['languages']
|
||||
: []
|
||||
);
|
||||
|
||||
// ── 5. Formats ────────────────────────────────────────────────────
|
||||
$this->db->setThesisFormats(
|
||||
$thesisId,
|
||||
isset($post['formats']) && is_array($post['formats'])
|
||||
? $post['formats']
|
||||
: []
|
||||
);
|
||||
|
||||
// ── 6. Tags ───────────────────────────────────────────────────────
|
||||
$keywordsRaw = trim($post['tag'] ?? '');
|
||||
$keywords = $keywordsRaw !== ''
|
||||
? array_map('trim', explode(',', $keywordsRaw))
|
||||
: [];
|
||||
$this->db->setThesisTags($thesisId, $keywords);
|
||||
|
||||
$this->db->commit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->db->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// ── Banner (outside transaction — filesystem op) ──────────────────────
|
||||
if (isset($post['remove_banner'])) {
|
||||
$currentBannerPath = $this->db->getThesisBannerPath($thesisId);
|
||||
if ($currentBannerPath && defined('STORAGE_ROOT')) {
|
||||
$absPath = STORAGE_ROOT . '/' . $currentBannerPath;
|
||||
if (file_exists($absPath)) {
|
||||
unlink($absPath);
|
||||
}
|
||||
}
|
||||
$this->db->setBannerPath($thesisId, null);
|
||||
} else {
|
||||
$this->db->handleBannerUpload($thesisId, $files['banner'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
// ── WCAG 3.3.1 helper ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Map a validation exception message to the name of the field that should
|
||||
* receive autofocus when the form is re-rendered.
|
||||
*
|
||||
* Returns null when no field mapping is found.
|
||||
*/
|
||||
public static function autofocusFieldForError(string $message): ?string
|
||||
{
|
||||
if (str_contains($message, 'titre') || str_contains($message, 'Titre')) return 'titre';
|
||||
if (str_contains($message, 'année') || str_contains($message, 'Année')) return 'année';
|
||||
if (str_contains($message, 'synopsis') || str_contains($message, 'Synopsis')) return 'synopsis';
|
||||
if (str_contains($message, 'auteur') || str_contains($message, 'Auteur')) return 'auteurice';
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Private helpers ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build the jury-members array from POST data.
|
||||
*
|
||||
* @param array $post Raw $_POST.
|
||||
* @return array<int, array{name: string, role: string, is_external: int}>
|
||||
*/
|
||||
private function collectJuryMembers(array $post): array
|
||||
{
|
||||
$members = [];
|
||||
|
||||
if (!empty(trim($post['jury_president'] ?? ''))) {
|
||||
$members[] = [
|
||||
'name' => trim($post['jury_president']),
|
||||
'role' => 'president',
|
||||
'is_external' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty(trim($post['jury_promoteur'] ?? ''))) {
|
||||
$members[] = [
|
||||
'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 !== '') {
|
||||
$members[] = [
|
||||
'name' => $name,
|
||||
'role' => 'lecteur',
|
||||
'is_external' => isset($post['jury_lecteurs_ext'][$i]) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $members;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
- [x] Extract `SearchController` — `src/SearchController.php`; rate-limiting, param sanitisation, DB queries, OG meta, and author-map construction moved out of `public/search.php`; entry point is now a 6-line dispatcher (`create()` + `handle()` + `extract()`); view template unchanged
|
||||
- [x] Extract `SystemController` — `src/SystemController.php` (452 lines); all status checks, disk/PHP info, log reading, nginx config reading, and line classifiers centralised; `system.php` reduced 582→282 lines; `system-fragment.php` reduced 213→137 lines with all duplicated `frag_*` helpers eliminated
|
||||
- [ ] Extract `ThesisEditController` — merges `edit.php` + `actions/edit.php`, deduplicates jury fieldset
|
||||
- [x] Extract `ThesisEditController` — `src/ThesisEditController.php` (285 lines); `load()` fetches thesis row, current language/format/jury selections and all lookup tables for the view; `save()` validates and persists metadata, authors, jury, languages, formats, tags, banner in a transaction; static `autofocusFieldForError()` centralises WCAG 3.3.1 field-name mapping; `admin/edit.php` reduced 191→162 lines; `actions/edit.php` reduced 153→53 lines
|
||||
- [ ] Extract remaining controllers one by one
|
||||
- [ ] Consolidate action handlers into controller methods
|
||||
- [x] Unify flash message keys project-wide to `_flash_error` / `_flash_success` — all callers already use `App::flash()`; removed dead legacy-key fallback chains (`error`, `admin_error`, `edit_error`, `form_error`, `success`, `admin_success`, `edit_success`) from `consumeFlash()`
|
||||
|
||||
Reference in New Issue
Block a user