release(v1.9.6): hardened resumable uploads, menu/tag UI polish and hidden temp folders (closes #67)
This commit is contained in:
@@ -5,116 +5,143 @@ require_once __DIR__ . '/../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/lib/ACL.php';
|
||||
require_once PROJECT_ROOT . '/src/models/UploadModel.php';
|
||||
|
||||
class UploadController {
|
||||
|
||||
public function handleUpload(): void {
|
||||
class UploadController
|
||||
{
|
||||
public function handleUpload(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ---- 1) CSRF (header or form field) ----
|
||||
$headersArr = array_change_key_case(getallheaders() ?: [], CASE_LOWER);
|
||||
$received = '';
|
||||
if (!empty($headersArr['x-csrf-token'])) {
|
||||
$received = trim($headersArr['x-csrf-token']);
|
||||
} elseif (!empty($_POST['csrf_token'])) {
|
||||
$received = trim($_POST['csrf_token']);
|
||||
} elseif (!empty($_POST['upload_token'])) {
|
||||
// legacy alias
|
||||
$received = trim($_POST['upload_token']);
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
$requestParams = ($method === 'GET') ? $_GET : $_POST;
|
||||
|
||||
// Detect Resumable.js chunk "test" requests (testChunks=true, default GET)
|
||||
$isResumableTest =
|
||||
($method === 'GET'
|
||||
&& isset($requestParams['resumableChunkNumber'])
|
||||
&& isset($requestParams['resumableIdentifier']));
|
||||
|
||||
// ---- 1) CSRF (skip for resumable GET tests – Resumable only cares about HTTP status) ----
|
||||
if (!$isResumableTest) {
|
||||
$headersArr = array_change_key_case(getallheaders() ?: [], CASE_LOWER);
|
||||
$received = '';
|
||||
|
||||
if (!empty($headersArr['x-csrf-token'])) {
|
||||
$received = trim($headersArr['x-csrf-token']);
|
||||
} elseif (!empty($requestParams['csrf_token'])) {
|
||||
$received = trim((string)$requestParams['csrf_token']);
|
||||
} elseif (!empty($requestParams['upload_token'])) {
|
||||
// legacy alias
|
||||
$received = trim((string)$requestParams['upload_token']);
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['csrf_token']) || $received !== $_SESSION['csrf_token']) {
|
||||
// Soft-fail so client can retry with refreshed token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'csrf_expired' => true,
|
||||
'csrf_token' => $_SESSION['csrf_token'],
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['csrf_token']) || $received !== $_SESSION['csrf_token']) {
|
||||
// Soft-fail so client can retry with refreshed token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'csrf_expired' => true,
|
||||
'csrf_token' => $_SESSION['csrf_token']
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ---- 2) Auth + account-level flags ----
|
||||
if (empty($_SESSION['authenticated'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$username = (string)($_SESSION['username'] ?? '');
|
||||
$userPerms = loadUserPermissions($username) ?: [];
|
||||
$isAdmin = ACL::isAdmin($userPerms);
|
||||
|
||||
|
||||
// Admins should never be blocked by account-level "disableUpload"
|
||||
if (!$isAdmin && !empty($userPerms['disableUpload'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Upload disabled for this user.']);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ---- 3) Folder-level WRITE permission (ACL) ----
|
||||
// Always require client to send the folder; fall back to GET if needed.
|
||||
$folderParam = isset($_POST['folder'])
|
||||
? (string)$_POST['folder']
|
||||
: (isset($_GET['folder']) ? (string)$_GET['folder'] : 'root');
|
||||
// Prefer the unified param array, fall back to GET only if needed.
|
||||
$folderParam = isset($requestParams['folder'])
|
||||
? (string)$requestParams['folder']
|
||||
: (isset($_GET['folder']) ? (string)$_GET['folder'] : 'root');
|
||||
|
||||
// Decode %xx (e.g., "test%20folder") then normalize
|
||||
$folderParam = rawurldecode($folderParam);
|
||||
$targetFolder = ACL::normalizeFolder($folderParam);
|
||||
// Decode %xx (e.g., "test%20folder") then normalize
|
||||
$folderParam = rawurldecode($folderParam);
|
||||
$targetFolder = ACL::normalizeFolder($folderParam);
|
||||
|
||||
// Admins bypass folder canWrite checks
|
||||
$username = (string)($_SESSION['username'] ?? '');
|
||||
$userPerms = loadUserPermissions($username) ?: [];
|
||||
$isAdmin = ACL::isAdmin($userPerms);
|
||||
// Admins bypass folder canWrite checks
|
||||
if (!$isAdmin && !ACL::canUpload($username, $userPerms, $targetFolder)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'error' => 'Forbidden: no write access to folder "' . $targetFolder . '".',
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$isAdmin && !ACL::canUpload($username, $userPerms, $targetFolder)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Forbidden: no write access to folder "'.$targetFolder.'".']);
|
||||
return;
|
||||
// ---- 4) Delegate to model (force the sanitized folder) ----
|
||||
$requestParams['folder'] = $targetFolder;
|
||||
// Keep legacy behavior for anything still reading $_POST directly
|
||||
$_POST['folder'] = $targetFolder;
|
||||
|
||||
$result = UploadModel::handleUpload($requestParams, $_FILES);
|
||||
|
||||
// ---- 5) Special handling for Resumable.js GET tests ----
|
||||
// Resumable only inspects HTTP status:
|
||||
// 200 => chunk exists (skip)
|
||||
// 404/other => chunk missing (upload)
|
||||
if ($isResumableTest && isset($result['status'])) {
|
||||
if ($result['status'] === 'found') {
|
||||
http_response_code(200);
|
||||
} else {
|
||||
http_response_code(202); // 202 Accepted = chunk not found
|
||||
}
|
||||
echo json_encode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 6) Normal response handling ----
|
||||
if (isset($result['error'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($result['status'])) {
|
||||
echo json_encode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result['success'] ?? 'File uploaded successfully',
|
||||
'newFilename' => $result['newFilename'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
// ---- 4) Delegate to model (force the sanitized folder) ----
|
||||
$_POST['folder'] = $targetFolder; // in case model reads superglobal
|
||||
$post = $_POST;
|
||||
$post['folder'] = $targetFolder;
|
||||
public function removeChunks(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$result = UploadModel::handleUpload($post, $_FILES);
|
||||
$receivedToken = isset($_POST['csrf_token']) ? trim((string)$_POST['csrf_token']) : '';
|
||||
if ($receivedToken !== ($_SESSION['csrf_token'] ?? '')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid CSRF token']);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 5) Response (unchanged) ----
|
||||
if (isset($result['error'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode($result);
|
||||
return;
|
||||
if (!isset($_POST['folder'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'No folder specified']);
|
||||
return;
|
||||
}
|
||||
|
||||
$folderRaw = (string)$_POST['folder'];
|
||||
$folder = ACL::normalizeFolder(rawurldecode($folderRaw));
|
||||
|
||||
echo json_encode(UploadModel::removeChunks($folder));
|
||||
}
|
||||
if (isset($result['status'])) {
|
||||
echo json_encode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => 'File uploaded successfully',
|
||||
'newFilename' => $result['newFilename'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
public function removeChunks(): void {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$receivedToken = isset($_POST['csrf_token']) ? trim($_POST['csrf_token']) : '';
|
||||
if ($receivedToken !== ($_SESSION['csrf_token'] ?? '')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid CSRF token']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($_POST['folder'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'No folder specified']);
|
||||
return;
|
||||
}
|
||||
|
||||
$folderRaw = (string)$_POST['folder'];
|
||||
$folder = ACL::normalizeFolder(rawurldecode($folderRaw));
|
||||
|
||||
echo json_encode(UploadModel::removeChunks($folder));
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,17 @@
|
||||
|
||||
require_once PROJECT_ROOT . '/config/config.php';
|
||||
|
||||
class UploadModel {
|
||||
|
||||
private static function sanitizeFolder(string $folder): string {
|
||||
class UploadModel
|
||||
{
|
||||
private static function sanitizeFolder(string $folder): string
|
||||
{
|
||||
// decode "%20", normalise slashes & trim via ACL helper
|
||||
$f = ACL::normalizeFolder(rawurldecode($folder));
|
||||
|
||||
// model uses '' to represent root
|
||||
if ($f === 'root') return '';
|
||||
if ($f === 'root') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// forbid dot segments / empty parts
|
||||
foreach (explode('/', $f) as $seg) {
|
||||
@@ -28,9 +31,13 @@ class UploadModel {
|
||||
return $f; // safe, normalised, with spaces allowed
|
||||
}
|
||||
|
||||
public static function handleUpload(array $post, array $files): array {
|
||||
// --- GET resumable test (make folder handling consistent)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($post['resumableTest'])) {
|
||||
public static function handleUpload(array $post, array $files): array
|
||||
{
|
||||
// --- GET resumable test (make folder handling consistent) ---
|
||||
if (
|
||||
(($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET')
|
||||
&& isset($post['resumableChunkNumber'], $post['resumableIdentifier'])
|
||||
) {
|
||||
$chunkNumber = (int)($post['resumableChunkNumber'] ?? 0);
|
||||
$resumableIdentifier = $post['resumableIdentifier'] ?? '';
|
||||
$folderSan = self::sanitizeFolder((string)($post['folder'] ?? 'root'));
|
||||
@@ -38,15 +45,16 @@ class UploadModel {
|
||||
$baseUploadDir = UPLOAD_DIR;
|
||||
if ($folderSan !== '') {
|
||||
$baseUploadDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$tempDir = $baseUploadDir . 'resumable_' . $resumableIdentifier . DIRECTORY_SEPARATOR;
|
||||
$tempDir = $baseUploadDir . 'resumable_' . $resumableIdentifier . DIRECTORY_SEPARATOR;
|
||||
$chunkFile = $tempDir . $chunkNumber;
|
||||
return ["status" => file_exists($chunkFile) ? "found" : "not found"];
|
||||
|
||||
return ['status' => file_exists($chunkFile) ? 'found' : 'not found'];
|
||||
}
|
||||
|
||||
// --- CHUNKED ---
|
||||
// --- CHUNKED (Resumable.js POST uploads) ---
|
||||
if (isset($post['resumableChunkNumber'])) {
|
||||
$chunkNumber = (int)$post['resumableChunkNumber'];
|
||||
$totalChunks = (int)$post['resumableTotalChunks'];
|
||||
@@ -54,109 +62,126 @@ class UploadModel {
|
||||
$resumableFilename = urldecode(basename($post['resumableFilename'] ?? ''));
|
||||
|
||||
if (!preg_match(REGEX_FILE_NAME, $resumableFilename)) {
|
||||
return ["error" => "Invalid file name: $resumableFilename"];
|
||||
return ['error' => "Invalid file name: $resumableFilename"];
|
||||
}
|
||||
|
||||
$folderSan = self::sanitizeFolder((string)($post['folder'] ?? 'root'));
|
||||
|
||||
if (empty($files['file']) || !isset($files['file']['name'])) {
|
||||
return ["error" => "No files received"];
|
||||
return ['error' => 'No files received'];
|
||||
}
|
||||
|
||||
$baseUploadDir = UPLOAD_DIR;
|
||||
if ($folderSan !== '') {
|
||||
$baseUploadDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
if (!is_dir($baseUploadDir) && !mkdir($baseUploadDir, 0775, true)) {
|
||||
return ["error" => "Failed to create upload directory"];
|
||||
return ['error' => 'Failed to create upload directory'];
|
||||
}
|
||||
|
||||
$tempDir = $baseUploadDir . 'resumable_' . $resumableIdentifier . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($tempDir) && !mkdir($tempDir, 0775, true)) {
|
||||
return ["error" => "Failed to create temporary chunk directory"];
|
||||
return ['error' => 'Failed to create temporary chunk directory'];
|
||||
}
|
||||
|
||||
$chunkErr = $files['file']['error'] ?? UPLOAD_ERR_NO_FILE;
|
||||
if ($chunkErr !== UPLOAD_ERR_OK) {
|
||||
return ["error" => "Upload error on chunk $chunkNumber"];
|
||||
return ['error' => "Upload error on chunk $chunkNumber"];
|
||||
}
|
||||
|
||||
$chunkFile = $tempDir . $chunkNumber;
|
||||
$tmpName = $files['file']['tmp_name'] ?? null;
|
||||
if (!$tmpName || !move_uploaded_file($tmpName, $chunkFile)) {
|
||||
return ["error" => "Failed to move uploaded chunk $chunkNumber"];
|
||||
return ['error' => "Failed to move uploaded chunk $chunkNumber"];
|
||||
}
|
||||
|
||||
// all chunks present?
|
||||
// All chunks present?
|
||||
for ($i = 1; $i <= $totalChunks; $i++) {
|
||||
if (!file_exists($tempDir . $i)) {
|
||||
return ["status" => "chunk uploaded"];
|
||||
return ['status' => 'chunk uploaded'];
|
||||
}
|
||||
}
|
||||
|
||||
// merge
|
||||
// Merge
|
||||
$targetPath = $baseUploadDir . $resumableFilename;
|
||||
if (!$out = fopen($targetPath, "wb")) {
|
||||
return ["error" => "Failed to open target file for writing"];
|
||||
if (!$out = fopen($targetPath, 'wb')) {
|
||||
return ['error' => 'Failed to open target file for writing'];
|
||||
}
|
||||
for ($i = 1; $i <= $totalChunks; $i++) {
|
||||
$chunkPath = $tempDir . $i;
|
||||
if (!file_exists($chunkPath)) { fclose($out); return ["error" => "Chunk $i missing during merge"]; }
|
||||
if (!$in = fopen($chunkPath, "rb")) { fclose($out); return ["error" => "Failed to open chunk $i"]; }
|
||||
while ($buff = fread($in, 4096)) { fwrite($out, $buff); }
|
||||
if (!file_exists($chunkPath)) {
|
||||
fclose($out);
|
||||
return ['error' => "Chunk $i missing during merge"];
|
||||
}
|
||||
if (!$in = fopen($chunkPath, 'rb')) {
|
||||
fclose($out);
|
||||
return ['error' => "Failed to open chunk $i"];
|
||||
}
|
||||
while ($buff = fread($in, 4096)) {
|
||||
fwrite($out, $buff);
|
||||
}
|
||||
fclose($in);
|
||||
}
|
||||
fclose($out);
|
||||
|
||||
// metadata
|
||||
$metadataKey = ($folderSan === '') ? "root" : $folderSan;
|
||||
// Metadata
|
||||
$metadataKey = ($folderSan === '') ? 'root' : $folderSan;
|
||||
$metadataFileName = str_replace(['/', '\\', ' '], '-', $metadataKey) . '_metadata.json';
|
||||
$metadataFile = META_DIR . $metadataFileName;
|
||||
$uploadedDate = date(DATE_TIME_FORMAT);
|
||||
$uploader = $_SESSION['username'] ?? "Unknown";
|
||||
$collection = file_exists($metadataFile) ? json_decode(file_get_contents($metadataFile), true) : [];
|
||||
if (!is_array($collection)) $collection = [];
|
||||
$uploader = $_SESSION['username'] ?? 'Unknown';
|
||||
$collection = file_exists($metadataFile)
|
||||
? json_decode(file_get_contents($metadataFile), true)
|
||||
: [];
|
||||
if (!is_array($collection)) {
|
||||
$collection = [];
|
||||
}
|
||||
if (!isset($collection[$resumableFilename])) {
|
||||
$collection[$resumableFilename] = ["uploaded" => $uploadedDate, "uploader" => $uploader];
|
||||
$collection[$resumableFilename] = [
|
||||
'uploaded' => $uploadedDate,
|
||||
'uploader' => $uploader,
|
||||
];
|
||||
file_put_contents($metadataFile, json_encode($collection, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// cleanup temp
|
||||
// Cleanup temp
|
||||
self::rrmdir($tempDir);
|
||||
|
||||
return ["success" => "File uploaded successfully"];
|
||||
return ['success' => 'File uploaded successfully'];
|
||||
}
|
||||
|
||||
// --- NON-CHUNKED ---
|
||||
// --- NON-CHUNKED (drag-and-drop / folder uploads) ---
|
||||
$folderSan = self::sanitizeFolder((string)($post['folder'] ?? 'root'));
|
||||
|
||||
$baseUploadDir = UPLOAD_DIR;
|
||||
if ($folderSan !== '') {
|
||||
$baseUploadDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $folderSan) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
if (!is_dir($baseUploadDir) && !mkdir($baseUploadDir, 0775, true)) {
|
||||
return ["error" => "Failed to create upload directory"];
|
||||
return ['error' => 'Failed to create upload directory'];
|
||||
}
|
||||
|
||||
$safeFileNamePattern = REGEX_FILE_NAME;
|
||||
$metadataCollection = [];
|
||||
$metadataChanged = [];
|
||||
|
||||
foreach ($files["file"]["name"] as $index => $fileName) {
|
||||
foreach ($files['file']['name'] as $index => $fileName) {
|
||||
if (($files['file']['error'][$index] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
|
||||
return ["error" => "Error uploading file"];
|
||||
return ['error' => 'Error uploading file'];
|
||||
}
|
||||
|
||||
$safeFileName = trim(urldecode(basename($fileName)));
|
||||
if (!preg_match($safeFileNamePattern, $safeFileName)) {
|
||||
return ["error" => "Invalid file name: " . $fileName];
|
||||
return ['error' => 'Invalid file name: ' . $fileName];
|
||||
}
|
||||
|
||||
$relativePath = '';
|
||||
if (isset($post['relativePath'])) {
|
||||
$relativePath = is_array($post['relativePath']) ? ($post['relativePath'][$index] ?? '') : $post['relativePath'];
|
||||
$relativePath = is_array($post['relativePath'])
|
||||
? ($post['relativePath'][$index] ?? '')
|
||||
: $post['relativePath'];
|
||||
}
|
||||
|
||||
$uploadDir = rtrim($baseUploadDir, '/\\') . DIRECTORY_SEPARATOR;
|
||||
@@ -164,34 +189,41 @@ class UploadModel {
|
||||
$subDir = dirname($relativePath);
|
||||
if ($subDir !== '.' && $subDir !== '') {
|
||||
$uploadDir = rtrim($baseUploadDir, '/\\') . DIRECTORY_SEPARATOR
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $subDir) . DIRECTORY_SEPARATOR;
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $subDir) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
$safeFileName = basename($relativePath);
|
||||
}
|
||||
|
||||
if (!is_dir($uploadDir) && !@mkdir($uploadDir, 0775, true)) {
|
||||
return ["error" => "Failed to create subfolder: " . $uploadDir];
|
||||
return ['error' => 'Failed to create subfolder: ' . $uploadDir];
|
||||
}
|
||||
|
||||
$targetPath = $uploadDir . $safeFileName;
|
||||
if (!move_uploaded_file($files["file"]["tmp_name"][$index], $targetPath)) {
|
||||
return ["error" => "Error uploading file"];
|
||||
if (!move_uploaded_file($files['file']['tmp_name'][$index], $targetPath)) {
|
||||
return ['error' => 'Error uploading file'];
|
||||
}
|
||||
|
||||
$metadataKey = ($folderSan === '') ? "root" : $folderSan;
|
||||
$metadataKey = ($folderSan === '') ? 'root' : $folderSan;
|
||||
$metadataFileName = str_replace(['/', '\\', ' '], '-', $metadataKey) . '_metadata.json';
|
||||
$metadataFile = META_DIR . $metadataFileName;
|
||||
|
||||
if (!isset($metadataCollection[$metadataKey])) {
|
||||
$metadataCollection[$metadataKey] = file_exists($metadataFile) ? json_decode(file_get_contents($metadataFile), true) : [];
|
||||
if (!is_array($metadataCollection[$metadataKey])) $metadataCollection[$metadataKey] = [];
|
||||
$metadataCollection[$metadataKey] = file_exists($metadataFile)
|
||||
? json_decode(file_get_contents($metadataFile), true)
|
||||
: [];
|
||||
if (!is_array($metadataCollection[$metadataKey])) {
|
||||
$metadataCollection[$metadataKey] = [];
|
||||
}
|
||||
$metadataChanged[$metadataKey] = false;
|
||||
}
|
||||
|
||||
if (!isset($metadataCollection[$metadataKey][$safeFileName])) {
|
||||
$uploadedDate = date(DATE_TIME_FORMAT);
|
||||
$uploader = $_SESSION['username'] ?? "Unknown";
|
||||
$metadataCollection[$metadataKey][$safeFileName] = ["uploaded" => $uploadedDate, "uploader" => $uploader];
|
||||
$uploader = $_SESSION['username'] ?? 'Unknown';
|
||||
$metadataCollection[$metadataKey][$safeFileName] = [
|
||||
'uploaded' => $uploadedDate,
|
||||
'uploader' => $uploader,
|
||||
];
|
||||
$metadataChanged[$metadataKey] = true;
|
||||
}
|
||||
}
|
||||
@@ -204,17 +236,17 @@ class UploadModel {
|
||||
}
|
||||
}
|
||||
|
||||
return ["success" => "Files uploaded successfully"];
|
||||
return ['success' => 'Files uploaded successfully'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Recursively removes a directory and its contents.
|
||||
*
|
||||
* @param string $dir The directory to remove.
|
||||
* @return void
|
||||
*/
|
||||
private static function rrmdir(string $dir): void {
|
||||
private static function rrmdir(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
@@ -231,7 +263,7 @@ class UploadModel {
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the temporary chunk directory for resumable uploads.
|
||||
*
|
||||
@@ -240,25 +272,26 @@ class UploadModel {
|
||||
* @param string $folder The folder name provided (URL-decoded).
|
||||
* @return array Returns a status array indicating success or error.
|
||||
*/
|
||||
public static function removeChunks(string $folder): array {
|
||||
public static function removeChunks(string $folder): array
|
||||
{
|
||||
$folder = urldecode($folder);
|
||||
// The folder name should exactly match the "resumable_" pattern.
|
||||
$regex = "/^resumable_" . PATTERN_FOLDER_NAME . "$/u";
|
||||
if (!preg_match($regex, $folder)) {
|
||||
return ["error" => "Invalid folder name"];
|
||||
return ['error' => 'Invalid folder name'];
|
||||
}
|
||||
|
||||
|
||||
$tempDir = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . $folder;
|
||||
if (!is_dir($tempDir)) {
|
||||
return ["success" => true, "message" => "Temporary folder already removed."];
|
||||
return ['success' => true, 'message' => 'Temporary folder already removed.'];
|
||||
}
|
||||
|
||||
|
||||
self::rrmdir($tempDir);
|
||||
|
||||
|
||||
if (!is_dir($tempDir)) {
|
||||
return ["success" => true, "message" => "Temporary folder removed."];
|
||||
} else {
|
||||
return ["error" => "Failed to remove temporary folder."];
|
||||
return ['success' => true, 'message' => 'Temporary folder removed.'];
|
||||
}
|
||||
|
||||
return ['error' => 'Failed to remove temporary folder.'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user