Completed
Push — master ( 340f44...217ed6 )
by Cedric
01:52
created

FlysystemDriver::moveFolderWithinStorage()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 22
ccs 0
cts 12
cp 0
rs 8.9197
cc 4
eloc 11
nc 4
nop 3
crap 20
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 78
    public function __construct(array $configuration = [])
72
    {
73 78
        parent::__construct($configuration);
74
        // The capabilities default of this driver. See CAPABILITY_* constants for possible values
75 78
        $this->capabilities =
76 26
            ResourceStorage::CAPABILITY_BROWSABLE
77 78
            | ResourceStorage::CAPABILITY_PUBLIC
78 78
            | ResourceStorage::CAPABILITY_WRITABLE;
79 78
    }
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
     * @throws FolderDoesNotExistException
448
     */
449
    public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
450
    {
451
        $trimmedSourceIdentifier = ltrim($sourceFolderIdentifier, '/');
452
        $trimmedTargetFolderIdentifier = ltrim($targetFolderIdentifier, '/');
453
454
        if (!$this->filesystem->get($trimmedSourceIdentifier)->isDir()) {
455
            throw new FolderDoesNotExistException("Source folder {$sourceFolderIdentifier} does not exist");
456
        }
457
458
        if (!$this->filesystem->get($trimmedTargetFolderIdentifier)->isDir()) {
459
            throw new FolderDoesNotExistException("Target folder {$trimmedTargetFolderIdentifier} does not exist");
460
        }
461
462
        $identityMap = [];
463
        $oldContents = $this->filesystem->listContents($trimmedSourceIdentifier, true);
464
465
        foreach ($oldContents as $item) {
466
            // $this->moveFileWithinStorage()$item
467
        }
468
469
        return $identityMap;
470
    }
471
472
    /**
473
     * Folder equivalent to copyFileWithinStorage().
474
     *
475
     * @param string $sourceFolderIdentifier
476
     * @param string $targetFolderIdentifier
477
     * @param string $newFolderName
478
     * @return bool
479
     */
480
    public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
481
    {
482
        // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
483
        // We can thus rely on this folder being present and just create the subfolder we want to copy to.
484
        $newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
485
486
        $trimmedSourcePath = ltrim($sourceFolderIdentifier, '/');
487
        $trimmedTargetPath = ltrim($newFolderIdentifier, '/');
488
489
        return $this->copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath);
490
    }
491
492
    /**
493
     * Returns the contents of a file. Beware that this requires to load the
494
     * complete file into memory and also may require fetching the file from an
495
     * external location. So this might be an expensive operation (both in terms
496
     * of processing resources and money) for large files.
497
     *
498
     * @param string $fileIdentifier
499
     * @return string The file contents
500
     */
501 3
    public function getFileContents($fileIdentifier)
502
    {
503 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 503 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...
504
    }
505
506
    /**
507
     * Sets the contents of a file to the specified value.
508
     *
509
     * @param string $fileIdentifier
510
     * @param string $contents
511
     * @return int The number of bytes written to the file
512
     */
513 3
    public function setFileContents($fileIdentifier, $contents)
514
    {
515 3
        $this->filesystem->put($fileIdentifier, $contents);
516
517 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 517 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...
518
    }
519
520
    /**
521
     * Checks if a file inside a folder exists
522
     *
523
     * @param string $fileName
524
     * @param string $folderIdentifier
525
     * @return bool
526
     */
527 3
    public function fileExistsInFolder($fileName, $folderIdentifier)
528
    {
529 3
        $identifier = $folderIdentifier . '/' . $fileName;
530 3
        $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
531 3
        return $this->fileExists($identifier);
532
    }
533
534
    /**
535
     * Checks if a folder inside a folder exists.
536
     *
537
     * @param string $folderName
538
     * @param string $folderIdentifier
539
     * @return bool
540
     */
541 3
    public function folderExistsInFolder($folderName, $folderIdentifier)
542
    {
543 3
        $identifier = $folderIdentifier . '/' . $folderName;
544 3
        $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
545 3
        return $this->folderExists($identifier);
546
    }
547
548
    /**
549
     * Returns a path to a local copy of a file for processing it. When changing the
550
     * file, you have to take care of replacing the current version yourself!
551
     *
552
     * @param string $fileIdentifier
553
     * @param bool $writable Set this to FALSE if you only need the file for read
554
     *                       operations. This might speed up things, e.g. by using
555
     *                       a cached local version. Never modify the file if you
556
     *                       have set this flag!
557
     * @return string The path to the file on the local disk
558
     */
