[BUGFIX] Handling of uploaded files not within open_basedir
[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\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
18 use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
19 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
20 use TYPO3\CMS\Core\Resource\Index\Indexer;
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 /********************
1138 * FILE ACTIONS
1139 ********************/
1140 /**
1141 * Moves a file from the local filesystem to this storage.
1142 *
1143 * @param string $localFilePath The file on the server's hard disk to add
1144 * @param Folder $targetFolder The target folder where the file should be added
1145 * @param string $targetFileName The name of the file to be add, If not set, the local file name is used
1146 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
1147 *
1148 * @throws \InvalidArgumentException
1149 * @throws Exception\ExistingTargetFileNameException
1150 * @return FileInterface
1151 */
1152 public function addFile($localFilePath, Folder $targetFolder, $targetFileName = '', $conflictMode = DuplicationBehavior::RENAME)
1153 {
1154 $localFilePath = PathUtility::getCanonicalPath($localFilePath);
1155 // File is not available locally NOR is it an uploaded file
1156 if (!is_uploaded_file($localFilePath) && !file_exists($localFilePath)) {
1157 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1158 }
1159 $conflictMode = DuplicationBehavior::cast($conflictMode);
1160 $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1161 $targetFileName = $this->sanitizeFileName($targetFileName ?: PathUtility::basename($localFilePath), $targetFolder);
1162
1163 $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1164
1165 $this->assureFileAddPermissions($targetFolder, $targetFileName);
1166 if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1167 throw new Exception\ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1168 } elseif ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1169 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1170 }
1171
1172 $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName);
1173 $file = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1174
1175 if ($this->autoExtractMetadataEnabled()) {
1176 $indexer = GeneralUtility::makeInstance(Indexer::class, $this);
1177 $indexer->extractMetaData($file);
1178 }
1179
1180 $this->emitPostFileAddSignal($file, $targetFolder);
1181
1182 return $file;
1183 }
1184
1185 /**
1186 * Updates a processed file with a new file from the local filesystem.
1187 *
1188 * @param string $localFilePath
1189 * @param ProcessedFile $processedFile
1190 * @param Folder $processingFolder
1191 * @return FileInterface
1192 * @throws \InvalidArgumentException
1193 * @internal use only
1194 */
1195 public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = null)
1196 {
1197 if (!file_exists($localFilePath)) {
1198 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1199 }
1200 if ($processingFolder === null) {
1201 $processingFolder = $this->getProcessingFolder();
1202 }
1203 $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1204
1205 // @todo check if we have to update the processed file other then the identifier
1206 $processedFile->setIdentifier($fileIdentifier);
1207 return $processedFile;
1208 }
1209
1210 /**
1211 * Creates a (cryptographic) hash for a file.
1212 *
1213 * @param FileInterface $fileObject
1214 * @param string $hash
1215 * @return string
1216 */
1217 public function hashFile(FileInterface $fileObject, $hash)
1218 {
1219 return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1220 }
1221
1222 /**
1223 * Creates a (cryptographic) hash for a fileIdentifier.
1224
1225 * @param string $fileIdentifier
1226 * @param string $hash
1227 *
1228 * @return string
1229 */
1230 public function hashFileByIdentifier($fileIdentifier, $hash)
1231 {
1232 return $this->driver->hash($fileIdentifier, $hash);
1233 }
1234
1235 /**
1236 * Hashes a file identifier, taking the case sensitivity of the file system
1237 * into account. This helps mitigating problems with case-insensitive
1238 * databases.
1239 *
1240 * @param string|FileInterface $file
1241 * @return string
1242 */
1243 public function hashFileIdentifier($file)
1244 {
1245 if (is_object($file) && $file instanceof FileInterface) {
1246 /** @var FileInterface $file */
1247 $file = $file->getIdentifier();
1248 }
1249 return $this->driver->hashIdentifier($file);
1250 }
1251
1252 /**
1253 * Returns a publicly accessible URL for a file.
1254 *
1255 * WARNING: Access to the file may be restricted by further means, e.g.
1256 * some web-based authentication. You have to take care of this yourself.
1257 *
1258 * @param ResourceInterface $resourceObject The file or folder object
1259 * @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)
1260 * @return string
1261 */
1262 public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = false)
1263 {
1264 $publicUrl = null;
1265 if ($this->isOnline()) {
1266 // Pre-process the public URL by an accordant slot
1267 $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
1268
1269 if (
1270 $publicUrl === null
1271 && $resourceObject instanceof File
1272 && ($helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($resourceObject)) !== false
1273 ) {
1274 $publicUrl = $helper->getPublicUrl($resourceObject, $relativeToCurrentScript);
1275 }
1276
1277 // If slot did not handle the signal, use the default way to determine public URL
1278 if ($publicUrl === null) {
1279 if ($this->hasCapability(self::CAPABILITY_PUBLIC)) {
1280 $publicUrl = $this->driver->getPublicUrl($resourceObject->getIdentifier());
1281 }
1282
1283 if ($publicUrl === null && $resourceObject instanceof FileInterface) {
1284 $queryParameterArray = array('eID' => 'dumpFile', 't' => '');
1285 if ($resourceObject instanceof File) {
1286 $queryParameterArray['f'] = $resourceObject->getUid();
1287 $queryParameterArray['t'] = 'f';
1288 } elseif ($resourceObject instanceof ProcessedFile) {
1289 $queryParameterArray['p'] = $resourceObject->getUid();
1290 $queryParameterArray['t'] = 'p';
1291 }
1292
1293 $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1294 $publicUrl = 'index.php?' . str_replace('+', '%20', http_build_query($queryParameterArray));
1295 }
1296
1297 // If requested, make the path relative to the current script in order to make it possible
1298 // to use the relative file
1299 if ($publicUrl !== null && $relativeToCurrentScript && !GeneralUtility::isValidUrl($publicUrl)) {
1300 $absolutePathToContainingFolder = PathUtility::dirname(PATH_site . $publicUrl);
1301 $pathPart = PathUtility::getRelativePathTo($absolutePathToContainingFolder);
1302 $filePart = substr(PATH_site . $publicUrl, strlen($absolutePathToContainingFolder) + 1);
1303 $publicUrl = $pathPart . $filePart;
1304 }
1305 }
1306 }
1307 return $publicUrl;
1308 }
1309
1310 /**
1311 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1312 *
1313 * @param FileInterface $fileObject The file object
1314 * @param string $context
1315 * @param array $configuration
1316 *
1317 * @return ProcessedFile
1318 * @throws \InvalidArgumentException
1319 */
1320 public function processFile(FileInterface $fileObject, $context, array $configuration)
1321 {
1322 if ($fileObject->getStorage() !== $this) {
1323 throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1324 }
1325 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1326
1327 return $processedFile;
1328 }
1329
1330 /**
1331 * Copies a file from the storage for local processing.
1332 *
1333 * @param FileInterface $fileObject
1334 * @param bool $writable
1335 * @return string Path to local file (either original or copied to some temporary local location)
1336 */
1337 public function getFileForLocalProcessing(FileInterface $fileObject, $writable = true)
1338 {
1339 $filePath = $this->driver->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1340 return $filePath;
1341 }
1342
1343 /**
1344 * Gets a file by identifier.
1345 *
1346 * @param string $identifier
1347 * @return FileInterface
1348 */
1349 public function getFile($identifier)
1350 {
1351 $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1352 if (!$this->driver->fileExists($identifier)) {
1353 $file->setMissing(true);
1354 }
1355 return $file;
1356 }
1357
1358 /**
1359 * Gets information about a file.
1360 *
1361 * @param FileInterface $fileObject
1362 * @return array
1363 * @internal
1364 */
1365 public function getFileInfo(FileInterface $fileObject)
1366 {
1367 return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1368 }
1369
1370 /**
1371 * Gets information about a file by its identifier.
1372 *
1373 * @param string $identifier
1374 * @param array $propertiesToExtract
1375 * @return array
1376 * @internal
1377 */
1378 public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array())
1379 {
1380 return $this->driver->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1381 }
1382
1383 /**
1384 * Unsets the file and folder name filters, thus making this storage return unfiltered filelists.
1385 *
1386 * @return void
1387 */
1388 public function unsetFileAndFolderNameFilters()
1389 {
1390 $this->fileAndFolderNameFilters = array();
1391 }
1392
1393 /**
1394 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1395 *
1396 * @return void
1397 */
1398 public function resetFileAndFolderNameFiltersToDefault()
1399 {
1400 $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1401 }
1402
1403 /**
1404 * Returns the file and folder name filters used by this storage.
1405 *
1406 * @return array
1407 */
1408 public function getFileAndFolderNameFilters()
1409 {
1410 return $this->fileAndFolderNameFilters;
1411 }
1412
1413 /**
1414 * @param array $filters
1415 * @return $this
1416 */
1417 public function setFileAndFolderNameFilters(array $filters)
1418 {
1419 $this->fileAndFolderNameFilters = $filters;
1420 return $this;
1421 }
1422
1423 /**
1424 * @param array $filter
1425 */
1426 public function addFileAndFolderNameFilter($filter)
1427 {
1428 $this->fileAndFolderNameFilters[] = $filter;
1429 }
1430
1431 /**
1432 * @param string $fileIdentifier
1433 *
1434 * @return string
1435 */
1436 public function getFolderIdentifierFromFileIdentifier($fileIdentifier)
1437 {
1438 return $this->driver->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1439 }
1440
1441 /**
1442 * Get file from folder
1443 *
1444 * @param string $fileName
1445 * @param Folder $folder
1446 * @return NULL|File|ProcessedFile
1447 */
1448 public function getFileInFolder($fileName, Folder $folder)
1449 {
1450 $identifier = $this->driver->getFileInFolder($fileName, $folder->getIdentifier());
1451 return $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1452 }
1453
1454 /**
1455 * @param Folder $folder
1456 * @param int $start
1457 * @param int $maxNumberOfItems
1458 * @param bool $useFilters
1459 * @param bool $recursive
1460 * @param string $sort Property name used to sort the items.
1461 * Among them may be: '' (empty, no sorting), name,
1462 * fileext, size, tstamp and rw.
1463 * If a driver does not support the given property, it
1464 * should fall back to "name".
1465 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1466 * @return File[]
1467 * @throws Exception\InsufficientFolderAccessPermissionsException
1468 */
1469 public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
1470 {
1471 $this->assureFolderReadPermission($folder);
1472
1473 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1474
1475 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : array();
1476 $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1477
1478 $items = array();
1479 foreach ($fileIdentifiers as $identifier) {
1480 if (isset($rows[$identifier])) {
1481 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1482 } else {
1483 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1484 }
1485 if ($fileObject instanceof FileInterface) {
1486 $key = $fileObject->getName();
1487 while (isset($items[$key])) {
1488 $key .= 'z';
1489 }
1490 $items[$key] = $fileObject;
1491 }
1492 }
1493
1494 return $items;
1495 }
1496
1497 /**
1498 * @param string $folderIdentifier
1499 * @param bool $useFilters
1500 * @param bool $recursive
1501 * @return array
1502 */
1503 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1504 {
1505 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : array();
1506 return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1507 }
1508
1509 /**
1510 * @param Folder $folder
1511 * @param bool $useFilters
1512 * @param bool $recursive
1513 * @return int Number of files in folder
1514 * @throws Exception\InsufficientFolderAccessPermissionsException
1515 */
1516 public function countFilesInFolder(Folder $folder, $useFilters = true, $recursive = false)
1517 {
1518 $this->assureFolderReadPermission($folder);
1519 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
1520 return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1521 }
1522
1523 /**
1524 * @param string $folderIdentifier
1525 * @param bool $useFilters
1526 * @param bool $recursive
1527 * @return array
1528 */
1529 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1530 {
1531 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : array();
1532 return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1533 }
1534
1535 /**
1536 * Returns TRUE if the specified file exists
1537 *
1538 * @param string $identifier
1539 * @return bool
1540 */
1541 public function hasFile($identifier)
1542 {
1543 // Allow if identifier is in processing folder
1544 if (!$this->isWithinProcessingFolder($identifier)) {
1545 $this->assureFolderReadPermission();
1546 }
1547 return $this->driver->fileExists($identifier);
1548 }
1549
1550 /**
1551 * Get all processing folders that live in this storage
1552 *
1553 * @return Folder[]
1554 */
1555 public function getProcessingFolders()
1556 {
1557 if ($this->processingFolders === null) {
1558 $this->processingFolders = array();
1559 $this->processingFolders[] = $this->getProcessingFolder();
1560 /** @var $storageRepository StorageRepository */
1561 $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1562 $allStorages = $storageRepository->findAll();
1563 foreach ($allStorages as $storage) {
1564 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1565 // See #66695 for details
1566 list($storageUid, $processingFolderIdentifier) = GeneralUtility::trimExplode(':', $storage->getStorageRecord()['processingfolder']);
1567 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1568 continue;
1569 }
1570 $potentialProcessingFolder = ResourceFactory::getInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1571 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1572 $this->processingFolders[] = $potentialProcessingFolder;
1573 }
1574 }
1575 }
1576
1577 return $this->processingFolders;
1578 }
1579
1580 /**
1581 * Returns TRUE if folder that is in current storage is set as
1582 * processing folder for one of the existing storages
1583 *
1584 * @param Folder $folder
1585 * @return bool
1586 */
1587 public function isProcessingFolder(Folder $folder)
1588 {
1589 $isProcessingFolder = false;
1590 foreach ($this->getProcessingFolders() as $processingFolder) {
1591 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1592 $isProcessingFolder = true;
1593 break;
1594 }
1595 }
1596 return $isProcessingFolder;
1597 }
1598
1599 /**
1600 * Checks if the queried file in the given folder exists
1601 *
1602 * @param string $fileName
1603 * @param Folder $folder
1604 * @return bool
1605 */
1606 public function hasFileInFolder($fileName, Folder $folder)
1607 {
1608 $this->assureFolderReadPermission($folder);
1609 return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1610 }
1611
1612 /**
1613 * Get contents of a file object
1614 *
1615 * @param FileInterface $file
1616 *
1617 * @throws Exception\InsufficientFileReadPermissionsException
1618 * @return string
1619 */
1620 public function getFileContents($file)
1621 {
1622 $this->assureFileReadPermission($file);
1623 return $this->driver->getFileContents($file->getIdentifier());
1624 }
1625
1626 /**
1627 * Outputs file Contents,
1628 * clears output buffer first and sends headers accordingly.
1629 *
1630 * @param FileInterface $file
1631 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1632 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1633 * @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
1634 * @return void
1635 */
1636 public function dumpFileContents(FileInterface $file, $asDownload = false, $alternativeFilename = null, $overrideMimeType = null)
1637 {
1638 $downloadName = $alternativeFilename ?: $file->getName();
1639 $contentDisposition = $asDownload ? 'attachment' : 'inline';
1640 header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1641 header('Content-Type: ' . ($overrideMimeType ?: $file->getMimeType()));
1642 header('Content-Length: ' . $file->getSize());
1643
1644 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1645 // See for more information: http://support.microsoft.com/kb/323308
1646 header("Cache-Control: ''");
1647 header('Last-Modified: ' .
1648 gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), array('mtime')))) . ' GMT',
1649 true,
1650 200
1651 );
1652 ob_clean();
1653 flush();
1654 while (ob_get_level() > 0) {
1655 ob_end_clean();
1656 }
1657 $this->driver->dumpFileContents($file->getIdentifier());
1658 }
1659
1660 /**
1661 * Set contents of a file object.
1662 *
1663 * @param AbstractFile $file
1664 * @param string $contents
1665 *
1666 * @throws \Exception|\RuntimeException
1667 * @throws Exception\InsufficientFileWritePermissionsException
1668 * @throws Exception\InsufficientUserPermissionsException
1669 * @return int The number of bytes written to the file
1670 */
1671 public function setFileContents(AbstractFile $file, $contents)
1672 {
1673 // Check if user is allowed to edit
1674 $this->assureFileWritePermissions($file);
1675 // Call driver method to update the file and update file index entry afterwards
1676 $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1677 $this->getIndexer()->updateIndexEntry($file);
1678 $this->emitPostFileSetContentsSignal($file, $contents);
1679 return $result;
1680 }
1681
1682 /**
1683 * Creates a new file
1684 *
1685 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1686 *
1687 * @param string $fileName The name of the file to be created
1688 * @param Folder $targetFolderObject The target folder where the file should be created
1689 *
1690 * @throws Exception\IllegalFileExtensionException
1691 * @throws Exception\InsufficientFolderWritePermissionsException
1692 * @return FileInterface The file object
1693 */
1694 public function createFile($fileName, Folder $targetFolderObject)
1695 {
1696 $this->assureFileAddPermissions($targetFolderObject, $fileName);
1697 $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1698 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1699 return ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1700 }
1701
1702 /**
1703 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1704 *
1705 * @param $fileObject FileInterface
1706 * @throws Exception\InsufficientFileAccessPermissionsException
1707 * @throws Exception\FileOperationErrorException
1708 * @return bool TRUE if deletion succeeded
1709 */
1710 public function deleteFile($fileObject)
1711 {
1712 $this->assureFileDeletePermissions($fileObject);
1713
1714 $this->emitPreFileDeleteSignal($fileObject);
1715
1716 if ($this->driver->fileExists($fileObject->getIdentifier())) {
1717 $result = $this->driver->deleteFile($fileObject->getIdentifier());
1718 if (!$result) {
1719 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1720 }
1721 }
1722 // Mark the file object as deleted
1723 if ($fileObject instanceof File) {
1724 $fileObject->setDeleted();
1725 }
1726
1727 $this->emitPostFileDeleteSignal($fileObject);
1728
1729 return true;
1730 }
1731
1732 /**
1733 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1734 * copies a source file (from any location) in to the target
1735 * folder, the latter has to be part of this storage
1736 *
1737 * @param FileInterface $file
1738 * @param Folder $targetFolder
1739 * @param string $targetFileName an optional destination fileName
1740 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
1741 *
1742 * @throws \Exception|Exception\AbstractFileOperationException
1743 * @throws Exception\ExistingTargetFileNameException
1744 * @return FileInterface
1745 */
1746 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1747 {
1748 $conflictMode = DuplicationBehavior::cast($conflictMode);
1749 if ($targetFileName === null) {
1750 $targetFileName = $file->getName();
1751 }
1752 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1753 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1754 $this->emitPreFileCopySignal($file, $targetFolder);
1755 // File exists and we should abort, let's abort
1756 if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1757 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1758 }
1759 // File exists and we should find another name, let's find another one
1760 if ($conflictMode->equals(DuplicationBehavior::RENAME) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1761 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1762 }
1763 $sourceStorage = $file->getStorage();
1764 // Call driver method to create a new file from an existing file object,
1765 // and return the new file object
1766 if ($sourceStorage === $this) {
1767 $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1768 } else {
1769 $tempPath = $file->getForLocalProcessing();
1770 $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1771 }
1772 $newFileObject = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1773 $this->emitPostFileCopySignal($file, $targetFolder);
1774 return $newFileObject;
1775 }
1776
1777 /**
1778 * Moves a $file into a $targetFolder
1779 * the target folder has to be part of this storage
1780 *
1781 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1782 *
1783 * @param FileInterface $file
1784 * @param Folder $targetFolder
1785 * @param string $targetFileName an optional destination fileName
1786 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
1787 *
1788 * @throws Exception\ExistingTargetFileNameException
1789 * @throws \RuntimeException
1790 * @return FileInterface
1791 */
1792 public function moveFile($file, $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1793 {
1794 $conflictMode = DuplicationBehavior::cast($conflictMode);
1795 if ($targetFileName === null) {
1796 $targetFileName = $file->getName();
1797 }
1798 $originalFolder = $file->getParentFolder();
1799 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1800 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1801 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1802 // File exists and we should abort, let's abort
1803 if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1804 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1805 } elseif ($conflictMode->equals(DuplicationBehavior::CANCEL)) {
1806 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1807 }
1808 }
1809 $this->emitPreFileMoveSignal($file, $targetFolder);
1810 $sourceStorage = $file->getStorage();
1811 // Call driver method to move the file and update the index entry
1812 try {
1813 if ($sourceStorage === $this) {
1814 $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1815 if (!$file instanceof AbstractFile) {
1816 throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1817 }
1818 $file->updateProperties(array('identifier' => $newIdentifier));
1819 } else {
1820 $tempPath = $file->getForLocalProcessing();
1821 $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1822 $sourceStorage->driver->deleteFile($file->getIdentifier());
1823 if ($file instanceof File) {
1824 $file->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1825 }
1826 }
1827 $this->getIndexer()->updateIndexEntry($file);
1828 } catch (\TYPO3\CMS\Core\Exception $e) {
1829 echo $e->getMessage();
1830 }
1831 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1832 return $file;
1833 }
1834
1835 /**
1836 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1837 *
1838 * @param FileInterface $file
1839 * @param string $targetFileName
1840 *
1841 * @throws Exception\InsufficientFileWritePermissionsException
1842 * @throws Exception\InsufficientFileReadPermissionsException
1843 * @throws Exception\InsufficientUserPermissionsException
1844 * @return FileInterface
1845 */
1846 public function renameFile($file, $targetFileName)
1847 {
1848 // @todo add $conflictMode setting
1849
1850 // The name should be different from the current.
1851 if ($file->getName() === $targetFileName) {
1852 return $file;
1853 }
1854 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1855 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1856 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1857
1858 // Call driver method to rename the file and update the index entry
1859 try {
1860 $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1861 if ($file instanceof File) {
1862 $file->updateProperties(array('identifier' => $newIdentifier));
1863 }
1864 $this->getIndexer()->updateIndexEntry($file);
1865 } catch (\RuntimeException $e) {
1866 }
1867
1868 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1869
1870 return $file;
1871 }
1872
1873 /**
1874 * Replaces a file with a local file (e.g. a freshly uploaded file)
1875 *
1876 * @param FileInterface $file
1877 * @param string $localFilePath
1878 *
1879 * @return FileInterface
1880 *
1881 * @throws Exception\IllegalFileExtensionException
1882 * @throws \InvalidArgumentException
1883 */
1884 public function replaceFile(FileInterface $file, $localFilePath)
1885 {
1886 $this->assureFileReplacePermissions($file);
1887 if (!$this->checkFileExtensionPermission($localFilePath)) {
1888 throw new Exception\IllegalFileExtensionException('Source file extension not allowed.', 1378132239);
1889 }
1890 if (!file_exists($localFilePath)) {
1891 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1892 }
1893 $this->emitPreFileReplaceSignal($file, $localFilePath);
1894 $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
1895 if ($file instanceof File) {
1896 $this->getIndexer()->updateIndexEntry($file);
1897 }
1898 if ($this->autoExtractMetadataEnabled()) {
1899 $indexer = GeneralUtility::makeInstance(Indexer::class, $this);
1900 $indexer->extractMetaData($file);
1901 }
1902 $this->emitPostFileReplaceSignal($file, $localFilePath);
1903
1904 return $file;
1905 }
1906
1907 /**
1908 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1909 *
1910 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1911 * @param Folder $targetFolder the target folder
1912 * @param string $targetFileName the file name to be written
1913 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
1914 * @return FileInterface The file object
1915 */
1916 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = null, $targetFileName = null, $conflictMode = DuplicationBehavior::CANCEL)
1917 {
1918 $conflictMode = DuplicationBehavior::cast($conflictMode);
1919 $localFilePath = $uploadedFileData['tmp_name'];
1920 if ($targetFolder === null) {
1921 $targetFolder = $this->getDefaultFolder();
1922 }
1923 if ($targetFileName === null) {
1924 $targetFileName = $uploadedFileData['name'];
1925 }
1926 $targetFileName = $this->driver->sanitizeFileName($targetFileName);
1927
1928 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1929 if ($this->hasFileInFolder($targetFileName, $targetFolder) && $conflictMode->equals(DuplicationBehavior::REPLACE)) {
1930 $file = $this->getFileInFolder($targetFileName, $targetFolder);
1931 $resultObject = $this->replaceFile($file, $localFilePath);
1932 } else {
1933 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, (string)$conflictMode);
1934 }
1935 return $resultObject;
1936 }
1937
1938 /********************
1939 * FOLDER ACTIONS
1940 ********************/
1941 /**
1942 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1943 * @todo check if this is a duplicate
1944 * @param Folder $folder
1945 * @return File[]
1946 */
1947 protected function getAllFileObjectsInFolder(Folder $folder)
1948 {
1949 $files = array();
1950 $folderQueue = array($folder);
1951 while (!empty($folderQueue)) {
1952 $folder = array_shift($folderQueue);
1953 foreach ($folder->getSubfolders() as $subfolder) {
1954 $folderQueue[] = $subfolder;
1955 }
1956 foreach ($folder->getFiles() as $file) {
1957 /** @var FileInterface $file */
1958 $files[$file->getIdentifier()] = $file;
1959 }
1960 }
1961
1962 return $files;
1963 }
1964
1965 /**
1966 * Moves a folder. If you want to move a folder from this storage to another
1967 * one, call this method on the target storage, otherwise you will get an exception.
1968 *
1969 * @param Folder $folderToMove The folder to move.
1970 * @param Folder $targetParentFolder The target parent folder
1971 * @param string $newFolderName
1972 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
1973 *
1974 * @throws \Exception|\TYPO3\CMS\Core\Exception
1975 * @throws \InvalidArgumentException
1976 * @throws InvalidTargetFolderException
1977 * @return Folder
1978 */
1979 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
1980 {
1981 // @todo add tests
1982 $originalFolder = $folderToMove->getParentFolder();
1983 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1984 $sourceStorage = $folderToMove->getStorage();
1985 $returnObject = null;
1986 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
1987 // @todo check if folder already exists in $targetParentFolder, handle this conflict then
1988 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1989 // Get all file objects now so we are able to update them after moving the folder
1990 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1991 if ($sourceStorage === $this) {
1992 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
1993 throw new InvalidTargetFolderException(
1994 sprintf(
1995 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
1996 $folderToMove->getName(),
1997 $targetParentFolder->getName()
1998 ),
1999 1422723050
2000 );
2001 }
2002 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2003 } else {
2004 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
2005 }
2006 // Update the identifier and storage of all file objects
2007 foreach ($fileObjects as $oldIdentifier => $fileObject) {
2008 $newIdentifier = $fileMappings[$oldIdentifier];
2009 $fileObject->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
2010 $this->getIndexer()->updateIndexEntry($fileObject);
2011 }
2012 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
2013 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
2014 return $returnObject;
2015 }
2016
2017 /**
2018 * Moves the given folder from a different storage to the target folder in this storage.
2019 *
2020 * @param Folder $folderToMove
2021 * @param Folder $targetParentFolder
2022 * @param string $newFolderName
2023 *
2024 * @return bool
2025 * @throws \RuntimeException
2026 */
2027 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName)
2028 {
2029 throw new \RuntimeException('Not yet implemented');
2030 }
2031
2032 /**
2033 * Copies a folder.
2034 *
2035 * @param FolderInterface $folderToCopy The folder to copy
2036 * @param FolderInterface $targetParentFolder The target folder
2037 * @param string $newFolderName
2038 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
2039 * @return Folder The new (copied) folder object
2040 * @throws InvalidTargetFolderException
2041 */
2042 public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
2043 {
2044 // @todo implement the $conflictMode handling
2045 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
2046 $returnObject = null;
2047 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
2048 if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
2049 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2050 }
2051 $sourceStorage = $folderToCopy->getStorage();
2052 // call driver method to move the file
2053 // that also updates the file object properties
2054 if ($sourceStorage === $this) {
2055 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
2056 throw new InvalidTargetFolderException(
2057 sprintf(
2058 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
2059 $folderToCopy->getName(),
2060 $targetParentFolder->getName()
2061 ),
2062 1422723059
2063 );
2064 }
2065 $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2066 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
2067 } else {
2068 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2069 }
2070 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
2071 return $returnObject;
2072 }
2073
2074 /**
2075 * Copies a folder between storages.
2076 *
2077 * @param Folder $folderToCopy
2078 * @param Folder $targetParentFolder
2079 * @param string $newFolderName
2080 *
2081 * @return bool
2082 * @throws \RuntimeException
2083 */
2084 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName)
2085 {
2086 throw new \RuntimeException('Not yet implemented.');
2087 }
2088
2089 /**
2090 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
2091 *
2092 * @param Folder $folderObject
2093 * @param string $newName
2094 * @throws \Exception
2095 * @throws \InvalidArgumentException
2096 * @return Folder
2097 */
2098 public function renameFolder($folderObject, $newName)
2099 {
2100
2101 // Renaming the folder should check if the parent folder is writable
2102 // We cannot do this however because we cannot extract the parent folder from a folder currently
2103 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
2104 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
2105 }
2106
2107 $sanitizedNewName = $this->driver->sanitizeFileName($newName);
2108 $returnObject = null;
2109 if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
2110 throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
2111 }
2112
2113 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
2114
2115 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
2116 $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
2117 // Update the identifier of all file objects
2118 foreach ($fileObjects as $oldIdentifier => $fileObject) {
2119 $newIdentifier = $fileMappings[$oldIdentifier];
2120 $fileObject->updateProperties(array('identifier' => $newIdentifier));
2121 $this->getIndexer()->updateIndexEntry($fileObject);
2122 }
2123 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
2124
2125 $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
2126
2127 return $returnObject;
2128 }
2129
2130 /**
2131 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
2132 *
2133 * @param Folder $folderObject
2134 * @param bool $deleteRecursively
2135 * @throws \RuntimeException
2136 * @throws Exception\InsufficientFolderAccessPermissionsException
2137 * @throws Exception\InsufficientUserPermissionsException
2138 * @throws Exception\FileOperationErrorException
2139 * @throws Exception\InvalidPathException
2140 * @return bool
2141 */
2142 public function deleteFolder($folderObject, $deleteRecursively = false)
2143 {
2144 $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
2145 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
2146 if (!$isEmpty && !$deleteRecursively) {
2147 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
2148 }
2149
2150 $this->emitPreFolderDeleteSignal($folderObject);
2151
2152 foreach ($this->getFilesInFolder($folderObject, 0, 0, false, $deleteRecursively) as $file) {
2153 $this->deleteFile($file);
2154 }
2155
2156 $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
2157
2158 $this->emitPostFolderDeleteSignal($folderObject);
2159
2160 return $result;
2161 }
2162
2163 /**
2164 * Returns the Identifier for a folder within a given folder.
2165 *
2166 * @param string $folderName The name of the target folder
2167 * @param Folder $parentFolder
2168 * @param bool $returnInaccessibleFolderObject
2169 * @return Folder|InaccessibleFolder
2170 * @throws \Exception
2171 * @throws Exception\InsufficientFolderAccessPermissionsException
2172 */
2173 public function getFolderInFolder($folderName, Folder $parentFolder, $returnInaccessibleFolderObject = false)
2174 {
2175 $folderIdentifier = $this->driver->getFolderInFolder($folderName, $parentFolder->getIdentifier());
2176 return $this->getFolder($folderIdentifier, $returnInaccessibleFolderObject);
2177 }
2178
2179 /**
2180 * @param Folder $folder
2181 * @param int $start
2182 * @param int $maxNumberOfItems
2183 * @param bool $useFilters
2184 * @param bool $recursive
2185 * @param string $sort Property name used to sort the items.
2186 * Among them may be: '' (empty, no sorting), name,
2187 * fileext, size, tstamp and rw.
2188 * If a driver does not support the given property, it
2189 * should fall back to "name".
2190 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
2191 * @return Folder[]
2192 */
2193 public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
2194 {
2195 $filters = $useFilters == true ? $this->fileAndFolderNameFilters : array();
2196
2197 $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
2198
2199 // Exclude processing folders
2200 foreach ($this->getProcessingFolders() as $processingFolder) {
2201 $processingIdentifier = $processingFolder->getIdentifier();
2202 if (isset($folderIdentifiers[$processingIdentifier])) {
2203 unset($folderIdentifiers[$processingIdentifier]);
2204 }
2205 }
2206 $folders = array();
2207 foreach ($folderIdentifiers as $folderIdentifier) {
2208 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, true);
2209 }
2210 return $folders;
2211 }
2212
2213 /**
2214 * @param Folder $folder
2215 * @param bool $useFilters
2216 * @param bool $recursive
2217 * @return int Number of subfolders
2218 * @throws Exception\InsufficientFolderAccessPermissionsException
2219 */
2220 public function countFoldersInFolder(Folder $folder, $useFilters = true, $recursive = false)
2221 {
2222 $this->assureFolderReadPermission($folder);
2223 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
2224 return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2225 }
2226
2227 /**
2228 * Returns TRUE if the specified folder exists.
2229 *
2230 * @param string $identifier
2231 * @return bool
2232 */
2233 public function hasFolder($identifier)
2234 {
2235 $this->assureFolderReadPermission();
2236 return $this->driver->folderExists($identifier);
2237 }
2238
2239 /**
2240 * Checks if the given file exists in the given folder
2241 *
2242 * @param string $folderName
2243 * @param Folder $folder
2244 * @return bool
2245 */
2246 public function hasFolderInFolder($folderName, Folder $folder)
2247 {
2248 $this->assureFolderReadPermission($folder);
2249 return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2250 }
2251
2252 /**
2253 * Creates a new folder.
2254 *
2255 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2256 *
2257 * @param string $folderName The new folder name
2258 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2259 * @return Folder
2260 * @throws Exception\ExistingTargetFolderException
2261 * @throws Exception\InsufficientFolderAccessPermissionsException
2262 * @throws Exception\InsufficientFolderWritePermissionsException
2263 * @throws \Exception
2264 */
2265 public function createFolder($folderName, Folder $parentFolder = null)
2266 {
2267 if ($parentFolder === null) {
2268 $parentFolder = $this->getRootLevelFolder();
2269 } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2270 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2271 }
2272 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2273 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2274 }
2275 if ($this->driver->folderExistsInFolder($folderName, $parentFolder->getIdentifier())) {
2276 throw new Exception\ExistingTargetFolderException('Folder "' . $folderName . '" already exists.', 1423347324);
2277 }
2278
2279 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2280
2281 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), true);
2282 $newFolder = $this->getFolder($newFolder);
2283
2284 $this->emitPostFolderAddSignal($newFolder);
2285
2286 return $newFolder;
2287 }
2288
2289 /**
2290 * Returns the default folder where new files are stored if no other folder is given.
2291 *
2292 * @return Folder
2293 */
2294 public function getDefaultFolder()
2295 {
2296 return $this->getFolder($this->driver->getDefaultFolder());
2297 }
2298
2299 /**
2300 * @param string $identifier
2301 * @param bool $returnInaccessibleFolderObject
2302 *
2303 * @return Folder|InaccessibleFolder
2304 * @throws \Exception
2305 * @throws Exception\InsufficientFolderAccessPermissionsException
2306 */
2307 public function getFolder($identifier, $returnInaccessibleFolderObject = false)
2308 {
2309 $data = $this->driver->getFolderInfoByIdentifier($identifier);
2310 $folder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2311
2312 try {
2313 $this->assureFolderReadPermission($folder);
2314 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2315 $folder = null;
2316 if ($returnInaccessibleFolderObject) {
2317 // if parent folder is readable return inaccessible folder object
2318 $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2319 if ($parentPermissions['r']) {
2320 $folder = GeneralUtility::makeInstance(
2321 \TYPO3\CMS\Core\Resource\InaccessibleFolder::class, $this, $data['identifier'], $data['name']
2322 );
2323 }
2324 }
2325
2326 if ($folder === null) {
2327 throw $e;
2328 }
2329 }
2330 return $folder;
2331 }
2332
2333 /**
2334 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2335 *
2336 * @param string $identifier
2337 * @return bool
2338 */
2339 public function isWithinProcessingFolder($identifier)
2340 {
2341 $inProcessingFolder = false;
2342 foreach ($this->getProcessingFolders() as $processingFolder) {
2343 if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2344 $inProcessingFolder = true;
2345 break;
2346 }
2347 }
2348 return $inProcessingFolder;
2349 }
2350
2351 /**
2352 * Checks if a resource (file or folder) is within the given folder
2353 *
2354 * @param Folder $folder
2355 * @param ResourceInterface $resource
2356 * @return bool
2357 * @throws \InvalidArgumentException
2358 */
2359 public function isWithinFolder(Folder $folder, ResourceInterface $resource)
2360 {
2361 if ($folder->getStorage() !== $this) {
2362 throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2363 }
2364 if ($folder->getStorage() !== $resource->getStorage()) {
2365 return false;
2366 }
2367 return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2368 }
2369
2370 /**
2371 * Returns the folders on the root level of the storage
2372 * or the first mount point of this storage for this user
2373 * if $respectFileMounts is set.
2374 *
2375 * @param bool $respectFileMounts
2376 * @return Folder
2377 */
2378 public function getRootLevelFolder($respectFileMounts = true)
2379 {
2380 if ($respectFileMounts && !empty($this->fileMounts)) {
2381 $mount = reset($this->fileMounts);
2382 return $mount['folder'];
2383 } else {
2384 return ResourceFactory::getInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2385 }
2386 }
2387
2388 /**
2389 * Emits sanitize fileName signal.
2390 *
2391 * @param string $fileName
2392 * @param Folder $targetFolder
2393 * @return string Modified target file name
2394 */
2395 protected function emitSanitizeFileNameSignal($fileName, Folder $targetFolder)
2396 {
2397 list($fileName) = $this->getSignalSlotDispatcher()->dispatch(ResourceStorage::class, self::SIGNAL_SanitizeFileName, array($fileName, $targetFolder, $this, $this->driver));
2398 return $fileName;
2399 }
2400
2401 /**
2402 * Emits file pre-add signal.
2403 *
2404 * @param string $targetFileName
2405 * @param Folder $targetFolder
2406 * @param string $sourceFilePath
2407 * @return string Modified target file name
2408 */
2409 protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath)
2410 {
2411 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileAdd, array(&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver));
2412 return $targetFileName;
2413 }
2414
2415 /**
2416 * Emits the file post-add signal.
2417 *
2418 * @param FileInterface $file
2419 * @param Folder $targetFolder
2420 * @return void
2421 */
2422 protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder)
2423 {
2424 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileAdd, array($file, $targetFolder));
2425 }
2426
2427 /**
2428 * Emits file pre-copy signal.
2429 *
2430 * @param FileInterface $file
2431 * @param Folder $targetFolder
2432 * @return void
2433 */
2434 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder)
2435 {
2436 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileCopy, array($file, $targetFolder));
2437 }
2438
2439 /**
2440 * Emits the file post-copy signal.
2441 *
2442 * @param FileInterface $file
2443 * @param Folder $targetFolder
2444 * @return void
2445 */
2446 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder)
2447 {
2448 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCopy, array($file, $targetFolder));
2449 }
2450
2451 /**
2452 * Emits the file pre-move signal.
2453 *
2454 * @param FileInterface $file
2455 * @param Folder $targetFolder
2456 * @return void
2457 */
2458 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder)
2459 {
2460 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileMove, array($file, $targetFolder));
2461 }
2462
2463 /**
2464 * Emits the file post-move signal.
2465 *
2466 * @param FileInterface $file
2467 * @param Folder $targetFolder
2468 * @param Folder $originalFolder
2469 * @return void
2470 */
2471 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, Folder $originalFolder)
2472 {
2473 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileMove, array($file, $targetFolder, $originalFolder));
2474 }
2475
2476 /**
2477 * Emits the file pre-rename signal
2478 *
2479 * @param FileInterface $file
2480 * @param $targetFolder
2481 * @return void
2482 */
2483 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder)
2484 {
2485 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileRename, array($file, $targetFolder));
2486 }
2487
2488 /**
2489 * Emits the file post-rename signal.
2490 *
2491 * @param FileInterface $file
2492 * @param $targetFolder
2493 * @return void
2494 */
2495 protected function emitPostFileRenameSignal(FileInterface $file, $targetFolder)
2496 {
2497 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileRename, array($file, $targetFolder));
2498 }
2499
2500 /**
2501 * Emits the file pre-replace signal.
2502 *
2503 * @param FileInterface $file
2504 * @param $localFilePath
2505 * @return void
2506 */
2507 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath)
2508 {
2509 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileReplace, array($file, $localFilePath));
2510 }
2511
2512 /**
2513 * Emits the file post-replace signal
2514 *
2515 * @param FileInterface $file
2516 * @param string $localFilePath
2517 * @return void
2518 */
2519 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath)
2520 {
2521 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileReplace, array($file, $localFilePath));
2522 }
2523
2524 /**
2525 * Emits the file post-create signal
2526 *
2527 * @param string $newFileIdentifier
2528 * @param Folder $targetFolder
2529 */
2530 protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder)
2531 {
2532 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCreate, array($newFileIdentifier, $targetFolder));
2533 }
2534
2535 /**
2536 * Emits the file pre-deletion signal.
2537 *
2538 * @param FileInterface $file
2539 * @return void
2540 */
2541 protected function emitPreFileDeleteSignal(FileInterface $file)
2542 {
2543 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileDelete, array($file));
2544 }
2545
2546 /**
2547 * Emits the file post-deletion signal
2548 *
2549 * @param FileInterface $file
2550 * @return void
2551 */
2552 protected function emitPostFileDeleteSignal(FileInterface $file)
2553 {
2554 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileDelete, array($file));
2555 }
2556
2557 /**
2558 * Emits the file post-set-contents signal
2559 *
2560 * @param FileInterface $file
2561 * @param mixed $content
2562 * @return void
2563 */
2564 protected function emitPostFileSetContentsSignal(FileInterface $file, $content)
2565 {
2566 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileSetContents, array($file, $content));
2567 }
2568
2569 /**
2570 * Emits the folder pre-add signal.
2571 *
2572 * @param Folder $targetFolder
2573 * @param string $name
2574 * @return void
2575 */
2576 protected function emitPreFolderAddSignal(Folder $targetFolder, $name)
2577 {
2578 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderAdd, array($targetFolder, $name));
2579 }
2580
2581 /**
2582 * Emits the folder post-add signal.
2583 *
2584 * @param Folder $folder
2585 * @return void
2586 */
2587 protected function emitPostFolderAddSignal(Folder $folder)
2588 {
2589 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderAdd, array($folder));
2590 }
2591
2592 /**
2593 * Emits the folder pre-copy signal.
2594 *
2595 * @param Folder $folder
2596 * @param Folder $targetFolder
2597 * @param $newName
2598 * @return void
2599 */
2600 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2601 {
2602 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderCopy, array($folder, $targetFolder, $newName));
2603 }
2604
2605 /**
2606 * Emits the folder post-copy signal.
2607 *
2608 * @param Folder $folder
2609 * @param Folder $targetFolder
2610 * @param $newName
2611 * @return void
2612 */
2613 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2614 {
2615 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderCopy, array($folder, $targetFolder, $newName));
2616 }
2617
2618 /**
2619 * Emits the folder pre-move signal.
2620 *
2621 * @param Folder $folder
2622 * @param Folder $targetFolder
2623 * @param $newName
2624 * @return void
2625 */
2626 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName)
2627 {
2628 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderMove, array($folder, $targetFolder, $newName));
2629 }
2630
2631 /**
2632 * Emits the folder post-move signal.
2633 *
2634 * @param Folder $folder
2635 * @param Folder $targetFolder
2636 * @param $newName
2637 * @param Folder $originalFolder
2638 * @return void
2639 */
2640 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder)
2641 {
2642 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderMove, array($folder, $targetFolder, $newName, $originalFolder));
2643 }
2644
2645 /**
2646 * Emits the folder pre-rename signal.
2647 *
2648 * @param Folder $folder
2649 * @param $newName
2650 * @return void
2651 */
2652 protected function emitPreFolderRenameSignal(Folder $folder, $newName)
2653 {
2654 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderRename, array($folder, $newName));
2655 }
2656
2657 /**
2658 * Emits the folder post-rename signal.
2659 *
2660 * @param Folder $folder
2661 * @param $newName
2662 * @return void
2663 */
2664 protected function emitPostFolderRenameSignal(Folder $folder, $newName)
2665 {
2666 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderRename, array($folder, $newName));
2667 }
2668
2669 /**
2670 * Emits the folder pre-deletion signal.
2671 *
2672 * @param Folder $folder
2673 * @return void
2674 */
2675 protected function emitPreFolderDeleteSignal(Folder $folder)
2676 {
2677 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderDelete, array($folder));
2678 }
2679
2680 /**
2681 * Emits folder post-deletion signal..
2682 *
2683 * @param Folder $folder
2684 * @return void
2685 */
2686 protected function emitPostFolderDeleteSignal(Folder $folder)
2687 {
2688 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderDelete, array($folder));
2689 }
2690
2691 /**
2692 * Emits file pre-processing signal when generating a public url for a file or folder.
2693 *
2694 * @param ResourceInterface $resourceObject
2695 * @param bool $relativeToCurrentScript
2696 * @param array $urlData
2697 */
2698 protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData)
2699 {
2700 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreGeneratePublicUrl, array($this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData));
2701 }
2702
2703 /**
2704 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2705 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2706 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2707 *
2708 * @param Folder $folder
2709 * @param string $theFile The input fileName to check
2710 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2711 *
2712 * @throws \RuntimeException
2713 * @return string A unique fileName inside $folder, based on $theFile.
2714 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2715 */
2716 protected function getUniqueName(Folder $folder, $theFile, $dontCheckForUnique = false)
2717 {
2718 static $maxNumber = 99, $uniqueNamePrefix = '';
2719 // Fetches info about path, name, extension of $theFile
2720 $origFileInfo = PathUtility::pathinfo($theFile);
2721 // Adds prefix
2722 if ($uniqueNamePrefix) {
2723 $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2724 $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2725 }
2726 // Check if the file exists and if not - return the fileName...
2727 // The destinations file
2728 $theDestFile = $origFileInfo['basename'];
2729 // If the file does NOT exist we return this fileName
2730 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2731 return $theDestFile;
2732 }
2733 // Well the fileName in its pure form existed. Now we try to append
2734 // numbers / unique-strings and see if we can find an available fileName
2735 // This removes _xx if appended to the file
2736 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2737 $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2738 for ($a = 1; $a <= $maxNumber + 1; $a++) {
2739 // First we try to append numbers
2740 if ($a <= $maxNumber) {
2741 $insert = '_' . sprintf('%02d', $a);
2742 } else {
2743 $insert = '_' . substr(md5(uniqid('', true)), 0, 6);
2744 }
2745 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2746 // The destinations file
2747 $theDestFile = $theTestFile;
2748 // If the file does NOT exist we return this fileName
2749 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2750 return $theDestFile;
2751 }
2752 }
2753 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2754 }
2755
2756 /**
2757 * Get the SignalSlot dispatcher.
2758 *
2759 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2760 */
2761 protected function getSignalSlotDispatcher()
2762 {
2763 if (!isset($this->signalSlotDispatcher)) {
2764 $this->signalSlotDispatcher = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
2765 }
2766 return $this->signalSlotDispatcher;
2767 }
2768
2769 /**
2770 * Gets the ObjectManager.
2771 *
2772 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2773 */
2774 protected function getObjectManager()
2775 {
2776 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
2777 }
2778
2779 /**
2780 * @return ResourceFactory
2781 */
2782 protected function getFileFactory()
2783 {
2784 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
2785 }
2786
2787 /**
2788 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
2789 */
2790 protected function getFileIndexRepository()
2791 {
2792 return FileIndexRepository::getInstance();
2793 }
2794
2795 /**
2796 * @return Service\FileProcessingService
2797 */
2798 protected function getFileProcessingService()
2799 {
2800 if (!$this->fileProcessingService) {
2801 $this->fileProcessingService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Service\FileProcessingService::class, $this, $this->driver);
2802 }
2803 return $this->fileProcessingService;
2804 }
2805
2806 /**
2807 * Gets the role of a folder.
2808 *
2809 * @param FolderInterface $folder Folder object to get the role from
2810 * @return string The role the folder has
2811 */
2812 public function getRole(FolderInterface $folder)
2813 {
2814 $folderRole = FolderInterface::ROLE_DEFAULT;
2815 $identifier = $folder->getIdentifier();
2816 if (method_exists($this->driver, 'getRole')) {
2817 $folderRole = $this->driver->getRole($folder->getIdentifier());
2818 }
2819 if (isset($this->fileMounts[$identifier])) {
2820 $folderRole = FolderInterface::ROLE_MOUNT;
2821
2822 if (!empty($this->fileMounts[$identifier]['read_only'])) {
2823 $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
2824 }
2825 if ($this->fileMounts[$identifier]['user_mount']) {
2826 $folderRole = FolderInterface::ROLE_USER_MOUNT;
2827 }
2828 }
2829 if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2830 $folderRole = FolderInterface::ROLE_PROCESSING;
2831 }
2832
2833 return $folderRole;
2834 }
2835
2836 /**
2837 * Getter function to return the folder where the files can
2838 * be processed. Does not check for access rights here.
2839 *
2840 * @return Folder
2841 */
2842 public function getProcessingFolder()
2843 {
2844 if (!isset($this->processingFolder)) {
2845 $processingFolder = self::DEFAULT_ProcessingFolder;
2846 if (!empty($this->storageRecord['processingfolder'])) {
2847 $processingFolder = $this->storageRecord['processingfolder'];
2848 }
2849 try {
2850 if (strpos($processingFolder, ':') !== false) {
2851 list ($storageUid, $processingFolderIdentifier) = explode(':', $processingFolder, 2);
2852 $storage = ResourceFactory::getInstance()->getStorageObject($storageUid);
2853 if ($storage->hasFolder($processingFolderIdentifier)) {
2854 $this->processingFolder = $storage->getFolder($processingFolderIdentifier);
2855 } else {
2856 $this->processingFolder = $storage->createFolder(ltrim($processingFolderIdentifier, '/'));
2857 }
2858 } else {
2859 if ($this->driver->folderExists($processingFolder) === false) {
2860 $this->processingFolder = $this->createFolder($processingFolder);
2861 } else {
2862 $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
2863 $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2864 }
2865 }
2866 } catch (Exception\InsufficientFolderWritePermissionsException $e) {
2867 $this->processingFolder = GeneralUtility::makeInstance(
2868 InaccessibleFolder::class, $this, $processingFolder, $processingFolder
2869 );
2870 } catch (Exception\ResourcePermissionsUnavailableException $e) {
2871 $this->processingFolder = GeneralUtility::makeInstance(
2872 InaccessibleFolder::class, $this, $processingFolder, $processingFolder
2873 );
2874 }
2875 }
2876 return $this->processingFolder;
2877 }
2878
2879 /**
2880 * Gets the driver Type configured for this storage.
2881 *
2882 * @return string
2883 */
2884 public function getDriverType()
2885 {
2886 return $this->storageRecord['driver'];
2887 }
2888
2889 /**
2890 * Gets the Indexer.
2891 *
2892 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
2893 */
2894 protected function getIndexer()
2895 {
2896 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $this);
2897 }
2898
2899 /**
2900 * @param bool $isDefault
2901 * @return void
2902 */
2903 public function setDefault($isDefault)
2904 {
2905 $this->isDefault = (bool)$isDefault;
2906 }
2907
2908 /**
2909 * @return bool
2910 */
2911 public function isDefault()
2912 {
2913 return $this->isDefault;
2914 }
2915 }