PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
← All changes | app/Hooks/Handlers/FileHandler.php +54 -41 1.202.1.0 View file →
@@ -3,12 +3,10 @@
3 3 namespace FluentBoards\App\Hooks\Handlers;
4 4
5 5 use DateTimeImmutable;
6 6 use Exception;
7 -use FluentBoards\App\Models\Meta;
8 -use FluentBoards\App\Services\Constant;
9 7 use FluentBoards\App\Services\Libs\FileSystem;
10 -use FluentBoardsPro\App\Models\Attachment;
8 +use FluentBoards\App\Models\Attachment;
11 9 use function Sodium\add;
12 10
13 11 class FileHandler
14 12 {
@@ -34,39 +32,48 @@
34 32 );
35 33 }
36 34
37 35 /**
38 - * Summary of deleteFileByUrl
39 - * @param mixed $file_url
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 40 * @return bool
41 41 */
42 - public function deleteFileByUrl($file_url)
42 + public function deleteAttachmentFile($attachment, $boardId = null)
43 43 {
44 - $exists = Attachment::where('full_url', $file_url)->exists();
45 - if($exists) {
46 - return;
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;
47 51 }
48 - // Convert the URL to the local file path
49 - $upload_dir = wp_upload_dir();
50 - $file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_url);
51 52
52 - // Check if the file exists
53 - if (file_exists($file_path)) {
54 - // Delete the file
55 - $deleted = unlink($file_path);
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);
56 58
57 - // Optionally, you can also remove the file from the media library
58 - // Note: This won't delete the file physically, but it will remove it from the media library
59 - $attachment_id = attachment_url_to_postid($file_url);
60 - if ($attachment_id) {
61 - wp_delete_attachment($attachment_id, true);
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;
62 70 }
71 + } elseif (Attachment::where('file_path', $attachment->file_path)->exists()) {
72 + return false;
73 + }
63 74
64 - // Return true if the file was successfully deleted
65 - return $deleted;
66 - }
67 - // Return false if the file does not exist
68 - return false;
75 + return (bool) wp_delete_file($filePath);
69 76 }
70 77
71 78
72 79 /**
@@ -89,13 +96,31 @@
89 96 * @throws Exception
90 97 */
91 98 public function handleMediaFileUpload($data)
92 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 +
93 118 // Check if the uploaded file is an image
94 - $wp_filetype = wp_check_filetype_and_ext( $_FILES['file']['tmp_name'], $_FILES['file']['name'] );
119 + $wp_filetype = wp_check_filetype_and_ext($tmp_name, $filename);
95 120
96 121 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
97 - throw new Exception('The uploaded file is not a valid image. Please try again.');
122 + throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards'));
98 123 }
99 124 require_once(ABSPATH . 'wp-admin/includes/image.php');
100 125 require_once( ABSPATH . 'wp-admin/includes/file.php' );
101 126 require_once( ABSPATH . 'wp-admin/includes/media.php' );
@@ -102,23 +127,11 @@
102 127 $attachment_id = media_handle_upload( 'file', 0, [] );
103 128
104 129 $attachment = wp_prepare_attachment_for_js( $attachment_id);
105 130 if(!$attachment) {
106 - throw new Exception('The uploaded file is not a valid image. Please try again.');
131 + throw new Exception(esc_html__('The uploaded file is not a valid image. Please try again.', 'fluent-boards'));
107 132 } else {
108 133 return $attachment;
109 134 }
110 135 }
111 136
112 - /**
113 - * This function will delete meta where default board default image is used.
114 - * @param $id
115 - * @return void
116 - */
117 - public function mediaFileDeleted($id)
118 - {
119 - $boardImage = Meta::where('object_id', $id)->where('object_type', Constant::BOARD_DEFAULT_IMAGE)->first();
120 - if($boardImage) {
121 - $boardImage->delete();
122 - }
123 - }
124 137 }