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

@@ -140,6 +140,12 @@ class FilepondHandler
chmod($targetPath, 0644);
error_log($this->logPrefix . ':process File saved to tmp | file_id=' . $fileId . ' | path=' . $targetPath);
// Track temp file in session so it survives page reloads
if (session_status() === PHP_SESSION_ACTIVE) {
$_SESSION['filepond_tmp'][$queueType] = $_SESSION['filepond_tmp'][$queueType] ?? [];
$_SESSION['filepond_tmp'][$queueType][] = $fileId;
}
$isPeerTubeQueue = str_starts_with($queueType, 'peertube_');
$isTfeAv = ($queueType === 'tfe' && preg_match('/^(video|audio)\//', $mimeType));
$shouldPeerTube = $isPeerTubeQueue || $isTfeAv;
@@ -207,7 +213,16 @@ class FilepondHandler
die('Méthode non autorisée.');
}
$dbId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT);
$fileId = trim($_GET['id'] ?? '');
// Hex IDs (32 chars) → temp files from tmp/filepond/
if (preg_match('/^[a-f0-9]{32}$/', $fileId)) {
$this->loadTempFile($fileId);
// loadTempFile exits; never returns
}
// Numeric IDs → DB files
$dbId = filter_var($fileId, FILTER_VALIDATE_INT);
if ($dbId === false || $dbId <= 0) {
http_response_code(400);
die('ID invalide.');
@@ -355,6 +370,11 @@ class FilepondHandler
die('Session invalide.');
}
// Remove from session tracking
if (session_status() === PHP_SESSION_ACTIVE) {
$this->removeFromSessionTmp($fileId);
}
$it = new RecursiveDirectoryIterator($tmpDir, RecursiveDirectoryIterator::SKIP_DOTS);
$files_it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files_it as $file) {
@@ -372,6 +392,153 @@ class FilepondHandler
// ── Internal helpers ──────────────────────────────────────────────────────
/**
* Get temp files for the current session and a specific queue type.
*
* Returns an array suitable for injection into FilePond's data-existing-files
* JSON attribute, so temp files survive page reloads.
*/
public static function getSessionTempFiles(string $queueType): array
{
if (session_status() !== PHP_SESSION_ACTIVE) {
return [];
}
$fileIds = $_SESSION['filepond_tmp'][$queueType] ?? [];
if (empty($fileIds)) {
return [];
}
$result = [];
$missing = [];
foreach ($fileIds as $fileId) {
$tmpDir = STORAGE_ROOT . '/tmp/filepond/' . $fileId;
$manifestPath = $tmpDir . '/manifest.json';
if (!is_dir($tmpDir) || !file_exists($manifestPath)) {
$missing[] = $fileId;
continue;
}
$manifest = json_decode(file_get_contents($manifestPath), true);
if (!is_array($manifest)) {
$missing[] = $fileId;
continue;
}
if (($manifest['session_id'] ?? '') !== session_id()) {
$missing[] = $fileId;
continue;
}
// Find the actual file
$actualFile = null;
$dh = opendir($tmpDir);
while (($entry = readdir($dh)) !== false) {
if ($entry === '.' || $entry === '..' || $entry === 'manifest.json') {
continue;
}
$actualFile = $tmpDir . '/' . $entry;
break;
}
closedir($dh);
if ($actualFile === null || !file_exists($actualFile)) {
$missing[] = $fileId;
continue;
}
$result[] = [
'source' => $fileId,
'options' => [
'type' => 'local',
'file' => [
'name' => $manifest['original_name'] ?? basename($actualFile),
'size' => (int)($manifest['size'] ?? filesize($actualFile)),
'type' => $manifest['mime'] ?? 'application/octet-stream',
],
],
];
}
// Clean up session entries for missing files
if (!empty($missing)) {
$_SESSION['filepond_tmp'][$queueType] = array_values(
array_diff($fileIds, $missing)
);
}
return $result;
}
/**
* Load a temp file (hex file_id) — streams the file from tmp/filepond/.
*/
private function loadTempFile(string $fileId): never
{
$tmpDir = STORAGE_ROOT . '/tmp/filepond/' . $fileId;
$manifestPath = $tmpDir . '/manifest.json';
if (!is_dir($tmpDir) || !file_exists($manifestPath)) {
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()) {
http_response_code(403);
die('Session invalide.');
}
$actualFile = null;
$dh = opendir($tmpDir);
while (($entry = readdir($dh)) !== false) {
if ($entry === '.' || $entry === '..' || $entry === 'manifest.json') {
continue;
}
$actualFile = $tmpDir . '/' . $entry;
break;
}
closedir($dh);
if ($actualFile === null || !file_exists($actualFile)) {
http_response_code(404);
die('Fichier temporaire introuvable.');
}
$mimeType = $manifest['mime'] ?? mime_content_type($actualFile);
$fileSize = filesize($actualFile);
$fileName = $manifest['original_name'] ?? basename($actualFile);
error_log($this->logPrefix . ':load TEMP | file_id=' . $fileId . ' | name=' . $fileName . ' | size=' . $fileSize);
header('Content-Type: ' . $mimeType);
header('Content-Length: ' . $fileSize);
header('Content-Disposition: inline; filename="' . addslashes($fileName) . '"');
header('Cache-Control: no-cache');
readfile($actualFile);
exit;
}
/**
* Remove a file_id from the session temp tracking array.
*/
private function removeFromSessionTmp(string $fileId): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
return;
}
foreach ($_SESSION['filepond_tmp'] ?? [] as $queueType => $ids) {
$idx = array_search($fileId, $ids, true);
if ($idx !== false) {
array_splice($_SESSION['filepond_tmp'][$queueType], $idx, 1);
if (empty($_SESSION['filepond_tmp'][$queueType])) {
unset($_SESSION['filepond_tmp'][$queueType]);
}
break;
}
}
}
/**
* Extract the first available file from $_FILES regardless of nesting depth.
*/