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