559
    public function getFileForLocalProcessing($fileIdentifier, $writable = true)
560
    {
561
        return $this->copyFileToTemporaryPath($fileIdentifier);
562
    }
563
564
    /**
565
     * Returns the permissions of a file/folder as an array
566
     * (keys r, w) of boolean flags
567
     *
568
     * @param string $identifier
569
     * @return array
570
     */
571
    public function getPermissions($identifier)
572
    {
573
        return array(
574
            'r' => true,
575
            'w' => true,
576
        );
577
    }
578
579
    /**
580
     * Directly output the contents of the file to the output
581
     * buffer. Should not take care of header files or flushing
582
     * buffer before. Will be taken care of by the Storage.
583
     *
584
     * @param string $identifier
585
     * @return void
586
     */
587
    public function dumpFileContents($identifier)
588
    {
589
        // TODO: Implement dumpFileContents() method.
590
        DebuggerUtility::var_dump([
591
            '$identifier' => $identifier,
592
        ], 'dumpFileContents');
593
    }
594
595
    /**
596
     * Checks if a given identifier is within a container, e.g. if
597
     * a file or folder is within another folder.
598
     * This can e.g. be used to check for web-mounts.
599
     *
600
     * Hint: this also needs to return TRUE if the given identifier
601
     * matches the container identifier to allow access to the root
602
     * folder of a filemount.
603
     *
604
     * @param string $folderIdentifier
605
     * @param string $identifier identifier to be checked against $folderIdentifier
606
     * @return bool TRUE if $content is within or matches $folderIdentifier
607
     */
608 3
    public function isWithin($folderIdentifier, $identifier)
609
    {
610 3
        if ($folderIdentifier === $identifier) {
611 3
            return true;
612
        }
613 3
        $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
614 3
        $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
615 3
        if ($folderIdentifier === $entryIdentifier) {
616
            return true;
617
        }
618
        // File identifier canonicalization will not modify a single slash so
619
        // we must not append another slash in that case.
620 3
        if ($folderIdentifier !== '/') {
621 3
            $folderIdentifier .= '/';
622 2
        }
623 3
        return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
624
    }
625
626
    /**
627
     * Returns information about a file.
628
     *
629
     * @param string $fileIdentifier
630
     * @param array $propertiesToExtract Array of properties which are be extracted
631
     *                                   If empty all will be extracted
632
     * @return array
633
     * @throws FileDoesNotExistException
634
     */
635 12
    public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = [])
636
    {
637 12
        $relativeDriverPath = ltrim($fileIdentifier, '/');
638 12
        if (!$this->filesystem->has($relativeDriverPath) || !$this->filesystem->get($relativeDriverPath)->isFile()) {
639 3
            throw new FileDoesNotExistException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
640
        }
641 9
        $dirPath = PathUtility::dirname($fileIdentifier);
642 9
        $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
643 9
        return $this->extractFileInformation($relativeDriverPath, $dirPath, $propertiesToExtract);
644
    }
645
646
    /**
647
     * Returns information about a file.
648
     *
649
     * @param string $folderIdentifier
650
     * @return array
651
     * @throws FolderDoesNotExistException
652
     */
653 6
    public function getFolderInfoByIdentifier($folderIdentifier)
654
    {
655 6
        $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
656
657 6
        if (!$this->folderExists($folderIdentifier)) {
658 3
            throw new FolderDoesNotExistException(
659 3
                'Folder "' . $folderIdentifier . '" does not exist.',
660 1
                1314516810
661 2
            );
662
        }
663
        return [
664 3
            'identifier' => $folderIdentifier,
665 3
            'name' => PathUtility::basename($folderIdentifier),
666 3
            'storage' => $this->storageUid
667 2
        ];
668
    }
669
670
    /**
671
     * Returns the identifier of a file inside the folder
672
     *
673
     * @param string $fileName
674
     * @param string $folderIdentifier
675
     * @return string file identifier
676
     */
677 3
    public function getFileInFolder($fileName, $folderIdentifier)
678
    {
679 3
        return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
680
    }
