Completed
Push — master ( a39d9e...ae61e8 )
by Cedric
02:49
created

FlysystemDriver::copyFolderRecursively()   B

Complexity

Conditions 6
Paths 26

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 22
ccs 0
cts 16
cp 0
rs 8.6737
cc 6
eloc 16
nc 26
nop 2
crap 42
1
<?php
2
3
namespace CedricZiel\FalFlysystem\Fal;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2016 Cedric Ziel <[email protected]>
9
 *
10
 *  All rights reserved
11
 *
12
 *  This script is part of the TYPO3 project. The TYPO3 project is
13
 *  free software; you can redistribute it and/or modify
14
 *  it under the terms of the GNU General Public License as published by
15
 *  the Free Software Foundation; either version 3 of the License, or
16
 *  (at your option) any later version.
17
 *
18
 *  The GNU General Public License can be found at
19
 *  http://www.gnu.org/copyleft/gpl.html.
20
 *
21
 *  This script is distributed in the hope that it will be useful,
22
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 *  GNU General Public License for more details.
25
 *
26
 *  This copyright notice MUST APPEAR in all copies of the script!
27
 ***************************************************************/
28
29
use League\Flysystem\Adapter\Local;
30
use League\Flysystem\AdapterInterface;
31
use League\Flysystem\Config;
32
use League\Flysystem\FileExistsException;
33
use League\Flysystem\FileNotFoundException;
34
use League\Flysystem\FilesystemInterface;
35
use TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver;
36
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
37
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
38
use TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException;
39
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
40
use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException;
41
use TYPO3\CMS\Core\Resource\ResourceStorage;
42
use TYPO3\CMS\Core\Utility\GeneralUtility;
43
use TYPO3\CMS\Core\Utility\PathUtility;
44
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
45
46
/**
47
 * Class FlysystemDriver
48
 * @package CedricZiel\FalFlysystem\Fal
49
 */
50
abstract class FlysystemDriver extends AbstractHierarchicalFilesystemDriver
51
{
52
    /**
53
     * @var FilesystemInterface
54
     */
55
    protected $filesystem;
56
57
    /**
58
     * @var AdapterInterface
59
     */
60
    protected $adapter;
61
62
    /**
63
     * @var string
64
     */
65
    protected $entryPath;
66
67
    /**
68
     * FlysystemDriver constructor.
69
     * @param array $configuration
70
     */
71 75
    public function __construct(array $configuration = [])
72
    {
73 75
        parent::__construct($configuration);
74
        // The capabilities default of this driver. See CAPABILITY_* constants for possible values
75 75
        $this->capabilities =
76 25
            ResourceStorage::CAPABILITY_BROWSABLE
77 75
            | ResourceStorage::CAPABILITY_PUBLIC
78 75
            | ResourceStorage::CAPABILITY_WRITABLE;
79 75
    }
80
81
    /**
82
     * Processes the configuration for this driver.
83
     * @return void
84
     */
85
    public function processConfiguration()
86
    {
87
    }
88
89
    /**
90
     * Merges the capabilities merged by the user at the storage
91
     * configuration into the actual capabilities of the driver
92
     * and returns the result.
93
     *
94
     * @param int $capabilities
95
     * @return int
96
     */
97
    public function mergeConfigurationCapabilities($capabilities)
98
    {
99
        $this->capabilities &= $capabilities;
100
        return $this->capabilities;
101
    }
102
103
    /**
104
     * Returns the identifier of the root level folder of the storage.
105
     *
106
     * @return string
107
     */
108
    public function getRootLevelFolder()
109
    {
110
        return '/';
111
    }
112
113
    /**
114
     * Returns the identifier of the default folder new files should be put into.
115
     *
116
     * @return string
117
     */
118 3
    public function getDefaultFolder()
119
    {
120 3
        $identifier = '/user_upload/';
121 3
        $createFolder = !$this->folderExists($identifier);
122 3
        if (true === $createFolder) {
123 3
            $identifier = $this->createFolder('user_upload');
124 2
        }
125 3
        return $identifier;
126
    }
127
128
    /**
129
     * Checks if a folder exists.
130
     *
131
     * @param string $folderIdentifier
132
     * @return bool
133
     */
134 12
    public function folderExists($folderIdentifier)
135
    {
136 12
        $normalizedIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
137 12
        $normalizedIdentifier = ltrim(rtrim($normalizedIdentifier, '/'), '/');
138
139 12
        if ('/' === $folderIdentifier) {
140
            return true;
141
        } else {
142
            return (
143 12
                $this->filesystem->has($normalizedIdentifier)
144 12
                && $this->filesystem->get($normalizedIdentifier)->isDir()
145 8
            );
146
        }
147
    }
148
149
    /**
150
     * Creates a folder, within a parent folder.
151
     * If no parent folder is given, a root level folder will be created
152
     *
153
     * @param string $newFolderName
154
     * @param string $parentFolderIdentifier
155
     * @param bool $recursive
156
     * @return string the Identifier of the new folder
157
     */
158 9
    public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false)
