Fix FilePond: maxFileSize as bytes + temp files survive page reload

1. maxFileSize bug: FileValidateSize plugin overrides core's maxFileSize
   setter. Core uses toBytes('1GB') = 1073741824, but plugin registers
   maxFileSize as [null, Type.INT] which calls toInt('1GB') = 1.
   Fix: all maxFileSize and perExtensionMaxSize values as raw bytes.
   Also fix option name: fileValidateSizeFilterItem → fileValidateSizeFilter.

2. Temp file persistence: files uploaded via FilePond went to
   tmp/filepond/ and vanished from the UI on page reload because
   data-existing-files only included DB-persisted files.
   Fix: session-track temp file_ids in handleProcess, inject via
   getSessionTempFiles() into data-existing-files, teach handleLoad
   to stream temp files from disk, and route JS remove → revert for hex IDs.
This commit is contained in:
Pontoporeia
2026-06-09 17:41:31 +02:00
parent c4a550f9d1
commit 1490c99268
6 changed files with 234 additions and 26 deletions

View File

@@ -45,6 +45,14 @@ if ($thesisId) {
],
];
}
// Include session temp files so uploads survive page reload
require_once APP_ROOT . '/src/FilepondHandler.php';
$tempFiles = FilepondHandler::getSessionTempFiles($queueType);
foreach ($tempFiles as $tf) {
$result[] = $tf;
}
return $result;
};

View File

@@ -10,7 +10,7 @@
* The server returns a file_id stored as item.serverId.
* 4. Form submit sends only file_ids (tiny payload), not the files themselves.
* 5. Type + size validation: via native FilePond options + FileValidateType/Size plugins
* plus fileValidateSizeFilterItem for per-extension size caps.
* plus fileValidateSizeFilter for per-extension size caps.
* 6. Order serialization: hidden inputs track file order using serverId (not filename).
* 7. HTMX cleanup: generic destroyFilePondsIn(target) for all swaps, not just known IDs.
* 8. Edit mode: loads existing files via data-existing-files JSON + server.load.
@@ -47,24 +47,27 @@
labelFileTypeNotAllowed: "Format non accepté",
fileValidateTypeLabelExpectedTypes:
"PDF, Images, Vidéos, Audio, VTT, Archives",
maxFileSize: "1GB",
maxFileSize: 1073741824, // 1 GB
labelMaxFileSizeExceeded: "Fichier trop volumineux",
labelMaxFileSize: "Taille max: {filesize}",
allowMultiple: true,
// Per-extension size limits: certain types get higher caps.
// Values in bytes; FileValidateSize plugin reads maxFileSize as INT,
// so numeric literals are required (string suffixes like "1GB" become
// parseInt("1GB") = 1 byte inside the plugin).
perExtensionMaxSize: {
pdf: "100MB",
mp4: "8GB",
webm: "8GB",
ogv: "8GB",
mov: "8GB",
mp3: "8GB",
ogg: "8GB",
oga: "8GB",
wav: "8GB",
flac: "8GB",
aac: "8GB",
m4a: "8GB",
pdf: 104857600, // 100 MB
mp4: 8589934592, // 8 GB
webm: 8589934592,
ogv: 8589934592,
mov: 8589934592,
mp3: 8589934592,
ogg: 8589934592,
oga: 8589934592,
wav: 8589934592,
flac: 8589934592,
aac: 8589934592,
m4a: 8589934592,
},
},
annexe: {
@@ -76,7 +79,7 @@
],
labelFileTypeNotAllowed: "Format non accepté",
fileValidateTypeLabelExpectedTypes: "PDF, ZIP, TAR, GZ",
maxFileSize: "1GB",
maxFileSize: 1073741824, // 1 GB
labelMaxFileSizeExceeded: "Fichier trop volumineux",
labelMaxFileSize: "Taille max: {filesize}",
allowMultiple: true,
@@ -85,7 +88,7 @@
acceptedFileTypes: ["image/jpeg", "image/png", "image/webp"],
labelFileTypeNotAllowed: "Seulement JPG, PNG ou WEBP",
fileValidateTypeLabelExpectedTypes: "JPG, PNG, WEBP",
maxFileSize: "20MB",
maxFileSize: 20971520, // 20 MB
labelMaxFileSizeExceeded: "Fichier trop volumineux",
labelMaxFileSize: "Taille max: {filesize}",
allowMultiple: false,
@@ -94,7 +97,7 @@
acceptedFileTypes: ["application/pdf"],
labelFileTypeNotAllowed: "Seulement PDF",
fileValidateTypeLabelExpectedTypes: "PDF",
maxFileSize: "100MB",
maxFileSize: 104857600, // 100 MB
labelMaxFileSizeExceeded: "Fichier trop volumineux",
labelMaxFileSize: "Taille max: {filesize}",
allowMultiple: false,
@@ -103,7 +106,7 @@
acceptedFileTypes: ["text/csv"],
labelFileTypeNotAllowed: "Seulement CSV",
fileValidateTypeLabelExpectedTypes: "CSV",
maxFileSize: "50MB",
maxFileSize: 52428800, // 50 MB
labelMaxFileSizeExceeded: "Fichier trop volumineux",
labelMaxFileSize: "Taille max: {filesize}",
allowMultiple: false,
@@ -119,6 +122,8 @@
* Parse a size string like "500MB" or "2GB" to bytes.
*/
function parseSize(str) {
// Already a number (bytes) — pass through
if (typeof str === 'number') return str;
var m = str.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
if (!m) return 0;
var val = parseFloat(m[1]);
@@ -277,7 +282,25 @@
// FilePond appends the source value (db_id) automatically
remove: (source, load, error) => {
console.log(`[filepond] remove called | db_id=${source}`);
console.log(`[filepond] remove called | id=${source}`);
// Hex IDs (32 chars) → temp files → use revert endpoint
if (/^[a-f0-9]{32}$/.test(source)) {
fetch(`${base}/revert.php`, {
method: "DELETE",
headers: { "X-CSRF-Token": csrfToken },
body: source,
})
.then((r) => {
console.log("[filepond] revert (from remove) response | ok=" + r.ok + " | status=" + r.status);
r.ok ? load() : error("Erreur suppression");
})
.catch((e) => {
console.error("[filepond] revert (from remove) fetch error", e);
error("Erreur réseau");
});
return;
}
// Numeric IDs → DB files → use remove endpoint
fetch(`${base}/remove.php`, {
method: "DELETE",
headers: {
@@ -341,10 +364,9 @@
labelButtonRetryItemLoad: "Réessayer",
labelButtonProcessItem: "Charger",
// ── Per-extension size validation ──────────────────────────────
// Uses fileValidateSizeFilterItem if the FileValidateSize plugin supports it.
// Per-extension size validation via FileValidateSize plugin hook.
// Falls back to beforeAddFile for silent rejection (the plugin shows the error).
fileValidateSizeFilterItem: (item) => {
fileValidateSizeFilter: (item) => {
var ext = getExt(item.filename);
if (ext && perExtMax[ext]) {
return parseSize(perExtMax[ext]); // per-extension cap for this item
@@ -352,10 +374,9 @@
return parseSize(cfg.maxFileSize); // queue default
},
// Fallback: if fileValidateSizeFilterItem is not available,
// beforeAddFile enforces per-extension limits (silent rejection).
// Fallback: beforeAddFile enforces per-extension limits (silent rejection).
beforeAddFile: (item) => {
// This check is redundant if fileValidateSizeFilterItem works,
// This check is redundant if fileValidateSizeFilter works,
// but serves as a fallback.
if (typeof item.file === "undefined") return true;
var f = item.file;