PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.0.13
Kirki – Freeform Page Builder, Website Builder & Customizer v6.0.13
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / libraries / framework / Filesystem / Filesystem.php
kirki / libraries / framework / Filesystem Last commit date
File.php 3 weeks ago FileSystemServiceProvider.php 3 weeks ago Fileable.php 3 weeks ago Filesystem.php 3 weeks ago Path.php 3 weeks ago UploadedFile.php 3 weeks ago
Filesystem.php
516 lines
1 <?php
2
3 /**
4 * WordPress-aware filesystem wrapper around WP_Filesystem with read, write, delete, and directory helpers.
5 * Integrates capability checks and sanitization for admin file operations.
6 * Supports Macroable extension for custom file helpers.
7 *
8 * @package Framework
9 * @subpackage Filesystem
10 * @since 1.0.0
11 */
12 namespace Kirki\Framework\Filesystem;
13
14 \defined('ABSPATH') || exit;
15 use Exception;
16 use Kirki\Framework\Exceptions\AuthorizationException;
17 use Kirki\Framework\Exceptions\NotFoundException;
18 use Kirki\Framework\Sanitizer;
19 use Kirki\Framework\Supports\Traits\Macroable;
20 use Kirki\Framework\Wordpress\Constants\Capabilities;
21 use WP_Filesystem_Base;
22 use function Kirki\Framework\Polyfill\str_starts_with;
23 use function Kirki\Framework\message;
24 class Filesystem
25 {
26 use Macroable;
27 /**
28 * The WordPress filesystem.
29 *
30 * @var WP_Filesystem_Base
31 *
32 * @since 1.0.0
33 */
34 protected $filesystem;
35 /**
36 * Create a new filesystem instance.
37 *
38 * @return void
39 *
40 * @throws \RuntimeException
41 *
42 * @since 1.0.0
43 */
44 public function __construct()
45 {
46 if (!\function_exists('WP_Filesystem')) {
47 require_once ABSPATH . 'wp-admin/includes/file.php';
48 }
49 WP_Filesystem();
50 global $wp_filesystem;
51 $this->filesystem = $wp_filesystem;
52 if (!$this->filesystem instanceof WP_Filesystem_Base) {
53 throw new \RuntimeException('WordPress filesystem is not available.');
54 }
55 }
56 /**
57 * Check if the given path is a file.
58 *
59 * @param mixed $file The file.
60 *
61 * @return bool
62 *
63 * @since 1.0.0
64 */
65 public function is_file($file)
66 {
67 return $this->filesystem->is_file($file);
68 }
69 /**
70 * Find path names matching a pattern.
71 *
72 * @param mixed $pattern The pattern.
73 * @param mixed $flags The flags.
74 *
75 * @return array
76 *
77 * @since 1.0.0
78 */
79 public function glob($pattern, $flags = 0)
80 {
81 return \glob($pattern, $flags);
82 }
83 /**
84 * Get the base name of a path.
85 *
86 * @param mixed $path The path.
87 *
88 * @return string
89 *
90 * @since 1.0.0
91 */
92 public function basename($path)
93 {
94 return \pathinfo($path, \PATHINFO_BASENAME);
95 }
96 /**
97 * Get the file name of a path.
98 *
99 * @param mixed $path The path.
100 *
101 * @return string
102 *
103 * @since 1.0.0
104 */
105 public function name($path)
106 {
107 return \pathinfo($path, \PATHINFO_FILENAME);
108 }
109 /**
110 * Get the file extension of a path.
111 *
112 * @param mixed $path The path.
113 *
114 * @return string
115 *
116 * @since 1.0.0
117 */
118 public function extension($path)
119 {
120 return \pathinfo($path, \PATHINFO_EXTENSION);
121 }
122 /**
123 * Copy a file to a new location.
124 *
125 * @param mixed $path The path.
126 * @param mixed $target The target.
127 *
128 * @return bool
129 *
130 * @since 1.0.0
131 */
132 public function copy($path, $target)
133 {
134 return $this->filesystem->copy($path, $target, \true, FS_CHMOD_FILE);
135 }
136 /**
137 * Move a file to a new location.
138 *
139 * @param mixed $path The path.
140 * @param mixed $target The target.
141 *
142 * @return bool
143 *
144 * @since 1.0.0
145 */
146 public function move($path, $target)
147 {
148 return $this->filesystem->move($path, $target, \true);
149 }
150 /**
151 * Delete the file at a given path.
152 *
153 * @param mixed $paths The paths.
154 *
155 * @return bool
156 *
157 * @since 1.0.0
158 */
159 public function delete($paths, bool $recursive = \true)
160 {
161 $paths = \is_array($paths) ? $paths : \func_get_args();
162 $success = \true;
163 foreach ($paths as $path) {
164 if (!$this->filesystem->delete($path, $recursive)) {
165 $success = \false;
166 }
167 }
168 return $success;
169 }
170 /**
171 * Get or set permissions of a file or directory.
172 *
173 * @param mixed $path The path.
174 * @param mixed $mode The mode.
175 *
176 * @return mixed
177 *
178 * @since 1.0.0
179 */
180 public function chmod($path, $mode = null)
181 {
182 if ($mode) {
183 return $this->filesystem->chmod($path, $mode);
184 }
185 return \substr(\sprintf('%o', \fileperms($path)), -4);
186 }
187 /**
188 * Append to a file.
189 *
190 * @param mixed $path The path.
191 * @param mixed $data The data payload.
192 *
193 * @return bool
194 *
195 * @since 1.0.0
196 */
197 public function append($path, $data)
198 {
199 if ($this->exists($path)) {
200 return $this->put($path, $this->get($path) . $data);
201 }
202 return $this->put($path, $data);
203 }
204 /**
205 * Prepend to a file.
206 *
207 * @param mixed $path The path.
208 * @param mixed $data The data payload.
209 *
210 * @return bool
211 *
212 * @since 1.0.0
213 */
214 public function prepend($path, $data)
215 {
216 if ($this->exists($path)) {
217 return $this->put($path, $data . $this->get($path));
218 }
219 return $this->put($path, $data);
220 }
221 /**
222 * Write the contents of a file.
223 *
224 * @param mixed $path The path.
225 * @param mixed $data The data payload.
226 *
227 * @return bool
228 *
229 * @since 1.0.0
230 */
231 public function put($path, $data)
232 {
233 if ($this->missing($path)) {
234 $this->make_dir($path);
235 }
236 return $this->filesystem->put_contents($path, $data, FS_CHMOD_FILE);
237 }
238 /**
239 * Get the contents of a file.
240 *
241 * @param mixed $path The path.
242 *
243 * @return string
244 *
245 * @throws NotFoundException
246 *
247 * @since 1.0.0
248 */
249 public function get($path)
250 {
251 if (!$this->is_file($path)) {
252 throw new NotFoundException(message('filesystem.file_not_found', $path));
253 }
254 $contents = $this->filesystem->get_contents($path);
255 if ($contents === \false) {
256 throw new NotFoundException(message('filesystem.file_not_found', $path));
257 }
258 return $contents;
259 }
260 /**
261 * Get the contents of a JSON file and decode it.
262 *
263 * @param mixed $path The path.
264 * @param mixed $flags The flags.
265 *
266 * @return array
267 *
268 * @since 1.0.0
269 */
270 public function json($path, $flags = 0)
271 {
272 return \json_decode($this->get($path), \true, 512, $flags);
273 }
274 /**
275 * Calculate the hash of a file.
276 *
277 * @param mixed $path The path.
278 * @param mixed $algorithm The algorithm.
279 *
280 * @return string
281 *
282 * @since 1.0.0
283 */
284 public function hash($path, $algorithm = 'md5')
285 {
286 return \hash_file($algorithm, $path);
287 }
288 /**
289 * Determine if a file or directory exists.
290 *
291 * @param mixed $path The path.
292 *
293 * @return bool
294 *
295 * @since 1.0.0
296 */
297 public function exists($path)
298 {
299 return $this->filesystem->exists($path);
300 }
301 /**
302 * Determine if a file or directory does not exist.
303 *
304 * @param mixed $path The path.
305 *
306 * @return bool
307 *
308 * @since 1.0.0
309 */
310 public function missing($path)
311 {
312 return !$this->exists($path);
313 }
314 /**
315 * Get the directory name of a path.
316 *
317 * @param mixed $path The path.
318 *
319 * @return string
320 *
321 * @since 1.0.0
322 */
323 public function dirname($path)
324 {
325 return \pathinfo($path, \PATHINFO_DIRNAME);
326 }
327 /**
328 * Get the file type of a path.
329 *
330 * @param mixed $path The path.
331 *
332 * @return string
333 *
334 * @since 1.0.0
335 */
336 public function type($path)
337 {
338 return \filetype($path);
339 }
340 /**
341 * Get the MIME type of a path.
342 *
343 * @param mixed $path The path.
344 *
345 * @return string
346 *
347 * @since 1.0.0
348 */
349 public function mime_type($path)
350 {
351 return \finfo_file(\finfo_open(\FILEINFO_MIME_TYPE), $path);
352 }
353 /**
354 * Get the size of a file.
355 *
356 * @param mixed $path The path.
357 *
358 * @return int|false
359 *
360 * @since 1.0.0
361 */
362 public function size($path)
363 {
364 return $this->filesystem->size($path);
365 }
366 /**
367 * Determine if a file is a directory.
368 *
369 * @param mixed $path The path.
370 *
371 * @return bool
372 *
373 * @since 1.0.0
374 */
375 public function is_directory($path)
376 {
377 return $this->filesystem->is_dir($path);
378 }
379 /**
380 * Determine if a file is readable.
381 *
382 * @param mixed $path The path.
383 *
384 * @return bool
385 *
386 * @since 1.0.0
387 */
388 public function is_readable($path)
389 {
390 return $this->filesystem->is_readable($path);
391 }
392 /**
393 * Determine if a file is writable.
394 *
395 * @param mixed $path The path.
396 *
397 * @return bool
398 *
399 * @since 1.0.0
400 */
401 public function is_writable($path)
402 {
403 return $this->filesystem->is_writable($path);
404 }
405 /**
406 * Get the last modified time of a file.
407 *
408 * @param mixed $path The path.
409 *
410 * @return int|false
411 *
412 * @since 1.0.0
413 */
414 public function last_modified($path)
415 {
416 return $this->filesystem->mtime($path);
417 }
418 /**
419 * Make a directory.
420 *
421 * @param mixed $path The path.
422 *
423 * @return bool
424 *
425 * @since 1.0.0
426 */
427 public function make_dir($path)
428 {
429 $path = \pathinfo($path, \PATHINFO_EXTENSION) !== '' ? $this->dirname($path) : $path;
430 if ($this->exists($path)) {
431 return \true;
432 }
433 return wp_mkdir_p($path);
434 }
435 /**
436 * Make a file or directory.
437 *
438 * @param string $path The path.
439 *
440 * @return bool
441 *
442 * @since 1.0.0
443 */
444 public function make(string $path)
445 {
446 if ($this->exists($path)) {
447 return;
448 }
449 if ($this->is_directory($path)) {
450 $this->make_dir($path);
451 return;
452 }
453 return $this->filesystem->touch($path);
454 }
455 /**
456 * Upload an {@see UploadedFile} instance to a specified directory.
457 *
458 * This method validates that the current user has permission to upload files, applies file name sanitization,
459 * ensures the upload directory exists (creating it if necessary),
460 * and then moves the uploaded file to the target location.
461 *
462 * @param string $path The relative subdirectory within the upload root to store the file.
463 * @param UploadedFile $file The uploaded file instance to store.
464 * @param string|null $name Optional. The file name to use for storage. Defaults to the original client file name.
465 *
466 * @return string Absolute path to the stored file on success.
467 *
468 * @throws AuthorizationException
469 *
470 * @since 1.0.0
471 */
472 public function upload(string $path, UploadedFile $file, $name = null)
473 {
474 if (!current_user_can(Capabilities::UPLOAD_FILES)) {
475 throw new AuthorizationException(message('auth.upload_forbidden'));
476 }
477 if (\is_null($name)) {
478 $name = $file->get_client_original_name();
479 }
480 $name = Sanitizer::apply_rule($name, Sanitizer::FILE_NAME);
481 $directory = $this->make_upload_directory($path);
482 if ($this->missing($directory)) {
483 $this->make_dir($directory);
484 }
485 $response = $file->move($directory, $name);
486 return $response->getPathname();
487 }
488 /**
489 * Make the upload directory.
490 *
491 * @param string $path The path.
492 *
493 * @return string
494 *
495 * @throws \Exception
496 * @throws AuthorizationException
497 *
498 * @since 1.0.0
499 */
500 protected function make_upload_directory(string $path)
501 {
502 $upload_directory = wp_upload_dir()['basedir'];
503 $base = \realpath($upload_directory);
504 if ($base === \false) {
505 throw new Exception(message('upload.directory_unavailable'));
506 }
507 $relative = Path::normalize($path);
508 $directory = \str_replace('\\', '/', Path::join($base, $relative));
509 $base_normalized = \str_replace('\\', '/', $base);
510 if ($directory !== $base_normalized && !str_starts_with($directory, $base_normalized . '/')) {
511 throw new AuthorizationException(message('auth.invalid_upload_path'));
512 }
513 return \str_replace('/', \DIRECTORY_SEPARATOR, $directory);
514 }
515 }
516