681
682
    /**
683
     * Returns a list of files inside the specified path
684
     *
685
     * @param string $folderIdentifier
686
     * @param int $start
687
     * @param int $numberOfItems
688
     * @param bool $recursive
689
     * @param array $filenameFilterCallbacks callbacks for filtering the items
690
     * @param string $sort Property name used to sort the items.
691
     *                     Among them may be: '' (empty, no sorting), name,
692
     *                     fileext, size, tstamp and rw.
693
     *                     If a driver does not support the given property, it
694
     *                     should fall back to "name".
695
     * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
696
     * @return array of FileIdentifiers
697
     */
698 3
    public function getFilesInFolder(
699
        $folderIdentifier,
700
        $start = 0,
701
        $numberOfItems = 0,
702
        $recursive = false,
703
        array $filenameFilterCallbacks = [],
704
        $sort = '',
705
        $sortRev = false
706
    ) {
707 3
        $calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/');
708 3
        $contents = $this->filesystem->listContents($calculatedFolderIdentifier);
709 3
        $files = [];
710
711
        /*
712
         * Filter directories
713
         */
714 3
        foreach ($contents as $directoryItem) {
715 3
            if ('file' === $directoryItem['type']) {
716 3
                $files['/' . $directoryItem['path']] = '/' . $directoryItem['path'];
717 2
            }
718 2
        }
719
720 3
        return $files;
721
    }
722
723
    /**
724
     * Returns the identifier of a folder inside the folder
725
     *
726
     * @param string $folderName The name of the target folder
727
     * @param string $folderIdentifier
728
     * @return string folder identifier
729
     */
730 3
    public function getFolderInFolder($folderName, $folderIdentifier)
731
    {
732 3
        $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
733 3
        return $folderIdentifier;
734
    }
735
736
    /**
737
     * Returns a list of folders inside the specified path
738
     *
739
     * @param string $folderIdentifier
740
     * @param int $start
741
     * @param int $numberOfItems
742
     * @param bool $recursive
743
     * @param array $folderNameFilterCallbacks callbacks for filtering the items
744
     * @param string $sort Property name used to sort the items.
745
     *                     Among them may be: '' (empty, no sorting), name,
746
     *                     fileext, size, tstamp and rw.
747
     *                     If a driver does not support the given property, it
748
     *                     should fall back to "name".
749
     * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
750
     * @return array of Folder Identifier
751
     * @TODO: Implement pagination with $start and $numberOfItems
752
     * @TODO: Implement directory filter callbacks
753
     * @TODO: Implement sorting
754
     */
755 3
    public function getFoldersInFolder(
756
        $folderIdentifier,
757
        $start = 0,
758
        $numberOfItems = 0,
759
        $recursive = false,
760
        array $folderNameFilterCallbacks = [],
761
        $sort = '',
762
        $sortRev = false
763
    ) {
764 3
        $calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/');
765 3
        $contents = $this->filesystem->listContents($calculatedFolderIdentifier);
766 3
        $directories = [];
767
768
        /*
769
         * Filter directories
770
         */
771 3
        foreach ($contents as $directoryItem) {
772 3
            if ('dir' === $directoryItem['type']) {
773 3
                $directories['/' . $directoryItem['path']]
774 3
                    = '/' . $directoryItem['path'];
775 2
            }
776 2
        }
777
778 3
        return $directories;
779
    }
780
781
    /**
782
     * Returns the number of files inside the specified path
783
     *
784
     * @param string $folderIdentifier
785
     * @param bool $recursive
786
     * @param array $filenameFilterCallbacks callbacks for filtering the items
787
     * @return int Number of files in folder
788
     * @TODO: Implement recursive count
789
     * @TODO: Implement filename filtering
790
     */
791
    public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = [])
792
    {
793
794
        return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
795
    }
796
797
    /**
798
     * Returns the number of folders inside the specified path
799
     *
800
     * @param string $folderIdentifier
801
     * @param bool $recursive
802
     * @param array $folderNameFilterCallbacks callbacks for filtering the items
803
     * @return int Number of folders in folder
804
     */
805 3
    public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = [])
806
    {
807 3
        $count = 0;
808 3
        $filesystemRelativeIdentifier = ltrim($folderIdentifier, '/');
809 3
        $directoryListing = $this->filesystem->listContents($filesystemRelativeIdentifier);
810 3
        foreach ($directoryListing as $entry) {
811 3
            if ('dir' === $entry['type']) {
812 3
                $count++;
813 2
            }
814 2
        }
815
816 3
        return $count;
817
    }
