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