86a7e89a0146ef6d4750184a745d5ef1428ac9da
[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 * @param Folder $folder
1317 * @param int $start
1318 * @param int $maxNumberOfItems
1319 * @param bool $useFilters
1320 * @param bool $recursive
1321 * @param string $sort Property name used to sort the items.
1322 * Among them may be: '' (empty, no sorting), name,
1323 * fileext, size, tstamp and rw.
1324 * If a driver does not support the given property, it
1325 * should fall back to "name".
1326 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1327 * @return File[]
1328 * @throws Exception\InsufficientFolderAccessPermissionsException
1329 */
1330 public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
1331 $this->assureFolderReadPermission($folder);
1332
1333 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1334
1335 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1336 $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1337
1338 $items = array();
1339 foreach ($fileIdentifiers as $identifier) {
1340 if (isset($rows[$identifier])) {
1341 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1342 } else {
1343 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1344 }
1345 if ($fileObject instanceof FileInterface) {
1346 $key = $fileObject->getName();
1347 while (isset($items[$key])) {
1348 $key .= 'z';
1349 }
1350 $items[$key] = $fileObject;
1351 }
1352 }
1353
1354 return $items;
1355 }
1356
1357 /**
1358 * @param string $folderIdentifier
1359 * @param bool $useFilters
1360 * @param bool $recursive
1361 * @return array
1362 */
1363 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1364 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1365 return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1366 }
1367
1368 /**
1369 * @param Folder $folder
1370 * @param bool $useFilters
1371 * @param bool $recursive
1372 * @return int Number of files in folder
1373 * @throws Exception\InsufficientFolderAccessPermissionsException
1374 */
1375 public function countFilesInFolder(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
1376 $this->assureFolderReadPermission($folder);
1377 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
1378 return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1379 }
1380
1381 /**
1382 * @param string $folderIdentifier
1383 * @param bool $useFilters
1384 * @param bool $recursive
1385 * @return array
1386 */
1387 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1388 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1389 return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1390 }
1391
1392 /**
1393 * Returns TRUE if the specified file exists
1394 *
1395 * @param string $identifier
1396 * @return bool
1397 */
1398 public function hasFile($identifier) {
1399 // Allow if identifier is in processing folder
1400 if (!$this->isWithinProcessingFolder($identifier)) {
1401 $this->assureFolderReadPermission();
1402 }
1403 return $this->driver->fileExists($identifier);
1404 }
1405
1406 /**
1407 * Get all processing folders that live in this storage
1408 *
1409 * @return Folder[]
1410 */
1411 public function getProcessingFolders() {
1412 if ($this->processingFolders === NULL) {
1413 $this->processingFolders = array();
1414 $this->processingFolders[] = $this->getProcessingFolder();
1415 /** @var $storageRepository StorageRepository */
1416 $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1417 $allStorages = $storageRepository->findAll();
1418 foreach ($allStorages as $storage) {
1419 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1420 // See #66695 for details
1421 list($storageUid, $processingFolderIdentifier) = GeneralUtility::trimExplode(':', $storage->getStorageRecord()['processingfolder']);
1422 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1423 continue;
1424 }
1425 $potentialProcessingFolder = ResourceFactory::getInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1426 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1427 $this->processingFolders[] = $potentialProcessingFolder;
1428 }
1429 }
1430 }
1431
1432 return $this->processingFolders;
1433 }
1434
1435 /**
1436 * Returns TRUE if folder that is in current storage is set as
1437 * processing folder for one of the existing storages
1438 *
1439 * @param Folder $folder
1440 * @return bool
1441 */
1442 public function isProcessingFolder(Folder $folder) {
1443 $isProcessingFolder = FALSE;
1444 foreach ($this->getProcessingFolders() as $processingFolder) {
1445 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1446 $isProcessingFolder = TRUE;
1447 break;
1448 }
1449 }
1450 return $isProcessingFolder;
1451 }
1452
1453 /**
1454 * Checks if the queried file in the given folder exists
1455 *
1456 * @param string $fileName
1457 * @param Folder $folder
1458 * @return bool
1459 */
1460 public function hasFileInFolder($fileName, Folder $folder) {
1461 $this->assureFolderReadPermission($folder);
1462 return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1463 }
1464
1465 /**
1466 * Get contents of a file object
1467 *
1468 * @param FileInterface $file
1469 *
1470 * @throws Exception\InsufficientFileReadPermissionsException
1471 * @return string
1472 */
1473 public function getFileContents($file) {
1474 $this->assureFileReadPermission($file);
1475 return $this->driver->getFileContents($file->getIdentifier());
1476 }
1477
1478 /**
1479 * Outputs file Contents,
1480 * clears output buffer first and sends headers accordingly.
1481 *
1482 * @param FileInterface $file
1483 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1484 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1485 * @return void
1486 */
1487 public function dumpFileContents(FileInterface $file, $asDownload = FALSE, $alternativeFilename = NULL) {
1488 $downloadName = $alternativeFilename ?: $file->getName();
1489 $contentDisposition = $asDownload ? 'attachment' : 'inline';
1490 header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1491 header('Content-Type: ' . $file->getMimeType());
1492 header('Content-Length: ' . $file->getSize());
1493
1494 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1495 // See for more information: http://support.microsoft.com/kb/323308
1496 header("Cache-Control: ''");
1497 header('Last-Modified: ' .
1498 gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), array('mtime')))) . ' GMT',
1499 TRUE,
1500 200
1501 );
1502 ob_clean();
1503 flush();
1504 while (ob_get_level() > 0) {
1505 ob_end_clean();
1506 }
1507 $this->driver->dumpFileContents($file->getIdentifier());
1508 }
1509
1510 /**
1511 * Set contents of a file object.
1512 *
1513 * @param AbstractFile $file
1514 * @param string $contents
1515 *
1516 * @throws \Exception|\RuntimeException
1517 * @throws Exception\InsufficientFileWritePermissionsException
1518 * @throws Exception\InsufficientUserPermissionsException
1519 * @return int The number of bytes written to the file
1520 */
1521 public function setFileContents(AbstractFile $file, $contents) {
1522 // Check if user is allowed to edit
1523 $this->assureFileWritePermissions($file);
1524 // Call driver method to update the file and update file index entry afterwards
1525 $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1526 $this->getIndexer()->updateIndexEntry($file);
1527 $this->emitPostFileSetContentsSignal($file, $contents);
1528 return $result;
1529 }
1530
1531 /**
1532 * Creates a new file
1533 *
1534 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1535 *
1536 * @param string $fileName The name of the file to be created
1537 * @param Folder $targetFolderObject The target folder where the file should be created
1538 *
1539 * @throws Exception\IllegalFileExtensionException
1540 * @throws Exception\InsufficientFolderWritePermissionsException
1541 * @return FileInterface The file object
1542 */
1543 public function createFile($fileName, Folder $targetFolderObject) {
1544 $this->assureFileAddPermissions($targetFolderObject, $fileName);
1545 $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1546 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1547 return ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1548 }
1549
1550 /**
1551 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1552 *
1553 * @param $fileObject FileInterface
1554 * @throws Exception\InsufficientFileAccessPermissionsException
1555 * @throws Exception\FileOperationErrorException
1556 * @return bool TRUE if deletion succeeded
1557 */
1558 public function deleteFile($fileObject) {
1559 $this->assureFileDeletePermissions($fileObject);
1560
1561 $this->emitPreFileDeleteSignal($fileObject);
1562
1563 if ($this->driver->fileExists($fileObject->getIdentifier())) {
1564 $result = $this->driver->deleteFile($fileObject->getIdentifier());
1565 if (!$result) {
1566 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1567 }
1568 }
1569 // Mark the file object as deleted
1570 if ($fileObject instanceof File) {
1571 $fileObject->setDeleted();
1572 }
1573
1574 $this->emitPostFileDeleteSignal($fileObject);
1575
1576 return TRUE;
1577 }
1578
1579 /**
1580 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1581 * copies a source file (from any location) in to the target
1582 * folder, the latter has to be part of this storage
1583 *
1584 * @param FileInterface $file
1585 * @param Folder $targetFolder
1586 * @param string $targetFileName an optional destination fileName
1587 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1588 *
1589 * @throws \Exception|Exception\AbstractFileOperationException
1590 * @throws Exception\ExistingTargetFileNameException
1591 * @return FileInterface
1592 */
1593 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1594 if ($targetFileName === NULL) {
1595 $targetFileName = $file->getName();
1596 }
1597 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1598 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1599 $this->emitPreFileCopySignal($file, $targetFolder);
1600 // File exists and we should abort, let's abort
1601 if ($conflictMode === 'cancel' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1602 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1603 }
1604 // File exists and we should find another name, let's find another one
1605 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1606 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1607 }
1608 $sourceStorage = $file->getStorage();
1609 // Call driver method to create a new file from an existing file object,
1610 // and return the new file object
1611 if ($sourceStorage === $this) {
1612 $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1613 } else {
1614 $tempPath = $file->getForLocalProcessing();
1615 $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1616 }
1617 $newFileObject = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1618 $this->emitPostFileCopySignal($file, $targetFolder);
1619 return $newFileObject;
1620 }
1621
1622 /**
1623 * Moves a $file into a $targetFolder
1624 * the target folder has to be part of this storage
1625 *
1626 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1627 *
1628 * @param FileInterface $file
1629 * @param Folder $targetFolder
1630 * @param string $targetFileName an optional destination fileName
1631 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1632 *
1633 * @throws Exception\ExistingTargetFileNameException
1634 * @throws \RuntimeException
1635 * @return FileInterface
1636 */
1637 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1638 if ($targetFileName === NULL) {
1639 $targetFileName = $file->getName();
1640 }
1641 $originalFolder = $file->getParentFolder();
1642 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1643 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1644 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1645 // File exists and we should abort, let's abort
1646 if ($conflictMode === 'renameNewFile') {
1647 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1648 } elseif ($conflictMode === 'cancel') {
1649 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1650 }
1651 }
1652 $this->emitPreFileMoveSignal($file, $targetFolder);
1653 $sourceStorage = $file->getStorage();
1654 // Call driver method to move the file and update the index entry
1655 try {
1656 if ($sourceStorage === $this) {
1657 $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1658 if (!$file instanceof AbstractFile) {
1659 throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1660 }
1661 $file->updateProperties(array('identifier' => $newIdentifier));
1662 } else {
1663 $tempPath = $file->getForLocalProcessing();
1664 $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1665 $sourceStorage->driver->deleteFile($file->getIdentifier());
1666 if ($file instanceof File) {
1667 $file->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1668 }
1669 }
1670 $this->getIndexer()->updateIndexEntry($file);
1671 } catch (\TYPO3\CMS\Core\Exception $e) {
1672 echo $e->getMessage();
1673 }
1674 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1675 return $file;
1676 }
1677
1678 /**
1679 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1680 *
1681 * @param FileInterface $file
1682 * @param string $targetFileName
1683 *
1684 * @throws Exception\InsufficientFileWritePermissionsException
1685 * @throws Exception\InsufficientFileReadPermissionsException
1686 * @throws Exception\InsufficientUserPermissionsException
1687 * @return FileInterface
1688 */
1689 public function renameFile($file, $targetFileName) {
1690 // @todo add $conflictMode setting
1691
1692 // The name should be different from the current.
1693 if ($file->getName() === $targetFileName) {
1694 return $file;
1695 }
1696 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1697 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1698 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1699
1700 // Call driver method to rename the file and update the index entry
1701 try {
1702 $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1703 if ($file instanceof File) {
1704 $file->updateProperties(array('identifier' => $newIdentifier));
1705 }
1706 $this->getIndexer()->updateIndexEntry($file);
1707 } catch (\RuntimeException $e) {
1708
1709 }
1710
1711 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1712
1713 return $file;
1714 }
1715
1716 /**
1717 * Replaces a file with a local file (e.g. a freshly uploaded file)
1718 *
1719 * @param FileInterface $file
1720 * @param string $localFilePath
1721 *
1722 * @return FileInterface
1723 *
1724 * @throws Exception\IllegalFileExtensionException
1725 * @throws \InvalidArgumentException
1726 */
1727 public function replaceFile(FileInterface $file, $localFilePath) {
1728 $this->assureFileWritePermissions($file);
1729 if (!$this->checkFileExtensionPermission($localFilePath)) {
1730 throw new Exception\IllegalFileExtensionException('Source file extension not allowed.', 1378132239);
1731 }
1732 if (!file_exists($localFilePath)) {
1733 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1734 }
1735 $this->emitPreFileReplaceSignal($file, $localFilePath);
1736 $result = $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
1737 if ($file instanceof File) {
1738 $this->getIndexer()->updateIndexEntry($file);
1739 }
1740 $this->emitPostFileReplaceSignal($file, $localFilePath);
1741 return $result;
1742 }
1743
1744 /**
1745 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1746 *
1747 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1748 * @param Folder $targetFolder the target folder
1749 * @param string $targetFileName the file name to be written
1750 * @param string $conflictMode possible value are 'cancel', 'replace'
1751 * @return FileInterface The file object
1752 */
1753 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1754 $localFilePath = $uploadedFileData['tmp_name'];
1755 if ($targetFolder === NULL) {
1756 $targetFolder = $this->getDefaultFolder();
1757 }
1758 if ($targetFileName === NULL) {
1759 $targetFileName = $uploadedFileData['name'];
1760 }
1761 // Handling $conflictMode is delegated to addFile()
1762 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1763 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1764 return $resultObject;
1765 }
1766
1767 /********************
1768 * FOLDER ACTIONS
1769 ********************/
1770 /**
1771 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1772 * @todo check if this is a duplicate
1773 * @param Folder $folder
1774 * @return File[]
1775 */
1776 protected function getAllFileObjectsInFolder(Folder $folder) {
1777 $files = array();
1778 $folderQueue = array($folder);
1779 while (!empty($folderQueue)) {
1780 $folder = array_shift($folderQueue);
1781 foreach ($folder->getSubfolders() as $subfolder) {
1782 $folderQueue[] = $subfolder;
1783 }
1784 foreach ($folder->getFiles() as $file) {
1785 /** @var FileInterface $file */
1786 $files[$file->getIdentifier()] = $file;
1787 }
1788 }
1789
1790 return $files;
1791 }
1792
1793 /**
1794 * Moves a folder. If you want to move a folder from this storage to another
1795 * one, call this method on the target storage, otherwise you will get an exception.
1796 *
1797 * @param Folder $folderToMove The folder to move.
1798 * @param Folder $targetParentFolder The target parent folder
1799 * @param string $newFolderName
1800 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1801 *
1802 * @throws \Exception|\TYPO3\CMS\Core\Exception
1803 * @throws \InvalidArgumentException
1804 * @throws InvalidTargetFolderException
1805 * @return Folder
1806 */
1807 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1808 // @todo add tests
1809 $originalFolder = $folderToMove->getParentFolder();
1810 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1811 $sourceStorage = $folderToMove->getStorage();
1812 $returnObject = NULL;
1813 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
1814 // @todo check if folder already exists in $targetParentFolder, handle this conflict then
1815 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1816 // Get all file objects now so we are able to update them after moving the folder
1817 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1818 if ($sourceStorage === $this) {
1819 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
1820 throw new InvalidTargetFolderException(
1821 sprintf(
1822 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
1823 $folderToMove->getName(),
1824 $targetParentFolder->getName()
1825 ),
1826 1422723050
1827 );
1828 }
1829 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1830 } else {
1831 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1832 }
1833 // Update the identifier and storage of all file objects
1834 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1835 $newIdentifier = $fileMappings[$oldIdentifier];
1836 $fileObject->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1837 $this->getIndexer()->updateIndexEntry($fileObject);
1838 }
1839 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1840 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
1841 return $returnObject;
1842 }
1843
1844 /**
1845 * Moves the given folder from a different storage to the target folder in this storage.
1846 *
1847 * @param Folder $folderToMove
1848 * @param Folder $targetParentFolder
1849 * @param string $newFolderName
1850 *
1851 * @return bool
1852 * @throws \RuntimeException
1853 */
1854 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
1855 throw new \RuntimeException('Not yet implemented');
1856 }
1857
1858 /**
1859 * Copies a folder.
1860 *
1861 * @param FolderInterface $folderToCopy The folder to copy
1862 * @param FolderInterface $targetParentFolder The target folder
1863 * @param string $newFolderName
1864 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1865 * @return Folder The new (copied) folder object
1866 * @throws InvalidTargetFolderException
1867 */
1868 public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1869 // @todo implement the $conflictMode handling
1870 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
1871 $returnObject = NULL;
1872 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
1873 if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
1874 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1875 }
1876 $sourceStorage = $folderToCopy->getStorage();
1877 // call driver method to move the file
1878 // that also updates the file object properties
1879 if ($sourceStorage === $this) {
1880 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
1881 throw new InvalidTargetFolderException(
1882 sprintf(
1883 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
1884 $folderToCopy->getName(),
1885 $targetParentFolder->getName()
1886 ),
1887 1422723059
1888 );
1889 }
1890 $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1891 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
1892 } else {
1893 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1894 }
1895 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
1896 return $returnObject;
1897 }
1898
1899 /**
1900 * Copies a folder between storages.
1901 *
1902 * @param Folder $folderToCopy
1903 * @param Folder $targetParentFolder
1904 * @param string $newFolderName
1905 *
1906 * @return bool
1907 * @throws \RuntimeException
1908 */
1909 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
1910 throw new \RuntimeException('Not yet implemented.');
1911 }
1912
1913 /**
1914 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1915 *
1916 * @param Folder $folderObject
1917 * @param string $newName
1918 * @throws \Exception
1919 * @throws \InvalidArgumentException
1920 * @return Folder
1921 */
1922 public function renameFolder($folderObject, $newName) {
1923
1924 // Renaming the folder should check if the parent folder is writable
1925 // We cannot do this however because we cannot extract the parent folder from a folder currently
1926 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1927 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1928 }
1929
1930 $sanitizedNewName = $this->driver->sanitizeFileName($newName);
1931 $returnObject = NULL;
1932 if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
1933 throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1934 }
1935
1936 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
1937
1938 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1939 $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
1940 // Update the identifier of all file objects
1941 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1942 $newIdentifier = $fileMappings[$oldIdentifier];
1943 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1944 $this->getIndexer()->updateIndexEntry($fileObject);
1945 }
1946 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1947
1948 $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
1949
1950 return $returnObject;
1951 }
1952
1953 /**
1954 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1955 *
1956 * @param Folder $folderObject
1957 * @param bool $deleteRecursively
1958 * @throws \RuntimeException
1959 * @throws Exception\InsufficientFolderAccessPermissionsException
1960 * @throws Exception\InsufficientUserPermissionsException
1961 * @throws Exception\FileOperationErrorException
1962 * @throws Exception\InvalidPathException
1963 * @return bool
1964 */
1965 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1966 $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
1967 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
1968 if (!$isEmpty && !$deleteRecursively) {
1969 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1970 }
1971
1972 $this->emitPreFolderDeleteSignal($folderObject);
1973
1974 foreach ($this->getFilesInFolder($folderObject, 0, 0, FALSE, $deleteRecursively) as $file) {
1975 $this->deleteFile($file);
1976 }
1977
1978 $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
1979
1980 $this->emitPostFolderDeleteSignal($folderObject);
1981
1982 return $result;
1983 }
1984
1985 /**
1986 * @param Folder $folder
1987 * @param int $start
1988 * @param int $maxNumberOfItems
1989 * @param bool $useFilters
1990 * @param bool $recursive
1991 * @param string $sort Property name used to sort the items.
1992 * Among them may be: '' (empty, no sorting), name,
1993 * fileext, size, tstamp and rw.
1994 * If a driver does not support the given property, it
1995 * should fall back to "name".
1996 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1997 * @return Folder[]
1998 */
1999 public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
2000 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
2001
2002 $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
2003
2004 // Exclude processing folders
2005 foreach ($this->getProcessingFolders() as $processingFolder) {
2006 $processingIdentifier = $processingFolder->getIdentifier();
2007 if (isset($folderIdentifiers[$processingIdentifier])) {
2008 unset($folderIdentifiers[$processingIdentifier]);
2009 }
2010 }
2011 $folders = array();
2012 foreach ($folderIdentifiers as $folderIdentifier) {
2013 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, TRUE);
2014 }
2015 return $folders;
2016 }
2017
2018 /**
2019 * @param Folder $folder
2020 * @param bool $useFilters
2021 * @param bool $recursive
2022 * @return integer Number of subfolders
2023 * @throws Exception\InsufficientFolderAccessPermissionsException
2024 */
2025 public function countFoldersInFolder(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
2026 $this->assureFolderReadPermission($folder);
2027 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
2028 return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2029 }
2030
2031 /**
2032 * Returns TRUE if the specified folder exists.
2033 *
2034 * @param string $identifier
2035 * @return bool
2036 */
2037 public function hasFolder($identifier) {
2038 $this->assureFolderReadPermission();
2039 return $this->driver->folderExists($identifier);
2040 }
2041
2042 /**
2043 * Checks if the given file exists in the given folder
2044 *
2045 * @param string $folderName
2046 * @param Folder $folder
2047 * @return bool
2048 */
2049 public function hasFolderInFolder($folderName, Folder $folder) {
2050 $this->assureFolderReadPermission($folder);
2051 return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2052 }
2053
2054 /**
2055 * Creates a new folder.
2056 *
2057 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2058 *
2059 * @param string $folderName The new folder name
2060 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2061 *
2062 * @throws Exception\InsufficientFolderWritePermissionsException
2063 * @throws \InvalidArgumentException
2064 * @return Folder The new folder object
2065 */
2066 public function createFolder($folderName, Folder $parentFolder = NULL) {
2067 if ($parentFolder === NULL) {
2068 $parentFolder = $this->getRootLevelFolder();
2069 } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2070 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2071 }
2072 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2073 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2074 }
2075
2076 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2077
2078 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), TRUE);
2079 $newFolder = $this->getFolder($newFolder);
2080
2081 $this->emitPostFolderAddSignal($newFolder);
2082
2083 return $newFolder;
2084 }
2085
2086 /**
2087 * Returns the default folder where new files are stored if no other folder is given.
2088 *
2089 * @return Folder
2090 */
2091 public function getDefaultFolder() {
2092 return $this->getFolder($this->driver->getDefaultFolder());
2093 }
2094
2095 /**
2096 * @param string $identifier
2097 * @param bool $returnInaccessibleFolderObject
2098 *
2099 * @return Folder
2100 * @throws \Exception
2101 * @throws Exception\InsufficientFolderAccessPermissionsException
2102 */
2103 public function getFolder($identifier, $returnInaccessibleFolderObject = FALSE) {
2104 $data = $this->driver->getFolderInfoByIdentifier($identifier);
2105 $folder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2106
2107 try {
2108 $this->assureFolderReadPermission($folder);
2109 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2110 $folder = NULL;
2111 if ($returnInaccessibleFolderObject) {
2112 // if parent folder is readable return inaccessible folder object
2113 $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2114 if ($parentPermissions['r']) {
2115 $folder = GeneralUtility::makeInstance(
2116 \TYPO3\CMS\Core\Resource\InaccessibleFolder::class, $this, $data['identifier'], $data['name']
2117 );
2118 }
2119 }
2120
2121 if ($folder === NULL) {
2122 throw $e;
2123 }
2124 }
2125 return $folder;
2126 }
2127
2128 /**
2129 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2130 *
2131 * @param string $identifier
2132 * @return bool
2133 */
2134 public function isWithinProcessingFolder($identifier) {
2135 $inProcessingFolder = FALSE;
2136 foreach ($this->getProcessingFolders() as $processingFolder) {
2137 if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2138 $inProcessingFolder = TRUE;
2139 break;
2140 }
2141 }
2142 return $inProcessingFolder;
2143 }
2144
2145 /**
2146 * Checks if a resource (file or folder) is within the given folder
2147 *
2148 * @param Folder $folder
2149 * @param ResourceInterface $resource
2150 * @return bool
2151 * @throws \InvalidArgumentException
2152 */
2153 public function isWithinFolder(Folder $folder, ResourceInterface $resource) {
2154 if ($folder->getStorage() !== $this) {
2155 throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2156 }
2157 if ($folder->getStorage() !== $resource->getStorage()) {
2158 return FALSE;
2159 }
2160 return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2161 }
2162
2163 /**
2164 * Returns the folders on the root level of the storage
2165 * or the first mount point of this storage for this user
2166 * if $respectFileMounts is set.
2167 *
2168 * @param bool $respectFileMounts
2169 * @return Folder
2170 */
2171 public function getRootLevelFolder($respectFileMounts = TRUE) {
2172 if ($respectFileMounts && count($this->fileMounts)) {
2173 $mount = reset($this->fileMounts);
2174 return $mount['folder'];
2175 } else {
2176 return ResourceFactory::getInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2177 }
2178 }
2179
2180 /**
2181 * Emits file pre-add signal.
2182 *
2183 * @param string $targetFileName
2184 * @param Folder $targetFolder
2185 * @param string $sourceFilePath
2186 * @return string Modified target file name
2187 */
2188 protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath) {
2189 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileAdd, array(&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver));
2190 return $targetFileName;
2191 }
2192
2193 /**
2194 * Emits the file post-add signal.
2195 *
2196 * @param FileInterface $file
2197 * @param Folder $targetFolder
2198 * @return void
2199 */
2200 protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder) {
2201 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileAdd, array($file, $targetFolder));
2202 }
2203
2204 /**
2205 * Emits file pre-copy signal.
2206 *
2207 * @param FileInterface $file
2208 * @param Folder $targetFolder
2209 * @return void
2210 */
2211 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder) {
2212 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileCopy, array($file, $targetFolder));
2213 }
2214
2215 /**
2216 * Emits the file post-copy signal.
2217 *
2218 * @param FileInterface $file
2219 * @param Folder $targetFolder
2220 * @return void
2221 */
2222 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder) {
2223 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCopy, array($file, $targetFolder));
2224 }
2225
2226 /**
2227 * Emits the file pre-move signal.
2228 *
2229 * @param FileInterface $file
2230 * @param Folder $targetFolder
2231 * @return void
2232 */
2233 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder) {
2234 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileMove, array($file, $targetFolder));
2235 }
2236
2237 /**
2238 * Emits the file post-move signal.
2239 *
2240 * @param FileInterface $file
2241 * @param Folder $targetFolder
2242 * @param Folder $originalFolder
2243 * @return void
2244 */
2245 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, Folder $originalFolder) {
2246 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileMove, array($file, $targetFolder, $originalFolder));
2247 }
2248
2249 /**
2250 * Emits the file pre-rename signal
2251 *
2252 * @param FileInterface $file
2253 * @param $targetFolder
2254 * @return void
2255 */
2256 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder) {
2257 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileRename, array($file, $targetFolder));
2258 }
2259
2260 /**
2261 * Emits the file post-rename signal.
2262 *
2263 * @param FileInterface $file
2264 * @param $targetFolder
2265 * @return void
2266 */
2267 protected function emitPostFileRenameSignal(FileInterface $file, $targetFolder) {
2268 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileRename, array($file, $targetFolder));
2269 }
2270
2271 /**
2272 * Emits the file pre-replace signal.
2273 *
2274 * @param FileInterface $file
2275 * @param $localFilePath
2276 * @return void
2277 */
2278 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath) {
2279 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileReplace, array($file, $localFilePath));
2280 }
2281
2282 /**
2283 * Emits the file post-replace signal
2284 *
2285 * @param FileInterface $file
2286 * @param string $localFilePath
2287 * @return void
2288 */
2289 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath) {
2290 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileReplace, array($file, $localFilePath));
2291 }
2292
2293 /**
2294 * Emits the file post-create signal
2295 *
2296 * @param string $newFileIdentifier
2297 * @param Folder $targetFolder
2298 */
2299 protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder) {
2300 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCreate, array($newFileIdentifier, $targetFolder));
2301 }
2302
2303 /**
2304 * Emits the file pre-deletion signal.
2305 *
2306 * @param FileInterface $file
2307 * @return void
2308 */
2309 protected function emitPreFileDeleteSignal(FileInterface $file) {
2310 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileDelete, array($file));
2311 }
2312
2313 /**
2314 * Emits the file post-deletion signal
2315 *
2316 * @param FileInterface $file
2317 * @return void
2318 */
2319 protected function emitPostFileDeleteSignal(FileInterface $file) {
2320 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileDelete, array($file));
2321 }
2322
2323 /**
2324 * Emits the file post-set-contents signal
2325 *
2326 * @param FileInterface $file
2327 * @param mixed $content
2328 * @return void
2329 */
2330 protected function emitPostFileSetContentsSignal(FileInterface $file, $content) {
2331 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileSetContents, array($file, $content));
2332 }
2333
2334 /**
2335 * Emits the folder pre-add signal.
2336 *
2337 * @param Folder $targetFolder
2338 * @param string $name
2339 * @return void
2340 */
2341 protected function emitPreFolderAddSignal(Folder $targetFolder, $name) {
2342 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderAdd, array($targetFolder, $name));
2343 }
2344
2345 /**
2346 * Emits the folder post-add signal.
2347 *
2348 * @param Folder $folder
2349 * @return void
2350 */
2351 protected function emitPostFolderAddSignal(Folder $folder) {
2352 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderAdd, array($folder));
2353 }
2354
2355 /**
2356 * Emits the folder pre-copy signal.
2357 *
2358 * @param Folder $folder
2359 * @param Folder $targetFolder
2360 * @param $newName
2361 * @return void
2362 */
2363 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2364 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderCopy, array($folder, $targetFolder, $newName));
2365 }
2366
2367 /**
2368 * Emits the folder post-copy signal.
2369 *
2370 * @param Folder $folder
2371 * @param Folder $targetFolder
2372 * @param $newName
2373 * @return void
2374 */
2375 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2376 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderCopy, array($folder, $targetFolder, $newName));
2377 }
2378
2379 /**
2380 * Emits the folder pre-move signal.
2381 *
2382 * @param Folder $folder
2383 * @param Folder $targetFolder
2384 * @param $newName
2385 * @return void
2386 */
2387 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName) {
2388 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderMove, array($folder, $targetFolder, $newName));
2389 }
2390
2391 /**
2392 * Emits the folder post-move signal.
2393 *
2394 * @param Folder $folder
2395 * @param Folder $targetFolder
2396 * @param $newName
2397 * @param Folder $originalFolder
2398 * @return void
2399 */
2400 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder) {
2401 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderMove, array($folder, $targetFolder, $newName, $originalFolder));
2402 }
2403
2404 /**
2405 * Emits the folder pre-rename signal.
2406 *
2407 * @param Folder $folder
2408 * @param $newName
2409 * @return void
2410 */
2411 protected function emitPreFolderRenameSignal(Folder $folder, $newName) {
2412 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderRename, array($folder, $newName));
2413 }
2414
2415 /**
2416 * Emits the folder post-rename signal.
2417 *
2418 * @param Folder $folder
2419 * @param $newName
2420 * @return void
2421 */
2422 protected function emitPostFolderRenameSignal(Folder $folder, $newName) {
2423 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderRename, array($folder, $newName));
2424 }
2425
2426 /**
2427 * Emits the folder pre-deletion signal.
2428 *
2429 * @param Folder $folder
2430 * @return void
2431 */
2432 protected function emitPreFolderDeleteSignal(Folder $folder) {
2433 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderDelete, array($folder));
2434 }
2435
2436 /**
2437 * Emits folder post-deletion signal..
2438 *
2439 * @param Folder $folder
2440 * @return void
2441 */
2442 protected function emitPostFolderDeleteSignal(Folder $folder) {
2443 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderDelete, array($folder));
2444 }
2445
2446 /**
2447 * Emits file pre-processing signal when generating a public url for a file or folder.
2448 *
2449 * @param ResourceInterface $resourceObject
2450 * @param bool $relativeToCurrentScript
2451 * @param array $urlData
2452 */
2453 protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData) {
2454 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreGeneratePublicUrl, array($this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData));
2455 }
2456
2457 /**
2458 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2459 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2460 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2461 *
2462 * @param Folder $folder
2463 * @param string $theFile The input fileName to check
2464 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2465 *
2466 * @throws \RuntimeException
2467 * @return string A unique fileName inside $folder, based on $theFile.
2468 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2469 */
2470 protected function getUniqueName(Folder $folder, $theFile, $dontCheckForUnique = FALSE) {
2471 static $maxNumber = 99, $uniqueNamePrefix = '';
2472 // Fetches info about path, name, extension of $theFile
2473 $origFileInfo = PathUtility::pathinfo($theFile);
2474 // Adds prefix
2475 if ($uniqueNamePrefix) {
2476 $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2477 $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2478 }
2479 // Check if the file exists and if not - return the fileName...
2480 // The destinations file
2481 $theDestFile = $origFileInfo['basename'];
2482 // If the file does NOT exist we return this fileName
2483 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2484 return $theDestFile;
2485 }
2486 // Well the fileName in its pure form existed. Now we try to append
2487 // numbers / unique-strings and see if we can find an available fileName
2488 // This removes _xx if appended to the file
2489 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2490 $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2491 for ($a = 1; $a <= $maxNumber + 1; $a++) {
2492 // First we try to append numbers
2493 if ($a <= $maxNumber) {
2494 $insert = '_' . sprintf('%02d', $a);
2495 } else {
2496 $insert = '_' . substr(md5(uniqid('', TRUE)), 0, 6);
2497 }
2498 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2499 // The destinations file
2500 $theDestFile = $theTestFile;
2501 // If the file does NOT exist we return this fileName
2502 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2503 return $theDestFile;
2504 }
2505 }
2506 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2507 }
2508
2509 /**
2510 * Get the SignalSlot dispatcher.
2511 *
2512 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2513 */
2514 protected function getSignalSlotDispatcher() {
2515 if (!isset($this->signalSlotDispatcher)) {
2516 $this->signalSlotDispatcher = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
2517 }
2518 return $this->signalSlotDispatcher;
2519 }
2520
2521 /**
2522 * Gets the ObjectManager.
2523 *
2524 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2525 */
2526 protected function getObjectManager() {
2527 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
2528 }
2529
2530 /**
2531 * @return ResourceFactory
2532 */
2533 protected function getFileFactory() {
2534 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
2535 }
2536
2537 /**
2538 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
2539 */
2540 protected function getFileIndexRepository() {
2541 return FileIndexRepository::getInstance();
2542 }
2543
2544 /**
2545 * @return Service\FileProcessingService
2546 */
2547 protected function getFileProcessingService() {
2548 if (!$this->fileProcessingService) {
2549 $this->fileProcessingService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Service\FileProcessingService::class, $this, $this->driver);
2550 }
2551 return $this->fileProcessingService;
2552 }
2553
2554 /**
2555 * Gets the role of a folder.
2556 *
2557 * @param FolderInterface $folder Folder object to get the role from
2558 * @return string The role the folder has
2559 */
2560 public function getRole(FolderInterface $folder) {
2561 $folderRole = FolderInterface::ROLE_DEFAULT;
2562 $identifier = $folder->getIdentifier();
2563 if (method_exists($this->driver, 'getRole')) {
2564 $folderRole = $this->driver->getRole($folder->getIdentifier());
2565 }
2566 if (isset($this->fileMounts[$identifier])) {
2567 $folderRole = FolderInterface::ROLE_MOUNT;
2568
2569 if (!empty($this->fileMounts[$identifier]['read_only'])) {
2570 $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
2571 }
2572 if ($this->fileMounts[$identifier]['user_mount']) {
2573 $folderRole = FolderInterface::ROLE_USER_MOUNT;
2574 }
2575 }
2576 if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2577 $folderRole = FolderInterface::ROLE_PROCESSING;
2578 }
2579
2580 return $folderRole;
2581 }
2582
2583 /**
2584 * Getter function to return the folder where the files can
2585 * be processed. Does not check for access rights here.
2586 *
2587 * @return Folder
2588 */
2589 public function getProcessingFolder() {
2590 if (!isset($this->processingFolder)) {
2591 $processingFolder = self::DEFAULT_ProcessingFolder;
2592 if (!empty($this->storageRecord['processingfolder'])) {
2593 $processingFolder = $this->storageRecord['processingfolder'];
2594 }
2595 try {
2596 if (strpos($processingFolder, ':') !== FALSE) {
2597 $this->processingFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($processingFolder);
2598 } else {
2599 if ($this->driver->folderExists($processingFolder) === FALSE) {
2600 $this->processingFolder = $this->createFolder($processingFolder);
2601 } else {
2602 $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
2603 $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2604 }
2605 }
2606 } catch(Exception\InsufficientFolderWritePermissionsException $e) {
2607 $this->processingFolder = GeneralUtility::makeInstance(
2608 InaccessibleFolder::class, $this, $processingFolder, $processingFolder
2609 );
2610 }
2611 }
2612 return $this->processingFolder;
2613 }
2614
2615 /**
2616 * Gets the driver Type configured for this storage.
2617 *
2618 * @return string
2619 */
2620 public function getDriverType() {
2621 return $this->storageRecord['driver'];
2622 }
2623
2624 /**
2625 * Gets the Indexer.
2626 *
2627 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
2628 */
2629 protected function getIndexer() {
2630 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $this);
2631 }
2632
2633 /**
2634 * @param bool $isDefault
2635 * @return void
2636 */
2637 public function setDefault($isDefault) {
2638 $this->isDefault = (bool)$isDefault;
2639 }
2640
2641 /**
2642 * @return bool
2643 */
2644 public function isDefault() {
2645 return $this->isDefault;
2646 }
2647
2648 }