159
    {
160 9
        $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
161 9
        $newFolderName = trim($newFolderName, '/');
162
163 9
        $newFolderName = $this->sanitizeFileName($newFolderName);
164 9
        $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
165 9
        $this->filesystem->createDir($newIdentifier);
166
167 9
        return $newIdentifier;
168
    }
169
170
    /**
171
     * Returns the public URL to a file.
172
     * Either fully qualified URL or relative to PATH_site (rawurlencoded).
173
     *
174
     * @param string $identifier
175
     * @return string
176
     */
177
    public function getPublicUrl($identifier)
178
    {
179
        return '/';
180
    }
181
182
    /**
183
     * Renames a folder in this storage.
184
     *
185
     * @param string $folderIdentifier
186
     * @param string $newName
187
     * @return array A map of old to new file identifiers of all affected resources
188
     */
189 3
    public function renameFolder($folderIdentifier, $newName)
190
    {
191 3
        $renameResult = $this->filesystem->rename($folderIdentifier, $newName);
192
193 3
        if (true === $renameResult) {
194 3
            return [$folderIdentifier => $newName];
195
        } else {
196
            return [$folderIdentifier => $folderIdentifier];
197
        }
198
    }
199
200
    /**
201
     * Removes a folder in filesystem.
202
     *
203
     * @param string $folderIdentifier
204
     * @param bool $deleteRecursively
205
     * @return bool
206
     * @throws FileOperationErrorException
207
     */
208
    public function deleteFolder($folderIdentifier, $deleteRecursively = false)
209
    {
210
        $folderIdentifier = ltrim($folderIdentifier, '/');
211
        $result = $this->filesystem->deleteDir(rtrim($folderIdentifier, '/'));
212
        if (false === $result) {
213
            throw new FileOperationErrorException(
214
                'Deleting folder "' . $folderIdentifier . '" failed.',
215
                1330119451
216
            );
217
        }
218
        return $result;
219
    }
220
221
    /**
222
     * Checks if a file exists.
223
     *
224
     * @param string $fileIdentifier
225
     * @return bool
226
     */
227 18
    public function fileExists($fileIdentifier)
228
    {
229 18
        if ($this->filesystem->has($fileIdentifier) && !$this->filesystem->get($fileIdentifier)->isDir()) {
230 18
            return true;
231
        }
232 12
        return false;
233
    }
234
235
    /**
236
     * Checks if a folder contains files and (if supported) other folders.
237
     *
238
     * @param string $folderIdentifier
239
     * @return bool TRUE if there are no files and folders within $folder
240
     */
241 3
    public function isFolderEmpty($folderIdentifier)
242
    {
243 3
        return 0 === count($this->filesystem->listContents($folderIdentifier));
244
    }
