mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
fix: resolve partage form submission issues
- Replace mb_strlen/mb_substr/mb_strtolower with strlen/substr/strtolower (mbstring extension missing on server, causing fatal error) - Scope annexes checkbox HTMX swap to #annexes-input-block with hx-select (prevents duplicating entire page inside Fichiers fieldset) - Split format+fichiers response: #format-fichiers-block (stable) and #format-extras-block (swappable, inside Fichiers fieldset). Format checkboxes use hx-select to extract only the extras, preserving file queue. - Keep format extras inline in Fichiers fieldset (no sub-fieldsets). Remove website legend input (URL only). - When PeerTube upload disabled, show direct file upload inputs for video/audio (name=files[]). - Add "Glissez-déposez" sort hint below TFE file queue. - Fix .fq-name overflow with width:0;min-width:100% chain. - Remove legend placeholder from .fq-item. - Merge "Récits et expérimentation" AP into "Narration Spéculative". Rename PACS to "Pratique de lart - outils critiques, arts et contexte simultanés". - Remove président·e field from jury fieldset, form templates, and controller validation. Keep DB column and display logic for existing data.
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -4,3 +4,11 @@
|
||||
- [x] Keep specific layouts/classes in form.css (admin-form grid, checkbox-group layout, etc.)
|
||||
- [x] Ensure selects, checkboxes, and radios are properly styled globally
|
||||
- [x] Converge towards the styled form appearance rather than unstyled
|
||||
- [x] Fix: replace mb_strlen/mb_substr/mb_strtolower with strlen/substr/strtolower (mbstring extension missing on server, caused fatal error on partage submit at ThesisCreateController line 511)
|
||||
- [x] Fix: annexes checkbox in partage form clears other file inputs — scoped HTMX swap to #annexes-input-block instead of replacing entire #format-fichiers-block
|
||||
- [x] Fix: website/video/audio inputs should be inline in Fichiers fieldset (not sub-fieldsets) — removed <fieldset class="fichiers-format-extra"> wrappers
|
||||
- [x] Fix: video/audio show direct upload input when PeerTube disabled — parallel inputs: PeerTube upload when enabled, direct `files[]` upload when disabled
|
||||
- [x] Fix: format checkboxes HTMX include missing has_annexes — added it so annexes state preserved across format changes
|
||||
- [x] Fix: format checkbox toggle clears file inputs — split into two blocks: #format-fichiers-block (stable: TFE/annexes/couverture/note) and #format-extras-block (swappable: website/video/audio extras)
|
||||
- [x] Fix: remove website label/legend input — website section now shows only URL field
|
||||
- [x] Fix: format-extras not appearing — moved #format-extras-block inside Fichiers fieldset (after annexes), uses hx-select to extract from response
|
||||
|
||||
@@ -40,7 +40,7 @@ try {
|
||||
$keywords = array_map('trim', explode(',', $raw));
|
||||
foreach ($keywords as $kw) {
|
||||
$kw = trim($kw);
|
||||
if ($kw === '' || mb_strlen($kw) > 100) continue;
|
||||
if ($kw === '' || strlen($kw) > 100) continue;
|
||||
|
||||
// Create tag if needed
|
||||
$insertTag->execute([$kw]);
|
||||
|
||||
@@ -165,18 +165,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
return $r ? (int)$r['id'] : null;
|
||||
};
|
||||
|
||||
// AP alias map: variant spellings → canonical DB name.
|
||||
// AP alias map: variant spellings → canonical code.
|
||||
$apAliases = [
|
||||
'l.i.e.n.s.' => 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes',
|
||||
'liens' => 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes',
|
||||
'l.i.e.n.s.' => 'LIENS',
|
||||
'liens' => 'LIENS',
|
||||
'lieux, interdisciplinarités, écologie, nécessité, systèmes'
|
||||
=> 'Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes',
|
||||
'récits et expérimentation' => 'Récits et expérimentation',
|
||||
'recits et experimentation' => 'Récits et expérimentation',
|
||||
'atelier pratiques situées' => 'Atelier Pratiques Situées',
|
||||
'design et politique du multiple' => 'Design et Politique du Multiple',
|
||||
'narration spéculative' => 'Narration Spéculative',
|
||||
=> 'LIENS',
|
||||
'récits et expérimentation' => 'NS',
|
||||
'recits et experimentation' => 'NS',
|
||||
'atelier pratiques situées' => 'APS',
|
||||
'design et politique du multiple' => 'DPM',
|
||||
'narration spéculative' => 'NS',
|
||||
'pacs' => 'PACS',
|
||||
'pratique de l''art' => 'PACS',
|
||||
];
|
||||
|
||||
// Resolve an AP string (code or full name) → ap_program id.
|
||||
@@ -184,14 +185,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') return null;
|
||||
|
||||
// 1. Try alias map (lowercase key)
|
||||
// 1. Try alias map (lowercase key) → canonical code
|
||||
$key = strtolower($raw);
|
||||
if (isset($apAliases[$key])) {
|
||||
$raw = $apAliases[$key];
|
||||
}
|
||||
|
||||
// 2. Exact name match
|
||||
$s = $importPdo->prepare("SELECT id FROM ap_programs WHERE name = ?");
|
||||
// 2. Match by code
|
||||
$s = $importPdo->prepare("SELECT id FROM ap_programs WHERE code = ?");
|
||||
$s->execute([$raw]);
|
||||
$r = $s->fetch();
|
||||
if ($r) return (int)$r['id'];
|
||||
@@ -257,7 +258,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
if ($title === '') $missing[] = 'titre';
|
||||
if ($year === 0) $missing[] = 'année';
|
||||
throw new Exception("Champ(s) requis manquant(s) : " . implode(', ', $missing)
|
||||
. " (id=\"" . ($identifier ?: '?') . "\", titre=\"" . mb_substr($title, 0, 80) . "\")");
|
||||
. " (id=\"" . ($identifier ?: '?') . "\", titre=\"" . substr($title, 0, 80) . "\")");
|
||||
}
|
||||
|
||||
$orientationId = $resolveOrientation($orientationCode);
|
||||
|
||||
@@ -546,6 +546,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xs);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* New-file queue items */
|
||||
@@ -618,6 +619,8 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.fq-size {
|
||||
|
||||
@@ -38,9 +38,10 @@ window.XamxamInitFileUploads = function () {
|
||||
function esc(s) { return s.replace(/[&<>"]/g, function(c){ return {'&':'&','<':'<','>':'>','"':'"'}[c]; }); }
|
||||
|
||||
// ── 1. TFE multi-file queue ────────────────────────────────────────────
|
||||
var picker = document.getElementById('tfe-files-input');
|
||||
var queue = document.getElementById('tfe-file-queue');
|
||||
var empty = document.getElementById('tfe-file-queue-empty');
|
||||
var picker = document.getElementById('tfe-files-input');
|
||||
var queue = document.getElementById('tfe-file-queue');
|
||||
var empty = document.getElementById('tfe-file-queue-empty');
|
||||
var sortHint = document.getElementById('tfe-file-queue-sort-hint');
|
||||
if (picker && queue) {
|
||||
console.log('[file-upload-queue] init TFE queue picker=', picker, 'multiple=', picker.multiple);
|
||||
var fileArray = [];
|
||||
@@ -66,8 +67,9 @@ window.XamxamInitFileUploads = function () {
|
||||
|
||||
function renderQueue() {
|
||||
queue.innerHTML = '';
|
||||
if (!fileArray.length) { empty.style.display = ''; injectHiddenFields([]); return; }
|
||||
if (!fileArray.length) { empty.style.display = ''; if (sortHint) sortHint.style.display = 'none'; injectHiddenFields([]); return; }
|
||||
empty.style.display = 'none';
|
||||
if (sortHint) sortHint.style.display = '';
|
||||
fileArray.forEach(function (file, idx) {
|
||||
var li = document.createElement('li');
|
||||
li.className = 'fq-item';
|
||||
@@ -76,8 +78,7 @@ window.XamxamInitFileUploads = function () {
|
||||
'<span class="fq-drag-handle" title="R\u00e9ordonner">\u2820</span>' +
|
||||
'<span class="fq-icon">' + iconFor(file) + '</span>' +
|
||||
'<span class="fq-info"><span class="fq-name">' + esc(file.name) + '</span>' +
|
||||
'<span class="fq-size">' + humanSize(file.size) + '</span>' +
|
||||
'<input type="text" class="fq-label admin-file-label-input" placeholder="L\u00e9gende / description (optionnel)"></span>' +
|
||||
'<span class="fq-size">' + humanSize(file.size) + '</span></span>' +
|
||||
'<button type="button" class="admin-btn-remove fq-remove" aria-label="Retirer ' + esc(file.name) + '">✕</button>';
|
||||
li.querySelector('.fq-remove').onclick = (function (i) { return function () { fileArray.splice(i, 1); renderQueue(); }; })(idx);
|
||||
queue.appendChild(li);
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
* HTMX fragment: returns the combined Format(s) + Fichiers block.
|
||||
* Called on every format checkbox change so the Fichiers fieldset adapts.
|
||||
*
|
||||
* Fixed inputs (always present):
|
||||
* Fixed inputs (always present in #format-fichiers-block):
|
||||
* 1. Image de couverture (optional)
|
||||
* 2. Note d'intention (PDF, required unless adminMode)
|
||||
* 3. TFE — multi-file upload (required unless adminMode)
|
||||
* 4. Annexes checkbox + file input
|
||||
*
|
||||
* Format-specific extra inputs (appended after the fixed three):
|
||||
* - Site web → URL + label fields
|
||||
* - Vidéo → TODO: PeerTube upload (notice shown)
|
||||
* - Audio → TODO: PeerTube upload (notice shown)
|
||||
* Format-specific extra inputs (separate #format-extras-block so toggling
|
||||
* formats does not destroy file queue state):
|
||||
* - Site web → URL field only
|
||||
* - Vidéo → PeerTube upload or direct file input
|
||||
* - Audio → PeerTube upload or direct file input
|
||||
* - (all others: Écriture, Performance, Objet éditorial, Installation, Autre)
|
||||
* → no extra input needed beyond the standard TFE file upload
|
||||
*
|
||||
@@ -68,7 +70,10 @@ $websiteUrl = htmlspecialchars($_POST['website_url'] ?? '');
|
||||
$websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
|
||||
$hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragment';
|
||||
|
||||
$hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
?>
|
||||
<!-- ═══════════════════ Format(s) + Fichiers (stable) ═══════════════════ -->
|
||||
<div id="format-fichiers-block">
|
||||
<input type="hidden" name="admin_mode" value="<?= $adminMode ? '1' : '0' ?>">
|
||||
<input type="hidden" name="edit_mode" value="<?= $editMode ? '1' : '0' ?>">
|
||||
@@ -84,9 +89,10 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
<fieldset class="admin-checkbox-group"
|
||||
<?= !$adminMode ? 'required aria-required="true"' : '' ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#format-fichiers-block"
|
||||
hx-target="#format-extras-block"
|
||||
hx-select="#format-extras-block"
|
||||
hx-trigger="change"
|
||||
hx-include="this, [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
|
||||
hx-include="this, [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
|
||||
hx-swap="outerHTML">
|
||||
<legend class="sr-only">Format(s) du TFE</legend>
|
||||
<ul>
|
||||
@@ -165,46 +171,43 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
<ul id="tfe-file-queue" class="tfe-file-queue sortable-list"
|
||||
aria-label="Fichiers sélectionnés (réordonnable)"></ul>
|
||||
<p id="tfe-file-queue-empty" class="tfe-queue-empty">Aucun fichier sélectionné.</p>
|
||||
<small class="admin-file-hint" id="tfe-file-queue-sort-hint" style="display:none;">Glissez-déposez les fichiers pour déterminer l'ordre d'affichage sur la page du TFE.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ── Annexes ── -->
|
||||
<?php
|
||||
$hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
?>
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="has_annexes" value="1"
|
||||
<?= $hasAnnexesChecked ? 'checked' : '' ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#format-fichiers-block"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
|
||||
hx-swap="outerHTML">
|
||||
Ce TFE comporte des annexes
|
||||
</label>
|
||||
<div id="annexes-input-block">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="has_annexes" value="1"
|
||||
<?= $hasAnnexesChecked ? 'checked' : '' ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#annexes-input-block"
|
||||
hx-select="#annexes-input-block"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
|
||||
hx-swap="outerHTML">
|
||||
Ce TFE comporte des annexes
|
||||
</label>
|
||||
</div>
|
||||
<?php if ($hasAnnexesChecked): ?>
|
||||
<div>
|
||||
<?php
|
||||
$name = 'annexes';
|
||||
$label = 'Annexes :';
|
||||
$accept = '.pdf,.zip,.tar,.gz';
|
||||
$hint = 'PDF ou archives ZIP/TAR. Max 500 MB.';
|
||||
$required = false;
|
||||
$multiple = true;
|
||||
include APP_ROOT . '/templates/partials/form/file-field.php';
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($hasAnnexesChecked): ?>
|
||||
<div>
|
||||
<?php
|
||||
$name = 'annexes';
|
||||
$label = 'Annexes :';
|
||||
$accept = '.pdf,.zip,.tar,.gz';
|
||||
$hint = 'PDF ou archives ZIP/TAR. Max 500 MB.';
|
||||
$required = false;
|
||||
$multiple = true;
|
||||
include APP_ROOT . '/templates/partials/form/file-field.php';
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Format-specific extras ── -->
|
||||
|
||||
<?php if ($hasSiteWeb): ?>
|
||||
<!-- Site web -->
|
||||
<fieldset class="fichiers-format-extra" id="fichiers-website">
|
||||
<legend>Site web</legend>
|
||||
<!-- ── Format-specific extras (swappable, inside Fichiers fieldset) ── -->
|
||||
<div id="format-extras-block" style="display:flex;flex-direction:column;gap:var(--space-s);">
|
||||
<?php if ($hasSiteWeb): ?>
|
||||
<div class="admin-form-group">
|
||||
<label for="website_url">URL du site<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<div class="admin-file-input">
|
||||
@@ -215,23 +218,12 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
<small>Le TFE sera affiché comme un site embarqué sur sa page publique.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-form-group">
|
||||
<label for="website_label">Légende :</label>
|
||||
<input type="text" id="website_label" name="website_label"
|
||||
value="<?= $websiteLabel ?>"
|
||||
placeholder="Description du site (optionnel)"
|
||||
class="admin-file-label-input"
|
||||
style="max-width:400px;">
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($hasVideo): ?>
|
||||
<fieldset class="fichiers-format-extra" id="fichiers-video">
|
||||
<legend>Vidéo</legend>
|
||||
<?php if ($hasVideo): ?>
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<div class="admin-form-group">
|
||||
<label for="peertube_video">Fichier vidéo<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<label for="peertube_video">Fichier vidéo (PeerTube)<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="peertube_video" name="peertube_video"
|
||||
accept="video/mp4,video/webm,video/ogg,video/quicktime,.mp4,.webm,.ogv,.mov"
|
||||
@@ -241,26 +233,22 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="admin-form-group fichiers-todo-notice">
|
||||
<p>
|
||||
🚧 <strong>À venir :</strong> l'upload vidéo sera géré directement via l'API PeerTube.
|
||||
La vidéo sera hébergée sur l'instance PeerTube de l'école et intégrée
|
||||
comme lecteur embarqué sur la page du TFE.
|
||||
</p>
|
||||
<p class="fichiers-todo-workaround">
|
||||
En attendant, déposez votre vidéo dans le champ TFE ci-dessus (ZIP si besoin).
|
||||
</p>
|
||||
<div class="admin-form-group">
|
||||
<label for="tfe-video-upload">Fichier vidéo<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="tfe-video-upload" name="files[]"
|
||||
accept="video/mp4,video/webm,video/ogg,video/quicktime,.mp4,.webm,.ogv,.mov"
|
||||
<?= !$adminMode ? 'required' : '' ?>>
|
||||
<small>MP4, WebM ou MOV. Max 500 MB.</small>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($hasAudio): ?>
|
||||
<fieldset class="fichiers-format-extra" id="fichiers-audio">
|
||||
<legend>Audio</legend>
|
||||
<?php if ($hasAudio): ?>
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<div class="admin-form-group">
|
||||
<label for="peertube_audio">Fichier audio<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<label for="peertube_audio">Fichier audio (PeerTube)<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="peertube_audio" name="peertube_audio"
|
||||
accept="audio/mpeg,audio/ogg,audio/wav,audio/flac,audio/aac,audio/mp4,.mp3,.ogg,.oga,.wav,.flac,.aac,.m4a"
|
||||
@@ -270,19 +258,18 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="admin-form-group fichiers-todo-notice">
|
||||
<p>
|
||||
🚧 <strong>À venir :</strong> l'upload audio sera géré via l'API PeerTube.
|
||||
Le fichier audio sera hébergé sur l'instance PeerTube de l'école et
|
||||
intégré comme lecteur embarqué sur la page du TFE.
|
||||
</p>
|
||||
<p class="fichiers-todo-workaround">
|
||||
En attendant, déposez votre fichier audio dans le champ TFE ci-dessus (ZIP si besoin).
|
||||
</p>
|
||||
<div class="admin-form-group">
|
||||
<label for="tfe-audio-upload">Fichier audio<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="tfe-audio-upload" name="files[]"
|
||||
accept="audio/mpeg,audio/ogg,audio/wav,audio/flac,audio/aac,audio/mp4,.mp3,.ogg,.oga,.wav,.flac,.aac,.m4a"
|
||||
<?= !$adminMode ? 'required' : '' ?>>
|
||||
<small>MP3, OGG, WAV, FLAC ou AAC. Max 500 MB.</small>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</fieldset><!-- /Fichiers -->
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
}
|
||||
|
||||
// Filter out PACS from AP programs for student forms (spec: admin-only AP)
|
||||
$apPrograms = array_values(array_filter($apPrograms, fn($ap) => ($ap['name'] ?? '') !== 'PACS'));
|
||||
$apPrograms = array_values(array_filter($apPrograms, fn($ap) => ($ap['code'] ?? '') !== 'PACS'));
|
||||
|
||||
$formData = $_SESSION['form_data_share_' . $slug] ?? [];
|
||||
unset($_SESSION['form_data_share_' . $slug]);
|
||||
@@ -305,8 +305,6 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
$n = old($formData, "jury_lecteur_externe:$i");
|
||||
if ($n !== '') $lecteursExternes[] = ['name' => $n];
|
||||
}
|
||||
$juryPresident = null;
|
||||
$showPresident = false;
|
||||
$showPromoteurUlb = true;
|
||||
$promoteurUlbConditional = true;
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ if ($thesisId <= 0 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
}
|
||||
|
||||
// Truncate justification to a safe length
|
||||
if (mb_strlen($justification) > 2000) {
|
||||
$justification = mb_substr($justification, 0, 2000);
|
||||
if (strlen($justification) > 2000) {
|
||||
$justification = substr($justification, 0, 2000);
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
@@ -427,10 +427,6 @@ class ThesisCreateController
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty(trim($post['jury_president'] ?? ''))) {
|
||||
$juryMembers[] = ['name' => trim($post['jury_president']), 'role' => 'president', 'is_external' => 0];
|
||||
}
|
||||
|
||||
if (!$adminMode && !$hasPromoteur) {
|
||||
throw new Exception('Veuillez indiquer au moins un·e promoteur·ice interne.');
|
||||
}
|
||||
@@ -508,8 +504,8 @@ class ThesisCreateController
|
||||
|
||||
// Note contextuelle (optional, max 1500 chars)
|
||||
$contextNote = $this->sanitiseString($post['context_note'] ?? '');
|
||||
if (mb_strlen($contextNote) > 1500) {
|
||||
$contextNote = mb_substr($contextNote, 0, 1500);
|
||||
if (strlen($contextNote) > 1500) {
|
||||
$contextNote = substr($contextNote, 0, 1500);
|
||||
}
|
||||
|
||||
// Backoffice fields (admin only)
|
||||
|
||||
@@ -650,15 +650,6 @@ class ThesisEditController
|
||||
}
|
||||
}
|
||||
|
||||
// President (optional, admin-only)
|
||||
if (!empty(trim($post['jury_president'] ?? ''))) {
|
||||
$members[] = [
|
||||
'name' => trim($post['jury_president']),
|
||||
'role' => 'president',
|
||||
'is_external' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// Backwards compat: old jury_lecteurs[]
|
||||
if (isset($post['jury_lecteurs'])) {
|
||||
foreach ($post['jury_lecteurs'] as $i => $name) {
|
||||
|
||||
@@ -1016,7 +1016,7 @@ class Database
|
||||
}
|
||||
|
||||
$normalise = static function (string $s): string {
|
||||
return preg_replace('/[^a-z0-9]/u', '', mb_strtolower($s));
|
||||
return preg_replace('/[^a-z0-9]/u', '', strtolower($s));
|
||||
};
|
||||
|
||||
$normNew = $normalise($title);
|
||||
@@ -1036,11 +1036,11 @@ class Database
|
||||
}
|
||||
|
||||
// Prefix match: one starts with the other (handles subtitle variations).
|
||||
$maxLen = max(mb_strlen($normNew), mb_strlen($normExisting));
|
||||
$maxLen = max(strlen($normNew), strlen($normExisting));
|
||||
if ($maxLen === 0) {
|
||||
continue;
|
||||
}
|
||||
$minLen = min(mb_strlen($normNew), mb_strlen($normExisting));
|
||||
$minLen = min(strlen($normNew), strlen($normExisting));
|
||||
if ($minLen >= 5) { // avoid matching very short fragments
|
||||
if (str_starts_with($normExisting, $normNew) || str_starts_with($normNew, $normExisting)) {
|
||||
return [
|
||||
@@ -1055,8 +1055,8 @@ class Database
|
||||
|
||||
// Levenshtein distance ≤ 10 % of the longer string.
|
||||
// levenshtein() is limited to 255 chars; use substrings for safety.
|
||||
$a = mb_substr($normNew, 0, 255);
|
||||
$b = mb_substr($normExisting, 0, 255);
|
||||
$a = substr($normNew, 0, 255);
|
||||
$b = substr($normExisting, 0, 255);
|
||||
$dist = levenshtein($a, $b);
|
||||
$threshold = (int)ceil($maxLen * 0.10);
|
||||
if ($dist <= $threshold) {
|
||||
|
||||
@@ -66,8 +66,7 @@ INSERT OR IGNORE INTO ap_programs (name, code) VALUES
|
||||
('Design et Politique du Multiple', 'DPM'),
|
||||
('Atelier Pratiques Situées', 'APS'),
|
||||
('Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'LIENS'),
|
||||
('Récits et expérimentation', 'RE'),
|
||||
('PACS', 'PACS');
|
||||
('Pratique de l''art - outils critiques, arts et contexte simultanés', 'PACS');
|
||||
|
||||
-- Master finality types
|
||||
CREATE TABLE IF NOT EXISTS finality_types (
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
$juryPromoteursUlb = [];
|
||||
$lecteursInternes = [];
|
||||
$lecteursExternes = [];
|
||||
$juryPresident = null;
|
||||
$showPresident = false;
|
||||
$showPromoteurUlb = true;
|
||||
$promoteurUlbConditional = false;
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@
|
||||
$juryPromoteursUlb = [];
|
||||
$lecteursInternes = [];
|
||||
$lecteursExternes = [];
|
||||
$juryPresident = null;
|
||||
foreach ($jury as $jm) {
|
||||
if ($jm['role'] === 'president') {
|
||||
$juryPresident = $jm['name'];
|
||||
} elseif ($jm['role'] === 'promoteur') {
|
||||
continue;
|
||||
}
|
||||
if ($jm['role'] === 'promoteur') {
|
||||
if (($jm['is_ulb'] ?? 0) == 1) {
|
||||
$juryPromoteursUlb[] = $jm;
|
||||
} else {
|
||||
@@ -69,7 +69,6 @@
|
||||
if (!empty($juryPromoteursUlb) && $juryPromoteurUlb === null) {
|
||||
$juryPromoteurUlb = $juryPromoteursUlb[0]['name'];
|
||||
}
|
||||
$showPresident = true;
|
||||
$showPromoteurUlb = true;
|
||||
$promoteurUlbConditional = false;
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
* array $orientations, $apPrograms, $finalityTypes, $languages, $formatTypes, $licenseTypes
|
||||
*
|
||||
* Jury data:
|
||||
* ?string $juryPromoteur, $juryPromoteurUlb, $juryPresident
|
||||
* ?string $juryPromoteur, $juryPromoteurUlb
|
||||
* array $juryPromoteurs, $juryPromoteursUlb
|
||||
* array $lecteursInternes, $lecteursExternes
|
||||
* bool $showPresident, $showPromoteurUlb, $promoteurUlbConditional
|
||||
* bool $showPromoteurUlb, $promoteurUlbConditional
|
||||
*
|
||||
* Licence / access:
|
||||
* bool $libreEnabled, $interneEnabled, $interditEnabled
|
||||
@@ -70,7 +70,6 @@ $juryPromoteursUlb = $juryPromoteursUlb ?? [];
|
||||
$lecteursInternes = $lecteursInternes ?? [];
|
||||
$lecteursExternes = $lecteursExternes ?? [];
|
||||
$juryPresident = $juryPresident ?? null;
|
||||
$showPresident = $showPresident ?? false;
|
||||
$showPromoteurUlb = $showPromoteurUlb ?? true;
|
||||
$promoteurUlbConditional = $promoteurUlbConditional ?? false;
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
* $juryPromoteursUlb array [{name: string}] Multiple promoteurs ULB
|
||||
* $lecteursInternes array [{name: string}]
|
||||
* $lecteursExternes array [{name: string}]
|
||||
* $juryPresident string|null President name (edit-only, optional)
|
||||
* $showPresident bool Show president field (default: false)
|
||||
* $juryPresident string|null (Deprecated — no longer displayed)
|
||||
* $showPromoteurUlb bool Show ULB promoteur field (default: true)
|
||||
* $promoteurUlbConditional bool If true, field is hidden unless finality=Approfondi
|
||||
*
|
||||
@@ -23,14 +22,12 @@ $juryPromoteurUlb = $juryPromoteurUlb ?? null;
|
||||
$juryPromoteursUlb = $juryPromoteursUlb ?? [];
|
||||
$lecteursInternes = $lecteursInternes ?? [];
|
||||
$lecteursExternes = $lecteursExternes ?? [];
|
||||
$juryPresident = $juryPresident ?? null;
|
||||
$showPresident = $showPresident ?? false;
|
||||
$showPromoteurUlb = $showPromoteurUlb ?? true;
|
||||
$promoteurUlbConditional = $promoteurUlbConditional ?? false;
|
||||
$adminMode = $adminMode ?? false;
|
||||
|
||||
// Add-mode repopulation from flash data
|
||||
$addMode = ($juryPromoteur === null && empty($juryPromoteurs) && $juryPromoteurUlb === null && empty($juryPromoteursUlb) && empty($lecteursInternes) && empty($lecteursExternes) && $juryPresident === null);
|
||||
$addMode = ($juryPromoteur === null && empty($juryPromoteurs) && $juryPromoteurUlb === null && empty($juryPromoteursUlb) && empty($lecteursInternes) && empty($lecteursExternes));
|
||||
if ($addMode && function_exists('old')) {
|
||||
// jury_promoteur may be array (new form) or scalar (legacy)
|
||||
$promoteursOld = old('jury_promoteur');
|
||||
@@ -52,7 +49,6 @@ if ($addMode && function_exists('old')) {
|
||||
} elseif (is_string($promoteursUlbOld) && trim($promoteursUlbOld) !== '') {
|
||||
$juryPromoteurUlb = $promoteursUlbOld;
|
||||
}
|
||||
$juryPresident = old('jury_president') ?: null;
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$n = old("jury_lecteur_interne:$i");
|
||||
if ($n !== '') $lecteursInternes[] = ['name' => $n];
|
||||
@@ -238,15 +234,6 @@ if ($addMode && function_exists('old')) {
|
||||
</button>
|
||||
</fieldset>
|
||||
|
||||
<?php if ($showPresident): ?>
|
||||
<!-- Président·e (admin edit only) -->
|
||||
<div>
|
||||
<label for="jury_president">Président·e :</label>
|
||||
<input type="text" id="jury_president" name="jury_president"
|
||||
value="<?= htmlspecialchars($juryPresident ?? '') ?>"
|
||||
placeholder="Nom du/de la président·e (interne)">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user