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