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