Completed
Push — master ( ae61e8...340f44 )
by Cedric
02:13
created

FlysystemDriver::renameFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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