PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.45 All 41 releases
← All changes | app/Services/Libs/FileSystem.php +105 -7 1.41trunk View file →
@@ -23,11 +23,19 @@
23 23 $fileName = end($arr);
24 24 if ($this->subDir) {
25 25 $fileName = $this->subDir . DIRECTORY_SEPARATOR . $fileName;
26 26 }
27 - return file_get_contents(
28 - $this->getDir() . DIRECTORY_SEPARATOR . $fileName
29 - );
27 +
28 + $filePath = $this->getDir() . DIRECTORY_SEPARATOR . $fileName;
29 +
30 + // Use WordPress Filesystem API
31 + global $wp_filesystem;
32 + if (!function_exists('WP_Filesystem')) {
33 + require_once(ABSPATH . 'wp-admin/includes/file.php');
34 + }
35 + WP_Filesystem();
36 +
37 + return $wp_filesystem->get_contents($filePath);
30 38 }
31 39
32 40 /**
33 41 * Get custom upload dir name of this application
@@ -55,8 +63,78 @@
55 63 return $this->_getDir() . DIRECTORY_SEPARATOR . $file;
56 64 }
57 65
58 66 /**
67 + * Resolve a stored attachment path only when it belongs to Fluent Boards uploads.
68 + *
69 + * @param string $storedPath
70 + * @param int|null $boardId
71 + * @return string|null
72 + */
73 + public function _resolveLocalAttachmentPath($storedPath, $boardId = null)
74 + {
75 + if (!$storedPath) {
76 + return null;
77 + }
78 +
79 + $storedFilename = rawurldecode((string) $storedPath);
80 + $isBareFilename = $storedFilename !== ''
81 + && strpos($storedFilename, '/') === false
82 + && strpos($storedFilename, '\\') === false;
83 +
84 + if ($isBareFilename && $boardId === null) {
85 + return null;
86 + }
87 +
88 + $filePath = $isBareFilename ? null : realpath($storedPath);
89 + $allowedDirectory = $this->_getDir();
90 + $pluginRoot = realpath($allowedDirectory);
91 +
92 + if (!$pluginRoot) {
93 + return null;
94 + }
95 +
96 + $pluginRoot = rtrim($pluginRoot, DIRECTORY_SEPARATOR);
97 + $allowedRoot = $pluginRoot;
98 +
99 + if ($boardId !== null) {
100 + if (!is_int($boardId) && !is_string($boardId)) {
101 + return null;
102 + }
103 +
104 + $boardId = filter_var($boardId, FILTER_VALIDATE_INT, [
105 + 'options' => ['min_range' => 1],
106 + ]);
107 + if ($boardId === false) {
108 + return null;
109 + }
110 +
111 + $allowedDirectory .= DIRECTORY_SEPARATOR . 'board_' . $boardId;
112 + if (is_link($allowedDirectory)) {
113 + return null;
114 + }
115 +
116 + $allowedRoot = realpath($allowedDirectory);
117 + $expectedBoardRoot = $pluginRoot . DIRECTORY_SEPARATOR . 'board_' . $boardId;
118 + if (!$allowedRoot || $allowedRoot !== $expectedBoardRoot) {
119 + return null;
120 + }
121 + }
122 +
123 + if ($isBareFilename) {
124 + $filePath = realpath($allowedDirectory . DIRECTORY_SEPARATOR . $storedFilename);
125 + }
126 +
127 + if (!$filePath || !$allowedRoot || !is_file($filePath)) {
128 + return null;
129 + }
130 +
131 + $allowedRoot = rtrim($allowedRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
132 +
133 + return strpos($filePath, $allowedRoot) === 0 ? $filePath : null;
134 + }
135 +
136 + /**
59 137 * Upload files into custom upload dir of this application
60 138 * @return array
61 139 */
62 140 public function _uploadFromRequest()
@@ -76,13 +154,28 @@
76 154 }
77 155
78 156 $this->overrideUploadDir();
79 157
80 - $uploadOverrides = ['test_form' => false];
158 + $uploadOverrides = [
159 + 'test_form' => false,
160 + // Accept the same allow-list FluentBoards validates against, so wp_handle_upload
161 + // doesn't reject broader types (e.g. .json, .md) by extension.
162 + 'mimes' => \FluentBoards\App\Services\UploadService::getAllowedMimeMap(),
163 + ];
81 164 $uploadedFiles = []; // Initialize the array
82 165
166 + if(is_object($files)) {
167 + $files = [$files];
168 + }
169 +
83 170 foreach ((array)$files as $file) {
171 +
84 172 $filesArray = $file->toArray();
173 +
174 + // tmp_name is the path to the uploaded file which is required for wp_handle_upload, new framework update
175 + // changed the way to get the tmp_name Reference to line # 448 in File.php
176 + $filesArray['tmp_name'] = $file->getRealPath();
177 +
85 178 $extraData = Arr::only($filesArray, ['name', 'size']);
86 179 $uploadsData = \wp_handle_upload($filesArray, $uploadOverrides);
87 180 // Add the full path to the file
88 181 $uploadsData['full_path'] = $this->_getAbsolutePathOfFile($uploadsData['file']);
@@ -103,9 +196,12 @@
103 196
104 197 foreach ($files as $file) {
105 198 $arr = explode('/', $file);
106 199 $fileName = end($arr);
107 - @unlink($this->getDir() . '/' . $fileName);
200 + $filePath = $this->getDir() . '/' . $fileName;
201 + if (file_exists($filePath)) {
202 + wp_delete_file($filePath);
203 + }
108 204 }
109 205 }
110 206
111 207 /**
@@ -137,8 +233,9 @@
137 233
138 234 if ($this->subDir) {
139 235 $fbsUploadDir .= DIRECTORY_SEPARATOR . $this->subDir;
140 236 if (!is_dir($param['basedir'] . $fbsUploadDir)) {
237 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- Creating custom upload subdirectory during upload process, WP_Filesystem not initialized
141 238 @mkdir($param['basedir'] . $fbsUploadDir, 0755);
142 239 }
143 240 }
144 241
@@ -155,10 +252,9 @@
155 252 * @return array $file
156 253 */
157 254 public function _renameFileName($file)
158 255 {
159 - $currentTimeStamp = (new \DateTimeImmutable())->getTimestamp();
160 - $prefix = $currentTimeStamp . '-';
256 + $prefix = wp_generate_uuid4() . '-';
161 257 $prefix = apply_filters('fluent_boards/uploaded_file_name_prefix', $prefix);
162 258 $file['name'] = $prefix . $file['name'];
163 259
164 260 return $file;
@@ -186,12 +282,14 @@
186 282 // Recursively delete subdirectory
187 283 $this->deleteContents("$dir/$file");
188 284 } else {
189 285 // Delete file
286 + // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- Recursive directory cleanup, direct file operations required
190 287 unlink("$dir/$file");
191 288 }
192 289 }
193 290 // Delete the directory itself. If the directory is not empty, it will not be deleted.
291 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Recursive directory removal, WP_Filesystem not suitable for this operation
194 292 return rmdir($dir);
195 293 }
196 294
197 295 public static function __callStatic($method, $params)