isFileTypeSupported($file)) { // throw new Exception('File type not supported'); // } // if ($file['size'] > $this->getFileUploadLimit()) { // throw new Exception('File size is too large'); // } // } private function getFileUploadLimit() { // Logic for calculating file upload limit as in your original code return min( wp_convert_hr_to_bytes(ini_get('upload_max_filesize')), wp_convert_hr_to_bytes(ini_get('post_max_size')), wp_max_upload_size() ); } /** * Delete an attachment's stored file once no attachment row references it. * * Local files are removed here, but only when the stored path belongs to * Fluent Boards. Files on a cloud driver are handed to the storage add-on * through the `fluent_boards/delete_remote_attachment_file` action, because * the core plugin holds no bucket credentials. * * @param mixed $attachment Attachment row or snapshot (driver, file_path, attachment_type). * @param int|null $boardId * @param array|null $sharedReferences Preloaded local references from getSharedLocalReferences(). * @return bool */ public function deleteAttachmentFile($attachment, $boardId = null, $sharedReferences = null) { if ( !$attachment || $attachment->attachment_type === 'url' || empty($attachment->file_path) ) { return false; } if (!empty($attachment->driver) && $attachment->driver !== 'local') { // Cloned tasks share the remote object, so keep it while another row points at it. if (Attachment::where('file_path', $attachment->file_path)->exists()) { return false; } do_action('fluent_boards/delete_remote_attachment_file', $attachment); return true; } $storedFilename = rawurldecode((string) $attachment->file_path); $isBareFilename = $storedFilename !== '' && strpos($storedFilename, '/') === false && strpos($storedFilename, '\\') === false; $filePath = FileSystem::resolveLocalAttachmentPath($attachment->file_path, $boardId); if (!$filePath) { return false; } // Legacy filenames need the board-qualified URL to distinguish same-named files across boards. if ($isBareFilename) { if ( empty($attachment->full_url) || $this->hasSharedLocalReference('full_url', $attachment->full_url, $sharedReferences) ) { return false; } } elseif ($this->hasSharedLocalReference('file_path', $attachment->file_path, $sharedReferences)) { return false; } return (bool) wp_delete_file($filePath); } /** * Batch the shared-reference lookups deleteAttachmentFile() makes for local files. * * @param iterable $attachments Deleted attachment rows or snapshots. * @return array ['file_path' => [value => true], 'full_url' => [value => true]] */ public function getSharedLocalReferences($attachments) { $values = ['file_path' => [], 'full_url' => []]; foreach ($attachments as $attachment) { if (!empty($attachment->file_path)) { $values['file_path'][] = (string) $attachment->file_path; } if (!empty($attachment->full_url)) { $values['full_url'][] = (string) $attachment->full_url; } } $references = ['file_path' => [], 'full_url' => []]; foreach ($values as $column => $columnValues) { foreach (array_chunk(array_values(array_unique($columnValues)), 200) as $chunk) { foreach (Attachment::whereIn($column, $chunk)->pluck($column)->toArray() as $value) { $references[$column][(string) $value] = true; } } } return $references; } private function hasSharedLocalReference($column, $value, $sharedReferences) { if (is_array($sharedReferences)) { return isset($sharedReferences[$column][(string) $value]); } return Attachment::where($column, $value)->exists(); } /** * Action Scheduler callback: delete the remote files of attachment rows that * were removed in bulk (a deleted board), outside the request that deleted them. * * @param array $files List of ['file_path' => string, 'driver' => string]. * @return void */ public function deleteRemoteAttachmentFiles($files) { foreach ((array) $files as $file) { if (!is_array($file) || empty($file['file_path']) || empty($file['driver'])) { continue; } $this->deleteAttachmentFile((object) [ 'attachment_type' => 'file', 'driver' => sanitize_key($file['driver']), 'file_path' => (string) $file['file_path'], ]); } } /** * Summary of isFileTypeSupported checking file type it will allow only file which is readable by browser * @param mixed $file * TODO: Refactorable: This can be in a Helper class. and we may pass it to frontend via wp_localize_script appvars * so that we can check similarly for better experience. * @return bool */ public function isFileTypeSupported($file) { // Define supported file types that are generally allowed by user $allowedMimeTypes = get_allowed_mime_types(); // Check if the file type is supported return in_array(strtolower($file['type']), $allowedMimeTypes); } /** * @throws Exception */ public function handleMediaFileUpload($data) { // Check if file was uploaded // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer if (!isset($_FILES['file']['tmp_name']) || !isset($_FILES['file']['name'])) { throw new Exception(esc_html__('No file was uploaded. Please try again.', 'fluent-boards')); } // Sanitize filename from request for validation // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer $filename = sanitize_file_name(wp_unslash($_FILES['file']['name'])); // Validate and sanitize tmp_name before use // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by REST API/controller layer $tmp_name = sanitize_text_field(wp_unslash($_FILES['file']['tmp_name'])); if (empty($tmp_name) || !file_exists($tmp_name) || !is_uploaded_file($tmp_name)) { throw new Exception(esc_html__('Invalid upload. Please try again.', 'fluent-boards')); } // Check if the uploaded file is an image $wp_filetype = wp_check_filetype_and_ext($tmp_name, $filename); if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) { throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards')); } require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once( ABSPATH . 'wp-admin/includes/file.php' ); require_once( ABSPATH . 'wp-admin/includes/media.php' ); $attachment_id = media_handle_upload( 'file', 0, [] ); $attachment = wp_prepare_attachment_for_js( $attachment_id); if(!$attachment) { throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards')); } else { return $attachment; } } }