Completed
Push — master ( 217ed6...22225b )
by Cedric
01:39
created

FlysystemDriver::addFile()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.4742

Importance

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