Fix migrations and deploy issues + errors + linting

- scan both pending/ and applied/ dirs so remote catch-up works
- fix remote 500s: run.php handles per-statement errors so VIEW rebuilds run after duplicate columns; replace mb_strimwidth with substr (no mbstring extension on server)
- add missing migration: 015_license_custom.sql (column existed in schema.sql but was never migrated)
- remote: fgetcsv enclosure single-char + AdminLogger permission-denied
guard + deploy always migrates
- fix admin-filters wrapping: restore flex-wrap, flex-basis on
inputs/selects, shrink-protect buttons
- fix phpstan: remove redundant ?? [] after isset guard in
ThesisEditController
- biome: exclude vendored min.js via includes patterns;
lint whole js dir; modernise beforeunload-guard.js
This commit is contained in:
Pontoporeia
2026-05-07 23:45:09 +02:00
parent bdd95341b0
commit e3896811c4
15 changed files with 153 additions and 71 deletions

View File

@@ -165,46 +165,78 @@ class ThesisEditController
// ── Basic validation (same required fields as create) ──────────────────
$errors = [];
$titre = trim($post['titre'] ?? '');
if ($titre === '') $errors[] = 'Le titre est requis.';
if ($titre === '') {
$errors[] = 'Le titre est requis.';
}
$auteurice = trim($post['auteurice'] ?? '');
if ($auteurice === '') $errors[] = "L'auteur·ice est requis.";
if ($auteurice === '') {
$errors[] = "L'auteur·ice est requis.";
}
$synopsis = trim($post['synopsis'] ?? '');
if ($synopsis === '') $errors[] = 'Le synopsis est requis.';
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.";
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.";
if ($orientationId <= 0) {
$errors[] = "L'orientation est requise.";
}
$apProgramId = intval($post['ap'] ?? 0);
if ($apProgramId <= 0) $errors[] = "L'atelier pluridisciplinaire est requis.";
if ($apProgramId <= 0) {
$errors[] = "L'atelier pluridisciplinaire est requis.";
}
$finalityId = intval($post['finality'] ?? 0);
if ($finalityId <= 0) $errors[] = 'La finalité est requise.';
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.';
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.';
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.';
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; }
if (trim((string)$n) !== '') {
$hasLecteurInt = true;
break;
}
}
foreach ($post['jury_lecteur_externe'] ?? [] as $n) {
if (trim((string)$n) !== '') { $hasLecteurExt = true; break; }
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 (!$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));
@@ -241,7 +273,7 @@ class ThesisEditController
$showContact = !empty($post['contact_public']);
$authorNames = [];
if ($authorsRaw !== '') {
$authorNames = array_values(array_filter(array_map('trim', explode(',', $authorsRaw)), fn($n) => $n !== ''));
$authorNames = array_values(array_filter(array_map('trim', explode(',', $authorsRaw)), fn ($n) => $n !== ''));
sort($authorNames, SORT_NATURAL);
}
$authorEntries = [];
@@ -402,7 +434,7 @@ class ThesisEditController
$authorName = trim($post['auteurice'] ?? 'unknown');
// Sort the raw comma-separated string alphabetically, then slugify.
$names = array_values(array_filter(array_map('trim', explode(',', $authorName)), fn($n) => $n !== ''));
$names = array_values(array_filter(array_map('trim', explode(',', $authorName)), fn ($n) => $n !== ''));
sort($names, SORT_NATURAL);
$authorSlug = $this->generateAuthorSlug(implode(', ', $names));
@@ -674,7 +706,7 @@ class ThesisEditController
// Backwards compat: old jury_lecteurs[]
if (isset($post['jury_lecteurs'])) {
foreach ($post['jury_lecteurs'] ?? [] as $i => $name) {
foreach ($post['jury_lecteurs'] as $i => $name) {
$name = trim($name);
if ($name !== '') {
$members[] = [