[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / ResourceStorage.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use TYPO3\CMS\Core\Core\Environment;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Http\FalDumpFileContentsDecoratorStream;
21 use TYPO3\CMS\Core\Http\Response;
22 use TYPO3\CMS\Core\Log\LogManager;
23 use TYPO3\CMS\Core\Registry;
24 use TYPO3\CMS\Core\Resource\Driver\StreamableDriverInterface;
25 use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
26 use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
27 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
28 use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
29 use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
30 use TYPO3\CMS\Core\Resource\Search\Result\DriverFilteredSearchResult;
31 use TYPO3\CMS\Core\Resource\Search\Result\EmptyFileSearchResult;
32 use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResult;
33 use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResultInterface;
34 use TYPO3\CMS\Core\Utility\Exception\NotImplementedMethodException;
35 use TYPO3\CMS\Core\Utility\GeneralUtility;
36 use TYPO3\CMS\Core\Utility\PathUtility;
37 use TYPO3\CMS\Extbase\Object\ObjectManager;
38 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
39
40 /**
41 * A "mount point" inside the TYPO3 file handling.
42 *
43 * A "storage" object handles
44 * - abstraction to the driver
45 * - permissions (from the driver, and from the user, + capabilities)
46 * - an entry point for files, folders, and for most other operations
47 *
48 * == Driver entry point
49 * The driver itself, that does the actual work on the file system,
50 * is inside the storage but completely shadowed by
51 * the storage, as the storage also handles the abstraction to the
52 * driver
53 *
54 * The storage can be on the local system, but can also be on a remote
55 * system. The combination of driver + configurable capabilities (storage
56 * is read-only e.g.) allows for flexible uses.
57 *
58 *
59 * == Permission system
60 * As all requests have to run through the storage, the storage knows about the
61 * permissions of a BE/FE user, the file permissions / limitations of the driver
62 * and has some configurable capabilities.
63 * Additionally, a BE user can use "filemounts" (known from previous installations)
64 * to limit his/her work-zone to only a subset (identifier and its subfolders/subfolders)
65 * of the user itself.
66 *
67 * Check 1: "User Permissions" [is the user allowed to write a file) [is the user allowed to write a file]
68 * Check 2: "File Mounts" of the User (act as subsets / filters to the identifiers) [is the user allowed to do something in this folder?]
69 * Check 3: "Capabilities" of Storage (then: of Driver) [is the storage/driver writable?]
70 * Check 4: "File permissions" of the Driver [is the folder writable?]
71 */
72 class ResourceStorage implements ResourceStorageInterface
73 {
74 /**
75 * The storage driver instance belonging to this storage.
76 *
77 * @var Driver\DriverInterface
78 */
79 protected $driver;
80
81 /**
82 * The database record for this storage
83 *
84 * @var array
85 */
86 protected $storageRecord;
87
88 /**
89 * The configuration belonging to this storage (decoded from the configuration field).
90 *
91 * @var array
92 */
93 protected $configuration;
94
95 /**
96 * @var Service\FileProcessingService
97 */
98 protected $fileProcessingService;
99
100 /**
101 * Whether to check if file or folder is in user mounts
102 * and the action is allowed for a user
103 * Default is FALSE so that resources are accessible for
104 * front end rendering or admins.
105 *
106 * @var bool
107 */
108 protected $evaluatePermissions = false;
109
110 /**
111 * User filemounts, added as an array, and used as filters
112 *
113 * @var array
114 */
115 protected $fileMounts = [];
116
117 /**
118 * The file permissions of the user (and their group) merged together and
119 * available as an array
120 *
121 * @var array
122 */
123 protected $userPermissions = [];
124
125 /**
126 * The capabilities of this storage as defined in the storage record.
127 * Also see the CAPABILITY_* constants below
128 *
129 * @var int
130 */
131 protected $capabilities;
132
133 /**
134 * @var Dispatcher
135 */
136 protected $signalSlotDispatcher;
137
138 /**
139 * @var Folder
140 */
141 protected $processingFolder;
142
143 /**
144 * All processing folders of this storage used in any storage
145 *
146 * @var Folder[]
147 */
148 protected $processingFolders;
149
150 /**
151 * whether this storage is online or offline in this request
152 *
153 * @var bool
154 */
155 protected $isOnline;
156
157 /**
158 * @var bool
159 */
160 protected $isDefault = false;
161
162 /**
163 * The filters used for the files and folder names.
164 *
165 * @var array
166 */
167 protected $fileAndFolderNameFilters = [];
168
169 /**
170 * Levels numbers used to generate hashed subfolders in the processing folder
171 */
172 const PROCESSING_FOLDER_LEVELS = 2;
173
174 /**
175 * Constructor for a storage object.
176 *
177 * @param Driver\DriverInterface $driver
178 * @param array $storageRecord The storage record row from the database
179 */
180 public function __construct(Driver\DriverInterface $driver, array $storageRecord)
181 {
182 $this->storageRecord = $storageRecord;
183 $this->configuration = $this->getResourceFactoryInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration'] ?? '');
184 $this->capabilities =
185 ($this->storageRecord['is_browsable'] ?? null ? self::CAPABILITY_BROWSABLE : 0) |
186 ($this->storageRecord['is_public'] ?? null ? self::CAPABILITY_PUBLIC : 0) |
187 ($this->storageRecord['is_writable'] ?? null ? self::CAPABILITY_WRITABLE : 0) |
188 // Always let the driver decide whether to set this capability
189 self::CAPABILITY_HIERARCHICAL_IDENTIFIERS;
190
191 $this->driver = $driver;
192 $this->driver->setStorageUid($storageRecord['uid'] ?? null);
193 $this->driver->mergeConfigurationCapabilities($this->capabilities);
194 try {
195 $this->driver->processConfiguration();
196 } catch (Exception\InvalidConfigurationException $e) {
197 // Configuration error
198 $this->isOnline = false;
199
200 $message = sprintf(
201 'Failed initializing storage [%d] "%s", error: %s',
202 $this->getUid(),
203 $this->getName(),
204 $e->getMessage()
205 );
206
207 // create a dedicated logger instance because we need a logger in the constructor
208 GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class)->error($message);
209 }
210 $this->driver->initialize();
211 $this->capabilities = $this->driver->getCapabilities();
212
213 $this->isDefault = (isset($storageRecord['is_default']) && $storageRecord['is_default'] == 1);
214 $this->resetFileAndFolderNameFiltersToDefault();
215 }
216
217 /**
218 * Gets the configuration.
219 *
220 * @return array
221 */
222 public function getConfiguration()
223 {
224 return $this->configuration;
225 }
226
227 /**
228 * Sets the configuration.
229 *
230 * @param array $configuration
231 */
232 public function setConfiguration(array $configuration)
233 {
234 $this->configuration = $configuration;
235 }
236
237 /**
238 * Gets the storage record.
239 *
240 * @return array
241 */
242 public function getStorageRecord()
243 {
244 return $this->storageRecord;
245 }
246
247 /**
248 * Sets the storage that belongs to this storage.
249 *
250 * @param Driver\DriverInterface $driver
251 * @return ResourceStorage
252 */
253 public function setDriver(Driver\DriverInterface $driver)
254 {
255 $this->driver = $driver;
256 return $this;
257 }
258
259 /**
260 * Returns the driver object belonging to this storage.
261 *
262 * @return Driver\DriverInterface
263 */
264 protected function getDriver()
265 {
266 return $this->driver;
267 }
268
269 /**
270 * Returns the name of this storage.
271 *
272 * @return string
273 */
274 public function getName()
275 {
276 return $this->storageRecord['name'];
277 }
278
279 /**
280 * Returns the UID of this storage.
281 *
282 * @return int
283 */
284 public function getUid()
285 {
286 return (int)($this->storageRecord['uid'] ?? 0);
287 }
288
289 /**
290 * Tells whether there are children in this storage.
291 *
292 * @return bool
293 */
294 public function hasChildren()
295 {
296 return true;
297 }
298
299 /*********************************
300 * Capabilities
301 ********************************/
302 /**
303 * Returns the capabilities of this storage.
304 *
305 * @return int
306 * @see \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_BROWSABLE
307 * @see \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_PUBLIC
308 * @see \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_WRITABLE
309 * @see \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_HIERARCHICAL_IDENTIFIERS
310 */
311 public function getCapabilities()
312 {
313 return (int)$this->capabilities;
314 }
315
316 /**
317 * Returns TRUE if this storage has the given capability.
318 *
319 * @param int $capability A capability, as defined in a CAPABILITY_* constant
320 * @return bool
321 */
322 protected function hasCapability($capability)
323 {
324 return ($this->capabilities & $capability) == $capability;
325 }
326
327 /**
328 * Returns TRUE if this storage is publicly available. This is just a
329 * configuration option and does not mean that it really *is* public. OTOH
330 * a storage that is marked as not publicly available will trigger the file
331 * publishing mechanisms of TYPO3.
332 *
333 * @return bool
334 */
335 public function isPublic()
336 {
337 return $this->hasCapability(self::CAPABILITY_PUBLIC);
338 }
339
340 /**
341 * Returns TRUE if this storage is writable. This is determined by the
342 * driver and the storage configuration; user permissions are not taken into account.
343 *
344 * @return bool
345 */
346 public function isWritable()
347 {
348 return $this->hasCapability(self::CAPABILITY_WRITABLE);
349 }
350
351 /**
352 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
353 *
354 * @return bool
355 */
356 public function isBrowsable()
357 {
358 return $this->isOnline() && $this->hasCapability(self::CAPABILITY_BROWSABLE);
359 }
360
361 /**
362 * Returns TRUE if this storage stores folder structure in file identifiers.
363 *
364 * @return bool
365 */
366 public function hasHierarchicalIdentifiers(): bool
367 {
368 return $this->hasCapability(self::CAPABILITY_HIERARCHICAL_IDENTIFIERS);
369 }
370
371 /**
372 * Search for files in a storage based on given restrictions
373 * and a possibly given folder.
374 *
375 * @param FileSearchDemand $searchDemand
376 * @param Folder|null $folder
377 * @param bool $useFilters Whether storage filters should be applied
378 * @return FileSearchResultInterface
379 */
380 public function searchFiles(FileSearchDemand $searchDemand, Folder $folder = null, bool $useFilters = true): FileSearchResultInterface
381 {
382 $folder = $folder ?? $this->getRootLevelFolder();
383 if (!$folder->checkActionPermission('read')) {
384 return new EmptyFileSearchResult();
385 }
386
387 return new DriverFilteredSearchResult(
388 new FileSearchResult(
389 $searchDemand->withFolder($folder)
390 ),
391 $this->driver,
392 $useFilters ? $this->getFileAndFolderNameFilters() : []
393 );
394 }
395
396 /**
397 * Returns TRUE if the identifiers used by this storage are case-sensitive.
398 *
399 * @return bool
400 */
401 public function usesCaseSensitiveIdentifiers()
402 {
403 return $this->driver->isCaseSensitiveFileSystem();
404 }
405
406 /**
407 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
408 *
409 * @return bool
410 */
411 public function isOnline()
412 {
413 if ($this->isOnline === null) {
414 if ($this->getUid() === 0) {
415 $this->isOnline = true;
416 }
417 // the storage is not marked as online for a longer time
418 if ($this->storageRecord['is_online'] == 0) {
419 $this->isOnline = false;
420 }
421 if ($this->isOnline !== false) {
422 // all files are ALWAYS available in the frontend
423 if (TYPO3_MODE === 'FE') {
424 $this->isOnline = true;
425 } else {
426 // check if the storage is disabled temporary for now
427 $registryObject = GeneralUtility::makeInstance(Registry::class);
428 $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
429 if ($offlineUntil && $offlineUntil > time()) {
430 $this->isOnline = false;
431 } else {
432 $this->isOnline = true;
433 }
434 }
435 }
436 }
437 return $this->isOnline;
438 }
439
440 /**
441 * Returns TRUE if auto extracting of metadata is enabled
442 *
443 * @return bool
444 */
445 public function autoExtractMetadataEnabled()
446 {
447 return !empty($this->storageRecord['auto_extract_metadata']);
448 }
449
450 /**
451 * Blows the "fuse" and marks the storage as offline.
452 *
453 * Can only be modified by an admin.
454 *
455 * Typically, this is only done if the configuration is wrong.
456 */
457 public function markAsPermanentlyOffline()
458 {
459 if ($this->getUid() > 0) {
460 // @todo: move this to the storage repository
461 GeneralUtility::makeInstance(ConnectionPool::class)
462 ->getConnectionForTable('sys_file_storage')
463 ->update(
464 'sys_file_storage',
465 ['is_online' => 0],
466 ['uid' => (int)$this->getUid()]
467 );
468 }
469 $this->storageRecord['is_online'] = 0;
470 $this->isOnline = false;
471 }
472
473 /**
474 * Marks this storage as offline for the next 5 minutes.
475 *
476 * Non-permanent: This typically happens for remote storages
477 * that are "flaky" and not available all the time.
478 */
479 public function markAsTemporaryOffline()
480 {
481 $registryObject = GeneralUtility::makeInstance(Registry::class);
482 $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() + 60 * 5);
483 $this->storageRecord['is_online'] = 0;
484 $this->isOnline = false;
485 }
486
487 /*********************************
488 * User Permissions / File Mounts
489 ********************************/
490 /**
491 * Adds a filemount as a "filter" for users to only work on a subset of a
492 * storage object
493 *
494 * @param string $folderIdentifier
495 * @param array $additionalData
496 *
497 * @throws Exception\FolderDoesNotExistException
498 */
499 public function addFileMount($folderIdentifier, $additionalData = [])
500 {
501 // check for the folder before we add it as a filemount
502 if ($this->driver->folderExists($folderIdentifier) === false) {
503 // if there is an error, this is important and should be handled
504 // as otherwise the user would see the whole storage without any restrictions for the filemounts
505 throw new Exception\FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
506 }
507 $data = $this->driver->getFolderInfoByIdentifier($folderIdentifier);
508 $folderObject = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
509 // Use the canonical identifier instead of the user provided one!
510 $folderIdentifier = $folderObject->getIdentifier();
511 if (
512 !empty($this->fileMounts[$folderIdentifier])
513 && empty($this->fileMounts[$folderIdentifier]['read_only'])
514 && !empty($additionalData['read_only'])
515 ) {
516 // Do not overwrite a regular mount with a read only mount
517 return;
518 }
519 if (empty($additionalData)) {
520 $additionalData = [
521 'path' => $folderIdentifier,
522 'title' => $folderIdentifier,
523 'folder' => $folderObject
524 ];
525 } else {
526 $additionalData['folder'] = $folderObject;
527 if (!isset($additionalData['title'])) {
528 $additionalData['title'] = $folderIdentifier;
529 }
530 }
531 $this->fileMounts[$folderIdentifier] = $additionalData;
532 }
533
534 /**
535 * Returns all file mounts that are registered with this storage.
536 *
537 * @return array
538 */
539 public function getFileMounts()
540 {
541 return $this->fileMounts;
542 }
543
544 /**
545 * Checks if the given subject is within one of the registered user
546 * file mounts. If not, working with the file is not permitted for the user.
547 *
548 * @param ResourceInterface $subject file or folder
549 * @param bool $checkWriteAccess If true, it is not only checked if the subject is within the file mount but also whether it isn't a read only file mount
550 * @return bool
551 */
552 public function isWithinFileMountBoundaries($subject, $checkWriteAccess = false)
553 {
554 if (!$this->evaluatePermissions) {
555 return true;
556 }
557 $isWithinFileMount = false;
558 if (!$subject) {
559 $subject = $this->getRootLevelFolder();
560 }
561 $identifier = $subject->getIdentifier();
562
563 // Allow access to processing folder
564 if ($this->isWithinProcessingFolder($identifier)) {
565 $isWithinFileMount = true;
566 } else {
567 // Check if the identifier of the subject is within at
568 // least one of the file mounts
569 $writableFileMountAvailable = false;
570 foreach ($this->fileMounts as $fileMount) {
571 /** @var Folder $folder */
572 $folder = $fileMount['folder'];
573 if ($this->driver->isWithin($folder->getIdentifier(), $identifier)) {
574 $isWithinFileMount = true;
575 if (!$checkWriteAccess) {
576 break;
577 }
578 if (empty($fileMount['read_only'])) {
579 $writableFileMountAvailable = true;
580 break;
581 }
582 }
583 }
584 $isWithinFileMount = $checkWriteAccess ? $writableFileMountAvailable : $isWithinFileMount;
585 }
586 return $isWithinFileMount;
587 }
588
589 /**
590 * Sets whether the permissions to access or write
591 * into this storage should be checked or not.
592 *
593 * @param bool $evaluatePermissions
594 */
595 public function setEvaluatePermissions($evaluatePermissions)
596 {
597 $this->evaluatePermissions = (bool)$evaluatePermissions;
598 }
599
600 /**
601 * Gets whether the permissions to access or write
602 * into this storage should be checked or not.
603 *
604 * @return bool $evaluatePermissions
605 */
606 public function getEvaluatePermissions()
607 {
608 return $this->evaluatePermissions;
609 }
610
611 /**
612 * Sets the user permissions of the storage.
613 *
614 * @param array $userPermissions
615 */
616 public function setUserPermissions(array $userPermissions)
617 {
618 $this->userPermissions = $userPermissions;
619 }
620
621 /**
622 * Checks if the ACL settings allow for a certain action
623 * (is a user allowed to read a file or copy a folder).
624 *
625 * @param string $action
626 * @param string $type either File or Folder
627 * @return bool
628 */
629 public function checkUserActionPermission($action, $type)
630 {
631 if (!$this->evaluatePermissions) {
632 return true;
633 }
634
635 $allow = false;
636 if (!empty($this->userPermissions[strtolower($action) . ucfirst(strtolower($type))])) {
637 $allow = true;
638 }
639
640 return $allow;
641 }
642
643 /**
644 * Checks if a file operation (= action) is allowed on a
645 * File/Folder/Storage (= subject).
646 *
647 * This method, by design, does not throw exceptions or do logging.
648 * Besides the usage from other methods in this class, it is also used by
649 * the Filelist UI to check whether an action is allowed and whether action
650 * related UI elements should thus be shown (move icon, edit icon, etc.)
651 *
652 * @param string $action action, can be read, write, delete, editMeta
653 * @param FileInterface $file
654 * @return bool
655 */
656 public function checkFileActionPermission($action, FileInterface $file)
657 {
658 $isProcessedFile = $file instanceof ProcessedFile;
659 // Check 1: Allow editing meta data of a file if it is in mount boundaries of a writable file mount
660 if ($action === 'editMeta') {
661 return !$isProcessedFile && $this->isWithinFileMountBoundaries($file, true);
662 }
663 // Check 2: Does the user have permission to perform the action? e.g. "readFile"
664 if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === false) {
665 return false;
666 }
667 // Check 3: No action allowed on files for denied file extensions
668 if (!$this->checkFileExtensionPermission($file->getName())) {
669 return false;
670 }
671 $isReadCheck = false;
672 if (in_array($action, ['read', 'copy', 'move', 'replace'], true)) {
673 $isReadCheck = true;
674 }
675 $isWriteCheck = false;
676 if (in_array($action, ['add', 'write', 'move', 'rename', 'replace', 'delete'], true)) {
677 $isWriteCheck = true;
678 }
679 // Check 4: Does the user have the right to perform the action?
680 // (= is he within the file mount borders)
681 if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file, $isWriteCheck)) {
682 return false;
683 }
684
685 $isMissing = false;
686 if (!$isProcessedFile && $file instanceof File) {
687 $isMissing = $file->isMissing();
688 }
689
690 if ($this->driver->fileExists($file->getIdentifier()) === false) {
691 $file->setMissing(true);
692 $isMissing = true;
693 }
694
695 // Check 5: Check the capabilities of the storage (and the driver)
696 if ($isWriteCheck && ($isMissing || !$this->isWritable())) {
697 return false;
698 }
699
700 // Check 6: "File permissions" of the driver (only when file isn't marked as missing)
701 if (!$isMissing) {
702 $filePermissions = $this->driver->getPermissions($file->getIdentifier());
703 if ($isReadCheck && !$filePermissions['r']) {
704 return false;
705 }
706 if ($isWriteCheck && !$filePermissions['w']) {
707 return false;
708 }
709 }
710 return true;
711 }
712
713 /**
714 * Checks if a folder operation (= action) is allowed on a Folder.
715 *
716 * This method, by design, does not throw exceptions or do logging.
717 * See the checkFileActionPermission() method above for the reasons.
718 *
719 * @param string $action
720 * @param Folder $folder
721 * @return bool
722 */
723 public function checkFolderActionPermission($action, Folder $folder = null)
724 {
725 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
726 if ($this->checkUserActionPermission($action, 'Folder') === false) {
727 return false;
728 }
729
730 // If we do not have a folder here, we cannot do further checks
731 if ($folder === null) {
732 return true;
733 }
734
735 $isReadCheck = false;
736 if (in_array($action, ['read', 'copy'], true)) {
737 $isReadCheck = true;
738 }
739 $isWriteCheck = false;
740 if (in_array($action, ['add', 'move', 'write', 'delete', 'rename'], true)) {
741 $isWriteCheck = true;
742 }
743 // Check 2: Does the user has the right to perform the action?
744 // (= is he within the file mount borders)
745 if (!$this->isWithinFileMountBoundaries($folder, $isWriteCheck)) {
746 return false;
747 }
748 // Check 3: Check the capabilities of the storage (and the driver)
749 if ($isReadCheck && !$this->isBrowsable()) {
750 return false;
751 }
752 if ($isWriteCheck && !$this->isWritable()) {
753 return false;
754 }
755
756 // Check 4: "Folder permissions" of the driver
757 $folderPermissions = $this->driver->getPermissions($folder->getIdentifier());
758 if ($isReadCheck && !$folderPermissions['r']) {
759 return false;
760 }
761 if ($isWriteCheck && !$folderPermissions['w']) {
762 return false;
763 }
764 return true;
765 }
766
767 /**
768 * @param ResourceInterface $fileOrFolder
769 * @deprecated This method will be removed without substitution, use the ResourceStorage API instead
770 * @return bool
771 */
772 public function checkFileAndFolderNameFilters(ResourceInterface $fileOrFolder)
773 {
774 trigger_error(__METHOD__ . ' is deprecated. This method will be removed without substitution, use the ResourceStorage API instead', \E_USER_DEPRECATED);
775 foreach ($this->fileAndFolderNameFilters as $filter) {
776 if (is_callable($filter)) {
777 $result = call_user_func($filter, $fileOrFolder->getName(), $fileOrFolder->getIdentifier(), $fileOrFolder->getParentFolder()->getIdentifier(), [], $this->driver);
778 // We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
779 // If calling the method succeeded and thus we can't use that as a return value.
780 if ($result === -1) {
781 return false;
782 }
783 if ($result === false) {
784 throw new \RuntimeException(
785 'Could not apply file/folder name filter ' . $filter[0] . '::' . $filter[1],
786 1525342106
787 );
788 }
789 }
790 }
791
792 return true;
793 }
794
795 /**
796 * If the fileName is given, checks it against the
797 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed.
798 *
799 * @param string $fileName full filename
800 * @return bool TRUE if extension/filename is allowed
801 */
802 protected function checkFileExtensionPermission($fileName)
803 {
804 $fileName = $this->driver->sanitizeFileName($fileName);
805 return GeneralUtility::verifyFilenameAgainstDenyPattern($fileName);
806 }
807
808 /**
809 * Assures read permission for given folder.
810 *
811 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder read permissions are checked.
812 * @throws Exception\InsufficientFolderAccessPermissionsException
813 */
814 protected function assureFolderReadPermission(Folder $folder = null)
815 {
816 if (!$this->checkFolderActionPermission('read', $folder)) {
817 if ($folder === null) {
818 throw new Exception\InsufficientFolderAccessPermissionsException(
819 'You are not allowed to read folders',
820 1430657869
821 );
822 }
823 throw new Exception\InsufficientFolderAccessPermissionsException(
824 'You are not allowed to access the given folder: "' . $folder->getName() . '"',
825 1375955684
826 );
827 }
828 }
829
830 /**
831 * Assures delete permission for given folder.
832 *
833 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder delete permissions are checked.
834 * @param bool $checkDeleteRecursively
835 * @throws Exception\InsufficientFolderAccessPermissionsException
836 * @throws Exception\InsufficientFolderWritePermissionsException
837 * @throws Exception\InsufficientUserPermissionsException
838 */
839 protected function assureFolderDeletePermission(Folder $folder, $checkDeleteRecursively)
840 {
841 // Check user permissions for recursive deletion if it is requested
842 if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
843 throw new Exception\InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
844 }
845 // Check user action permission
846 if (!$this->checkFolderActionPermission('delete', $folder)) {
847 throw new Exception\InsufficientFolderAccessPermissionsException(
848 'You are not allowed to delete the given folder: "' . $folder->getName() . '"',
849 1377779039
850 );
851 }
852 // Check if the user has write permissions to folders
853 // Would be good if we could check for actual write permissions in the containing folder
854 // but we cannot since we have no access to the containing folder of this file.
855 if (!$this->checkUserActionPermission('write', 'Folder')) {
856 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
857 }
858 }
859
860 /**
861 * Assures read permission for given file.
862 *
863 * @param FileInterface $file
864 * @throws Exception\InsufficientFileAccessPermissionsException
865 * @throws Exception\IllegalFileExtensionException
866 */
867 protected function assureFileReadPermission(FileInterface $file)
868 {
869 if (!$this->checkFileActionPermission('read', $file)) {
870 throw new Exception\InsufficientFileAccessPermissionsException(
871 'You are not allowed to access that file: "' . $file->getName() . '"',
872 1375955429
873 );
874 }
875 if (!$this->checkFileExtensionPermission($file->getName())) {
876 throw new Exception\IllegalFileExtensionException(
877 'You are not allowed to use that file extension. File: "' . $file->getName() . '"',
878 1375955430
879 );
880 }
881 }
882
883 /**
884 * Assures write permission for given file.
885 *
886 * @param FileInterface $file
887 * @throws Exception\IllegalFileExtensionException
888 * @throws Exception\InsufficientFileWritePermissionsException
889 * @throws Exception\InsufficientUserPermissionsException
890 */
891 protected function assureFileWritePermissions(FileInterface $file)
892 {
893 // Check if user is allowed to write the file and $file is writable
894 if (!$this->checkFileActionPermission('write', $file)) {
895 throw new Exception\InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
896 }
897 if (!$this->checkFileExtensionPermission($file->getName())) {
898 throw new Exception\IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
899 }
900 }
901
902 /**
903 * Assure replace permission for given file.
904 *
905 * @param FileInterface $file
906 * @throws Exception\InsufficientFileWritePermissionsException
907 * @throws Exception\InsufficientFolderWritePermissionsException
908 */
909 protected function assureFileReplacePermissions(FileInterface $file)
910 {
911 // Check if user is allowed to replace the file and $file is writable
912 if (!$this->checkFileActionPermission('replace', $file)) {
913 throw new Exception\InsufficientFileWritePermissionsException('Replacing file "' . $file->getIdentifier() . '" is not allowed.', 1436899571);
914 }
915 // Check if parentFolder is writable for the user
916 if (!$this->checkFolderActionPermission('write', $file->getParentFolder())) {
917 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $file->getIdentifier() . '"', 1436899572);
918 }
919 }
920
921 /**
922 * Assures delete permission for given file.
923 *
924 * @param FileInterface $file
925 * @throws Exception\IllegalFileExtensionException
926 * @throws Exception\InsufficientFileWritePermissionsException
927 * @throws Exception\InsufficientFolderWritePermissionsException
928 */
929 protected function assureFileDeletePermissions(FileInterface $file)
930 {
931 // Check for disallowed file extensions
932 if (!$this->checkFileExtensionPermission($file->getName())) {
933 throw new Exception\IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
934 }
935 // Check further permissions if file is not a processed file
936 if (!$file instanceof ProcessedFile) {
937 // Check if user is allowed to delete the file and $file is writable
938 if (!$this->checkFileActionPermission('delete', $file)) {
939 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '"', 1319550425);
940 }
941 // Check if the user has write permissions to folders
942 // Would be good if we could check for actual write permissions in the containing folder
943 // but we cannot since we have no access to the containing folder of this file.
944 if (!$this->checkUserActionPermission('write', 'Folder')) {
945 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
946 }
947 }
948 }
949
950 /**
951 * Checks if a file/user has the permission to be written to a Folder/Storage.
952 * If not, throws an exception.
953 *
954 * @param Folder $targetFolder The target folder where the file should be written
955 * @param string $targetFileName The file name which should be written into the storage
956 *
957 * @throws Exception\InsufficientFolderWritePermissionsException
958 * @throws Exception\IllegalFileExtensionException
959 * @throws Exception\InsufficientUserPermissionsException
960 */
961 protected function assureFileAddPermissions($targetFolder, $targetFileName)
962 {
963 // Check for a valid file extension
964 if (!$this->checkFileExtensionPermission($targetFileName)) {
965 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
966 }
967 // Makes sure the user is allowed to upload
968 if (!$this->checkUserActionPermission('add', 'File')) {
969 throw new Exception\InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
970 }
971 // Check if targetFolder is writable
972 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
973 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
974 }
975 }
976
977 /**
978 * Checks if a file has the permission to be uploaded to a Folder/Storage.
979 * If not, throws an exception.
980 *
981 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
982 * @param Folder $targetFolder The target folder where the file should be uploaded
983 * @param string $targetFileName the destination file name $_FILES['file1']['name']
984 * @param int $uploadedFileSize
985 *
986 * @throws Exception\InsufficientFolderWritePermissionsException
987 * @throws Exception\UploadException
988 * @throws Exception\IllegalFileExtensionException
989 * @throws Exception\UploadSizeException
990 * @throws Exception\InsufficientUserPermissionsException
991 */
992 protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize)
993 {
994 // Makes sure this is an uploaded file
995 if (!is_uploaded_file($localFilePath)) {
996 throw new Exception\UploadException('The upload has failed, no uploaded file found!', 1322110455);
997 }
998 // Max upload size (kb) for files.
999 $maxUploadFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
1000 if ($maxUploadFileSize > 0 && $uploadedFileSize >= $maxUploadFileSize) {
1001 unlink($localFilePath);
1002 throw new Exception\UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
1003 }
1004 $this->assureFileAddPermissions($targetFolder, $targetFileName);
1005 }
1006
1007 /**
1008 * Checks for permissions to move a file.
1009 *
1010 * @throws \RuntimeException
1011 * @throws Exception\InsufficientFolderAccessPermissionsException
1012 * @throws Exception\InsufficientUserPermissionsException
1013 * @throws Exception\IllegalFileExtensionException
1014 * @param FileInterface $file
1015 * @param Folder $targetFolder
1016 * @param string $targetFileName
1017 */
1018 protected function assureFileMovePermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
1019 {
1020 // Check if targetFolder is within this storage
1021 if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
1022 throw new \RuntimeException('The target folder is not in the same storage. Target folder given: "' . $targetFolder->getIdentifier() . '"', 1422553107);
1023 }
1024 // Check for a valid file extension
1025 if (!$this->checkFileExtensionPermission($targetFileName)) {
1026 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
1027 }
1028 // Check if user is allowed to move and $file is readable and writable
1029 if (!$file->getStorage()->checkFileActionPermission('move', $file)) {
1030 throw new Exception\InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
1031 }
1032 // Check if target folder is writable
1033 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1034 throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219350);
1035 }
1036 }
1037
1038 /**
1039 * Checks for permissions to rename a file.
1040 *
1041 * @param FileInterface $file
1042 * @param string $targetFileName
1043 * @throws Exception\InsufficientFileWritePermissionsException
1044 * @throws Exception\IllegalFileExtensionException
1045 * @throws Exception\InsufficientFileReadPermissionsException
1046 * @throws Exception\InsufficientUserPermissionsException
1047 */
1048 protected function assureFileRenamePermissions(FileInterface $file, $targetFileName)
1049 {
1050 // Check if file extension is allowed
1051 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
1052 throw new Exception\IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663);
1053 }
1054 // Check if user is allowed to rename
1055 if (!$this->checkFileActionPermission('rename', $file)) {
1056 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename files. File given: "' . $file->getName() . '"', 1319219351);
1057 }
1058 // Check if the user is allowed to write to folders
1059 // Although it would be good to check, we cannot check here if the folder actually is writable
1060 // because we do not know in which folder the file resides.
1061 // So we rely on the driver to throw an exception in case the renaming failed.
1062 if (!$this->checkFolderActionPermission('write')) {
1063 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219352);
1064 }
1065 }
1066
1067 /**
1068 * Check if a file has the permission to be copied on a File/Folder/Storage,
1069 * if not throw an exception
1070 *
1071 * @param FileInterface $file
1072 * @param Folder $targetFolder
1073 * @param string $targetFileName
1074 *
1075 * @throws Exception
1076 * @throws Exception\InsufficientFolderWritePermissionsException
1077 * @throws Exception\IllegalFileExtensionException
1078 * @throws Exception\InsufficientFileReadPermissionsException
1079 * @throws Exception\InsufficientUserPermissionsException
1080 */
1081 protected function assureFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
1082 {
1083 // Check if targetFolder is within this storage, this should never happen
1084 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1085 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
1086 }
1087 // Check if user is allowed to copy
1088 if (!$file->getStorage()->checkFileActionPermission('copy', $file)) {
1089 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '"', 1319550426);
1090 }
1091 // Check if targetFolder is writable
1092 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1093 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
1094 }
1095 // Check for a valid file extension
1096 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
1097 throw new Exception\IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
1098 }
1099 }
1100
1101 /**
1102 * Check if a file has the permission to be copied on a File/Folder/Storage,
1103 * if not throw an exception
1104 *
1105 * @param FolderInterface $folderToCopy
1106 * @param FolderInterface $targetParentFolder
1107 *
1108 * @throws Exception
1109 * @throws Exception\InsufficientFolderWritePermissionsException
1110 * @throws Exception\IllegalFileExtensionException
1111 * @throws Exception\InsufficientFileReadPermissionsException
1112 * @throws Exception\InsufficientUserPermissionsException
1113 * @throws \RuntimeException
1114 */
1115 protected function assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder)
1116 {
1117 // Check if targetFolder is within this storage, this should never happen
1118 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1119 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
1120 }
1121 if (!$folderToCopy instanceof Folder) {
1122 throw new \RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type folder.', 1384209020);
1123 }
1124 // Check if user is allowed to copy and the folder is readable
1125 if (!$folderToCopy->getStorage()->checkFolderActionPermission('copy', $folderToCopy)) {
1126 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
1127 }
1128 if (!$targetParentFolder instanceof Folder) {
1129 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type folder.', 1384209021);
1130 }
1131 // Check if targetFolder is writable
1132 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1133 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
1134 }
1135 }
1136
1137 /**
1138 * Check if a file has the permission to be copied on a File/Folder/Storage,
1139 * if not throw an exception
1140 *
1141 * @param FolderInterface $folderToMove
1142 * @param FolderInterface $targetParentFolder
1143 *
1144 * @throws \InvalidArgumentException
1145 * @throws Exception\InsufficientFolderWritePermissionsException
1146 * @throws Exception\IllegalFileExtensionException
1147 * @throws Exception\InsufficientFileReadPermissionsException
1148 * @throws Exception\InsufficientUserPermissionsException
1149 * @throws \RuntimeException
1150 */
1151 protected function assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder)
1152 {
1153 // Check if targetFolder is within this storage, this should never happen
1154 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1155 throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1156 }
1157 if (!$folderToMove instanceof Folder) {
1158 throw new \RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
1159 }
1160 // Check if user is allowed to move and the folder is writable
1161 // In fact we would need to check if the parent folder of the folder to move is writable also
1162 // But as of now we cannot extract the parent folder from this folder
1163 if (!$folderToMove->getStorage()->checkFolderActionPermission('move', $folderToMove)) {
1164 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
1165 }
1166 if (!$targetParentFolder instanceof Folder) {
1167 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
1168 }
1169 // Check if targetFolder is writable
1170 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1171 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1172 }
1173 }
1174
1175 /**
1176 * Clean a fileName from not allowed characters
1177 *
1178 * @param string $fileName The name of the file to be add, If not set, the local file name is used
1179 * @param Folder $targetFolder The target folder where the file should be added
1180 *
1181 * @throws \InvalidArgumentException
1182 * @throws Exception\ExistingTargetFileNameException
1183 * @return string
1184 */
1185 public function sanitizeFileName($fileName, Folder $targetFolder = null)
1186 {
1187 $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1188 $fileName = $this->driver->sanitizeFileName($fileName);
1189
1190 // The file name could be changed by an external slot
1191 $fileName = $this->emitSanitizeFileNameSignal($fileName, $targetFolder);
1192
1193 return $fileName;
1194 }
1195
1196 /********************
1197 * FILE ACTIONS
1198 ********************/
1199 /**
1200 * Moves a file from the local filesystem to this storage.
1201 *
1202 * @param string $localFilePath The file on the server's hard disk to add
1203 * @param Folder $targetFolder The target folder where the file should be added
1204 * @param string $targetFileName The name of the file to be add, If not set, the local file name is used
1205 * @param string $conflictMode a value of the DuplicationBehavior enumeration
1206 * @param bool $removeOriginal if set the original file will be removed after successful operation
1207 *
1208 * @throws \InvalidArgumentException
1209 * @throws Exception\ExistingTargetFileNameException
1210 * @return FileInterface
1211 */
1212 public function addFile($localFilePath, Folder $targetFolder, $targetFileName = '', $conflictMode = DuplicationBehavior::RENAME, $removeOriginal = true)
1213 {
1214 $localFilePath = PathUtility::getCanonicalPath($localFilePath);
1215 // File is not available locally NOR is it an uploaded file
1216 if (!is_uploaded_file($localFilePath) && !file_exists($localFilePath)) {
1217 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1218 }
1219 $conflictMode = DuplicationBehavior::cast($conflictMode);
1220 $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1221 $targetFileName = $this->sanitizeFileName($targetFileName ?: PathUtility::basename($localFilePath), $targetFolder);
1222
1223 $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1224
1225 $this->assureFileAddPermissions($targetFolder, $targetFileName);
1226
1227 $replaceExisting = false;
1228 if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1229 throw new Exception\ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1230 }
1231 if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1232 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1233 } elseif ($conflictMode->equals(DuplicationBehavior::REPLACE) && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1234 $replaceExisting = true;
1235 }
1236
1237 $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName, $removeOriginal);
1238 $file = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1239
1240 if ($replaceExisting && $file instanceof File) {
1241 $this->getIndexer()->updateIndexEntry($file);
1242 }
1243
1244 $this->emitPostFileAddSignal($file, $targetFolder);
1245
1246 return $file;
1247 }
1248
1249 /**
1250 * Updates a processed file with a new file from the local filesystem.
1251 *
1252 * @param string $localFilePath
1253 * @param ProcessedFile $processedFile
1254 * @param Folder $processingFolder
1255 * @return FileInterface
1256 * @throws \InvalidArgumentException
1257 * @internal use only
1258 */
1259 public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = null)
1260 {
1261 if (!file_exists($localFilePath)) {
1262 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1263 }
1264 if ($processingFolder === null) {
1265 $processingFolder = $this->getProcessingFolder($processedFile->getOriginalFile());
1266 }
1267 $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1268 // @todo check if we have to update the processed file other then the identifier
1269 $processedFile->setIdentifier($fileIdentifier);
1270 return $processedFile;
1271 }
1272
1273 /**
1274 * Creates a (cryptographic) hash for a file.
1275 *
1276 * @param FileInterface $fileObject
1277 * @param string $hash
1278 * @return string
1279 */
1280 public function hashFile(FileInterface $fileObject, $hash)
1281 {
1282 return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1283 }
1284
1285 /**
1286 * Creates a (cryptographic) hash for a fileIdentifier.
1287 *
1288 * @param string $fileIdentifier
1289 * @param string $hash
1290 *
1291 * @return string
1292 */
1293 public function hashFileByIdentifier($fileIdentifier, $hash)
1294 {
1295 return $this->driver->hash($fileIdentifier, $hash);
1296 }
1297
1298 /**
1299 * Hashes a file identifier, taking the case sensitivity of the file system
1300 * into account. This helps mitigating problems with case-insensitive
1301 * databases.
1302 *
1303 * @param string|FileInterface $file
1304 * @return string
1305 */
1306 public function hashFileIdentifier($file)
1307 {
1308 if (is_object($file) && $file instanceof FileInterface) {
1309 /** @var FileInterface $file */
1310 $file = $file->getIdentifier();
1311 }
1312 return $this->driver->hashIdentifier($file);
1313 }
1314
1315 /**
1316 * Returns a publicly accessible URL for a file.
1317 *
1318 * WARNING: Access to the file may be restricted by further means, e.g.
1319 * some web-based authentication. You have to take care of this yourself.
1320 *
1321 * @param ResourceInterface $resourceObject The file or folder object
1322 * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
1323 * @return string|null NULL if file is missing or deleted, the generated url otherwise
1324 */
1325 public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = false)
1326 {
1327 $publicUrl = null;
1328 if ($this->isOnline()) {
1329 // Pre-process the public URL by an accordant slot
1330 $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, ['publicUrl' => &$publicUrl]);
1331
1332 if (
1333 $publicUrl === null
1334 && $resourceObject instanceof File
1335 && ($helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($resourceObject)) !== false
1336 ) {
1337 $publicUrl = $helper->getPublicUrl($resourceObject, $relativeToCurrentScript);
1338 }
1339
1340 // If slot did not handle the signal, use the default way to determine public URL
1341 if ($publicUrl === null) {
1342 if ($this->hasCapability(self::CAPABILITY_PUBLIC)) {
1343 $publicUrl = $this->driver->getPublicUrl($resourceObject->getIdentifier());
1344 }
1345
1346 if ($publicUrl === null && $resourceObject instanceof FileInterface) {
1347 $queryParameterArray = ['eID' => 'dumpFile', 't' => ''];
1348 if ($resourceObject instanceof File) {
1349 $queryParameterArray['f'] = $resourceObject->getUid();
1350 $queryParameterArray['t'] = 'f';
1351 } elseif ($resourceObject instanceof ProcessedFile) {
1352 $queryParameterArray['p'] = $resourceObject->getUid();
1353 $queryParameterArray['t'] = 'p';
1354 }
1355
1356 $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1357 $publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
1358 $publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
1359 }
1360
1361 // If requested, make the path relative to the current script in order to make it possible
1362 // to use the relative file
1363 if ($publicUrl !== null && $relativeToCurrentScript && !GeneralUtility::isValidUrl($publicUrl)) {
1364 $absolutePathToContainingFolder = PathUtility::dirname(Environment::getPublicPath() . '/' . $publicUrl);
1365 $pathPart = PathUtility::getRelativePathTo($absolutePathToContainingFolder);
1366 $filePart = substr(Environment::getPublicPath() . '/' . $publicUrl, strlen($absolutePathToContainingFolder) + 1);
1367 $publicUrl = $pathPart . $filePart;
1368 }
1369 }
1370 }
1371 return $publicUrl;
1372 }
1373
1374 /**
1375 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1376 *
1377 * @param FileInterface $fileObject The file object
1378 * @param string $context
1379 * @param array $configuration
1380 *
1381 * @return ProcessedFile
1382 * @throws \InvalidArgumentException
1383 */
1384 public function processFile(FileInterface $fileObject, $context, array $configuration)
1385 {
1386 if ($fileObject->getStorage() !== $this) {
1387 throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1388 }
1389 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1390
1391 return $processedFile;
1392 }
1393
1394 /**
1395 * Copies a file from the storage for local processing.
1396 *
1397 * @param FileInterface $fileObject
1398 * @param bool $writable
1399 * @return string Path to local file (either original or copied to some temporary local location)
1400 */
1401 public function getFileForLocalProcessing(FileInterface $fileObject, $writable = true)
1402 {
1403 $filePath = $this->driver->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1404 return $filePath;
1405 }
1406
1407 /**
1408 * Gets a file by identifier.
1409 *
1410 * @param string $identifier
1411 * @return FileInterface
1412 */
1413 public function getFile($identifier)
1414 {
1415 $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1416 if (!$this->driver->fileExists($identifier)) {
1417 $file->setMissing(true);
1418 }
1419 return $file;
1420 }
1421
1422 /**
1423 * Gets information about a file.
1424 *
1425 * @param FileInterface $fileObject
1426 * @return array
1427 * @internal
1428 */
1429 public function getFileInfo(FileInterface $fileObject)
1430 {
1431 return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1432 }
1433
1434 /**
1435 * Gets information about a file by its identifier.
1436 *
1437 * @param string $identifier
1438 * @param array $propertiesToExtract
1439 * @return array
1440 * @internal
1441 */
1442 public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = [])
1443 {
1444 return $this->driver->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1445 }
1446
1447 /**
1448 * Unsets the file and folder name filters, thus making this storage return unfiltered filelists.
1449 */
1450 public function unsetFileAndFolderNameFilters()
1451 {
1452 $this->fileAndFolderNameFilters = [];
1453 }
1454
1455 /**
1456 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1457 */
1458 public function resetFileAndFolderNameFiltersToDefault()
1459 {
1460 $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1461 }
1462
1463 /**
1464 * Returns the file and folder name filters used by this storage.
1465 *
1466 * @return array
1467 */
1468 public function getFileAndFolderNameFilters()
1469 {
1470 return $this->fileAndFolderNameFilters;
1471 }
1472
1473 /**
1474 * @param array $filters
1475 * @return $this
1476 */
1477 public function setFileAndFolderNameFilters(array $filters)
1478 {
1479 $this->fileAndFolderNameFilters = $filters;
1480 return $this;
1481 }
1482
1483 /**
1484 * @param callable $filter
1485 */
1486 public function addFileAndFolderNameFilter($filter)
1487 {
1488 $this->fileAndFolderNameFilters[] = $filter;
1489 }
1490
1491 /**
1492 * @param string $fileIdentifier
1493 *
1494 * @return string
1495 */
1496 public function getFolderIdentifierFromFileIdentifier($fileIdentifier)
1497 {
1498 return $this->driver->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1499 }
1500
1501 /**
1502 * Get file from folder
1503 *
1504 * @param string $fileName
1505 * @param Folder $folder
1506 * @return File|ProcessedFile|null
1507 */
1508 public function getFileInFolder($fileName, Folder $folder)
1509 {
1510 $identifier = $this->driver->getFileInFolder($fileName, $folder->getIdentifier());
1511 return $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1512 }
1513
1514 /**
1515 * @param Folder $folder
1516 * @param int $start
1517 * @param int $maxNumberOfItems
1518 * @param bool $useFilters
1519 * @param bool $recursive
1520 * @param string $sort Property name used to sort the items.
1521 * Among them may be: '' (empty, no sorting), name,
1522 * fileext, size, tstamp and rw.
1523 * If a driver does not support the given property, it
1524 * should fall back to "name".
1525 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1526 * @return File[]
1527 * @throws Exception\InsufficientFolderAccessPermissionsException
1528 */
1529 public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
1530 {
1531 $this->assureFolderReadPermission($folder);
1532
1533 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1534
1535 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1536 $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1537
1538 $items = [];
1539 foreach ($fileIdentifiers as $identifier) {
1540 if (isset($rows[$identifier])) {
1541 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1542 } else {
1543 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1544 }
1545 if ($fileObject instanceof FileInterface) {
1546 $key = $fileObject->getName();
1547 while (isset($items[$key])) {
1548 $key .= 'z';
1549 }
1550 $items[$key] = $fileObject;
1551 }
1552 }
1553
1554 return $items;
1555 }
1556
1557 /**
1558 * @param string $folderIdentifier
1559 * @param bool $useFilters
1560 * @param bool $recursive
1561 * @return array
1562 */
1563 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1564 {
1565 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1566 return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1567 }
1568
1569 /**
1570 * @param Folder $folder
1571 * @param bool $useFilters
1572 * @param bool $recursive
1573 * @return int Number of files in folder
1574 * @throws Exception\InsufficientFolderAccessPermissionsException
1575 */
1576 public function countFilesInFolder(Folder $folder, $useFilters = true, $recursive = false)
1577 {
1578 $this->assureFolderReadPermission($folder);
1579 $filters = $useFilters ? $this->fileAndFolderNameFilters : [];
1580 return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1581 }
1582
1583 /**
1584 * @param string $folderIdentifier
1585 * @param bool $useFilters
1586 * @param bool $recursive
1587 * @return array
1588 */
1589 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1590 {
1591 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1592 return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1593 }
1594
1595 /**
1596 * Returns TRUE if the specified file exists
1597 *
1598 * @param string $identifier
1599 * @return bool
1600 */
1601 public function hasFile($identifier)
1602 {
1603 // Allow if identifier is in processing folder
1604 if (!$this->isWithinProcessingFolder($identifier)) {
1605 $this->assureFolderReadPermission();
1606 }
1607 return $this->driver->fileExists($identifier);
1608 }
1609
1610 /**
1611 * Get all processing folders that live in this storage
1612 *
1613 * @return Folder[]
1614 */
1615 public function getProcessingFolders()
1616 {
1617 if ($this->processingFolders === null) {
1618 $this->processingFolders = [];
1619 $this->processingFolders[] = $this->getProcessingFolder();
1620 /** @var StorageRepository $storageRepository */
1621 $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
1622 $allStorages = $storageRepository->findAll();
1623 foreach ($allStorages as $storage) {
1624 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1625 // See #66695 for details
1626 list($storageUid, $processingFolderIdentifier) = array_pad(GeneralUtility::trimExplode(':', $storage->getStorageRecord()['processingfolder']), 2, null);
1627 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1628 continue;
1629 }
1630 $potentialProcessingFolder = $this->getResourceFactoryInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1631 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1632 $this->processingFolders[] = $potentialProcessingFolder;
1633 }
1634 }
1635 }
1636
1637 return $this->processingFolders;
1638 }
1639
1640 /**
1641 * Returns TRUE if folder that is in current storage is set as
1642 * processing folder for one of the existing storages
1643 *
1644 * @param Folder $folder
1645 * @return bool
1646 */
1647 public function isProcessingFolder(Folder $folder)
1648 {
1649 $isProcessingFolder = false;
1650 foreach ($this->getProcessingFolders() as $processingFolder) {
1651 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1652 $isProcessingFolder = true;
1653 break;
1654 }
1655 }
1656 return $isProcessingFolder;
1657 }
1658
1659 /**
1660 * Checks if the queried file in the given folder exists
1661 *
1662 * @param string $fileName
1663 * @param Folder $folder
1664 * @return bool
1665 */
1666 public function hasFileInFolder($fileName, Folder $folder)
1667 {
1668 $this->assureFolderReadPermission($folder);
1669 return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1670 }
1671
1672 /**
1673 * Get contents of a file object
1674 *
1675 * @param FileInterface $file
1676 *
1677 * @throws Exception\InsufficientFileReadPermissionsException
1678 * @return string
1679 */
1680 public function getFileContents($file)
1681 {
1682 $this->assureFileReadPermission($file);
1683 return $this->driver->getFileContents($file->getIdentifier());
1684 }
1685
1686 /**
1687 * Returns a PSR-7 Response which can be used to stream the requested file
1688 *
1689 * @param FileInterface $file
1690 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1691 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1692 * @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
1693 * @return ResponseInterface
1694 */
1695 public function streamFile(
1696 FileInterface $file,
1697 bool $asDownload = false,
1698 string $alternativeFilename = null,
1699 string $overrideMimeType = null
1700 ): ResponseInterface {
1701 if (!$this->driver instanceof StreamableDriverInterface) {
1702 return $this->getPseudoStream($file, $asDownload, $alternativeFilename, $overrideMimeType);
1703 }
1704
1705 $properties = [
1706 'as_download' => $asDownload,
1707 'filename_overwrite' => $alternativeFilename,
1708 'mimetype_overwrite' => $overrideMimeType,
1709 ];
1710 return $this->driver->streamFile($file->getIdentifier(), $properties);
1711 }
1712
1713 /**
1714 * Wrap DriverInterface::dumpFileContents into a SelfEmittableStreamInterface
1715 *
1716 * @param FileInterface $file
1717 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1718 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1719 * @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
1720 * @return ResponseInterface
1721 */
1722 protected function getPseudoStream(
1723 FileInterface $file,
1724 bool $asDownload = false,
1725 string $alternativeFilename = null,
1726 string $overrideMimeType = null
1727 ) {
1728 $downloadName = $alternativeFilename ?: $file->getName();
1729 $contentDisposition = $asDownload ? 'attachment' : 'inline';
1730
1731 $stream = new FalDumpFileContentsDecoratorStream($file->getIdentifier(), $this->driver, $file->getSize());
1732 $headers = [
1733 'Content-Disposition' => $contentDisposition . '; filename="' . $downloadName . '"',
1734 'Content-Type' => $overrideMimeType ?: $file->getMimeType(),
1735 'Content-Length' => (string)$file->getSize(),
1736 'Last-Modified' => gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), ['mtime']))) . ' GMT',
1737 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1738 // See for more information: http://support.microsoft.com/kb/323308
1739 'Cache-Control' => '',
1740 ];
1741
1742 return new Response($stream, 200, $headers);
1743 }
1744
1745 /**
1746 * Set contents of a file object.
1747 *
1748 * @param AbstractFile $file
1749 * @param string $contents
1750 *
1751 * @throws \Exception|\RuntimeException
1752 * @throws Exception\InsufficientFileWritePermissionsException
1753 * @throws Exception\InsufficientUserPermissionsException
1754 * @return int The number of bytes written to the file
1755 */
1756 public function setFileContents(AbstractFile $file, $contents)
1757 {
1758 // Check if user is allowed to edit
1759 $this->assureFileWritePermissions($file);
1760 $this->emitPreFileSetContentsSignal($file, $contents);
1761 // Call driver method to update the file and update file index entry afterwards
1762 $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1763 if ($file instanceof File) {
1764 $this->getIndexer()->updateIndexEntry($file);
1765 }
1766 $this->emitPostFileSetContentsSignal($file, $contents);
1767 return $result;
1768 }
1769
1770 /**
1771 * Creates a new file
1772 *
1773 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1774 *
1775 * @param string $fileName The name of the file to be created
1776 * @param Folder $targetFolderObject The target folder where the file should be created
1777 *
1778 * @throws Exception\IllegalFileExtensionException
1779 * @throws Exception\InsufficientFolderWritePermissionsException
1780 * @return FileInterface The file object
1781 */
1782 public function createFile($fileName, Folder $targetFolderObject)
1783 {
1784 $this->assureFileAddPermissions($targetFolderObject, $fileName);
1785 $this->emitPreFileCreateSignal($fileName, $targetFolderObject);
1786 $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1787 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1788 return $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1789 }
1790
1791 /**
1792 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1793 *
1794 * @param FileInterface $fileObject
1795 * @throws Exception\InsufficientFileAccessPermissionsException
1796 * @throws Exception\FileOperationErrorException
1797 * @return bool TRUE if deletion succeeded
1798 */
1799 public function deleteFile($fileObject)
1800 {
1801 $this->assureFileDeletePermissions($fileObject);
1802
1803 $this->emitPreFileDeleteSignal($fileObject);
1804 $deleted = true;
1805
1806 if ($this->driver->fileExists($fileObject->getIdentifier())) {
1807 // Disable permission check to find nearest recycler and move file without errors
1808 $currentPermissions = $this->evaluatePermissions;
1809 $this->evaluatePermissions = false;
1810
1811 $recyclerFolder = $this->getNearestRecyclerFolder($fileObject);
1812 if ($recyclerFolder === null) {
1813 $result = $this->driver->deleteFile($fileObject->getIdentifier());
1814 } else {
1815 $result = $this->moveFile($fileObject, $recyclerFolder);
1816 $deleted = false;
1817 }
1818
1819 $this->evaluatePermissions = $currentPermissions;
1820
1821 if (!$result) {
1822 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1823 }
1824 }
1825 // Mark the file object as deleted
1826 if ($deleted && $fileObject instanceof AbstractFile) {
1827 $fileObject->setDeleted();
1828 }
1829
1830 $this->emitPostFileDeleteSignal($fileObject);
1831
1832 return true;
1833 }
1834
1835 /**
1836 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1837 * copies a source file (from any location) in to the target
1838 * folder, the latter has to be part of this storage
1839 *
1840 * @param FileInterface $file
1841 * @param Folder $targetFolder
1842 * @param string $targetFileName an optional destination fileName
1843 * @param string $conflictMode a value of the DuplicationBehavior enumeration
1844 *
1845 * @throws \Exception|Exception\AbstractFileOperationException
1846 * @throws Exception\ExistingTargetFileNameException
1847 * @return FileInterface
1848 */
1849 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1850 {
1851 $conflictMode = DuplicationBehavior::cast($conflictMode);
1852 if ($targetFileName === null) {
1853 $targetFileName = $file->getName();
1854 }
1855 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1856 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1857 $this->emitPreFileCopySignal($file, $targetFolder);
1858 // File exists and we should abort, let's abort
1859 if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1860 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1861 }
1862 // File exists and we should find another name, let's find another one
1863 if ($conflictMode->equals(DuplicationBehavior::RENAME) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1864 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1865 }
1866 $sourceStorage = $file->getStorage();
1867 // Call driver method to create a new file from an existing file object,
1868 // and return the new file object
1869 if ($sourceStorage === $this) {
1870 $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1871 } else {
1872 $tempPath = $file->getForLocalProcessing();
1873 $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1874 }
1875 $newFileObject = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1876 $this->emitPostFileCopySignal($file, $targetFolder);
1877 return $newFileObject;
1878 }
1879
1880 /**
1881 * Moves a $file into a $targetFolder
1882 * the target folder has to be part of this storage
1883 *
1884 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1885 *
1886 * @param FileInterface $file
1887 * @param Folder $targetFolder
1888 * @param string $targetFileName an optional destination fileName
1889 * @param string $conflictMode a value of the DuplicationBehavior enumeration
1890 *
1891 * @throws Exception\ExistingTargetFileNameException
1892 * @throws \RuntimeException
1893 * @return FileInterface
1894 */
1895 public function moveFile($file, $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1896 {
1897 $conflictMode = DuplicationBehavior::cast($conflictMode);
1898 if ($targetFileName === null) {
1899 $targetFileName = $file->getName();
1900 }
1901 $originalFolder = $file->getParentFolder();
1902 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1903 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1904 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1905 // File exists and we should abort, let's abort
1906 if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1907 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1908 } elseif ($conflictMode->equals(DuplicationBehavior::CANCEL)) {
1909 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1910 }
1911 }
1912 $this->emitPreFileMoveSignal($file, $targetFolder, $sanitizedTargetFileName);
1913 $sourceStorage = $file->getStorage();
1914 // Call driver method to move the file and update the index entry
1915 try {
1916 if ($sourceStorage === $this) {
1917 $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1918 if (!$file instanceof AbstractFile) {
1919 throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1920 }
1921 $file->updateProperties(['identifier' => $newIdentifier]);
1922 } else {
1923 $tempPath = $file->getForLocalProcessing();
1924 $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1925
1926 // Disable permission check to find nearest recycler and move file without errors
1927 $currentPermissions = $sourceStorage->evaluatePermissions;
1928 $sourceStorage->evaluatePermissions = false;
1929
1930 $recyclerFolder = $sourceStorage->getNearestRecyclerFolder($file);
1931 if ($recyclerFolder === null) {
1932 $sourceStorage->driver->deleteFile($file->getIdentifier());
1933 } else {
1934 $sourceStorage->moveFile($file, $recyclerFolder);
1935 }
1936 $sourceStorage->evaluatePermissions = $currentPermissions;
1937 if ($file instanceof File) {
1938 $file->updateProperties(['storage' => $this->getUid(), 'identifier' => $newIdentifier]);
1939 }
1940 }
1941 $this->getIndexer()->updateIndexEntry($file);
1942 } catch (\TYPO3\CMS\Core\Exception $e) {
1943 echo $e->getMessage();
1944 }
1945 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1946 return $file;
1947 }
1948
1949 /**
1950 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1951 *
1952 * @param FileInterface $file
1953 * @param string $targetFileName
1954 * @param string $conflictMode
1955 * @return FileInterface
1956 * @throws ExistingTargetFileNameException
1957 */
1958 public function renameFile($file, $targetFileName, $conflictMode = DuplicationBehavior::RENAME)
1959 {
1960 // The name should be different from the current.
1961 if ($file->getName() === $targetFileName) {
1962 return $file;
1963 }
1964 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1965 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1966 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1967
1968 $conflictMode = DuplicationBehavior::cast($conflictMode);
1969
1970 // Call driver method to rename the file and update the index entry
1971 try {
1972 $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1973 if ($file instanceof File) {
1974 $file->updateProperties(['identifier' => $newIdentifier]);
1975 }
1976 $this->getIndexer()->updateIndexEntry($file);
1977 } catch (ExistingTargetFileNameException $exception) {
1978 if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1979 $newName = $this->getUniqueName($file->getParentFolder(), $sanitizedTargetFileName);
1980 $file = $this->renameFile($file, $newName);
1981 } elseif ($conflictMode->equals(DuplicationBehavior::CANCEL)) {
1982 throw $exception;
1983 } elseif ($conflictMode->equals(DuplicationBehavior::REPLACE)) {
1984 $sourceFileIdentifier = substr($file->getCombinedIdentifier(), 0, strrpos($file->getCombinedIdentifier(), '/') + 1) . $targetFileName;
1985 $sourceFile = $this->getResourceFactoryInstance()->getFileObjectFromCombinedIdentifier($sourceFileIdentifier);
1986 $file = $this->replaceFile($sourceFile, Environment::getPublicPath() . '/' . $file->getPublicUrl());
1987 }
1988 } catch (\RuntimeException $e) {
1989 }
1990
1991 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1992
1993 return $file;
1994 }
1995
1996 /**
1997 * Replaces a file with a local file (e.g. a freshly uploaded file)
1998 *
1999 * @param FileInterface $file
2000 * @param string $localFilePath
2001 *
2002 * @return FileInterface
2003 *
2004 * @throws Exception\IllegalFileExtensionException
2005 * @throws \InvalidArgumentException
2006 */
2007 public function replaceFile(FileInterface $file, $localFilePath)
2008 {
2009 $this->assureFileReplacePermissions($file);
2010 if (!file_exists($localFilePath)) {
2011 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
2012 }
2013 $this->emitPreFileReplaceSignal($file, $localFilePath);
2014 $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
2015 if ($file instanceof File) {
2016 $this->getIndexer()->updateIndexEntry($file);
2017 }
2018 $this->emitPostFileReplaceSignal($file, $localFilePath);
2019
2020 return $file;
2021 }
2022
2023 /**
2024 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
2025 *
2026 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
2027 * @param Folder $targetFolder the target folder
2028 * @param string $targetFileName the file name to be written
2029 * @param string $conflictMode a value of the DuplicationBehavior enumeration
2030 * @return FileInterface The file object
2031 */
2032 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = null, $targetFileName = null, $conflictMode = DuplicationBehavior::CANCEL)
2033 {
2034 $conflictMode = DuplicationBehavior::cast($conflictMode);
2035 $localFilePath = $uploadedFileData['tmp_name'];
2036 if ($targetFolder === null) {
2037 $targetFolder = $this->getDefaultFolder();
2038 }
2039 if ($targetFileName === null) {
2040 $targetFileName = $uploadedFileData['name'];
2041 }
2042 $targetFileName = $this->driver->sanitizeFileName($targetFileName);
2043
2044 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
2045 if ($this->hasFileInFolder($targetFileName, $targetFolder) && $conflictMode->equals(DuplicationBehavior::REPLACE)) {
2046 $file = $this->getFileInFolder($targetFileName, $targetFolder);
2047 $resultObject = $this->replaceFile($file, $localFilePath);
2048 } else {
2049 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, (string)$conflictMode);
2050 }
2051 return $resultObject;
2052 }
2053
2054 /********************
2055 * FOLDER ACTIONS
2056 ********************/
2057 /**
2058 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
2059 * @todo check if this is a duplicate
2060 * @param Folder $folder
2061 * @return File[]
2062 */
2063 protected function getAllFileObjectsInFolder(Folder $folder)
2064 {
2065 $files = [];
2066 $folderQueue = [$folder];
2067 while (!empty($folderQueue)) {
2068 $folder = array_shift($folderQueue);
2069 foreach ($folder->getSubfolders() as $subfolder) {
2070 $folderQueue[] = $subfolder;
2071 }
2072 foreach ($folder->getFiles() as $file) {
2073 /** @var FileInterface $file */
2074 $files[$file->getIdentifier()] = $file;
2075 }
2076 }
2077
2078 return $files;
2079 }
2080
2081 /**
2082 * Moves a folder. If you want to move a folder from this storage to another
2083 * one, call this method on the target storage, otherwise you will get an exception.
2084 *
2085 * @param Folder $folderToMove The folder to move.
2086 * @param Folder $targetParentFolder The target parent folder
2087 * @param string $newFolderName
2088 * @param string $conflictMode a value of the DuplicationBehavior enumeration
2089 *
2090 * @throws \Exception|\TYPO3\CMS\Core\Exception
2091 * @throws \InvalidArgumentException
2092 * @throws InvalidTargetFolderException
2093 * @return Folder
2094 */
2095 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
2096 {
2097 // @todo add tests
2098 $originalFolder = $folderToMove->getParentFolder();
2099 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
2100 $sourceStorage = $folderToMove->getStorage();
2101 $returnObject = null;
2102 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
2103 // @todo check if folder already exists in $targetParentFolder, handle this conflict then
2104 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
2105 // Get all file objects now so we are able to update them after moving the folder
2106 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
2107 if ($sourceStorage === $this) {
2108 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
2109 throw new InvalidTargetFolderException(
2110 sprintf(
2111 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
2112 $folderToMove->getName(),
2113 $targetParentFolder->getName()
2114 ),
2115 1422723050
2116 );
2117 }
2118 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2119 } else {
2120 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
2121 }
2122 // Update the identifier and storage of all file objects
2123 foreach ($fileObjects as $oldIdentifier => $fileObject) {
2124 $newIdentifier = $fileMappings[$oldIdentifier];
2125 $fileObject->updateProperties(['storage' => $this->getUid(), 'identifier' => $newIdentifier]);
2126 $this->getIndexer()->updateIndexEntry($fileObject);
2127 }
2128 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
2129 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
2130 return $returnObject;
2131 }
2132
2133 /**
2134 * Moves the given folder from a different storage to the target folder in this storage.
2135 *
2136 * @param Folder $folderToMove
2137 * @param Folder $targetParentFolder
2138 * @param string $newFolderName
2139 * @throws NotImplementedMethodException
2140 */
2141 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName)
2142 {
2143 throw new NotImplementedMethodException('Not yet implemented', 1476046361);
2144 }
2145
2146 /**
2147 * Copies a folder.
2148 *
2149 * @param FolderInterface $folderToCopy The folder to copy
2150 * @param FolderInterface $targetParentFolder The target folder
2151 * @param string $newFolderName
2152 * @param string $conflictMode a value of the DuplicationBehavior enumeration
2153 * @return Folder The new (copied) folder object
2154 * @throws InvalidTargetFolderException
2155 */
2156 public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
2157 {
2158 // @todo implement the $conflictMode handling
2159 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
2160 $returnObject = null;
2161 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
2162 if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
2163 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2164 }
2165 $sourceStorage = $folderToCopy->getStorage();
2166 // call driver method to move the file
2167 // that also updates the file object properties
2168 if ($sourceStorage === $this) {
2169 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
2170 throw new InvalidTargetFolderException(
2171 sprintf(
2172 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
2173 $folderToCopy->getName(),
2174 $targetParentFolder->getName()
2175 ),
2176 1422723059
2177 );
2178 }
2179 $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2180 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
2181 } else {
2182 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2183 }
2184 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
2185 return $returnObject;
2186 }
2187
2188 /**
2189 * Copies a folder between storages.
2190 *
2191 * @param Folder $folderToCopy
2192 * @param Folder $targetParentFolder
2193 * @param string $newFolderName
2194 * @throws NotImplementedMethodException
2195 */
2196 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName)
2197 {
2198 throw new NotImplementedMethodException('Not yet implemented.', 1476046386);
2199 }
2200
2201 /**
2202 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
2203 *
2204 * @param Folder $folderObject
2205 * @param string $newName
2206 * @throws \Exception
2207 * @throws \InvalidArgumentException
2208 * @return Folder
2209 */
2210 public function renameFolder($folderObject, $newName)
2211 {
2212
2213 // Renaming the folder should check if the parent folder is writable
2214 // We cannot do this however because we cannot extract the parent folder from a folder currently
2215 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
2216 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
2217 }
2218
2219 $sanitizedNewName = $this->driver->sanitizeFileName($newName);
2220 $returnObject = null;
2221 if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
2222 throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
2223 }
2224
2225 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
2226
2227 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
2228 $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
2229 // Update the identifier of all file objects
2230 foreach ($fileObjects as $oldIdentifier => $fileObject) {
2231 $newIdentifier = $fileMappings[$oldIdentifier];
2232 $fileObject->updateProperties(['identifier' => $newIdentifier]);
2233 $this->getIndexer()->updateIndexEntry($fileObject);
2234 }
2235 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
2236
2237 $this->emitPostFolderRenameSignal($returnObject, $returnObject->getName());
2238
2239 return $returnObject;
2240 }
2241
2242 /**
2243 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
2244 *
2245 * @param Folder $folderObject
2246 * @param bool $deleteRecursively
2247 * @throws \RuntimeException
2248 * @throws Exception\InsufficientFolderAccessPermissionsException
2249 * @throws Exception\InsufficientUserPermissionsException
2250 * @throws Exception\FileOperationErrorException
2251 * @throws Exception\InvalidPathException
2252 * @return bool
2253 */
2254 public function deleteFolder($folderObject, $deleteRecursively = false)
2255 {
2256 $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
2257 $this->assureFolderDeletePermission($folderObject, $deleteRecursively && !$isEmpty);
2258 if (!$isEmpty && !$deleteRecursively) {
2259 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
2260 }
2261
2262 $this->emitPreFolderDeleteSignal($folderObject);
2263
2264 foreach ($this->getFilesInFolder($folderObject, 0, 0, false, $deleteRecursively) as $file) {
2265 $this->deleteFile($file);
2266 }
2267
2268 $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
2269
2270 $this->emitPostFolderDeleteSignal($folderObject);
2271
2272 return $result;
2273 }
2274
2275 /**
2276 * Returns the Identifier for a folder within a given folder.
2277 *
2278 * @param string $folderName The name of the target folder
2279 * @param Folder $parentFolder
2280 * @param bool $returnInaccessibleFolderObject
2281 * @return Folder|InaccessibleFolder
2282 * @throws \Exception
2283 * @throws Exception\InsufficientFolderAccessPermissionsException
2284 */
2285 public function getFolderInFolder($folderName, Folder $parentFolder, $returnInaccessibleFolderObject = false)
2286 {
2287 $folderIdentifier = $this->driver->getFolderInFolder($folderName, $parentFolder->getIdentifier());
2288 return $this->getFolder($folderIdentifier, $returnInaccessibleFolderObject);
2289 }
2290
2291 /**
2292 * @param Folder $folder
2293 * @param int $start
2294 * @param int $maxNumberOfItems
2295 * @param bool $useFilters
2296 * @param bool $recursive
2297 * @param string $sort Property name used to sort the items.
2298 * Among them may be: '' (empty, no sorting), name,
2299 * fileext, size, tstamp and rw.
2300 * If a driver does not support the given property, it
2301 * should fall back to "name".
2302 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
2303 * @return Folder[]
2304 */
2305 public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
2306 {
2307 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
2308
2309 $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
2310
2311 // Exclude processing folders
2312 foreach ($this->getProcessingFolders() as $processingFolder) {
2313 $processingIdentifier = $processingFolder->getIdentifier();
2314 if (isset($folderIdentifiers[$processingIdentifier])) {
2315 unset($folderIdentifiers[$processingIdentifier]);
2316 }
2317 }
2318 $folders = [];
2319 foreach ($folderIdentifiers as $folderIdentifier) {
2320 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, true);
2321 }
2322 return $folders;
2323 }
2324
2325 /**
2326 * @param Folder $folder
2327 * @param bool $useFilters
2328 * @param bool $recursive
2329 * @return int Number of subfolders
2330 * @throws Exception\InsufficientFolderAccessPermissionsException
2331 */
2332 public function countFoldersInFolder(Folder $folder, $useFilters = true, $recursive = false)
2333 {
2334 $this->assureFolderReadPermission($folder);
2335 $filters = $useFilters ? $this->fileAndFolderNameFilters : [];
2336 return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2337 }
2338
2339 /**
2340 * Returns TRUE if the specified folder exists.
2341 *
2342 * @param string $identifier
2343 * @return bool
2344 */
2345 public function hasFolder($identifier)
2346 {
2347 $this->assureFolderReadPermission();
2348 return $this->driver->folderExists($identifier);
2349 }
2350
2351 /**
2352 * Checks if the given file exists in the given folder
2353 *
2354 * @param string $folderName
2355 * @param Folder $folder
2356 * @return bool
2357 */
2358 public function hasFolderInFolder($folderName, Folder $folder)
2359 {
2360 $this->assureFolderReadPermission($folder);
2361 return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2362 }
2363
2364 /**
2365 * Creates a new folder.
2366 *
2367 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2368 *
2369 * @param string $folderName The new folder name
2370 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2371 * @return Folder
2372 * @throws Exception\ExistingTargetFolderException
2373 * @throws Exception\InsufficientFolderAccessPermissionsException
2374 * @throws Exception\InsufficientFolderWritePermissionsException
2375 * @throws \Exception
2376 */
2377 public function createFolder($folderName, Folder $parentFolder = null)
2378 {
2379 if ($parentFolder === null) {
2380 $parentFolder = $this->getRootLevelFolder();
2381 } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2382 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2383 }
2384 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2385 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2386 }
2387 if ($this->driver->folderExistsInFolder($folderName, $parentFolder->getIdentifier())) {
2388 throw new Exception\ExistingTargetFolderException('Folder "' . $folderName . '" already exists.', 1423347324);
2389 }
2390
2391 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2392
2393 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), true);
2394 $newFolder = $this->getFolder($newFolder);
2395
2396 $this->emitPostFolderAddSignal($newFolder);
2397
2398 return $newFolder;
2399 }
2400
2401 /**
2402 * Retrieves information about a folder
2403 *
2404 * @param Folder $folder
2405 * @return array
2406 */
2407 public function getFolderInfo(Folder $folder)
2408 {
2409 return $this->driver->getFolderInfoByIdentifier($folder->getIdentifier());
2410 }
2411
2412 /**
2413 * Returns the default folder where new files are stored if no other folder is given.
2414 *
2415 * @return Folder
2416 */
2417 public function getDefaultFolder()
2418 {
2419 return $this->getFolder($this->driver->getDefaultFolder());
2420 }
2421
2422 /**
2423 * @param string $identifier
2424 * @param bool $returnInaccessibleFolderObject
2425 *
2426 * @return Folder|InaccessibleFolder
2427 * @throws \Exception
2428 * @throws Exception\InsufficientFolderAccessPermissionsException
2429 */
2430 public function getFolder($identifier, $returnInaccessibleFolderObject = false)
2431 {
2432 $data = $this->driver->getFolderInfoByIdentifier($identifier);
2433 $folder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2434
2435 try {
2436 $this->assureFolderReadPermission($folder);
2437 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2438 $folder = null;
2439 if ($returnInaccessibleFolderObject) {
2440 // if parent folder is readable return inaccessible folder object
2441 $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2442 if ($parentPermissions['r']) {
2443 $folder = GeneralUtility::makeInstance(
2444 InaccessibleFolder::class,
2445 $this,
2446 $data['identifier'],
2447 $data['name']
2448 );
2449 }
2450 }
2451
2452 if ($folder === null) {
2453 throw $e;
2454 }
2455 }
2456 return $folder;
2457 }
2458
2459 /**
2460 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2461 *
2462 * @param string $identifier
2463 * @return bool
2464 */
2465 public function isWithinProcessingFolder($identifier)
2466 {
2467 $inProcessingFolder = false;
2468 foreach ($this->getProcessingFolders() as $processingFolder) {
2469 if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2470 $inProcessingFolder = true;
2471 break;
2472 }
2473 }
2474 return $inProcessingFolder;
2475 }
2476
2477 /**
2478 * Checks if a resource (file or folder) is within the given folder
2479 *
2480 * @param Folder $folder
2481 * @param ResourceInterface $resource
2482 * @return bool
2483 * @throws \InvalidArgumentException
2484 */
2485 public function isWithinFolder(Folder $folder, ResourceInterface $resource)
2486 {
2487 if ($folder->getStorage() !== $this) {
2488 throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2489 }
2490 if ($folder->getStorage() !== $resource->getStorage()) {
2491 return false;
2492 }
2493 return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2494 }
2495
2496 /**
2497 * Returns the folders on the root level of the storage
2498 * or the first mount point of this storage for this user
2499 * if $respectFileMounts is set.
2500 *
2501 * @param bool $respectFileMounts
2502 * @return Folder
2503 */
2504 public function getRootLevelFolder($respectFileMounts = true)
2505 {
2506 if ($respectFileMounts && !empty($this->fileMounts)) {
2507 $mount = reset($this->fileMounts);
2508 return $mount['folder'];
2509 }
2510 return $this->getResourceFactoryInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2511 }
2512
2513 /**
2514 * Emits sanitize fileName signal.
2515 *
2516 * @param string $fileName
2517 * @param Folder $targetFolder
2518 * @return string Modified target file name
2519 */
2520 protected function emitSanitizeFileNameSignal($fileName, Folder $targetFolder)
2521 {
2522 list($fileName) = $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_SanitizeFileName, [$fileName, $targetFolder, $this, $this->driver]);
2523 return $fileName;
2524 }
2525
2526 /**
2527 * Emits file pre-add signal.
2528 *
2529 * @param string $targetFileName
2530 * @param Folder $targetFolder
2531 * @param string $sourceFilePath
2532 * @return string Modified target file name
2533 */
2534 protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath)
2535 {
2536 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileAdd, [&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver]);
2537 return $targetFileName;
2538 }
2539
2540 /**
2541 * Emits the file post-add signal.
2542 *
2543 * @param FileInterface $file
2544 * @param Folder $targetFolder
2545 */
2546 protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder)
2547 {
2548 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileAdd, [$file, $targetFolder]);
2549 }
2550
2551 /**
2552 * Emits file pre-copy signal.
2553 *
2554 * @param FileInterface $file
2555 * @param Folder $targetFolder
2556 */
2557 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder)
2558 {
2559 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileCopy, [$file, $targetFolder]);
2560 }
2561
2562 /**
2563 * Emits the file post-copy signal.
2564 *
2565 * @param FileInterface $file
2566 * @param Folder $targetFolder
2567 */
2568 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder)
2569 {
2570 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileCopy, [$file, $targetFolder]);
2571 }
2572
2573 /**
2574 * Emits the file pre-move signal.
2575 *
2576 * @param FileInterface $file
2577 * @param Folder $targetFolder
2578 * @param string $targetFileName
2579 */
2580 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder, string $targetFileName)
2581 {
2582 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileMove, [$file, $targetFolder, $targetFileName]);
2583 }
2584
2585 /**
2586 * Emits the file post-move signal.
2587 *
2588 * @param FileInterface $file
2589 * @param Folder $targetFolder
2590 * @param FolderInterface $originalFolder
2591 */
2592 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, FolderInterface $originalFolder)
2593 {
2594 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileMove, [$file, $targetFolder, $originalFolder]);
2595 }
2596
2597 /**
2598 * Emits the file pre-rename signal
2599 *
2600 * @param FileInterface $file
2601 * @param $targetFolder
2602 */
2603 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder)
2604 {
2605 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileRename, [$file, $targetFolder]);
2606 }
2607
2608 /**
2609 * Emits the file post-rename signal.
2610 *
2611 * @param FileInterface $file
2612 * @param string $sanitizedTargetFileName
2613 */
2614 protected function emitPostFileRenameSignal(FileInterface $file, $sanitizedTargetFileName)
2615 {
2616 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileRename, [$file, $sanitizedTargetFileName]);
2617 }
2618
2619 /**
2620 * Emits the file pre-replace signal.
2621 *
2622 * @param FileInterface $file
2623 * @param $localFilePath
2624 */
2625 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath)
2626 {
2627 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileReplace, [$file, $localFilePath]);
2628 }
2629
2630 /**
2631 * Emits the file post-replace signal
2632 *
2633 * @param FileInterface $file
2634 * @param string $localFilePath
2635 */
2636 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath)
2637 {
2638 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileReplace, [$file, $localFilePath]);
2639 }
2640
2641 /**
2642 * Emits the file pre-create signal
2643 *
2644 * @param string $fileName
2645 * @param Folder $targetFolder
2646 */
2647 protected function emitPreFileCreateSignal(string $fileName, Folder $targetFolder)
2648 {
2649 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileCreate, [$fileName, $targetFolder]);
2650 }
2651
2652 /**
2653 * Emits the file post-create signal
2654 *
2655 * @param string $newFileIdentifier
2656 * @param Folder $targetFolder
2657 */
2658 protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder)
2659 {
2660 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileCreate, [$newFileIdentifier, $targetFolder]);
2661 }
2662
2663 /**
2664 * Emits the file pre-deletion signal.
2665 *
2666 * @param FileInterface $file
2667 */
2668 protected function emitPreFileDeleteSignal(FileInterface $file)
2669 {
2670 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileDelete, [$file]);
2671 }
2672
2673 /**
2674 * Emits the file post-deletion signal
2675 *
2676 * @param FileInterface $file
2677 */
2678 protected function emitPostFileDeleteSignal(FileInterface $file)
2679 {
2680 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileDelete, [$file]);
2681 }
2682
2683 /**
2684 * Emits the file pre-set-contents signal
2685 *
2686 * @param FileInterface $file
2687 * @param mixed $content
2688 */
2689 protected function emitPreFileSetContentsSignal(FileInterface $file, $content)
2690 {
2691 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileSetContents, [$file, $content]);
2692 }
2693
2694 /**
2695 * Emits the file post-set-contents signal
2696 *
2697 * @param FileInterface $file
2698 * @param mixed $content
2699 */
2700 protected function emitPostFileSetContentsSignal(FileInterface $file, $content)
2701 {
2702 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileSetContents, [$file, $content]);
2703 }
2704
2705 /**
2706 * Emits the folder pre-add signal.
2707 *
2708 * @param Folder $targetFolder
2709 * @param string $name
2710 */
2711 protected function emitPreFolderAddSignal(Folder $targetFolder, $name)
2712 {
2713 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderAdd, [$targetFolder, $name]);
2714 }
2715
2716 /**
2717 * Emits the folder post-add signal.
2718 *
2719 * @param Folder $folder
2720 */
2721 protected function emitPostFolderAddSignal(Folder $folder)
2722 {
2723 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderAdd, [$folder]);
2724 }
2725
2726 /**
2727 * Emits the folder pre-copy signal.
2728 *
2729 * @param Folder $folder
2730 * @param Folder $targetFolder
2731 * @param $newName
2732 */
2733 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2734 {
2735 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderCopy, [$folder, $targetFolder, $newName]);
2736 }
2737
2738 /**
2739 * Emits the folder post-copy signal.
2740 *
2741 * @param Folder $folder
2742 * @param Folder $targetFolder
2743 * @param $newName
2744 */
2745 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2746 {
2747 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderCopy, [$folder, $targetFolder, $newName]);
2748 }
2749
2750 /**
2751 * Emits the folder pre-move signal.
2752 *
2753 * @param Folder $folder
2754 * @param Folder $targetFolder
2755 * @param $newName
2756 */
2757 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName)
2758 {
2759 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderMove, [$folder, $targetFolder, $newName]);
2760 }
2761
2762 /**
2763 * Emits the folder post-move signal.
2764 *
2765 * @param Folder $folder
2766 * @param Folder $targetFolder
2767 * @param string $newName
2768 * @param Folder $originalFolder
2769 */
2770 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder)
2771 {
2772 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderMove, [$folder, $targetFolder, $newName, $originalFolder]);
2773 }
2774
2775 /**
2776 * Emits the folder pre-rename signal.
2777 *
2778 * @param Folder $folder
2779 * @param string $newName
2780 */
2781 protected function emitPreFolderRenameSignal(Folder $folder, $newName)
2782 {
2783 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderRename, [$folder, $newName]);
2784 }
2785
2786 /**
2787 * Emits the folder post-rename signal.
2788 *
2789 * @param Folder $folder
2790 * @param string $newName
2791 */
2792 protected function emitPostFolderRenameSignal(Folder $folder, $newName)
2793 {
2794 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderRename, [$folder, $newName]);
2795 }
2796
2797 /**
2798 * Emits the folder pre-deletion signal.
2799 *
2800 * @param Folder $folder
2801 */
2802 protected function emitPreFolderDeleteSignal(Folder $folder)
2803 {
2804 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderDelete, [$folder]);
2805 }
2806
2807 /**
2808 * Emits folder post-deletion signal..
2809 *
2810 * @param Folder $folder
2811 */
2812 protected function emitPostFolderDeleteSignal(Folder $folder)
2813 {
2814 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderDelete, [$folder]);
2815 }
2816
2817 /**
2818 * Emits file pre-processing signal when generating a public url for a file or folder.
2819 *
2820 * @param ResourceInterface $resourceObject
2821 * @param bool $relativeToCurrentScript
2822 * @param array $urlData
2823 */
2824 protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData)
2825 {
2826 $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreGeneratePublicUrl, [$this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData]);
2827 }
2828
2829 /**
2830 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2831 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber.
2832 * Hereafter a unique string will be appended.
2833 * This function is used by fx. DataHandler when files are attached to records
2834 * and needs to be uniquely named in the uploads/* folders
2835 *
2836 * @param FolderInterface $folder
2837 * @param string $theFile The input fileName to check
2838 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2839 *
2840 * @throws \RuntimeException
2841 * @return string A unique fileName inside $folder, based on $theFile.
2842 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2843 */
2844 protected function getUniqueName(FolderInterface $folder, $theFile, $dontCheckForUnique = false)
2845 {
2846 $maxNumber = 99;
2847 // Fetches info about path, name, extension of $theFile
2848 $origFileInfo = PathUtility::pathinfo($theFile);
2849 // Check if the file exists and if not - return the fileName...
2850 // The destinations file
2851 $theDestFile = $origFileInfo['basename'];
2852 // If the file does NOT exist we return this fileName
2853 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2854 return $theDestFile;
2855 }
2856 // Well the fileName in its pure form existed. Now we try to append
2857 // numbers / unique-strings and see if we can find an available fileName
2858 // This removes _xx if appended to the file
2859 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2860 $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2861 for ($a = 1; $a <= $maxNumber + 1; $a++) {
2862 // First we try to append numbers
2863 if ($a <= $maxNumber) {
2864 $insert = '_' . sprintf('%02d', $a);
2865 } else {
2866 $insert = '_' . substr(md5(uniqid('', true)), 0, 6);
2867 }
2868 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2869 // The destinations file
2870 $theDestFile = $theTestFile;
2871 // If the file does NOT exist we return this fileName
2872 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2873 return $theDestFile;
2874 }
2875 }
2876 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2877 }
2878
2879 /**
2880 * Get the SignalSlot dispatcher.
2881 *
2882 * @return Dispatcher
2883 */
2884 protected function getSignalSlotDispatcher()
2885 {
2886 if (!isset($this->signalSlotDispatcher)) {
2887 $this->signalSlotDispatcher = $this->getObjectManager()->get(Dispatcher::class);
2888 }
2889 return $this->signalSlotDispatcher;
2890 }
2891
2892 /**
2893 * Gets the ObjectManager.
2894 *
2895 * @return ObjectManager
2896 */
2897 protected function getObjectManager()
2898 {
2899 return GeneralUtility::makeInstance(ObjectManager::class);
2900 }
2901
2902 /**
2903 * @return ResourceFactory
2904 */
2905 protected function getFileFactory()
2906 {
2907 return GeneralUtility::makeInstance(ResourceFactory::class);
2908 }
2909
2910 /**
2911 * @return Index\FileIndexRepository
2912 */
2913 protected function getFileIndexRepository()
2914 {
2915 return FileIndexRepository::getInstance();
2916 }
2917
2918 /**
2919 * @return Service\FileProcessingService
2920 */
2921 protected function getFileProcessingService()
2922 {
2923 if (!$this->fileProcessingService) {
2924 $this->fileProcessingService = GeneralUtility::makeInstance(Service\FileProcessingService::class, $this, $this->driver);
2925 }
2926 return $this->fileProcessingService;
2927 }
2928
2929 /**
2930 * Gets the role of a folder.
2931 *
2932 * @param FolderInterface $folder Folder object to get the role from
2933 * @return string The role the folder has
2934 */
2935 public function getRole(FolderInterface $folder)
2936 {
2937 $folderRole = FolderInterface::ROLE_DEFAULT;
2938 $identifier = $folder->getIdentifier();
2939 if (method_exists($this->driver, 'getRole')) {
2940 $folderRole = $this->driver->getRole($folder->getIdentifier());
2941 }
2942 if (isset($this->fileMounts[$identifier])) {
2943 $folderRole = FolderInterface::ROLE_MOUNT;
2944
2945 if (!empty($this->fileMounts[$identifier]['read_only'])) {
2946 $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
2947 }
2948 if ($this->fileMounts[$identifier]['user_mount']) {
2949 $folderRole = FolderInterface::ROLE_USER_MOUNT;
2950 }
2951 }
2952 if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2953 $folderRole = FolderInterface::ROLE_PROCESSING;
2954 }
2955
2956 return $folderRole;
2957 }
2958
2959 /**
2960 * Getter function to return the folder where the files can
2961 * be processed. Does not check for access rights here.
2962 *
2963 * @param File $file Specific file you want to have the processing folder for
2964 * @return Folder
2965 */
2966 public function getProcessingFolder(File $file = null)
2967 {
2968 // If a file is given, make sure to return the processing folder of the correct storage
2969 if ($file !== null && $file->getStorage()->getUid() !== $this->getUid()) {
2970 return $file->getStorage()->getProcessingFolder($file);
2971 }
2972 if (!isset($this->processingFolder)) {
2973 $processingFolder = self::DEFAULT_ProcessingFolder;
2974 if (!empty($this->storageRecord['processingfolder'])) {
2975 $processingFolder = $this->storageRecord['processingfolder'];
2976 }
2977 try {
2978 if (strpos($processingFolder, ':') !== false) {
2979 list($storageUid, $processingFolderIdentifier) = explode(':', $processingFolder, 2);
2980 $storage = $this->getResourceFactoryInstance()->getStorageObject($storageUid);
2981 if ($storage->hasFolder($processingFolderIdentifier)) {
2982 $this->processingFolder = $storage->getFolder($processingFolderIdentifier);
2983 } else {
2984 $rootFolder = $storage->getRootLevelFolder(false);
2985 $currentEvaluatePermissions = $storage->getEvaluatePermissions();
2986 $storage->setEvaluatePermissions(false);
2987 $this->processingFolder = $storage->createFolder(
2988 ltrim($processingFolderIdentifier, '/'),
2989 $rootFolder
2990 );
2991 $storage->setEvaluatePermissions($currentEvaluatePermissions);
2992 }
2993 } else {
2994 if ($this->driver->folderExists($processingFolder) === false) {
2995 $rootFolder = $this->getRootLevelFolder(false);
2996 try {
2997 $currentEvaluatePermissions = $this->evaluatePermissions;
2998 $this->evaluatePermissions = false;
2999 $this->processingFolder = $this->createFolder(
3000 $processingFolder,
3001 $rootFolder
3002 );
3003 $this->evaluatePermissions = $currentEvaluatePermissions;
3004 } catch (\InvalidArgumentException $e) {
3005 $this->processingFolder = GeneralUtility::makeInstance(
3006 InaccessibleFolder::class,
3007 $this,
3008 $processingFolder,
3009 $processingFolder
3010 );
3011 }
3012 } else {
3013 $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
3014 $this->processingFolder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
3015 }
3016 }
3017 } catch (Exception\InsufficientFolderWritePermissionsException $e) {
3018 $this->processingFolder = GeneralUtility::makeInstance(
3019 InaccessibleFolder::class,
3020 $this,
3021 $processingFolder,
3022 $processingFolder
3023 );
3024 } catch (Exception\ResourcePermissionsUnavailableException $e) {
3025 $this->processingFolder = GeneralUtility::makeInstance(
3026 InaccessibleFolder::class,
3027 $this,
3028 $processingFolder,
3029 $processingFolder
3030 );
3031 }
3032 }
3033
3034 $processingFolder = $this->processingFolder;
3035 if (!empty($file)) {
3036 $processingFolder = $this->getNestedProcessingFolder($file, $processingFolder);
3037 }
3038 return $processingFolder;
3039 }
3040
3041 /**
3042 * Getter function to return the the file's corresponding hashed subfolder
3043 * of the processed folder
3044 *
3045 * @param File $file
3046 * @param Folder $rootProcessingFolder
3047 * @return Folder
3048 * @throws Exception\InsufficientFolderWritePermissionsException
3049 */
3050 protected function getNestedProcessingFolder(File $file, Folder $rootProcessingFolder)
3051 {
3052 $processingFolder = $rootProcessingFolder;
3053 $nestedFolderNames = $this->getNamesForNestedProcessingFolder(
3054 $file->getIdentifier(),
3055 self::PROCESSING_FOLDER_LEVELS
3056 );
3057
3058 try {
3059 foreach ($nestedFolderNames as $folderName) {
3060 if ($processingFolder->hasFolder($folderName)) {
3061 $processingFolder = $processingFolder->getSubfolder($folderName);
3062 } else {
3063 $currentEvaluatePermissions = $processingFolder->getStorage()->getEvaluatePermissions();
3064 $processingFolder->getStorage()->setEvaluatePermissions(false);
3065 $processingFolder = $processingFolder->createFolder($folderName);
3066 $processingFolder->getStorage()->setEvaluatePermissions($currentEvaluatePermissions);
3067 }
3068 }
3069 } catch (Exception\FolderDoesNotExistException $e) {
3070 }
3071
3072 return $processingFolder;
3073 }
3074
3075 /**
3076 * Generates appropriate hashed sub-folder path for a given file identifier
3077 *
3078 * @param string $fileIdentifier
3079 * @param int $levels
3080 * @return string[]
3081 */
3082 protected function getNamesForNestedProcessingFolder($fileIdentifier, $levels)
3083 {
3084 $names = [];
3085 if ($levels === 0) {
3086 return $names;
3087 }
3088 $hash = md5($fileIdentifier);
3089 for ($i = 1; $i <= $levels; $i++) {
3090 $names[] = substr($hash, $i, 1);
3091 }
3092 return $names;
3093 }
3094
3095 /**
3096 * Gets the driver Type configured for this storage.
3097 *
3098 * @return string
3099 */
3100 public function getDriverType()
3101 {
3102 return $this->storageRecord['driver'];
3103 }
3104
3105 /**
3106 * Gets the Indexer.
3107 *
3108 * @return Index\Indexer
3109 */
3110 protected function getIndexer()
3111 {
3112 return GeneralUtility::makeInstance(Index\Indexer::class, $this);
3113 }
3114
3115 /**
3116 * @param bool $isDefault
3117 */
3118 public function setDefault($isDefault)
3119 {
3120 $this->isDefault = (bool)$isDefault;
3121 }
3122
3123 /**
3124 * @return bool
3125 */
3126 public function isDefault()
3127 {
3128 return $this->isDefault;
3129 }
3130
3131 /**
3132 * @return ResourceFactory
3133 */
3134 public function getResourceFactoryInstance(): ResourceFactory
3135 {
3136 return ResourceFactory::getInstance();
3137 }
3138
3139 /**
3140 * Returns the current BE user.
3141 *
3142 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
3143 */
3144 protected function getBackendUser()
3145 {
3146 return $GLOBALS['BE_USER'];
3147 }
3148
3149 /**
3150 * Get the nearest Recycler folder for given file
3151 *
3152 * Return null if:
3153 * - There is no folder with ROLE_RECYCLER in the rootline of the given File
3154 * - File is a ProcessedFile (we don't know the concept of recycler folders for processedFiles)
3155 * - File is located in a folder with ROLE_RECYCLER
3156 *
3157 * @param FileInterface $file
3158 * @return Folder|null
3159 */
3160 protected function getNearestRecyclerFolder(FileInterface $file)
3161 {
3162 if ($file instanceof ProcessedFile) {
3163 return null;
3164 }
3165 // if the storage is not browsable we cannot fetch the parent folder of the file so no recycler handling is possible
3166 if (!$this->isBrowsable()) {
3167 return null;
3168 }
3169
3170 $recyclerFolder = null;
3171 $folder = $file->getParentFolder();
3172
3173 do {
3174 if ($folder->getRole() === FolderInterface::ROLE_RECYCLER) {
3175 break;
3176 }
3177
3178 foreach ($folder->getSubfolders() as $subFolder) {
3179 if ($subFolder->getRole() === FolderInterface::ROLE_RECYCLER) {
3180 $recyclerFolder = $subFolder;
3181 break;
3182 }
3183 }
3184
3185 $parentFolder = $folder->getParentFolder();
3186 $isFolderLoop = $folder->getIdentifier() === $parentFolder->getIdentifier();
3187 $folder = $parentFolder;
3188 } while ($recyclerFolder === null && !$isFolderLoop);
3189
3190 return $recyclerFolder;
3191 }
3192 }