245
246
    /**
247
     * Adds a file from the local server hard disk to a given path in TYPO3s
248
     * virtual file system. This assumes that the local file exists, so no
249
     * further check is done here! After a successful the original file must
250
     * not exist anymore.
251
     *
252
     * @param string $localFilePath (within PATH_site)
253
     * @param string $targetFolderIdentifier
254
     * @param string $newFileName optional, if not given original name is used
255
     * @param bool $removeOriginal if set the original file will be removed
256
     *                                after successful operation
257
     * @return string the identifier of the new file
258
     */
259 3
    public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true)
260
    {
261 3
        $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
262 3
        $newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath));
263 3
        $newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName;
264
265 3
        $targetPath = ltrim($newFileIdentifier, '/');
266
267 3
        $content = file_get_contents($localFilePath);
268
269 3
        if ($removeOriginal) {
270 2
            $result = $this->filesystem->put($targetPath, $content);
271
            unlink($localFilePath);
272
        } else {
273 3
            $result = $this->filesystem->put($targetPath, $content);
274
        }
275 3
        if ($result === false || !$this->filesystem->has($targetPath)) {
276
            throw new \RuntimeException('Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.');
277
        }
278 3
        clearstatcache();
279 3
        return $newFileIdentifier;
280
    }
281
282
    /**
283
     * Creates a new (empty) file and returns the identifier.
284
     *
285
     * @param string $fileName
286
     * @param string $parentFolderIdentifier
287
     * @return string
288
     * @throws InvalidFileNameException
289
     */
290 3
    public function createFile($fileName, $parentFolderIdentifier)
291
    {
292 3
        if (!$this->isValidFilename($fileName)) {
293
            throw new InvalidFileNameException(
294
                'Invalid characters in fileName "' . $fileName . '"',
295
                1320572272
296
            );
297
        }
298
299 3
        $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
300 3
        $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier(
301 3
            $parentFolderIdentifier . $this->sanitizeFileName(ltrim($fileName, '/'))
302 2
        );
303
304 3
        $path = ltrim($parentFolderIdentifier . $fileName, '/');
305 3
        $result = $this->filesystem->put($path, '');
306
307 3
        if ($result !== true) {
308
            throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854);
309
        }
310
311 3
        return $fileIdentifier;
312
    }
313
314
    /**
315
     * Copies a file *within* the current storage.
316
     * Note that this is only about an inner storage copy action,
317
     * where a file is just copied to another folder in the same storage.
318
     *
319
     * @param string $fileIdentifier
320
     * @param string $targetFolderIdentifier
321
     * @param string $fileName
322
     * @return string the Identifier of the new file
323
     */
324
    public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
325
    {
326
        $newFileIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetFolderIdentifier . '/' . $fileName);
327
328
        $trimmedSourceFile = ltrim($fileIdentifier, '/');
329
        $trimmedTargetFile = ltrim($newFileIdentifier, '/');
330
331
        return $this->filesystem->copy($trimmedSourceFile, $trimmedTargetFile);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->filesystem...e, $trimmedTargetFile); (boolean) is incompatible with the return type declared by the interface TYPO3\CMS\Core\Resource\...::copyFileWithinStorage of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
332
    }
333
334
    /**
335
     * Renames a file in this storage.
336
     *
337
     * @param string $fileIdentifier
338
     * @param string $newName The target path (including the file name!)
339
     * @return string The identifier of the file after renaming
340
     * @throws ExistingTargetFileNameException
341
     */
342 6
    public function renameFile($fileIdentifier, $newName)
343
    {
344
        // Makes sure the Path given as parameter is valid
345 6
        $newName = $this->sanitizeFileName($newName);
346
347 6
        $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newName);
348
        // The target should not exist already
349 6
        if ($this->fileExists($newIdentifier)) {
350 3
            throw new ExistingTargetFileNameException(
351 3
                'The target file "' . $newIdentifier . '" already exists.',
352 1
                1320291063
353 2
            );
354
        }
