// UPLOAD
if($task === 'upload'){
// upload path must be dir
if(!$is_dir) json_error('invalid dir ' . $post_path);
// upload path must be writeable
if(!is_writable($path)) json_error('upload dir ' . $post_path . ' is not writeable');
// get $_FILES['file']
$file = isset($_FILES) && isset($_FILES['file']) && is_array($_FILES['file']) ? $_FILES['file'] : false;
// invalid $_FILES['file']
if(empty($file) || !isset($file['error']) || is_array($file['error'])) json_error('invalid $_FILES[]');
// PHP meaningful file upload errors / https://www.php.net/manual/en/features.file-upload.errors.php
if($file['error'] !== 0) {
$upload_errors = array(
1 => 'Uploaded file exceeds upload_max_filesize directive in php.ini',
2 => 'Uploaded file exceeds MAX_FILE_SIZE directive specified in the HTML form',
3 => 'The uploaded file was only partially uploaded',
4 => 'No file was uploaded',
6 => 'Missing a temporary folder',
7 => 'Failed to write file to disk.',
8 => 'A PHP extension stopped the file upload.'
);
json_error(isset($upload_errors[$file['error']]) ? $upload_errors[$file['error']] : 'unknown error');
}
// invalid $file['size']
if(!isset($file['size']) || empty($file['size'])) json_error('invalid file size');
// $file['size'] must not exceed $config['upload_max_filesize']
if(config::$config['upload_max_filesize'] && $file['size'] > config::$config['upload_max_filesize']) json_error('File size [' . $file['size'] . '] exceeds upload_max_filesize option [' . config::$config['upload_max_filesize'] . ']');
// filename
$filename = $file['name'];
// security: slashes are never ever allowed in filenames / always basenamed() but just in case
if(strpos($filename, '/') !== false || strpos($filename, '\\') !== false) json_error('Illegal \slash/ in filename ' . $filename);
// allow only valid file types from config::$config['upload_allowed_file_types'] / 'image/*, .pdf, .mp4'
$allowed_file_types = !empty(config::$config['upload_allowed_file_types']) ? array_filter(array_map('trim', explode(',', config::$config['upload_allowed_file_types']))) : false;
if(!empty($allowed_file_types)){
$mime = get_mime($file['tmp_name']) ?: $file['type']; // mime from PHP or upload[type]
$ext = strrchr(strtolower($filename), '.');
$is_valid = false;
// check if extension match || wildcard match mime type image/*
foreach ($allowed_file_types as $allowed_file_type) if($ext === ('.'.ltrim($allowed_file_type, '.')) || fnmatch($allowed_file_type, $mime)) {
$is_valid = true;
break;
}
if(!$is_valid) json_error('invalid file type ' . $filename);
// extra security: check if image is image
if(function_exists('exif_imagetype') && in_array($ext, ['.gif', '.jpeg', '.jpg', '.png', '.swf', '.psd', '.bmp', '.tif', '.tiff', 'webp', 'avif']) && !@exif_imagetype($file['tmp_name'])) json_error('invalid image type ' . $filename);
}
// file naming if !overwrite and file exists
if(config::$config['upload_exists'] !== 'overwrite' && file_exists("$path/$filename")){
// fail if !increment / 'upload_exists' => 'fail' || false || '' empty
if(config::$config['upload_exists'] !== 'increment') json_error("$filename already exists");
// increment filename / 'upload_exists' => 'increment'
$name = pathinfo($filename, PATHINFO_FILENAME);
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$inc = 1;
while(file_exists($path . '/' . $name . '-' . $inc . '.' . $ext)) $inc ++;
$filename = $name . '-' . $inc . '.' . $ext;
}
// all is well! attempt to move_uploaded_file()
if(@move_uploaded_file($file['tmp_name'], "$path/$filename")) fm_json_exit(true, array(
'success' => true,
'filename' => $filename, // return filename in case it was incremented or renamed
'url' => get_url_path("$path/$filename") // for usage with showLinkToFileUploadResult
));
// error if failed to move uploaded file
json_error('failed to move_uploaded_file()');