| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentBoards\App\Hooks\Handlers; |
| 4 |
|
| 5 |
use DateTimeImmutable; |
| 6 |
use Exception; |
| 7 |
use FluentBoards\App\Services\Libs\FileSystem; |
| 8 |
use FluentBoards\App\Models\Attachment; |
| 9 |
use function Sodium\add; |
| 10 |
|
| 11 |
class FileHandler |
| 12 |
{ |
| 13 |
// private function validateFile($file) |
| 14 |
// { |
| 15 |
// if (!$file) { |
| 16 |
// throw new Exception('File is empty.'); |
| 17 |
// } |
| 18 |
// if (!$this->isFileTypeSupported($file)) { |
| 19 |
// throw new Exception('File type not supported'); |
| 20 |
// } |
| 21 |
// if ($file['size'] > $this->getFileUploadLimit()) { |
| 22 |
// throw new Exception('File size is too large'); |
| 23 |
// } |
| 24 |
// } |
| 25 |
|
| 26 |
private function getFileUploadLimit() { |
| 27 |
// Logic for calculating file upload limit as in your original code |
| 28 |
return min( |
| 29 |
wp_convert_hr_to_bytes(ini_get('upload_max_filesize')), |
| 30 |
wp_convert_hr_to_bytes(ini_get('post_max_size')), |
| 31 |
wp_max_upload_size() |
| 32 |
); |
| 33 |
} |
| 34 |
|
| 35 |
/** |
| 36 |
* Delete a local attachment only when its stored path belongs to Fluent Boards. |
| 37 |
* |
| 38 |
* @param mixed $attachment |
| 39 |
* @param int|null $boardId |
| 40 |
* @return bool |
| 41 |
*/ |
| 42 |
public function deleteAttachmentFile($attachment, $boardId = null) |
| 43 |
{ |
| 44 |
if ( |
| 45 |
!$attachment || |
| 46 |
$attachment->attachment_type === 'url' || |
| 47 |
(!empty($attachment->driver) && $attachment->driver !== 'local') || |
| 48 |
empty($attachment->file_path) |
| 49 |
) { |
| 50 |
return false; |
| 51 |
} |
| 52 |
|
| 53 |
$storedFilename = rawurldecode((string) $attachment->file_path); |
| 54 |
$isBareFilename = $storedFilename !== '' |
| 55 |
&& strpos($storedFilename, '/') === false |
| 56 |
&& strpos($storedFilename, '\\') === false; |
| 57 |
$filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId); |
| 58 |
|
| 59 |
if (!$filePath) { |
| 60 |
return false; |
| 61 |
} |
| 62 |
|
| 63 |
// Legacy filenames need the board-qualified URL to distinguish same-named files across boards. |
| 64 |
if ($isBareFilename) { |
| 65 |
if ( |
| 66 |
empty($attachment->full_url) || |
| 67 |
Attachment::where('full_url', $attachment->full_url)->exists() |
| 68 |
) { |
| 69 |
return false; |
| 70 |
} |
| 71 |
} elseif (Attachment::where('file_path', $attachment->file_path)->exists()) { |
| 72 |
return false; |
| 73 |
} |
| 74 |
|
| 75 |
return (bool) wp_delete_file($filePath); |
| 76 |
} |
| 77 |
|
| 78 |
|
| 79 |
/** |
| 80 |
* Summary of isFileTypeSupported checking file type it will allow only file which is readable by browser |
| 81 |
* @param mixed $file |
| 82 |
* TODO: Refactorable: This can be in a Helper class. and we may pass it to frontend via wp_localize_script appvars |
| 83 |
* so that we can check similarly for better experience. |
| 84 |
* @return bool |
| 85 |
*/ |
| 86 |
public function isFileTypeSupported($file) |
| 87 |
{ |
| 88 |
// Define supported file types that are generally allowed by user |
| 89 |
$allowedMimeTypes = get_allowed_mime_types(); |
| 90 |
|
| 91 |
// Check if the file type is supported |
| 92 |
return in_array(strtolower($file['type']), $allowedMimeTypes); |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* @throws Exception |
| 97 |
*/ |
| 98 |
public function handleMediaFileUpload($data) |
| 99 |
{ |
| 100 |
// Check if file was uploaded |
| 101 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer |
| 102 |
if (!isset($_FILES['file']['tmp_name']) || !isset($_FILES['file']['name'])) { |
| 103 |
throw new Exception(esc_html__('No file was uploaded. Please try again.', 'fluent-boards')); |
| 104 |
} |
| 105 |
|
| 106 |
// Sanitize filename from request for validation |
| 107 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer |
| 108 |
$filename = sanitize_file_name(wp_unslash($_FILES['file']['name'])); |
| 109 |
|
| 110 |
// Validate and sanitize tmp_name before use |
| 111 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer |
| 112 |
$tmp_name = sanitize_text_field(wp_unslash($_FILES['file']['tmp_name'])); |
| 113 |
|
| 114 |
if (empty($tmp_name) || !file_exists($tmp_name) || !is_uploaded_file($tmp_name)) { |
| 115 |
throw new Exception(esc_html__('Invalid upload. Please try again.', 'fluent-boards')); |
| 116 |
} |
| 117 |
|
| 118 |
// Check if the uploaded file is an image |
| 119 |
$wp_filetype = wp_check_filetype_and_ext($tmp_name, $filename); |
| 120 |
|
| 121 |
if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) { |
| 122 |
throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards')); |
| 123 |
} |
| 124 |
require_once(ABSPATH . 'wp-admin/includes/image.php'); |
| 125 |
require_once( ABSPATH . 'wp-admin/includes/file.php' ); |
| 126 |
require_once( ABSPATH . 'wp-admin/includes/media.php' ); |
| 127 |
$attachment_id = media_handle_upload( 'file', 0, [] ); |
| 128 |
|
| 129 |
$attachment = wp_prepare_attachment_for_js( $attachment_id); |
| 130 |
if(!$attachment) { |
| 131 |
throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards')); |
| 132 |
} else { |
| 133 |
return $attachment; |
| 134 |
} |
| 135 |
} |
| 136 |
|
| 137 |
} |
| 138 |
|