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