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
fluent-boards / app / Services / Libs / FileSystem.php

FileSystem.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration trunk, at app/Services/Libs/FileSystem.php

311 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Services\Libs;
4
5 use FluentBoards\Framework\Support\Arr;
6
7 class FileSystem
8 {
9 protected $subDir = '';
10
11 public function _setSubDir($subDir)
12 {
13 $this->subDir = $subDir;
14 return $this;
15 }
16 /**
17 * Read file content from custom upload dir of this application
18 * @return string [path]
19 */
20 public function _get($file)
21 {
22 $arr = explode('/', $file);
23 $fileName = end($arr);
24 if ($this->subDir) {
25 $fileName = $this->subDir . DIRECTORY_SEPARATOR . $fileName;
26 }
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);
38 }
39
40 /**
41 * Get custom upload dir name of this application
42 * @return string [directory path]
43 */
44 public function _getDir()
45 {
46 $uploadDir = wp_upload_dir();
47
48 $fbsUploadDir = apply_filters('fluent_boards/upload_folder_name', FLUENT_BOARDS_UPLOAD_DIR);
49
50 if ($this->subDir) {
51 return $uploadDir['basedir'] . DIRECTORY_SEPARATOR . $fbsUploadDir. DIRECTORY_SEPARATOR. $this->subDir;
52 }
53
54 return $uploadDir['basedir'] . DIRECTORY_SEPARATOR . $fbsUploadDir;
55 }
56
57 /**
58 * Get absolute path of file using custom upload dir name of this application
59 * @return string [file path]
60 */
61 public function _getAbsolutePathOfFile($file)
62 {
63 return $this->_getDir() . DIRECTORY_SEPARATOR . $file;
64 }
65
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 /**
137 * Upload files into custom upload dir of this application
138 * @return array
139 */
140 public function _uploadFromRequest()
141 {
142 return $this->_put(FluentBoards('request')->files());
143 }
144
145 /**
146 * Upload files into custom upload dir of this application
147 * @param array $files
148 * @return array
149 */
150 public function _put($files)
151 {
152 if (!function_exists('wp_handle_upload')) {
153 require_once(ABSPATH . 'wp-admin/includes/file.php');
154 }
155
156 $this->overrideUploadDir();
157
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
165
166 if(is_object($files)) {
167 $files = [$files];
168 }
169
170 foreach ((array)$files as $file) {
171
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
178 $extraData = Arr::only($filesArray, ['name', 'size']);
179 $uploadsData = \wp_handle_upload($filesArray, $uploadOverrides);
180 // Add the full path to the file
181 $uploadsData['full_path'] = $this->_getAbsolutePathOfFile($uploadsData['file']);
182 $uploadedFiles[] = array_merge($extraData, $uploadsData);
183 }
184
185 return $uploadedFiles;
186 }
187
188 /**
189 * Delete a file from custom upload directory of this application
190 * @param array $files
191 * @return void
192 */
193 public function _delete($files)
194 {
195 $files = (array)$files;
196
197 foreach ($files as $file) {
198 $arr = explode('/', $file);
199 $fileName = end($arr);
200 $filePath = $this->getDir() . '/' . $fileName;
201 if (file_exists($filePath)) {
202 wp_delete_file($filePath);
203 }
204 }
205 }
206
207 /**
208 * Register filters for custom upload dir
209 */
210 public function _overrideUploadDir()
211 {
212 add_filter('wp_handle_upload_prefilter', function ($file) {
213 add_filter('upload_dir', [$this, '_setCustomUploadDir']);
214
215 add_filter('wp_handle_upload', function ($fileinfo) {
216 remove_filter('upload_dir', [$this, '_setCustomUploadDir']);
217 $fileinfo['file'] = basename($fileinfo['file']);
218 return $fileinfo;
219 });
220
221 return $this->_renameFileName($file);
222 });
223 }
224
225 /**
226 * Set plugin's custom upload dir
227 * @param array $param
228 * @return array $param
229 */
230 public function _setCustomUploadDir($param)
231 {
232 $fbsUploadDir = apply_filters('fluent_boards/upload_folder_name', FLUENT_BOARDS_UPLOAD_DIR);
233
234 if ($this->subDir) {
235 $fbsUploadDir .= DIRECTORY_SEPARATOR . $this->subDir;
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
238 @mkdir($param['basedir'] . $fbsUploadDir, 0755);
239 }
240 }
241
242 $param['url'] = $param['baseurl'] . DIRECTORY_SEPARATOR . $fbsUploadDir;
243
244 $param['path'] = $param['basedir'] . DIRECTORY_SEPARATOR . $fbsUploadDir;
245
246 return $param;
247 }
248
249 /**
250 * Rename the uploaded file name before saving
251 * @param array $file
252 * @return array $file
253 */
254 public function _renameFileName($file)
255 {
256 $prefix = wp_generate_uuid4() . '-';
257 $prefix = apply_filters('fluent_boards/uploaded_file_name_prefix', $prefix);
258 $file['name'] = $prefix . $file['name'];
259
260 return $file;
261 }
262
263 public function _deleteDir($dir)
264 {
265 $dir = $this->getDir() . DIRECTORY_SEPARATOR . $dir;
266 if (is_dir($dir)) {
267 $this->deleteContents($dir);
268 }
269 }
270
271 /**
272 * Delete a directory and its content
273 * Lead Note: this method should be called via scheduler or queue
274 * @param string $dir
275 * @return bool
276 */
277 private function deleteContents($dir)
278 {
279 $files = array_diff(scandir($dir), ['.', '..']);
280 foreach ($files as $file) {
281 if(is_dir("$dir/$file")) {
282 // Recursively delete subdirectory
283 $this->deleteContents("$dir/$file");
284 } else {
285 // Delete file
286 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- Recursive directory cleanup, direct file operations required
287 unlink("$dir/$file");
288 }
289 }
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
292 return rmdir($dir);
293 }
294
295 public static function __callStatic($method, $params)
296 {
297 $instance = new static;
298
299 return call_user_func_array([$instance, $method], $params);
300 }
301
302 public function __call($method, $params)
303 {
304 $hiddenMethod = "_" . $method;
305
306 $method = method_exists($this, $hiddenMethod) ? $hiddenMethod : $method;
307
308 return call_user_func_array([$this, $method], $params);
309 }
310 }
311