mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
filepond: fix crash 'can't access property main, n.status is undefined'
Fixes three root causes of FilePond errors on TFE upload forms: 1. server.process.onerror accessed .status on a string (XHR response text body) — now extracts the body safely. 2. server.load was a bare URL string with no error handling — converted to object with onload/onerror to prevent FilePond internal _write crash when load.php returns HTTP errors. 3. destroyFilePondsIn now aborts in-flight processing before pond.destroy() to prevent stale XHR callbacks firing on a torn-down FilePond instance. Server-side: FilepondHandler now emits Content-Type: text/plain on all responses (PHP defaults to text/html on die(), confusing FilePond's response parser).
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -13,3 +13,11 @@ Reference: `docs/autosave-system.md` → "HTMX v2 Migration Plan" section.
|
||||
- [x] Fix backend `$isAjax` detection: also recognize `HX-Request` header (page.php, apropos.php, form-help.php)
|
||||
- [x] Form-help inline editors: add OverType toolbar + HTMX auto-save + remove save buttons
|
||||
- [x] Markdown cheatsheet modal: reusable dialog on all OverType editors
|
||||
|
||||
## FilePond error fixes
|
||||
|
||||
- [x] Fix `server.process.onerror`: don't access `response.status` on string (response is XHR text body)
|
||||
- [x] Fix `server.load`: convert from string URL to object with proper `onload`/`onerror` handlers
|
||||
- [x] Fix `server.process.onload`: guard against non-ID responses (e.g. HTML error pages disguised as 200)
|
||||
- [x] Fix `destroyFilePondsIn`: abort in-flight uploads before destroying to prevent stale XHR callbacks crashing `_write`
|
||||
- [x] Fix `FilepondHandler.php`: set `Content-Type: text/plain` header at top of `handleProcess`, `handleLoad`, `handleRevert`, `handleRemove` so PHP doesn't default to `text/html` on `die()`
|
||||
|
||||
@@ -252,17 +252,21 @@
|
||||
},
|
||||
onload: (response) => {
|
||||
var id = response.trim();
|
||||
// Guard: if the server returned an error message disguised as 200,
|
||||
// treat it as a processing error so FilePond doesn't treat it as a serverId.
|
||||
if (id.length > 64 || /[<>\n\r]/.test(id)) {
|
||||
console.error("[filepond] process onload | unexpected response | body=" + id.substring(0, 200));
|
||||
throw new Error("Réponse serveur inattendue.");
|
||||
}
|
||||
console.log(`[filepond] process onload | serverId=${id}`);
|
||||
return id; // file_id stored as serverId
|
||||
},
|
||||
onerror: (response) => {
|
||||
console.error(
|
||||
"[filepond] process onerror | status=" +
|
||||
response.status +
|
||||
" | body=" +
|
||||
response,
|
||||
);
|
||||
return response;
|
||||
// response is the raw XHR response text (string), not an XHR object.
|
||||
// Log it and return a human-readable error message.
|
||||
var body = typeof response === 'string' ? response : (response && response.body ? response.body : String(response || ''));
|
||||
console.error("[filepond] process onerror | body=" + body);
|
||||
return body || "Erreur lors du téléversement.";
|
||||
},
|
||||
},
|
||||
|
||||
@@ -274,11 +278,25 @@
|
||||
console.log("[filepond] revert OK");
|
||||
},
|
||||
onerror: (r) => {
|
||||
console.error(`[filepond] revert ERROR | body=${r}`);
|
||||
var body = typeof r === 'string' ? r : (r && r.body ? r.body : '');
|
||||
console.error(`[filepond] revert ERROR | body=${body || r}`);
|
||||
},
|
||||
},
|
||||
|
||||
load: `${base}/load.php?id=`,
|
||||
load: {
|
||||
url: `${base}/load.php?id=`,
|
||||
method: "GET",
|
||||
onload: (response) => {
|
||||
// response is the blob from the server; pass through unchanged
|
||||
return response;
|
||||
},
|
||||
onerror: (response) => {
|
||||
var body = typeof response === 'string' ? response : (response && response.body ? response.body : String(response || ''));
|
||||
console.error("[filepond] load onerror | body=" + body);
|
||||
// Return a descriptive error — FilePond will fire an error event.
|
||||
return body || "Fichier introuvable.";
|
||||
},
|
||||
},
|
||||
// FilePond appends the source value (db_id) automatically
|
||||
|
||||
remove: (source, load, error) => {
|
||||
@@ -500,6 +518,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
// Abort any in-flight uploads before destroying to prevent
|
||||
// FilePond internal crashes when XHR callbacks fire on a
|
||||
// torn-down instance ("can't access property main").
|
||||
var files = pond.getFiles();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var f = files[i];
|
||||
if (f.status === 4 || f.status === 2 || f.status === 3) {
|
||||
// FileStatus: PROCESSING=4, PROCESSING_QUEUED=2, PROCESSING=4
|
||||
// (FilePond 4.x internal: 4 = processing)
|
||||
// Abort processing to avoid stale XHR callbacks
|
||||
try { pond.removeFile(f); } catch (_abort) {}
|
||||
}
|
||||
}
|
||||
pond.destroy();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ class FilepondHandler
|
||||
|
||||
public function handleProcess(): never
|
||||
{
|
||||
// All responses from this endpoint must be text/plain
|
||||
// (PHP defaults to text/html, which confuses FilePond on error).
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
error_log($this->logPrefix . ':process ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | files_keys=' . implode(',', array_keys($_FILES)) . ' | post_keys=' . implode(',', array_keys($_POST)));
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
@@ -197,7 +201,6 @@ class FilepondHandler
|
||||
file_put_contents($tmpDir . '/manifest.json', json_encode($manifest, JSON_UNESCAPED_SLASHES));
|
||||
|
||||
error_log($this->logPrefix . ':process SUCCESS | file_id=' . $fileId . ' | queue_type=' . $queueType . ' | name=' . $originalName);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $fileId;
|
||||
exit;
|
||||
}
|
||||
@@ -209,6 +212,7 @@ class FilepondHandler
|
||||
public function handleLoad(): never
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(405);
|
||||
die('Méthode non autorisée.');
|
||||
}
|
||||
@@ -224,6 +228,7 @@ class FilepondHandler
|
||||
// Numeric IDs → DB files
|
||||
$dbId = filter_var($fileId, FILTER_VALIDATE_INT);
|
||||
if ($dbId === false || $dbId <= 0) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(400);
|
||||
die('ID invalide.');
|
||||
}
|
||||
@@ -237,6 +242,7 @@ class FilepondHandler
|
||||
|
||||
if (!$fileRow) {
|
||||
error_log($this->logPrefix . ':load DB NOT FOUND | db_id=' . $dbId);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(404);
|
||||
die('Fichier introuvable.');
|
||||
}
|
||||
@@ -259,6 +265,7 @@ class FilepondHandler
|
||||
exit;
|
||||
}
|
||||
if (str_starts_with($filePath, 'http://') || str_starts_with($filePath, 'https://')) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(404);
|
||||
die('URL — pas de flux direct.');
|
||||
}
|
||||
@@ -266,6 +273,7 @@ class FilepondHandler
|
||||
$absPath = STORAGE_ROOT . '/' . $filePath;
|
||||
if (!file_exists($absPath) || !is_readable($absPath)) {
|
||||
error_log($this->logPrefix . ':load DISK MISSING | db_id=' . $dbId . ' | absPath=' . $absPath);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(404);
|
||||
die('Fichier absent du disque.');
|
||||
}
|
||||
@@ -286,6 +294,8 @@ class FilepondHandler
|
||||
|
||||
public function handleRemove(): never
|
||||
{
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
||||
http_response_code(405);
|
||||
die('Méthode non autorisée.');
|
||||
@@ -339,6 +349,8 @@ class FilepondHandler
|
||||
|
||||
public function handleRevert(): never
|
||||
{
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
||||
http_response_code(405);
|
||||
die('Méthode non autorisée.');
|
||||
@@ -480,12 +492,14 @@ class FilepondHandler
|
||||
$manifestPath = $tmpDir . '/manifest.json';
|
||||
|
||||
if (!is_dir($tmpDir) || !file_exists($manifestPath)) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(404);
|
||||
die('Fichier temporaire introuvable.');
|
||||
}
|
||||
|
||||
$manifest = json_decode(file_get_contents($manifestPath), true);
|
||||
if (!is_array($manifest) || ($manifest['session_id'] ?? '') !== session_id()) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(403);
|
||||
die('Session invalide.');
|
||||
}
|
||||
@@ -502,6 +516,7 @@ class FilepondHandler
|
||||
closedir($dh);
|
||||
|
||||
if ($actualFile === null || !file_exists($actualFile)) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(404);
|
||||
die('Fichier temporaire introuvable.');
|
||||
}
|
||||
@@ -569,6 +584,7 @@ class FilepondHandler
|
||||
|
||||
private function validateMimeExt(string $queueType, string $mimeType, string $ext): void
|
||||
{
|
||||
// Content-Type already set by handleProcess() header
|
||||
$allowedMimes = self::QUEUE_MIME_MAP[$queueType] ?? null;
|
||||
if ($allowedMimes !== null) {
|
||||
if (!in_array($mimeType, $allowedMimes, true)) {
|
||||
|
||||
@@ -26,3 +26,6 @@
|
||||
{"timestamp":"2026-06-09T19:01:33+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"page","action":"edit","status":"success","context":{"slug":"licenses"}}
|
||||
{"timestamp":"2026-06-09T19:12:39+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"form_structure","action":"edit","status":"success","context":{"section":"fieldset_access"}}
|
||||
{"timestamp":"2026-06-09T19:12:43+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"form_structure","action":"edit","status":"success","context":{"section":"fieldset_access"}}
|
||||
{"timestamp":"2026-06-09T19:26:57+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"page","action":"edit","status":"success","context":{"slug":"about"}}
|
||||
{"timestamp":"2026-06-09T19:27:00+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"page","action":"edit","status":"success","context":{"slug":"about"}}
|
||||
{"timestamp":"2026-06-09T19:33:27+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"share_link","action":"create","status":"success","context":{"slug":"20260609-IHRZDYKJ","has_password":true,"expires_at":null,"objet_restriction":"tfe"}}
|
||||
|
||||
Reference in New Issue
Block a user