Guard no-JS file uploads: disabled filepond_mode by default, server-side fallback

The partage/admin form had a hardcoded filepond_mode=1 hidden input,
so without JavaScript the server always entered the FilePond async
path — which found no hex IDs and silently dropped all files.

Three-layer fix:
1. HTML: filepond_mode input starts disabled with value=0; JS enables
   it and sets value=1 on DOMContentLoaded (and after HTMX swaps).
   Disabled inputs aren't submitted → server gets no filepond_mode
   → naturally falls to legacy  path.
2. JS: enableFilepondMode() called on page load and hx:afterSwap so
   FilePond-enhanced forms always send filepond_mode=1.
3. Server (defense-in-depth): ThesisFileHandler::hasFilePondQueueData()
   scans POST['queue_file'] for 32-char hex IDs; ThesisCreateController
   and ThesisEditController use it alongside filepond_mode, so even if
   the flag somehow arrives without async upload IDs, the  path
   takes over.
This commit is contained in:
Pontoporeia
2026-06-11 10:32:38 +02:00
parent 63e65d9856
commit 4b37a05be3
6 changed files with 61 additions and 7 deletions

View File

@@ -198,7 +198,13 @@ class ThesisCreateController
$folderPath = $objet . '/' . $data['annee'] . '/' . $folderName . '/';
$filePrefix = $folderName;
if (!empty($post['filepond_mode'])) {
// Determine upload path: FilePond async (JS enabled, hex IDs present)
// vs. legacy multipart (no JS, or JS failed). The hidden filepond_mode
// input starts disabled (value=0, not submitted); JS enables it on load.
// Defense-in-depth: if filepond_mode=1 but no hex IDs, fall back to $_FILES.
$useFilePond = !empty($post['filepond_mode']) && $this->hasFilePondQueueData($post);
if ($useFilePond) {
// New path: files already on server via async FilePond uploads
// Cover and note_intention also go through FilePond async flow
$this->handleFilePondSingleFile($thesisId, $post, 'cover', $folderPath, $filePrefix);

View File

@@ -359,8 +359,12 @@ class ThesisEditController
mkdir($dirAbs, 0755, true);
}
// Determine upload path: FilePond async (JS enabled, hex IDs present)
// vs. legacy multipart. Defense-in-depth fallback for no-JS scenarios.
$useFilePond = !empty($post['filepond_mode']) && $this->hasFilePondQueueData($post);
// ── Cover image (outside transaction — filesystem op) ─────────────────
if (!empty($post['filepond_mode'])) {
if ($useFilePond) {
// Delete old cover only if a genuinely new cover was uploaded (hex file_id).
// Existing cover preserved in FilePond sends its DB integer ID — skip.
$coverIdRaw = ($post['queue_file']['cover'] ?? null);
@@ -387,7 +391,7 @@ class ThesisEditController
}
// ── Note d'intention (replace if uploaded) ────────────────────────────
if (!empty($post['filepond_mode'])) {
if ($useFilePond) {
// Only delete + replace if a genuinely new file was uploaded (hex file_id).
// Existing files preserved in the FilePond pool send their DB integer ID;
// we must NOT delete them — they're already stored.
@@ -454,7 +458,7 @@ class ThesisEditController
}
}
if (!empty($post['filepond_mode'])) {
if ($useFilePond) {
// New path: files already on server via async FilePond uploads
$nextNum = $tfeCount + 1;
$nextNum = $this->handleFilePondQueueFiles($thesisId, $post, 'tfe', $folderPath, $filePrefix, $nextNum);

View File

@@ -853,6 +853,33 @@ trait ThesisFileHandler
// ── FilePond async file processing ──────────────────────────────────────
/**
* Check whether the POST data contains actual FilePond hex IDs (32-char hex)
* rather than standard file upload data.
*
* Without JS the hidden filepond_mode input is disabled and not submitted;
* this is a defense-in-depth fallback: if filepond_mode=1 somehow arrives but
* no async upload IDs are present, we treat it as a legacy $_FILES submission.
*/
private function hasFilePondQueueData(array $post): bool
{
$queueKeys = ['cover', 'note_intention', 'tfe', 'annexe'];
foreach ($queueKeys as $key) {
$raw = $post['queue_file'][$key] ?? null;
if ($raw === null || $raw === '') {
continue;
}
$ids = is_array($raw) ? $raw : [$raw];
foreach ($ids as $id) {
$id = is_string($id) ? trim($id) : '';
if ($id !== '' && preg_match('/^[a-f0-9]{32}$/', $id)) {
return true;
}
}
}
return false;
}
/**
* Process a single file from the FilePond async flow (cover, note_intention).
*