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