Completed
Push — master ( 6f2211...36891a )
by Frank
31:39 queued 30:16
created

Local::normalizeFileInfo()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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