355
356 3
        $sourcePath = ltrim($fileIdentifier, '/');
357 3
        $targetPath = ltrim($newIdentifier, '/');
358 3
        $result = $this->filesystem->rename($sourcePath, $targetPath);
359 3
        if ($result === false) {
360
            throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
361
        }
362 5
        return $newIdentifier;
363
    }
364
365
    /**
366
     * Replaces a file with file in local file system.
367
     *
368
     * @param string $fileIdentifier
369
     * @param string $localFilePath
370
     * @return bool TRUE if the operation succeeded
371
     */
372
    public function replaceFile($fileIdentifier, $localFilePath)
373
    {
374
        // TODO: Implement replaceFile() method.
375
        DebuggerUtility::var_dump([
376
            '$fileIdentifier' => $fileIdentifier,
377
            '$localFilePath' => $localFilePath
378
        ], 'replaceFile');
379
    }
380
381
    /**
382
     * Removes a file from the filesystem. This does not check if the file is
383
     * still used or if it is a bad idea to delete it for some other reason
384
     * this has to be taken care of in the upper layers (e.g. the Storage)!
385
     *
386
     * @param string $fileIdentifier
387
     * @return bool TRUE if deleting the file succeeded
388
     */
389 3
    public function deleteFile($fileIdentifier)
390
    {
391 3
        return $this->filesystem->delete($fileIdentifier);
392
    }
393
394
    /**
395
     * Creates a hash for a file.
396
     *
397
     * @param string $fileIdentifier
398
     * @param string $hashAlgorithm The hash algorithm to use
399
     * @return string
400
     */
401 9
    public function hash($fileIdentifier, $hashAlgorithm)
402
    {
403 9
        if (!in_array($hashAlgorithm, ['sha1', 'md5'])) {
404 3
            throw new \InvalidArgumentException(
405 3
                'Hash algorithm "' . $hashAlgorithm . '" is not supported.',
406 1
                1304964032
407 2
            );
408
        }
409 6
        $propertiesToHash = ['name', 'size', 'mtime', 'identifier'];
410
        switch ($hashAlgorithm) {
411 6
            case 'sha1':
412 3
                $hash = sha1(implode('-', $this->getFileInfoByIdentifier($fileIdentifier, $propertiesToHash)));
413 3
                break;
414 2
            case 'md5':
415 3
                $hash = md5(implode('-', $this->getFileInfoByIdentifier($fileIdentifier, $propertiesToHash)));
416 3
                break;
417
            default:
418
                throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
419
        }
420 6
        return $hash;
421
    }
422
423
    /**
424
     * Moves a file *within* the current storage.
425
     * Note that this is only about an inner-storage move action,
426
     * where a file is just moved to another folder in the same storage.
427
     *
428
     * @param string $fileIdentifier
429
     * @param string $targetFolderIdentifier
430
     * @param string $newFileName
431
     * @return string
432
     */
433
    public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
434
    {
435
        // TODO: Implement moveFileWithinStorage() method.
436
    }
437
438
    /**
439
     * Folder equivalent to moveFileWithinStorage().
440
     *
441
     * @param string $sourceFolderIdentifier
442
     * @param string $targetFolderIdentifier
443
     * @param string $newFolderName
444
     * @return array All files which are affected, map of old => new file identifiers
445
     */
446
    public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
447
    {
448
        // TODO: Implement moveFolderWithinStorage() method.
449
    }
450
451
    /**
452
     * Folder equivalent to copyFileWithinStorage().
453
     *
454
     * @param string $sourceFolderIdentifier
455
     * @param string $targetFolderIdentifier
456
     * @param string $newFolderName
457
     * @return bool
458
     */
459
    public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
