PluginProbe
WPIDE – File Manager & Code Editor / 3.2
WPIDE – File Manager & Code Editor v3.2
3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 3.4 All 54 releases
wpide / App / Services / Storage / Adapters / WPFileSystem.php

WPFileSystem.php in WPIDE – File Manager & Code Editor 3.2, at App/Services/Storage/Adapters/WPFileSystem.php

576 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPIDE\App\Services\Storage\Adapters;
3
4 use finfo as Finfo;
5 use DirectoryIterator;
6 use FilesystemIterator;
7 use League\Flysystem\Config;
8 use League\Flysystem\Exception;
9 use League\Flysystem\NotSupportedException;
10 use League\Flysystem\UnreadableFileException;
11 use League\Flysystem\Util;
12 use League\Flysystem\Adapter\AbstractAdapter;
13 use LogicException;
14 use RecursiveDirectoryIterator;
15 use RecursiveIteratorIterator;
16 use SplFileInfo;
17 use WP_Filesystem_Base;
18
19 class WPFileSystem extends AbstractAdapter
20 {
21 /**
22 * @var int
23 */
24 const SKIP_LINKS = 0001;
25
26 /**
27 * @var int
28 */
29 const DISALLOW_LINKS = 0002;
30
31 /**
32 * @var array
33 */
34 protected static $permissions = [
35 'file' => [
36 'public' => 0644,
37 'private' => 0600,
38 ],
39 'dir' => [
40 'public' => 0755,
41 'private' => 0700,
42 ],
43 ];
44
45 /**
46 * @var string
47 */
48 protected $pathSeparator = DIRECTORY_SEPARATOR;
49
50 /**
51 * @var array
52 */
53 protected $permissionMap;
54
55 /**
56 * @var int
57 */
58 private $linkHandling;
59
60 /**
61 * @var WP_Filesystem_Base
62 */
63 protected $fs;
64
65 /**
66 * Constructor.
67 *
68 * @param string $root
69 * @param WP_Filesystem_Base $wp_fs
70 * @param int $linkHandling
71 * @param array $permissions
72 *
73 * @throws LogicException
74 * @throws Exception
75 */
76 public function __construct($root, $wp_fs, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
77 {
78
79 $this->fs = $wp_fs;
80
81 $root = is_link($root) ? realpath($root) : $root;
82 $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
83 $this->ensureDirectory($root);
84
85 if ( ! $this->fs->is_dir($root) || ! $this->fs->is_readable($root)) {
86 throw new LogicException('The root path ' . $root . ' is not readable.');
87 }
88
89 $this->setPathPrefix($root);
90 $this->linkHandling = $linkHandling;
91 }
92
93 /**
94 * Attempts to use the correct path for the FS method being used
95 *
96 * @param string $abs_path
97 *
98 * @return string
99 */
100 protected function getRelativePath( $abs_path ): string
101 {
102 return str_replace( ABSPATH, $this->fs->abspath(), $abs_path );
103 }
104
105 /**
106 * Ensure the root directory exists.
107 *
108 * @param string $root root directory path
109 *
110 * @return void
111 *
112 * @throws Exception in case the root directory can not be created
113 */
114 protected function ensureDirectory($root)
115 {
116 $root = $this->getRelativePath( $root );
117
118 if ( ! $this->fs->is_dir($root)) {
119 $umask = umask(0);
120
121 if ( ! wp_mkdir_p($root) ) {
122 $mkdirError = error_get_last();
123 }
124
125 umask($umask);
126 clearstatcache(false, $root);
127
128 if ( ! $this->fs->is_dir($root)) {
129 $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
130 throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage));
131 }
132 }
133 }
134
135 /**
136 * @inheritdoc
137 */
138 public function has($path)
139 {
140 $path = $this->getRelativePath( $path );
141 $location = $this->applyPathPrefix($path);
142
143 return $this->fs->exists($location);
144 }
145
146 /**
147 * @inheritdoc
148 * @throws Exception
149 */
150 public function write($path, $contents, Config $config)
151 {
152 $path = $this->getRelativePath( $path );
153 $location = $this->applyPathPrefix($path);
154 $this->ensureDirectory(dirname($location));
155
156 if (($size = $this->fs->put_contents($location, $contents)) === false) {
157 return false;
158 }
159
160 $type = 'file';
161 $result = compact('contents', 'type', 'size', 'path');
162
163 if ($visibility = $config->get('visibility')) {
164 $result['visibility'] = $visibility;
165 $this->setVisibility($path, $visibility);
166 }
167
168 return $result;
169 }
170
171 /**
172 * @inheritdoc
173 * @throws Exception
174 */
175 public function writeStream($path, $resource, Config $config)
176 {
177 $path = $this->getRelativePath( $path );
178 $location = $this->applyPathPrefix($path);
179 $this->ensureDirectory(dirname($location));
180 $stream = fopen($location, 'w+b');
181
182 if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
183 return false;
184 }
185
186 $type = 'file';
187 $result = compact('type', 'path');
188
189 if ($visibility = $config->get('visibility')) {
190 $this->setVisibility($path, $visibility);
191 $result['visibility'] = $visibility;
192 }
193
194 return $result;
195 }
196
197 /**
198 * @inheritdoc
199 */
200 public function readStream($path)
201 {
202 $path = $this->getRelativePath( $path );
203 $location = $this->applyPathPrefix($path);
204 $stream = fopen($location, 'rb');
205
206 return ['type' => 'file', 'path' => $path, 'stream' => $stream];
207 }
208
209 /**
210 * @inheritdoc
211 */
212 public function updateStream($path, $resource, Config $config)
213 {
214 $path = $this->getRelativePath( $path );
215 return $this->writeStream($path, $resource, $config);
216 }
217
218 /**
219 * @inheritdoc
220 */
221 public function update($path, $contents, Config $config)
222 {
223 $path = $this->getRelativePath( $path );
224 $location = $this->applyPathPrefix($path);
225
226 $size = $this->fs->put_contents($location, $contents);
227
228 if ($size === false) {
229 return false;
230 }
231
232 $type = 'file';
233
234 $result = compact('type', 'path', 'size', 'contents');
235
236 if ($visibility = $config->get('visibility')) {
237 $this->setVisibility($path, $visibility);
238 $result['visibility'] = $visibility;
239 }
240
241 return $result;
242 }
243
244 /**
245 * @inheritdoc
246 */
247 public function read($path)
248 {
249 $path = $this->getRelativePath( $path );
250 $location = $this->applyPathPrefix($path);
251 $contents = $this->fs->get_contents($location);
252
253 if ($contents === false) {
254 return false;
255 }
256
257 return ['type' => 'file', 'path' => $path, 'contents' => $contents];
258 }
259
260 /**
261 * @inheritdoc
262 * @throws Exception
263 */
264 public function rename($path, $newpath)
265 {
266 $path = $this->getRelativePath( $path );
267 $location = $this->applyPathPrefix($path);
268 $destination = $this->applyPathPrefix($newpath);
269 $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
270 $this->ensureDirectory($parentDirectory);
271
272 return $this->fs->move($location, $destination);
273 }
274
275 /**
276 * @inheritdoc
277 * @throws Exception
278 */
279 public function copy($path, $newpath): bool
280 {
281 $path = $this->getRelativePath( $path );
282 $location = $this->applyPathPrefix($path);
283 $destination = $this->applyPathPrefix($newpath);
284 $this->ensureDirectory(dirname($destination));
285
286 return $this->fs->copy($location, $destination);
287 }
288
289 /**
290 * @inheritdoc
291 */
292 public function delete($path): bool
293 {
294 $path = $this->getRelativePath( $path );
295 $location = $this->applyPathPrefix($path);
296
297 return $this->fs->delete($location);
298 }
299
300 /**
301 * @inheritdoc
302 */
303 public function listContents($directory = '', $recursive = false): array
304 {
305
306 $result = [];
307 $directory = $this->getRelativePath( $directory );
308 $location = $this->applyPathPrefix($directory);
309
310 if ( ! $this->fs->is_dir($location)) {
311 return [];
312 }
313
314 $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);
315
316 foreach ($iterator as $file) {
317 $path = $this->getFilePath($file);
318
319 if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
320 continue;
321 }
322
323 $result[] = $this->normalizeFileInfo($file);
324 }
325
326 unset($iterator);
327
328 return array_filter($result);
329 }
330
331 /**
332 * @inheritdoc
333 */
334 public function getMetadata($path)
335 {
336 $path = $this->getRelativePath( $path );
337 $location = $this->applyPathPrefix($path);
338 clearstatcache(false, $location);
339 $info = new SplFileInfo($location);
340
341 return $this->normalizeFileInfo($info);
342 }
343
344 /**
345 * @inheritdoc
346 */
347 public function getSize($path)
348 {
349 $path = $this->getRelativePath( $path );
350 return $this->getMetadata($path);
351 }
352
353 /**
354 * @inheritdoc
355 */
356 public function getMimetype($path)
357 {
358 $path = $this->getRelativePath( $path );
359 $location = $this->applyPathPrefix($path);
360 $finfo = new Finfo(FILEINFO_MIME_TYPE);
361 $mimetype = $finfo->file($location);
362
363 if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) {
364 $mimetype = Util\MimeType::detectByFilename($location);
365 }
366
367 return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype];
368 }
369
370 /**
371 * @inheritdoc
372 */
373 public function getTimestamp($path)
374 {
375 $path = $this->getRelativePath( $path );
376 return $this->getMetadata($path);
377 }
378
379 /**
380 * @inheritdoc
381 */
382 public function getVisibility($path)
383 {
384 $path = $this->getRelativePath( $path );
385 $location = $this->applyPathPrefix($path);
386 clearstatcache(false, $location);
387 $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
388 $type = $this->fs->is_dir($location) ? 'dir' : 'file';
389
390 foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) {
391 if ($visibilityPermissions == $permissions) {
392 return compact('path', 'visibility');
393 }
394 }
395
396 $visibility = substr(sprintf('%o', fileperms($location)), -4);
397
398 return compact('path', 'visibility');
399 }
400
401 /**
402 * @inheritdoc
403 */
404 public function setVisibility($path, $visibility)
405 {
406 $path = $this->getRelativePath( $path );
407 $location = $this->applyPathPrefix($path);
408 $type = $this->fs->is_dir($location) ? 'dir' : 'file';
409 $success = $this->fs->chmod($location, $this->permissionMap[$type][$visibility]);
410
411 if ($success === false) {
412 return false;
413 }
414
415 return compact('path', 'visibility');
416 }
417
418 /**
419 * @inheritdoc
420 */
421 public function createDir($dirname, Config $config)
422 {
423
424 $dirname = $this->getRelativePath( $dirname );
425 $location = $this->applyPathPrefix($dirname);
426 $umask = umask(0);
427 $return = ['path' => $dirname, 'type' => 'dir'];
428
429 if ( ! is_dir($location)) {
430 if (!wp_mkdir_p($location)) {
431 error_log('cannot create dir '.$location);
432 $return = false;
433 }
434 }
435
436 umask($umask);
437
438 return $return;
439 }
440
441 /**
442 * @inheritdoc
443 */
444 public function deleteDir($dirname): bool
445 {
446 $dirname = $this->getRelativePath( $dirname );
447 $location = $this->applyPathPrefix($dirname);
448
449 if ( ! $this->fs->is_dir($location)) {
450 return false;
451 }
452
453 $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);
454
455 /** @var SplFileInfo $file */
456 foreach ($contents as $file) {
457 $this->guardAgainstUnreadableFileInfo($file);
458 $this->deleteFileInfoObject($file);
459 }
460
461 unset($contents);
462
463 return $this->fs->rmdir($location);
464 }
465
466 /**
467 * @param SplFileInfo $file
468 */
469 protected function deleteFileInfoObject(SplFileInfo $file)
470 {
471 switch ($file->getType()) {
472 case 'dir':
473 $this->fs->rmdir($file->getRealPath());
474 break;
475 case 'link':
476 $this->fs->delete($file->getPathname());
477 break;
478 default:
479 $this->fs->delete($file->getRealPath());
480 }
481 }
482
483 /**
484 * Normalize the file info.
485 *
486 * @param SplFileInfo $file
487 *
488 * @return array|void
489 *
490 * @throws NotSupportedException
491 */
492 protected function normalizeFileInfo(SplFileInfo $file)
493 {
494 if ( ! $file->isLink()) {
495 return $this->mapFileInfo($file);
496 }
497
498 if ($this->linkHandling & self::DISALLOW_LINKS) {
499 throw NotSupportedException::forLink($file);
500 }
501 }
502
503 /**
504 * Get the normalized path from a SplFileInfo object.
505 *
506 * @param SplFileInfo $file
507 *
508 * @return string
509 */
510 protected function getFilePath(SplFileInfo $file): string
511 {
512 $location = $file->getPathname();
513 $path = $this->removePathPrefix($location);
514
515 return trim(str_replace('\\', '/', $path), '/');
516 }
517
518 /**
519 * @param string $path
520 * @param int $mode
521 *
522 * @return RecursiveIteratorIterator
523 */
524 protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
525 {
526 return new RecursiveIteratorIterator(
527 new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
528 $mode
529 );
530 }
531
532 /**
533 * @param string $path
534 *
535 * @return DirectoryIterator
536 */
537 protected function getDirectoryIterator($path): DirectoryIterator
538 {
539 $path = $this->getRelativePath( $path );
540 return new DirectoryIterator($path);
541 }
542
543 /**
544 * @param SplFileInfo $file
545 *
546 * @return array
547 */
548 protected function mapFileInfo(SplFileInfo $file): array
549 {
550 $normalized = [
551 'type' => $file->getType(),
552 'path' => $this->getFilePath($file),
553 ];
554
555 $normalized['timestamp'] = $file->getMTime();
556
557 if ($normalized['type'] === 'file') {
558 $normalized['size'] = $file->getSize();
559 }
560
561 return $normalized;
562 }
563
564 /**
565 * @param SplFileInfo $file
566 *
567 * @throws UnreadableFileException
568 */
569 protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
570 {
571 if ( ! $file->isReadable()) {
572 throw UnreadableFileException::forFileInfo($file);
573 }
574 }
575 }
576