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 +108 -7 1.23trunk 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,14 +154,32 @@
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 + ];
164 + $uploadedFiles = []; // Initialize the array
81 165
166 + if(is_object($files)) {
167 + $files = [$files];
168 + }
169 +
82 170 foreach ((array)$files as $file) {
171 +
83 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 +
84 178 $extraData = Arr::only($filesArray, ['name', 'size']);
85 179 $uploadsData = \wp_handle_upload($filesArray, $uploadOverrides);
180 + // Add the full path to the file
181 + $uploadsData['full_path'] = $this->_getAbsolutePathOfFile($uploadsData['file']);
86 182 $uploadedFiles[] = array_merge($extraData, $uploadsData);
87 183 }
88 184
89 185 return $uploadedFiles;
@@ -100,9 +196,12 @@
100 196
101 197 foreach ($files as $file) {
102 198 $arr = explode('/', $file);
103 199 $fileName = end($arr);
104 - @unlink($this->getDir() . '/' . $fileName);
200 + $filePath = $this->getDir() . '/' . $fileName;
201 + if (file_exists($filePath)) {
202 + wp_delete_file($filePath);
203 + }
105 204 }
106 205 }
107 206
108 207 /**
@@ -134,8 +233,9 @@
134 233
135 234 if ($this->subDir) {
136 235 $fbsUploadDir .= DIRECTORY_SEPARATOR . $this->subDir;
137 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
138 238 @mkdir($param['basedir'] . $fbsUploadDir, 0755);
139 239 }
140 240 }
141 241
@@ -152,10 +252,9 @@
152 252 * @return array $file
153 253 */
154 254 public function _renameFileName($file)
155 255 {
156 - $currentTimeStamp = (new \DateTimeImmutable())->getTimestamp();
157 - $prefix = $currentTimeStamp . '-';
256 + $prefix = wp_generate_uuid4() . '-';
158 257 $prefix = apply_filters('fluent_boards/uploaded_file_name_prefix', $prefix);
159 258 $file['name'] = $prefix . $file['name'];
160 259
161 260 return $file;
@@ -183,12 +282,14 @@
183 282 // Recursively delete subdirectory
184 283 $this->deleteContents("$dir/$file");
185 284 } else {
186 285 // Delete file
286 + // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- Recursive directory cleanup, direct file operations required
187 287 unlink("$dir/$file");
188 288 }
189 289 }
190 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
191 292 return rmdir($dir);
192 293 }
193 294
194 295 public static function __callStatic($method, $params)