460
    {
461
        // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
462
        // We can thus rely on this folder being present and just create the subfolder we want to copy to.
463
        $newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
464
465
        $trimmedSourcePath = ltrim($sourceFolderIdentifier, '/');
466
        $trimmedTargetPath = ltrim($newFolderIdentifier, '/');
467
468
        return $this->copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath);
469
    }
470
471
    /**
472
     * Returns the contents of a file. Beware that this requires to load the
473
     * complete file into memory and also may require fetching the file from an
474
     * external location. So this might be an expensive operation (both in terms
475
     * of processing resources and money) for large files.
476
     *
477
     * @param string $fileIdentifier
478
     * @return string The file contents
479
     */
480 3
    public function getFileContents($fileIdentifier)
481
    {
482 3
        return $this->filesystem->read($fileIdentifier);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->filesystem->read($fileIdentifier); of type string|false adds false to the return on line 482 which is incompatible with the return type declared by the interface TYPO3\CMS\Core\Resource\...erface::getFileContents of type string. It seems like you forgot to handle an error condition.
Loading history...
483
    }
484
485
    /**
486
     * Sets the contents of a file to the specified value.
487
     *
488
     * @param string $fileIdentifier
489
     * @param string $contents
490
     * @return int The number of bytes written to the file
491
     */
492 3
    public function setFileContents($fileIdentifier, $contents)
493
    {
494 3
        $this->filesystem->put($fileIdentifier, $contents);
495
496 3
        return $this->filesystem->getSize($fileIdentifier);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->filesystem->getSize($fileIdentifier); of type integer|false adds false to the return on line 496 which is incompatible with the return type declared by the interface TYPO3\CMS\Core\Resource\...erface::setFileContents of type integer. It seems like you forgot to handle an error condition.
Loading history...
497
    }
498
499
    /**
500
     * Checks if a file inside a folder exists
501
     *
502
     * @param string $fileName
503
     * @param string $folderIdentifier
504
     * @return bool
505
     */
506 3
    public function fileExistsInFolder($fileName, $folderIdentifier)
507
    {
508 3
        $identifier = $folderIdentifier . '/' . $fileName;
509 3
        $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
510 3
        return $this->fileExists($identifier);
511
    }
512
513
    /**
514
     * Checks if a folder inside a folder exists.
515
     *
516
     * @param string $folderName
517
     * @param string $folderIdentifier
518
     * @return bool
519
     */
520 3
    public function folderExistsInFolder($folderName, $folderIdentifier)
521
    {
522 3
        $identifier = $folderIdentifier . '/' . $folderName;
523 3
        $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
524 3
        return $this->folderExists($identifier);
525
    }
526
527
    /**
528
     * Returns a path to a local copy of a file for processing it. When changing the
529
     * file, you have to take care of replacing the current version yourself!
530
     *
531
     * @param string $fileIdentifier
532
     * @param bool $writable Set this to FALSE if you only need the file for read
533
     *                       operations. This might speed up things, e.g. by using
534
     *                       a cached local version. Never modify the file if you
535
     *                       have set this flag!
536
     * @return string The path to the file on the local disk
537
     */
538
    public function getFileForLocalProcessing($fileIdentifier, $writable = true)
539
    {
540
        return $this->copyFileToTemporaryPath($fileIdentifier);
541
    }
542
543
    /**
544
     * Returns the permissions of a file/folder as an array
545
     * (keys r, w) of boolean flags
546
     *
547
     * @param string $identifier
548
     * @return array
549
     */
550
    public function getPermissions($identifier)
551
    {
552
        return array(
553
            'r' => true,
554
            'w' => true,
555
        );
556
    }
557
558
    /**
559
     * Directly output the contents of the file to the output
560
     * buffer. Should not take care of header files or flushing
561
     * buffer before. Will be taken care of by the Storage.
562
     *
563
     * @param string $identifier
564
     * @return void
565
     */
566
    public function dumpFileContents($identifier)
567
    {
568
        // TODO: Implement dumpFileContents() method.
569
        DebuggerUtility::var_dump([
570
            '$identifier' => $identifier,
571
        ], 'dumpFileContents');
572
    }