818
819
    /**
820
     * Extracts information about a file from the filesystem.
821
     *
822
     * @param string $filePath The absolute path to the file
823
     * @param string $containerPath The relative path to the file's container
824
     * @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted
825
     * @return array
826
     */
827 9
    protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array())
828
    {
829 9
        if (empty($propertiesToExtract)) {
830
            $propertiesToExtract = array(
831 3
                'size',
832 2
                'atime',
833 2
                'atime',
834 2
                'mtime',
835 2
                'ctime',
836 2
                'mimetype',
837 2
                'name',
838 2
                'identifier',
839 2
                'identifier_hash',
840 2
                'storage',
841
                'folder_hash'
842 2
            );
843 2
        }
844 9
        $fileInformation = array();
845 9
        foreach ($propertiesToExtract as $property) {
846 9
            $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
847 6
        }
848 9
        return $fileInformation;
849
    }
850
851
    /**
852
     * Extracts a specific FileInformation from the FileSystems.
853
     *
854
     * @param string $fileIdentifier
855
     * @param string $containerPath
856
     * @param string $property
857
     *
858
     * @return bool|int|string
859
     * @throws \InvalidArgumentException
860
     */
861 9
    public function getSpecificFileInformation($fileIdentifier, $containerPath, $property)
862
    {
863 9
        $baseName = basename($fileIdentifier);
864 9
        $parts = explode('/', $fileIdentifier);
865 9
        $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
866
867 9
        $file = $this->filesystem->getMetadata($fileIdentifier);
868
869
        switch ($property) {
870 9
            case 'size':
871 9
                return $file['size'];
872 6
            case 'atime':
873 3
                return $file['timestamp'];
874 6
            case 'mtime':
875 9
                return $file['timestamp'];
876 6
            case 'ctime':
877 3
                return $file['timestamp'];
878 6
            case 'name':
879 9
                return $baseName;
880 6
            case 'mimetype':
881 3
                return 'application/octet-stream';
882 6
            case 'identifier':
883 9
                return $identifier;
884 2
            case 'storage':
885 3
                return $this->storageUid;
886 2
            case 'identifier_hash':
887 3
                return $this->hashIdentifier($identifier);
888 2
            case 'folder_hash':
889 3
                if (1 < count($parts)) {
890 3
                    return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
891 3
                } elseif (1 === count($parts)) {
892 3
                    return sha1('/');
893
                } else {
894
                    return '';
895
                }
896
            default:
897
                throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
898
        }
899
    }
900
901
    /**
902
     * Copies a file to a temporary path and returns that path.
903
     *
904
     * @param string $fileIdentifier
905
     * @return string The temporary path
906
     * @throws \RuntimeException
907
     */
908
    protected function copyFileToTemporaryPath($fileIdentifier)
909
    {
910
        $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
911
        $contents = $this->filesystem->read(ltrim($fileIdentifier, '/'));
912
913
        $res = fopen($temporaryPath, 'w');
914
        $result = fwrite($res, $contents);
915
        fclose($res);
916
917
        if (false === $result) {
918
            throw new \RuntimeException(
919
                'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.',
920
                1320577649
921
            );
922
        }
923
        return $temporaryPath;
924
    }
925
926
    /**
927
     * @param string $trimmedSourcePath
928
     * @param string $trimmedTargetPath
929
     * @return bool
930
     */
931
    protected function copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath)
932
    {
933
        try {
934
            $contents = $this->filesystem->listContents($trimmedSourcePath, true);
935
            foreach ($contents as $item) {
936
                if ('file' === $item['type']) {
937
                    try {
938
                        $relPath = substr_replace($trimmedSourcePath, '', 0, strlen($item['path']));
939
                        $targetPath = $trimmedTargetPath . $relPath . '/' . $item['basename'];
940
                        $this->filesystem->copy($item['path'], $targetPath);
941
                    } catch (FileExistsException $fee) {
942
                        continue;
943
                    } catch (FileNotFoundException $fnfe) {
944
                        return false;
945
                    }
946
                }
947
            }
948
        } catch (\Exception $e) {
949
            return false;
950
        }
951
        return true;
952
    }
953
}
954