mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-27 00:59:18 +02:00
Fix form field required states & missing fields per spec
- Admin add: add contact_public checkbox (matching edit form) - All forms: formats checkbox-list now required - All forms: jury promoteur·ice interne required, lecteur·ice interne/externe required - All forms: licence select now required - Admin edit: add E-mail de confirmation fieldset - Partage: contact always visible when provided (no contact_public field) - Partage: filter PACS from AP programs dropdown - Server-side validation: formats, jury, licence required (create + edit controllers) - Autofocus mappings for new validation errors - No duplicate asterisks — verified across all rendered fields - fix: add missing old() function in admin edit controller - refactor: move admin email field to Backoffice as Contact interne, never send email - Untrack admin.log (covered by .gitignore)
This commit is contained in:
@@ -174,19 +174,25 @@ class ThesisCreateController
|
||||
|
||||
try {
|
||||
$thesisId = $this->db->createThesis([
|
||||
'year' => $data['annee'],
|
||||
'orientation_id' => $data['orientationId'],
|
||||
'ap_program_id' => $data['apProgramId'],
|
||||
'finality_id' => $data['finalityId'],
|
||||
'title' => $data['titre'],
|
||||
'subtitle' => $data['subtitle'],
|
||||
'synopsis' => $data['synopsis'],
|
||||
'file_size_info' => $data['durationInfo'],
|
||||
'baiu_link' => $data['lien'],
|
||||
'year' => $data['annee'],
|
||||
'orientation_id' => $data['orientationId'],
|
||||
'ap_program_id' => $data['apProgramId'],
|
||||
'finality_id' => $data['finalityId'],
|
||||
'title' => $data['titre'],
|
||||
'subtitle' => $data['subtitle'],
|
||||
'synopsis' => $data['synopsis'],
|
||||
'context_note' => $data['contextNote'],
|
||||
'file_size_info' => $data['durationInfo'],
|
||||
'baiu_link' => $data['lien'],
|
||||
'license_id' => $data['licenseId'],
|
||||
'license_custom' => $data['licenseCustom'],
|
||||
'access_type_id' => $data['accessTypeId'],
|
||||
'objet' => $data['objet'],
|
||||
'objet' => $data['objet'],
|
||||
'remarks' => $data['remarks'],
|
||||
'jury_points' => $data['juryPoints'],
|
||||
'exemplaire_baiu' => $data['exemplaireBaiu'],
|
||||
'exemplaire_erg' => $data['exemplaireErg'],
|
||||
'cc4r' => $data['cc4r'],
|
||||
]);
|
||||
|
||||
$identifier = $this->db->getThesisIdentifier($thesisId);
|
||||
@@ -247,15 +253,28 @@ class ThesisCreateController
|
||||
if (str_contains($message, 'langue')) {
|
||||
return 'languages';
|
||||
}
|
||||
if (str_contains($message, 'promoteur')) {
|
||||
return 'jury_promoteur';
|
||||
}
|
||||
if (str_contains($message, 'lecteur·ice interne')) {
|
||||
return 'jury_lecteur_interne[]';
|
||||
}
|
||||
if (str_contains($message, 'lecteur·ice externe')) {
|
||||
return 'jury_lecteur_externe[]';
|
||||
}
|
||||
if (str_contains($message, 'format')) {
|
||||
return 'formats';
|
||||
}
|
||||
if (str_contains($message, 'mots-clés')) {
|
||||
return 'tag';
|
||||
}
|
||||
if (str_contains($message, 'licence')) {
|
||||
return 'license_id';
|
||||
}
|
||||
if (str_contains($message, 'Lien URL')) {
|
||||
return 'lien';
|
||||
}
|
||||
if (str_contains($message, 'e-mail de confirmation')) {
|
||||
return 'confirmation_email';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,7 +304,13 @@ class ThesisCreateController
|
||||
}
|
||||
|
||||
$mail = !empty($post['mail']) ? $this->sanitiseString($post['mail']) : '';
|
||||
$showContact = !empty($post['contact_public']) ? true : false;
|
||||
// contact_public: respected if present (admin form); defaults to true for student forms
|
||||
// where the spec says contact is always visible when provided.
|
||||
if (array_key_exists('contact_public', $post)) {
|
||||
$showContact = !empty($post['contact_public']);
|
||||
} else {
|
||||
$showContact = $mail !== '';
|
||||
}
|
||||
|
||||
$annee = filter_var($post['année'] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($annee === false || $annee < 2000 || $annee > ((int) date('Y') + 1)) {
|
||||
@@ -326,6 +351,9 @@ class ThesisCreateController
|
||||
|
||||
// Jury members — new structure: separate interne/externe lecteurs
|
||||
$juryMembers = [];
|
||||
$hasPromoteur = false;
|
||||
$hasLecteurInt = false;
|
||||
$hasLecteurExt = false;
|
||||
if (!empty(trim($post['jury_promoteur'] ?? ''))) {
|
||||
$juryMembers[] = [
|
||||
'name' => trim($post['jury_promoteur']),
|
||||
@@ -333,6 +361,7 @@ class ThesisCreateController
|
||||
'is_external' => 0,
|
||||
'is_ulb' => 0,
|
||||
];
|
||||
$hasPromoteur = true;
|
||||
}
|
||||
if (!empty(trim($post['jury_promoteur_ulb_name'] ?? ''))) {
|
||||
$juryMembers[] = [
|
||||
@@ -346,12 +375,14 @@ class ThesisCreateController
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$juryMembers[] = ['name' => $name, 'role' => 'lecteur', 'is_external' => 0];
|
||||
$hasLecteurInt = true;
|
||||
}
|
||||
}
|
||||
foreach ($post['jury_lecteur_externe'] ?? [] as $name) {
|
||||
$name = trim($name);
|
||||
if ($name !== '') {
|
||||
$juryMembers[] = ['name' => $name, 'role' => 'lecteur', 'is_external' => 1];
|
||||
$hasLecteurExt = true;
|
||||
}
|
||||
}
|
||||
// Keep backwards compat with old jury_lecteurs (from old-style forms)
|
||||
@@ -364,6 +395,11 @@ class ThesisCreateController
|
||||
'role' => 'lecteur',
|
||||
'is_external' => isset($post['jury_lecteurs_ext'][$i]) ? 1 : 0,
|
||||
];
|
||||
if (isset($post['jury_lecteurs_ext'][$i]) && $post['jury_lecteurs_ext'][$i]) {
|
||||
$hasLecteurExt = true;
|
||||
} else {
|
||||
$hasLecteurInt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +407,16 @@ class ThesisCreateController
|
||||
$juryMembers[] = ['name' => trim($post['jury_president']), 'role' => 'president', 'is_external' => 0];
|
||||
}
|
||||
|
||||
if (!$hasPromoteur) {
|
||||
throw new Exception('Veuillez indiquer au moins un·e promoteur·ice interne.');
|
||||
}
|
||||
if (!$hasLecteurInt) {
|
||||
throw new Exception('Veuillez indiquer au moins un·e lecteur·ice interne.');
|
||||
}
|
||||
if (!$hasLecteurExt) {
|
||||
throw new Exception('Veuillez indiquer au moins un·e lecteur·ice externe.');
|
||||
}
|
||||
|
||||
// Keywords (max 10)
|
||||
$tagRaw = $this->sanitiseString($post['tag'] ?? '');
|
||||
$keywords = $tagRaw !== '' ? array_map('trim', explode(',', $tagRaw)) : [];
|
||||
@@ -386,13 +432,19 @@ class ThesisCreateController
|
||||
throw new Exception('Veuillez sélectionner au moins une langue.');
|
||||
}
|
||||
|
||||
// Formats (optional)
|
||||
// Formats (at least one required)
|
||||
$formatIds = isset($post['formats']) && is_array($post['formats'])
|
||||
? array_map('intval', $post['formats'])
|
||||
: [];
|
||||
if (empty($formatIds)) {
|
||||
throw new Exception('Veuillez sélectionner au moins un format.');
|
||||
}
|
||||
|
||||
$licenseId = filter_var($post['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
|
||||
$licenseCustom = trim($post['license_custom'] ?? '');
|
||||
if (!$licenseId && $licenseCustom === '') {
|
||||
throw new Exception('Veuillez sélectionner une licence ou en préciser une.');
|
||||
}
|
||||
|
||||
// Access type — must be one of the enabled types; default 2 (Interne)
|
||||
$accessTypeId = filter_var($post['access_type_id'] ?? '', FILTER_VALIDATE_INT);
|
||||
@@ -413,20 +465,39 @@ class ThesisCreateController
|
||||
}
|
||||
}
|
||||
|
||||
// Confirmation e-mail (optional)
|
||||
$confirmationEmail = trim($post['confirmation_email'] ?? '');
|
||||
if ($confirmationEmail !== '') {
|
||||
$confirmationEmail = filter_var($confirmationEmail, FILTER_VALIDATE_EMAIL);
|
||||
if ($confirmationEmail === false) {
|
||||
throw new Exception("L'adresse e-mail de confirmation n'est pas valide.");
|
||||
// Contact interne (optional, admin-only)
|
||||
$contactInterne = trim($post['contact_interne'] ?? '');
|
||||
if ($contactInterne !== '') {
|
||||
$contactInterne = filter_var($contactInterne, FILTER_VALIDATE_EMAIL);
|
||||
if ($contactInterne === false) {
|
||||
throw new Exception("L'adresse de contact interne n'est pas valide.");
|
||||
}
|
||||
}
|
||||
|
||||
// Note contextuelle (optional, max 1500 chars)
|
||||
$contextNote = $this->sanitiseString($post['context_note'] ?? '');
|
||||
if (mb_strlen($contextNote) > 1500) {
|
||||
$contextNote = mb_substr($contextNote, 0, 1500);
|
||||
}
|
||||
|
||||
// Backoffice fields (admin only)
|
||||
$remarks = trim($post['remarks'] ?? '');
|
||||
$juryPoints = $post['jury_points'] ?? null;
|
||||
if ($juryPoints !== null && $juryPoints !== '') {
|
||||
$juryPoints = filter_var($juryPoints, FILTER_VALIDATE_FLOAT);
|
||||
if ($juryPoints === false || $juryPoints < 0 || $juryPoints > 20) {
|
||||
throw new Exception('La note du jury doit être comprise entre 0 et 20.');
|
||||
}
|
||||
}
|
||||
$exemplaireBaiu = !empty($post['exemplaire_baiu']);
|
||||
$exemplaireErg = !empty($post['exemplaire_erg']);
|
||||
$cc4r = !empty($post['cc2r']);
|
||||
|
||||
return compact(
|
||||
'authorNames',
|
||||
'mail',
|
||||
'showContact',
|
||||
'confirmationEmail',
|
||||
'contactInterne',
|
||||
'annee',
|
||||
'orientationId',
|
||||
'apProgramId',
|
||||
@@ -443,7 +514,13 @@ class ThesisCreateController
|
||||
'licenseCustom',
|
||||
'lien',
|
||||
'accessTypeId',
|
||||
'objet'
|
||||
'objet',
|
||||
'contextNote',
|
||||
'remarks',
|
||||
'juryPoints',
|
||||
'exemplaireBaiu',
|
||||
'exemplaireErg',
|
||||
'cc4r'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,54 @@ class ThesisEditController
|
||||
throw new InvalidArgumentException('ID de TFE invalide.');
|
||||
}
|
||||
|
||||
// ── Basic validation (same required fields as create) ──────────────────
|
||||
$errors = [];
|
||||
$titre = trim($post['titre'] ?? '');
|
||||
if ($titre === '') $errors[] = 'Le titre est requis.';
|
||||
$auteurice = trim($post['auteurice'] ?? '');
|
||||
if ($auteurice === '') $errors[] = "L'auteur·ice est requis.";
|
||||
$synopsis = trim($post['synopsis'] ?? '');
|
||||
if ($synopsis === '') $errors[] = 'Le synopsis est requis.';
|
||||
$annee = intval($post['année'] ?? 0);
|
||||
if ($annee < 2000 || $annee > ((int)date('Y') + 1)) $errors[] = "L'année est invalide.";
|
||||
$orientationId = intval($post['orientation'] ?? 0);
|
||||
if ($orientationId <= 0) $errors[] = "L'orientation est requise.";
|
||||
$apProgramId = intval($post['ap'] ?? 0);
|
||||
if ($apProgramId <= 0) $errors[] = "L'atelier pluridisciplinaire est requis.";
|
||||
$finalityId = intval($post['finality'] ?? 0);
|
||||
if ($finalityId <= 0) $errors[] = 'La finalité est requise.';
|
||||
|
||||
// Languages
|
||||
$langIds = isset($post['languages']) && is_array($post['languages']) ? $post['languages'] : [];
|
||||
if (empty($langIds)) $errors[] = 'Au moins une langue est requise.';
|
||||
|
||||
// Formats
|
||||
$fmtIds = isset($post['formats']) && is_array($post['formats']) ? $post['formats'] : [];
|
||||
if (empty($fmtIds)) $errors[] = 'Au moins un format est requis.';
|
||||
|
||||
// Licence
|
||||
$licenseId = filter_var($post['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
|
||||
$licenseCustom = trim($post['license_custom'] ?? '');
|
||||
if (!$licenseId && $licenseCustom === '') $errors[] = 'Une licence est requise.';
|
||||
|
||||
// Jury
|
||||
$hasPromoteur = !empty(trim($post['jury_promoteur'] ?? ''));
|
||||
$hasLecteurInt = false;
|
||||
$hasLecteurExt = false;
|
||||
foreach ($post['jury_lecteur_interne'] ?? [] as $n) {
|
||||
if (trim((string)$n) !== '') { $hasLecteurInt = true; break; }
|
||||
}
|
||||
foreach ($post['jury_lecteur_externe'] ?? [] as $n) {
|
||||
if (trim((string)$n) !== '') { $hasLecteurExt = true; break; }
|
||||
}
|
||||
if (!$hasPromoteur) $errors[] = 'Un·e promoteur·ice interne est requis.';
|
||||
if (!$hasLecteurInt) $errors[] = 'Au moins un·e lecteur·ice interne est requis.';
|
||||
if (!$hasLecteurExt) $errors[] = 'Au moins un·e lecteur·ice externe est requis.';
|
||||
|
||||
if (!empty($errors)) {
|
||||
throw new RuntimeException(implode(' ', $errors));
|
||||
}
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
try {
|
||||
@@ -531,6 +579,33 @@ class ThesisEditController
|
||||
if (str_contains($message, 'auteur') || str_contains($message, 'Auteur')) {
|
||||
return 'auteurice';
|
||||
}
|
||||
if (str_contains($message, 'orientation')) {
|
||||
return 'orientation';
|
||||
}
|
||||
if (str_contains($message, 'atelier')) {
|
||||
return 'ap';
|
||||
}
|
||||
if (str_contains($message, 'finalité')) {
|
||||
return 'finality';
|
||||
}
|
||||
if (str_contains($message, 'langue')) {
|
||||
return 'languages';
|
||||
}
|
||||
if (str_contains($message, 'format')) {
|
||||
return 'formats';
|
||||
}
|
||||
if (str_contains($message, 'licence')) {
|
||||
return 'license_id';
|
||||
}
|
||||
if (str_contains($message, 'promoteur')) {
|
||||
return 'jury_promoteur';
|
||||
}
|
||||
if (str_contains($message, 'lecteur·ice interne')) {
|
||||
return 'jury_lecteur_interne[]';
|
||||
}
|
||||
if (str_contains($message, 'lecteur·ice externe')) {
|
||||
return 'jury_lecteur_externe[]';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1848,13 +1848,16 @@ class Database
|
||||
INSERT INTO theses (
|
||||
identifier, title, subtitle, year,
|
||||
orientation_id, ap_program_id, finality_id,
|
||||
synopsis, file_size_info,
|
||||
synopsis, context_note, file_size_info,
|
||||
baiu_link, license_id, license_custom,
|
||||
access_type_id,
|
||||
objet,
|
||||
is_published,
|
||||
remarks, jury_points,
|
||||
exemplaire_baiu, exemplaire_erg,
|
||||
cc4r,
|
||||
submitted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
');
|
||||
|
||||
$validObjet = ['tfe', 'thèse', 'frart'];
|
||||
@@ -1869,12 +1872,18 @@ class Database
|
||||
(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,
|
||||
$data['license_id'] ?? null,
|
||||
!empty($data['license_custom']) ? $data['license_custom'] : null,
|
||||
isset($data['access_type_id']) ? (int)$data['access_type_id'] : 2, // default: Interne
|
||||
$objet,
|
||||
!empty($data['remarks']) ? $data['remarks'] : null,
|
||||
isset($data['jury_points']) && $data['jury_points'] !== '' ? (float)$data['jury_points'] : null,
|
||||
!empty($data['exemplaire_baiu']) ? 1 : 0,
|
||||
!empty($data['exemplaire_erg']) ? 1 : 0,
|
||||
!empty($data['cc4r']) ? 1 : 0,
|
||||
]);
|
||||
|
||||
return (int)$this->pdo->lastInsertId();
|
||||
|
||||
Reference in New Issue
Block a user