573
574
    /**
575
     * Checks if a given identifier is within a container, e.g. if
576
     * a file or folder is within another folder.
577
     * This can e.g. be used to check for web-mounts.
578
     *
579
     * Hint: this also needs to return TRUE if the given identifier
580
     * matches the container identifier to allow access to the root
581
     * folder of a filemount.
582
     *
583
     * @param string $folderIdentifier
584
     * @param string $identifier identifier to be checked against $folderIdentifier
585
     * @return bool TRUE if $content is within or matches $folderIdentifier
586
     */
587
    public function isWithin($folderIdentifier, $identifier)
588
    {
589
        $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
590
        $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
591
        if ($folderIdentifier === $entryIdentifier) {
592
            return true;
593
        }
594
        // File identifier canonicalization will not modify a single slash so
595
        // we must not append another slash in that case.
596
        if ($folderIdentifier !== '/') {
597
            $folderIdentifier .= '/';
598
        }
599
        return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
600
    }
601
602
    /**
603
     * Returns information about a file.
604
     *
605
     * @param string $fileIdentifier
606
     * @param array $propertiesToExtract Array of properties which are be extracted
607
     *                                   If empty all will be extracted
608
     * @return array
609
     * @throws FileDoesNotExistException
610
     */
611 12
    public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = [])
612
    {
613 12
        $relativeDriverPath = ltrim($fileIdentifier, '/');
614 12
        if (!$this->filesystem->has($relativeDriverPath) || !$this->filesystem->get($relativeDriverPath)->isFile()) {
615 3
            throw new FileDoesNotExistException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
616
        }
617 9
        $dirPath = PathUtility::dirname($fileIdentifier);
618 9
        $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
619 9
        return $this->extractFileInformation($relativeDriverPath, $dirPath, $propertiesToExtract);
620
    }
621
622
    /**
623
     * Returns information about a file.
624
     *
625
     * @param string $folderIdentifier
626
     * @return array
627
     * @throws FolderDoesNotExistException
628
     */
629 6
    public function getFolderInfoByIdentifier($folderIdentifier)
630
    {
631 6
        $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
632
633 6
        if (!$this->folderExists($folderIdentifier)) {
634 3
            throw new FolderDoesNotExistException(
635 3
                'Folder "' . $folderIdentifier . '" does not exist.',
636 1
                1314516810
637 2
            );
638
        }
639
        return [
640 3
            'identifier' => $folderIdentifier,
641 3
            'name' => PathUtility::basename($folderIdentifier),
642 3
            'storage' => $this->storageUid
643 2
        ];
644
    }
645
646
    /**
647
     * Returns the identifier of a file inside the folder
648
     *
649
     * @param string $fileName
650
     * @param string $folderIdentifier
651
     * @return string file identifier
652
     */
653 3
    public function getFileInFolder($fileName, $folderIdentifier)
654
    {
655 3
        return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
656
    }
657
658
    /**
659
     * Returns a list of files inside the specified path
660
     *
661
     * @param string $folderIdentifier
662
     * @param int $start
663
     * @param int $numberOfItems
664
     * @param bool $recursive
665
     * @param array $filenameFilterCallbacks callbacks for filtering the items
666
     * @param string $sort Property name used to sort the items.
667
     *                     Among them may be: '' (empty, no sorting), name,
668
     *                     fileext, size, tstamp and rw.
669
     *                     If a driver does not support the given property, it
670
     *                     should fall back to "name".
671
     * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
672
     * @return array of FileIdentifiers
673
     */
