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