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