674 3
    public function getFilesInFolder(
675
        $folderIdentifier,
676
        $start = 0,
677
        $numberOfItems = 0,
678
        $recursive = false,
679
        array $filenameFilterCallbacks = [],
680
        $sort = '',
681
        $sortRev = false
682
    ) {
683 3
        $calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/');
684 3
        $contents = $this->filesystem->listContents($calculatedFolderIdentifier);
685 3
        $files = [];
686
687
        /*
688
         * Filter directories
689
         */
690 3
        foreach ($contents as $directoryItem) {
691 3
            if ('file' === $directoryItem['type']) {
692 3
                $files['/' . $directoryItem['path']] = '/' . $directoryItem['path'];
693 2
            }
694 2
        }
695
696 3
        return $files;
697
    }
698
699
    /**
700
     * Returns the identifier of a folder inside the folder
701
     *
702
     * @param string $folderName The name of the target folder
703
     * @param string $folderIdentifier
704
     * @return string folder identifier
705
     */
706 3
    public function getFolderInFolder($folderName, $folderIdentifier)
707
    {
708 3
        $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
709 3
        return $folderIdentifier;
710
    }
711
712
    /**
713
     * Returns a list of folders inside the specified path
714
     *
715
     * @param string $folderIdentifier
716
     * @param int $start
717
     * @param int $numberOfItems
718
     * @param bool $recursive
719
     * @param array $folderNameFilterCallbacks callbacks for filtering the items
720
     * @param string $sort Property name used to sort the items.
721
     *                     Among them may be: '' (empty, no sorting), name,
722
     *                     fileext, size, tstamp and rw.
723
     *                     If a driver does not support the given property, it
724
     *                     should fall back to "name".
725
     * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
726
     * @return array of Folder Identifier
727
     * @TODO: Implement pagination with $start and $numberOfItems
728
     * @TODO: Implement directory filter callbacks
729
     * @TODO: Implement sorting
730
     */
731 3
    public function getFoldersInFolder(
732
        $folderIdentifier,
733
        $start = 0,
734
        $numberOfItems = 0,
735
        $recursive = false,
736
        array $folderNameFilterCallbacks = [],
737
        $sort = '',
738
        $sortRev = false
739
    ) {
740 3
        $calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/');
741 3
        $contents = $this->filesystem->listContents($calculatedFolderIdentifier);
742 3
        $directories = [];
743
744
        /*
745
         * Filter directories
746
         */
747 3
        foreach ($contents as $directoryItem) {
748 3
            if ('dir' === $directoryItem['type']) {
749 3
                $directories['/' . $directoryItem['path']]
750 3
                    = '/' . $directoryItem['path'];
751 2
            }
752 2
        }
753
754 3
        return $directories;
755
    }
756
757
    /**
758
     * Returns the number of files inside the specified path
759
     *
760
     * @param string $folderIdentifier
761
     * @param bool $recursive
762
     * @param array $filenameFilterCallbacks callbacks for filtering the items
763
     * @return int Number of files in folder
764
     * @TODO: Implement recursive count
765
     * @TODO: Implement filename filtering
766
     */
767
    public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = [])
768
    {
769
770
        return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
771
    }
772
773
    /**
774
     * Returns the number of folders inside the specified path
775
     *
776
     * @param string $folderIdentifier
777
     * @param bool $recursive
778
     * @param array $folderNameFilterCallbacks callbacks for filtering the items
779
     * @return int Number of folders in folder
780
     */
781 3
    public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = [])
782
    {
783 3
        $count = 0;
784 3
        $filesystemRelativeIdentifier = ltrim($folderIdentifier, '/');
785 3
        $directoryListing = $this->filesystem->listContents($filesystemRelativeIdentifier);
786 3
        foreach ($directoryListing as $entry) {
787 3
            if ('dir' === $entry['type']) {
788 3
                $count++;
789 2
            }
790 2
        }
791
792 3
        return $count;
793
    }
794
795
    /**
796
     * Extracts information about a file from the filesystem.
797
     *
798
     * @param string $filePath The absolute path to the file
799
     * @param string $containerPath The relative path to the file's container
800
     * @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted
801
     * @return array
802
     */
803 9
    protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array())
