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