804
    {
805 9
        if (empty($propertiesToExtract)) {
806
            $propertiesToExtract = array(
807 3
                'size',
808 2
                'atime',
809 2
                'atime',
810 2
                'mtime',
811 2
                'ctime',
812 2
                'mimetype',
813 2
                'name',
814 2
                'identifier',
815 2
                'identifier_hash',
816 2
                'storage',
817
                'folder_hash'
818 2
            );
819 2
        }
820 9
        $fileInformation = array();
821 9
        foreach ($propertiesToExtract as $property) {
822 9
            $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
823 6
        }
824 9
        return $fileInformation;
825
    }
826
827
    /**
828
     * Extracts a specific FileInformation from the FileSystems.
829
     *
830
     * @param string $fileIdentifier
831
     * @param string $containerPath
832
     * @param string $property
833
     *
834
     * @return bool|int|string
835
     * @throws \InvalidArgumentException
836
     */
837 9
    public function getSpecificFileInformation($fileIdentifier, $containerPath, $property)
838
    {
839 9
        $baseName = basename($fileIdentifier);
840 9
        $parts = explode('/', $fileIdentifier);
841 9
        $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
842
843 9
        $file = $this->filesystem->getMetadata($fileIdentifier);
844
845
        switch ($property) {
846 9
            case 'size':
847 9
                return $file['size'];
848 6
            case 'atime':
849 3
                return $file['timestamp'];
850 6
            case 'mtime':
851 9
                return $file['timestamp'];
852 6
            case 'ctime':
853 3
                return $file['timestamp'];
854 6
            case 'name':
855 9
                return $baseName;
856 6
            case 'mimetype':
857 3
                return 'application/octet-stream';
858 6
            case 'identifier':
859 9
                return $identifier;
860 2
            case 'storage':
861 3
                return $this->storageUid;
862 2
            case 'identifier_hash':
863 3
                return $this->hashIdentifier($identifier);
864 2
            case 'folder_hash':
865 3
                if (1 < count($parts)) {
866 3
                    return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
867 3
                } elseif (1 === count($parts)) {
868 3
                    return sha1('/');
869
                } else {
870
                    return '';
871
                }
872
            default:
873
                throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
874
        }
875
    }
876
877
    /**
878
     * Copies a file to a temporary path and returns that path.
879
     *
880
     * @param string $fileIdentifier
881
     * @return string The temporary path
882
     * @throws \RuntimeException
883
     */
884
    protected function copyFileToTemporaryPath($fileIdentifier)
885
    {
886
        $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
887
        $contents = $this->filesystem->read(ltrim($fileIdentifier, '/'));
888
889
        $res = fopen($temporaryPath, 'w');
890
        $result = fwrite($res, $contents);
891
        fclose($res);
892
893
        if (false === $result) {
894
            throw new \RuntimeException(
895
                'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.',
896
                1320577649
897
            );
898
        }
899
        return $temporaryPath;
900
    }
901
902
    /**
903
     * @param string $trimmedSourcePath
904
     * @param string $trimmedTargetPath
905
     * @return bool
906
     */
907
    protected function copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath)
908
    {
909
        try {
910
            $contents = $this->filesystem->listContents($trimmedSourcePath, true);
911
            foreach ($contents as $item) {
912
                if ('file' === $item['type']) {
913
                    try {
914
                        $relPath = substr_replace($trimmedSourcePath, '', 0, strlen($item['path']));
915
                        $targetPath = $trimmedTargetPath . $relPath . '/' . $item['basename'];
916
                        $this->filesystem->copy($item['path'], $targetPath);
917
                    } catch (FileExistsException $fee) {
918
                        continue;
919
                    } catch (FileNotFoundException $fnfe) {
920
                        return false;
921
                    }
922
                }
923
            }
924
        } catch (\Exception $e) {
925
            return false;
926
        }
927
        return true;
928
    }
929
}
930