[BUGFIX] Prevent infinite loop in FAL access check
[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 throw new Exception\InsufficientFolderAccessPermissionsException(
701 'You are not allowed to access the given folder: "' . $folder->getName() . '"',
702 1375955684
703 );
704 }
705 }
706
707 /**
708 * Assures delete permission for given folder.
709 *
710 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder delete permissions are checked.
711 * @param bool $checkDeleteRecursively
712 * @return void
713 * @throws Exception\InsufficientFolderAccessPermissionsException
714 * @throws Exception\InsufficientFolderWritePermissionsException
715 * @throws Exception\InsufficientUserPermissionsException
716 */
717 protected function assureFolderDeletePermission(Folder $folder, $checkDeleteRecursively) {
718 // Check user permissions for recursive deletion if it is requested
719 if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
720 throw new Exception\InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
721 }
722 // Check user action permission
723 if (!$this->checkFolderActionPermission('delete', $folder)) {
724 throw new Exception\InsufficientFolderAccessPermissionsException(
725 'You are not allowed to delete the given folder: "' . $folder->getName() . '"',
726 1377779039
727 );
728 }
729 // Check if the user has write permissions to folders
730 // Would be good if we could check for actual write permissions in the containig folder
731 // but we cannot since we have no access to the containing folder of this file.
732 if (!$this->checkUserActionPermission('write', 'Folder')) {
733 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
734 }
735 }
736
737 /**
738 * Assures read permission for given file.
739 *
740 * @param FileInterface $file
741 * @return void
742 * @throws Exception\InsufficientFileAccessPermissionsException
743 * @throws Exception\IllegalFileExtensionException
744 */
745 protected function assureFileReadPermission(FileInterface $file) {
746 if (!$this->checkFileActionPermission('read', $file)) {
747 throw new Exception\InsufficientFileAccessPermissionsException(
748 'You are not allowed to access that file: "' . $file->getName() . '"',
749 1375955429
750 );
751 }
752 if (!$this->checkFileExtensionPermission($file->getName())) {
753 throw new Exception\IllegalFileExtensionException(
754 'You are not allowed to use that file extension. File: "' . $file->getName() . '"',
755 1375955430
756 );
757 }
758 }
759
760 /**
761 * Assures write permission for given file.
762 *
763 * @param FileInterface $file
764 * @return void
765 * @throws Exception\IllegalFileExtensionException
766 * @throws Exception\InsufficientFileWritePermissionsException
767 * @throws Exception\InsufficientUserPermissionsException
768 */
769 protected function assureFileWritePermissions(FileInterface $file) {
770 // Check if user is allowed to write the file and $file is writable
771 if (!$this->checkFileActionPermission('write', $file)) {
772 throw new Exception\InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
773 }
774 if (!$this->checkFileExtensionPermission($file->getName())) {
775 throw new Exception\IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
776 }
777 }
778
779 /**
780 * Assures delete permission for given file.
781 *
782 * @param FileInterface $file
783 * @return void
784 * @throws Exception\IllegalFileExtensionException
785 * @throws Exception\InsufficientFileWritePermissionsException
786 * @throws Exception\InsufficientFolderWritePermissionsException
787 */
788 protected function assureFileDeletePermissions(FileInterface $file) {
789 // Check for disallowed file extensions
790 if (!$this->checkFileExtensionPermission($file->getName())) {
791 throw new Exception\IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
792 }
793 // Check further permissions if file is not a processed file
794 if (!$file instanceof ProcessedFile) {
795 // Check if user is allowed to delete the file and $file is writable
796 if (!$this->checkFileActionPermission('delete', $file)) {
797 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '"', 1319550425);
798 }
799 // Check if the user has write permissions to folders
800 // Would be good if we could check for actual write permissions in the containig folder
801 // but we cannot since we have no access to the containing folder of this file.
802 if (!$this->checkUserActionPermission('write', 'Folder')) {
803 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
804 }
805 }
806 }
807
808 /**
809 * Checks if a file/user has the permission to be written to a Folder/Storage.
810 * If not, throws an exception.
811 *
812 * @param Folder $targetFolder The target folder where the file should be written
813 * @param string $targetFileName The file name which should be written into the storage
814 * @return void
815 *
816 * @throws Exception\InsufficientFolderWritePermissionsException
817 * @throws Exception\IllegalFileExtensionException
818 * @throws Exception\InsufficientUserPermissionsException
819 */
820 protected function assureFileAddPermissions($targetFolder, $targetFileName) {
821 // Check for a valid file extension
822 if (!$this->checkFileExtensionPermission($targetFileName)) {
823 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
824 }
825 // Makes sure the user is allowed to upload
826 if (!$this->checkUserActionPermission('add', 'File')) {
827 throw new Exception\InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
828 }
829 // Check if targetFolder is writable
830 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
831 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
832 }
833 }
834
835 /**
836 * Checks if a file has the permission to be uploaded to a Folder/Storage.
837 * If not, throws an exception.
838 *
839 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
840 * @param Folder $targetFolder The target folder where the file should be uploaded
841 * @param string $targetFileName the destination file name $_FILES['file1']['name']
842 * @param int $uploadedFileSize
843 * @return void
844 *
845 * @throws Exception\InsufficientFolderWritePermissionsException
846 * @throws Exception\UploadException
847 * @throws Exception\IllegalFileExtensionException
848 * @throws Exception\UploadSizeException
849 * @throws Exception\InsufficientUserPermissionsException
850 */
851 protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
852 // Makes sure this is an uploaded file
853 if (!is_uploaded_file($localFilePath)) {
854 throw new Exception\UploadException('The upload has failed, no uploaded file found!', 1322110455);
855 }
856 // Max upload size (kb) for files.
857 $maxUploadFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
858 if ($uploadedFileSize >= $maxUploadFileSize) {
859 unlink($localFilePath);
860 throw new Exception\UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
861 }
862 $this->assureFileAddPermissions($targetFolder, $targetFileName);
863 }
864
865 /**
866 * Checks for permissions to move a file.
867 *
868 * @throws \RuntimeException
869 * @throws Exception\InsufficientFolderAccessPermissionsException
870 * @throws Exception\InsufficientUserPermissionsException
871 * @throws Exception\IllegalFileExtensionException
872 * @param FileInterface $file
873 * @param Folder $targetFolder
874 * @param string $targetFileName
875 * @return void
876 */
877 protected function assureFileMovePermissions(FileInterface $file, Folder $targetFolder, $targetFileName) {
878 // Check if targetFolder is within this storage
879 if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
880 throw new \RuntimeException('The target folder is not in the same storage. Target folder given: "' . $targetFolder . '"', 1422553107);
881 }
882 // Check for a valid file extension
883 if (!$this->checkFileExtensionPermission($targetFileName)) {
884 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
885 }
886 // Check if user is allowed to move and $file is readable and writable
887 if (!$file->getStorage()->checkFileActionPermission('move', $file)) {
888 throw new Exception\InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
889 }
890 // Check if target folder is writable
891 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
892 throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219350);
893 }
894 }
895
896 /**
897 * Checks for permissions to rename a file.
898 *
899 * @param FileInterface $file
900 * @param string $targetFileName
901 * @throws Exception\InsufficientFileWritePermissionsException
902 * @throws Exception\IllegalFileExtensionException
903 * @throws Exception\InsufficientFileReadPermissionsException
904 * @throws Exception\InsufficientUserPermissionsException
905 * @return void
906 */
907 protected function assureFileRenamePermissions(FileInterface $file, $targetFileName) {
908 // Check if file extension is allowed
909 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
910 throw new Exception\IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663);
911 }
912 // Check if user is allowed to rename
913 if (!$this->checkFileActionPermission('rename', $file)) {
914 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename files. File given."' . $file . '"', 1319219351);
915 }
916 // Check if the user is allowed to write to folders
917 // Although it would be good to check, we cannot check here if the folder actually is writable
918 // because we do not know in which folder the file resides.
919 // So we rely on the driver to throw an exception in case the renaming failed.
920 if (!$this->checkFolderActionPermission('write')) {
921 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219352);
922 }
923 }
924
925 /**
926 * Check if a file has the permission to be copied on a File/Folder/Storage,
927 * if not throw an exception
928 *
929 * @param FileInterface $file
930 * @param Folder $targetFolder
931 * @param string $targetFileName
932 *
933 * @throws Exception
934 * @throws Exception\InsufficientFolderWritePermissionsException
935 * @throws Exception\IllegalFileExtensionException
936 * @throws Exception\InsufficientFileReadPermissionsException
937 * @throws Exception\InsufficientUserPermissionsException
938 * @return void
939 */
940 protected function assureFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName) {
941 // Check if targetFolder is within this storage, this should never happen
942 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
943 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
944 }
945 // Check if user is allowed to copy
946 if (!$file->getStorage()->checkFileActionPermission('copy', $file)) {
947 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '"', 1319550426);
948 }
949 // Check if targetFolder is writable
950 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
951 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
952 }
953 // Check for a valid file extension
954 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
955 throw new Exception\IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
956 }
957 }
958
959 /**
960 * Check if a file has the permission to be copied on a File/Folder/Storage,
961 * if not throw an exception
962 *
963 * @param FolderInterface $folderToCopy
964 * @param FolderInterface $targetParentFolder
965 * @return void
966 *
967 * @throws Exception
968 * @throws Exception\InsufficientFolderWritePermissionsException
969 * @throws Exception\IllegalFileExtensionException
970 * @throws Exception\InsufficientFileReadPermissionsException
971 * @throws Exception\InsufficientUserPermissionsException
972 * @throws \RuntimeException
973 */
974 protected function assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder) {
975 // Check if targetFolder is within this storage, this should never happen
976 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
977 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
978 }
979 if (!$folderToCopy instanceof Folder) {
980 throw new \RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type folder.', 1384209020);
981 }
982 // Check if user is allowed to copy and the folder is readable
983 if (!$folderToCopy->getStorage()->checkFolderActionPermission('copy', $folderToCopy)) {
984 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
985 }
986 if (!$targetParentFolder instanceof Folder) {
987 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type folder.', 1384209021);
988 }
989 // Check if targetFolder is writable
990 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
991 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
992 }
993 }
994
995 /**
996 * Check if a file has the permission to be copied on a File/Folder/Storage,
997 * if not throw an exception
998 *
999 * @param FolderInterface $folderToMove
1000 * @param FolderInterface $targetParentFolder
1001 *
1002 * @throws \InvalidArgumentException
1003 * @throws Exception\InsufficientFolderWritePermissionsException
1004 * @throws Exception\IllegalFileExtensionException
1005 * @throws Exception\InsufficientFileReadPermissionsException
1006 * @throws Exception\InsufficientUserPermissionsException
1007 * @throws \RuntimeException
1008 * @return void
1009 */
1010 protected function assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder) {
1011 // Check if targetFolder is within this storage, this should never happen
1012 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1013 throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1014 }
1015 if (!$folderToMove instanceof Folder) {
1016 throw new \RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
1017 }
1018 // Check if user is allowed to move and the folder is writable
1019 // In fact we would need to check if the parent folder of the folder to move is writable also
1020 // But as of now we cannot extract the parent folder from this folder
1021 if (!$folderToMove->getStorage()->checkFolderActionPermission('move', $folderToMove)) {
1022 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
1023 }
1024 if (!$targetParentFolder instanceof Folder) {
1025 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
1026 }
1027 // Check if targetFolder is writable
1028 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1029 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1030 }
1031 }
1032
1033 /********************
1034 * FILE ACTIONS
1035 ********************/
1036 /**
1037 * Moves a file from the local filesystem to this storage.
1038 *
1039 * @param string $localFilePath The file on the server's hard disk to add
1040 * @param Folder $targetFolder The target folder where the file should be added
1041 * @param string $targetFileName The name of the file to be add, If not set, the local file name is used
1042 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
1043 *
1044 * @throws \InvalidArgumentException
1045 * @throws Exception\ExistingTargetFileNameException
1046 * @return FileInterface
1047 */
1048 public function addFile($localFilePath, Folder $targetFolder, $targetFileName = '', $conflictMode = 'changeName') {
1049 $localFilePath = PathUtility::getCanonicalPath($localFilePath);
1050 if (!file_exists($localFilePath)) {
1051 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1052 }
1053 $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1054 $targetFileName = $this->driver->sanitizeFileName($targetFileName ?: PathUtility::basename($localFilePath));
1055
1056 $this->assureFileAddPermissions($targetFolder, $targetFileName);
1057
1058 // We do not care whether the file exists yet because $targetFileName may be changed by an
1059 // external slot and only then we should check how to proceed according to $conflictMode
1060 $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1061
1062 if ($conflictMode === 'cancel' && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1063 throw new Exception\ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1064 } elseif ($conflictMode === 'changeName') {
1065 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1066 }
1067
1068 $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName);
1069 $file = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1070
1071 $this->emitPostFileAddSignal($file, $targetFolder);
1072
1073 return $file;
1074 }
1075
1076 /**
1077 * Updates a processed file with a new file from the local filesystem.
1078 *
1079 * @param string $localFilePath
1080 * @param ProcessedFile $processedFile
1081 * @param Folder $processingFolder
1082 * @return FileInterface
1083 * @throws \InvalidArgumentException
1084 * @internal use only
1085 */
1086 public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = NULL) {
1087 if (!file_exists($localFilePath)) {
1088 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1089 }
1090 if ($processingFolder === NULL) {
1091 $processingFolder = $this->getProcessingFolder();
1092 }
1093 $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1094
1095 // @todo check if we have to update the processed file other then the identifier
1096 $processedFile->setIdentifier($fileIdentifier);
1097 return $processedFile;
1098 }
1099
1100 /**
1101 * Creates a (cryptographic) hash for a file.
1102 *
1103 * @param FileInterface $fileObject
1104 * @param string $hash
1105 * @return string
1106 */
1107 public function hashFile(FileInterface $fileObject, $hash) {
1108 return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1109 }
1110
1111 /**
1112 * Creates a (cryptographic) hash for a fileIdentifier.
1113
1114 * @param string $fileIdentifier
1115 * @param string $hash
1116 *
1117 * @return string
1118 */
1119 public function hashFileByIdentifier($fileIdentifier, $hash) {
1120 return $this->driver->hash($fileIdentifier, $hash);
1121 }
1122
1123 /**
1124 * Hashes a file identifier, taking the case sensitivity of the file system
1125 * into account. This helps mitigating problems with case-insensitive
1126 * databases.
1127 *
1128 * @param string|FileInterface $file
1129 * @return string
1130 */
1131 public function hashFileIdentifier($file) {
1132 if (is_object($file) && $file instanceof FileInterface) {
1133 /** @var FileInterface $file */
1134 $file = $file->getIdentifier();
1135 }
1136 return $this->driver->hashIdentifier($file);
1137 }
1138
1139 /**
1140 * Returns a publicly accessible URL for a file.
1141 *
1142 * WARNING: Access to the file may be restricted by further means, e.g.
1143 * some web-based authentication. You have to take care of this yourself.
1144 *
1145 * @param ResourceInterface $resourceObject The file or folder object
1146 * @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)
1147 * @return string
1148 */
1149 public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = FALSE) {
1150 $publicUrl = NULL;
1151 if ($this->isOnline()) {
1152 // Pre-process the public URL by an accordant slot
1153 $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
1154 // If slot did not handle the signal, use the default way to determine public URL
1155 if ($publicUrl === NULL) {
1156
1157 if ($this->hasCapability(self::CAPABILITY_PUBLIC)) {
1158 $publicUrl = $this->driver->getPublicUrl($resourceObject->getIdentifier());
1159 }
1160
1161 if ($publicUrl === NULL && $resourceObject instanceof FileInterface) {
1162 $queryParameterArray = array('eID' => 'dumpFile', 't' => '');
1163 if ($resourceObject instanceof File) {
1164 $queryParameterArray['f'] = $resourceObject->getUid();
1165 $queryParameterArray['t'] = 'f';
1166 } elseif ($resourceObject instanceof ProcessedFile) {
1167 $queryParameterArray['p'] = $resourceObject->getUid();
1168 $queryParameterArray['t'] = 'p';
1169 }
1170
1171 $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1172 $publicUrl = 'index.php?' . str_replace('+', '%20', http_build_query($queryParameterArray));
1173 }
1174
1175 // If requested, make the path relative to the current script in order to make it possible
1176 // to use the relative file
1177 if ($publicUrl !== NULL && $relativeToCurrentScript && !GeneralUtility::isValidUrl($publicUrl)) {
1178 $absolutePathToContainingFolder = PathUtility::dirname(PATH_site . $publicUrl);
1179 $pathPart = PathUtility::getRelativePathTo($absolutePathToContainingFolder);
1180 $filePart = substr(PATH_site . $publicUrl, strlen($absolutePathToContainingFolder) + 1);
1181 $publicUrl = $pathPart . $filePart;
1182 }
1183 }
1184 }
1185 return $publicUrl;
1186 }
1187
1188 /**
1189 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1190 *
1191 * @param FileInterface $fileObject The file object
1192 * @param string $context
1193 * @param array $configuration
1194 *
1195 * @return ProcessedFile
1196 * @throws \InvalidArgumentException
1197 */
1198 public function processFile(FileInterface $fileObject, $context, array $configuration) {
1199 if ($fileObject->getStorage() !== $this) {
1200 throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1201 }
1202 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1203
1204 return $processedFile;
1205 }
1206
1207 /**
1208 * Copies a file from the storage for local processing.
1209 *
1210 * @param FileInterface $fileObject
1211 * @param bool $writable
1212 * @return string Path to local file (either original or copied to some temporary local location)
1213 */
1214 public function getFileForLocalProcessing(FileInterface $fileObject, $writable = TRUE) {
1215 $filePath = $this->driver->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1216 return $filePath;
1217 }
1218
1219 /**
1220 * Gets a file by identifier.
1221 *
1222 * @param string $identifier
1223 * @return FileInterface
1224 */
1225 public function getFile($identifier) {
1226 $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1227 if (!$this->driver->fileExists($identifier)) {
1228 $file->setMissing(TRUE);
1229 }
1230 return $file;
1231 }
1232
1233 /**
1234 * Gets information about a file.
1235 *
1236 * @param FileInterface $fileObject
1237 * @return array
1238 * @internal
1239 */
1240 public function getFileInfo(FileInterface $fileObject) {
1241 return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1242 }
1243
1244 /**
1245 * Gets information about a file by its identifier.
1246 *
1247 * @param string $identifier
1248 * @param array $propertiesToExtract
1249 * @return array
1250 * @internal
1251 */
1252 public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array()) {
1253 return $this->driver->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1254 }
1255
1256 /**
1257 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
1258 *
1259 * @return void
1260 */
1261 public function unsetFileAndFolderNameFilters() {
1262 $this->fileAndFolderNameFilters = array();
1263 }
1264
1265 /**
1266 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1267 *
1268 * @return void
1269 */
1270 public function resetFileAndFolderNameFiltersToDefault() {
1271 $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1272 }
1273
1274 /**
1275 * Returns the file and folder name filters used by this storage.
1276 *
1277 * @return array
1278 */
1279 public function getFileAndFolderNameFilters() {
1280 return $this->fileAndFolderNameFilters;
1281 }
1282
1283 /**
1284 * @param array $filters
1285 * @return $this
1286 */
1287 public function setFileAndFolderNameFilters(array $filters) {
1288 $this->fileAndFolderNameFilters = $filters;
1289 return $this;
1290 }
1291
1292 /**
1293 * @param array $filter
1294 */
1295 public function addFileAndFolderNameFilter($filter) {
1296 $this->fileAndFolderNameFilters[] = $filter;
1297 }
1298
1299 /**
1300 * @param string $fileIdentifier
1301 *
1302 * @return string
1303 */
1304 public function getFolderIdentifierFromFileIdentifier($fileIdentifier) {
1305 return $this->driver->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1306 }
1307
1308 /**
1309 * @param Folder $folder
1310 * @param int $start
1311 * @param int $maxNumberOfItems
1312 * @param bool $useFilters
1313 * @param bool $recursive
1314 * @param string $sort Property name used to sort the items.
1315 * Among them may be: '' (empty, no sorting), name,
1316 * fileext, size, tstamp and rw.
1317 * If a driver does not support the given property, it
1318 * should fall back to "name".
1319 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1320 * @return File[]
1321 * @throws Exception\InsufficientFolderAccessPermissionsException
1322 */
1323 public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
1324 $this->assureFolderReadPermission($folder);
1325
1326 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1327
1328 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1329 $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1330
1331 $items = array();
1332 foreach ($fileIdentifiers as $identifier) {
1333 if (isset($rows[$identifier])) {
1334 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1335 } else {
1336 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1337 }
1338 if ($fileObject instanceof FileInterface) {
1339 $key = $fileObject->getName();
1340 while (isset($items[$key])) {
1341 $key .= 'z';
1342 }
1343 $items[$key] = $fileObject;
1344 }
1345 }
1346
1347 return $items;
1348 }
1349
1350 /**
1351 * @param string $folderIdentifier
1352 * @param bool $useFilters
1353 * @param bool $recursive
1354 * @return array
1355 */
1356 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1357 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1358 return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1359 }
1360
1361 /**
1362 * @param Folder $folder
1363 * @param bool $useFilters
1364 * @param bool $recursive
1365 * @return int Number of files in folder
1366 */
1367 public function countFilesInFolder(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
1368 $this->assureFolderReadPermission($folder);
1369 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
1370 return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1371 }
1372
1373 /**
1374 * @param string $folderIdentifier
1375 * @param bool $useFilters
1376 * @param bool $recursive
1377 * @return array
1378 */
1379 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1380 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1381 return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1382 }
1383
1384 /**
1385 * Returns TRUE if the specified file exists
1386 *
1387 * @param string $identifier
1388 * @return bool
1389 */
1390 public function hasFile($identifier) {
1391 // Allow if identifier is in processing folder
1392 if (!$this->isWithinProcessingFolder($identifier)) {
1393 $this->assureFolderReadPermission();
1394 }
1395 return $this->driver->fileExists($identifier);
1396 }
1397
1398 /**
1399 * Get all processing folders that live in this storage
1400 *
1401 * @return Folder[]
1402 */
1403 public function getProcessingFolders() {
1404 if ($this->processingFolders === NULL) {
1405 $this->processingFolders = array();
1406 $this->processingFolders[] = $this->getProcessingFolder();
1407 /** @var $storageRepository StorageRepository */
1408 $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1409 $allStorages = $storageRepository->findAll();
1410 foreach ($allStorages as $storage) {
1411 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1412 // See #66695 for details
1413 list($storageUid, $processingFolderIdentifier) = GeneralUtility::trimExplode(':', $storage->getStorageRecord()['processingfolder']);
1414 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1415 continue;
1416 }
1417 $potentialProcessingFolder = ResourceFactory::getInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1418 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1419 $this->processingFolders[] = $potentialProcessingFolder;
1420 }
1421 }
1422 }
1423
1424 return $this->processingFolders;
1425 }
1426
1427 /**
1428 * Returns TRUE if folder that is in current storage is set as
1429 * processing folder for one of the existing storages
1430 *
1431 * @param Folder $folder
1432 * @return bool
1433 */
1434 public function isProcessingFolder(Folder $folder) {
1435 $isProcessingFolder = FALSE;
1436 foreach ($this->getProcessingFolders() as $processingFolder) {
1437 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1438 $isProcessingFolder = TRUE;
1439 break;
1440 }
1441 }
1442 return $isProcessingFolder;
1443 }
1444
1445 /**
1446 * Checks if the queried file in the given folder exists
1447 *
1448 * @param string $fileName
1449 * @param Folder $folder
1450 * @return bool
1451 */
1452 public function hasFileInFolder($fileName, Folder $folder) {
1453 $this->assureFolderReadPermission($folder);
1454 return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1455 }
1456
1457 /**
1458 * Get contents of a file object
1459 *
1460 * @param FileInterface $file
1461 *
1462 * @throws Exception\InsufficientFileReadPermissionsException
1463 * @return string
1464 */
1465 public function getFileContents($file) {
1466 $this->assureFileReadPermission($file);
1467 return $this->driver->getFileContents($file->getIdentifier());
1468 }
1469
1470 /**
1471 * Outputs file Contents,
1472 * clears output buffer first and sends headers accordingly.
1473 *
1474 * @param FileInterface $file
1475 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1476 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1477 * @return void
1478 */
1479 public function dumpFileContents(FileInterface $file, $asDownload = FALSE, $alternativeFilename = NULL) {
1480 $downloadName = $alternativeFilename ?: $file->getName();
1481 $contentDisposition = $asDownload ? 'attachment' : 'inline';
1482 header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1483 header('Content-Type: ' . $file->getMimeType());
1484 header('Content-Length: ' . $file->getSize());
1485
1486 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1487 // See for more information: http://support.microsoft.com/kb/323308
1488 header("Cache-Control: ''");
1489 header('Last-Modified: ' .
1490 gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), array('mtime')))) . ' GMT',
1491 TRUE,
1492 200
1493 );
1494 ob_clean();
1495 flush();
1496 while (ob_get_level() > 0) {
1497 ob_end_clean();
1498 }
1499 $this->driver->dumpFileContents($file->getIdentifier());
1500 }
1501
1502 /**
1503 * Set contents of a file object.
1504 *
1505 * @param AbstractFile $file
1506 * @param string $contents
1507 *
1508 * @throws \Exception|\RuntimeException
1509 * @throws Exception\InsufficientFileWritePermissionsException
1510 * @throws Exception\InsufficientUserPermissionsException
1511 * @return int The number of bytes written to the file
1512 */
1513 public function setFileContents(AbstractFile $file, $contents) {
1514 // Check if user is allowed to edit
1515 $this->assureFileWritePermissions($file);
1516 // Call driver method to update the file and update file index entry afterwards
1517 $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1518 $this->getIndexer()->updateIndexEntry($file);
1519 $this->emitPostFileSetContentsSignal($file, $contents);
1520 return $result;
1521 }
1522
1523 /**
1524 * Creates a new file
1525 *
1526 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1527 *
1528 * @param string $fileName The name of the file to be created
1529 * @param Folder $targetFolderObject The target folder where the file should be created
1530 *
1531 * @throws Exception\IllegalFileExtensionException
1532 * @throws Exception\InsufficientFolderWritePermissionsException
1533 * @return FileInterface The file object
1534 */
1535 public function createFile($fileName, Folder $targetFolderObject) {
1536 $this->assureFileAddPermissions($targetFolderObject, $fileName);
1537 $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1538 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1539 return ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1540 }
1541
1542 /**
1543 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1544 *
1545 * @param $fileObject FileInterface
1546 * @throws Exception\InsufficientFileAccessPermissionsException
1547 * @throws Exception\FileOperationErrorException
1548 * @return bool TRUE if deletion succeeded
1549 */
1550 public function deleteFile($fileObject) {
1551 $this->assureFileDeletePermissions($fileObject);
1552
1553 $this->emitPreFileDeleteSignal($fileObject);
1554
1555 $result = $this->driver->deleteFile($fileObject->getIdentifier());
1556 if ($result === FALSE) {
1557 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1558 }
1559 // Mark the file object as deleted
1560 if ($fileObject instanceof File) {
1561 $fileObject->setDeleted();
1562 }
1563
1564 $this->emitPostFileDeleteSignal($fileObject);
1565
1566 return TRUE;
1567 }
1568
1569 /**
1570 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1571 * copies a source file (from any location) in to the target
1572 * folder, the latter has to be part of this storage
1573 *
1574 * @param FileInterface $file
1575 * @param Folder $targetFolder
1576 * @param string $targetFileName an optional destination fileName
1577 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1578 *
1579 * @throws \Exception|Exception\AbstractFileOperationException
1580 * @throws Exception\ExistingTargetFileNameException
1581 * @return FileInterface
1582 */
1583 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1584 if ($targetFileName === NULL) {
1585 $targetFileName = $file->getName();
1586 }
1587 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1588 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1589 $this->emitPreFileCopySignal($file, $targetFolder);
1590 // File exists and we should abort, let's abort
1591 if ($conflictMode === 'cancel' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1592 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1593 }
1594 // File exists and we should find another name, let's find another one
1595 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1596 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1597 }
1598 $sourceStorage = $file->getStorage();
1599 // Call driver method to create a new file from an existing file object,
1600 // and return the new file object
1601 if ($sourceStorage === $this) {
1602 $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1603 } else {
1604 $tempPath = $file->getForLocalProcessing();
1605 $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1606 }
1607 $newFileObject = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1608 $this->emitPostFileCopySignal($file, $targetFolder);
1609 return $newFileObject;
1610 }
1611
1612 /**
1613 * Moves a $file into a $targetFolder
1614 * the target folder has to be part of this storage
1615 *
1616 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1617 *
1618 * @param FileInterface $file
1619 * @param Folder $targetFolder
1620 * @param string $targetFileName an optional destination fileName
1621 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1622 *
1623 * @throws Exception\ExistingTargetFileNameException
1624 * @throws \RuntimeException
1625 * @return FileInterface
1626 */
1627 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1628 if ($targetFileName === NULL) {
1629 $targetFileName = $file->getName();
1630 }
1631 $originalFolder = $file->getParentFolder();
1632 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1633 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1634 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1635 // File exists and we should abort, let's abort
1636 if ($conflictMode === 'renameNewFile') {
1637 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1638 } elseif ($conflictMode === 'cancel') {
1639 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1640 }
1641 }
1642 $this->emitPreFileMoveSignal($file, $targetFolder);
1643 $sourceStorage = $file->getStorage();
1644 // Call driver method to move the file and update the index entry
1645 try {
1646 if ($sourceStorage === $this) {
1647 $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1648 if (!$file instanceof AbstractFile) {
1649 throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1650 }
1651 $file->updateProperties(array('identifier' => $newIdentifier));
1652 } else {
1653 $tempPath = $file->getForLocalProcessing();
1654 $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1655 $sourceStorage->driver->deleteFile($file->getIdentifier());
1656 if ($file instanceof File) {
1657 $file->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1658 }
1659 }
1660 $this->getIndexer()->updateIndexEntry($file);
1661 } catch (\TYPO3\CMS\Core\Exception $e) {
1662 echo $e->getMessage();
1663 }
1664 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1665 return $file;
1666 }
1667
1668 /**
1669 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1670 *
1671 * @param FileInterface $file
1672 * @param string $targetFileName
1673 *
1674 * @throws Exception\InsufficientFileWritePermissionsException
1675 * @throws Exception\InsufficientFileReadPermissionsException
1676 * @throws Exception\InsufficientUserPermissionsException
1677 * @return FileInterface
1678 */
1679 public function renameFile($file, $targetFileName) {
1680 // @todo add $conflictMode setting
1681
1682 // The name should be different from the current.
1683 if ($file->getName() === $targetFileName) {
1684 return $file;
1685 }
1686 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1687 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1688 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1689
1690 // Call driver method to rename the file and update the index entry
1691 try {
1692 $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1693 if ($file instanceof File) {
1694 $file->updateProperties(array('identifier' => $newIdentifier));
1695 }
1696 $this->getIndexer()->updateIndexEntry($file);
1697 } catch (\RuntimeException $e) {
1698
1699 }
1700
1701 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1702
1703 return $file;
1704 }
1705
1706 /**
1707 * Replaces a file with a local file (e.g. a freshly uploaded file)
1708 *
1709 * @param FileInterface $file
1710 * @param string $localFilePath
1711 *
1712 * @return FileInterface
1713 *
1714 * @throws Exception\IllegalFileExtensionException
1715 * @throws \InvalidArgumentException
1716 */
1717 public function replaceFile(FileInterface $file, $localFilePath) {
1718 $this->assureFileWritePermissions($file);
1719 if (!$this->checkFileExtensionPermission($localFilePath)) {
1720 throw new Exception\IllegalFileExtensionException('Source file extension not allowed.', 1378132239);
1721 }
1722 if (!file_exists($localFilePath)) {
1723 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1724 }
1725 $this->emitPreFileReplaceSignal($file, $localFilePath);
1726 $result = $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
1727 if ($file instanceof File) {
1728 $this->getIndexer()->updateIndexEntry($file);
1729 }
1730 $this->emitPostFileReplaceSignal($file, $localFilePath);
1731 return $result;
1732 }
1733
1734 /**
1735 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1736 *
1737 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1738 * @param Folder $targetFolder the target folder
1739 * @param string $targetFileName the file name to be written
1740 * @param string $conflictMode possible value are 'cancel', 'replace'
1741 * @return FileInterface The file object
1742 */
1743 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1744 $localFilePath = $uploadedFileData['tmp_name'];
1745 if ($targetFolder === NULL) {
1746 $targetFolder = $this->getDefaultFolder();
1747 }
1748 if ($targetFileName === NULL) {
1749 $targetFileName = $uploadedFileData['name'];
1750 }
1751 // Handling $conflictMode is delegated to addFile()
1752 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1753 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1754 return $resultObject;
1755 }
1756
1757 /********************
1758 * FOLDER ACTIONS
1759 ********************/
1760 /**
1761 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1762 * @todo check if this is a duplicate
1763 * @param Folder $folder
1764 * @return File[]
1765 */
1766 protected function getAllFileObjectsInFolder(Folder $folder) {
1767 $files = array();
1768 $folderQueue = array($folder);
1769 while (!empty($folderQueue)) {
1770 $folder = array_shift($folderQueue);
1771 foreach ($folder->getSubfolders() as $subfolder) {
1772 $folderQueue[] = $subfolder;
1773 }
1774 foreach ($folder->getFiles() as $file) {
1775 /** @var FileInterface $file */
1776 $files[$file->getIdentifier()] = $file;
1777 }
1778 }
1779
1780 return $files;
1781 }
1782
1783 /**
1784 * Moves a folder. If you want to move a folder from this storage to another
1785 * one, call this method on the target storage, otherwise you will get an exception.
1786 *
1787 * @param Folder $folderToMove The folder to move.
1788 * @param Folder $targetParentFolder The target parent folder
1789 * @param string $newFolderName
1790 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1791 *
1792 * @throws \Exception|\TYPO3\CMS\Core\Exception
1793 * @throws \InvalidArgumentException
1794 * @throws InvalidTargetFolderException
1795 * @return Folder
1796 */
1797 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1798 // @todo add tests
1799 $originalFolder = $folderToMove->getParentFolder();
1800 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1801 $sourceStorage = $folderToMove->getStorage();
1802 $returnObject = NULL;
1803 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
1804 // @todo check if folder already exists in $targetParentFolder, handle this conflict then
1805 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1806 // Get all file objects now so we are able to update them after moving the folder
1807 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1808 if ($sourceStorage === $this) {
1809 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
1810 throw new InvalidTargetFolderException(
1811 sprintf(
1812 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
1813 $folderToMove->getName(),
1814 $targetParentFolder->getName()
1815 ),
1816 1422723050
1817 );
1818 }
1819 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1820 } else {
1821 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1822 }
1823 // Update the identifier and storage of all file objects
1824 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1825 $newIdentifier = $fileMappings[$oldIdentifier];
1826 $fileObject->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1827 $this->getIndexer()->updateIndexEntry($fileObject);
1828 }
1829 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1830 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
1831 return $returnObject;
1832 }
1833
1834 /**
1835 * Moves the given folder from a different storage to the target folder in this storage.
1836 *
1837 * @param Folder $folderToMove
1838 * @param Folder $targetParentFolder
1839 * @param string $newFolderName
1840 *
1841 * @return bool
1842 * @throws \RuntimeException
1843 */
1844 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
1845 throw new \RuntimeException('Not yet implemented');
1846 }
1847
1848 /**
1849 * Copies a folder.
1850 *
1851 * @param FolderInterface $folderToCopy The folder to copy
1852 * @param FolderInterface $targetParentFolder The target folder
1853 * @param string $newFolderName
1854 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1855 * @return Folder The new (copied) folder object
1856 * @throws InvalidTargetFolderException
1857 */
1858 public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1859 // @todo implement the $conflictMode handling
1860 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
1861 $returnObject = NULL;
1862 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
1863 if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
1864 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1865 }
1866 $sourceStorage = $folderToCopy->getStorage();
1867 // call driver method to move the file
1868 // that also updates the file object properties
1869 if ($sourceStorage === $this) {
1870 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
1871 throw new InvalidTargetFolderException(
1872 sprintf(
1873 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
1874 $folderToCopy->getName(),
1875 $targetParentFolder->getName()
1876 ),
1877 1422723059
1878 );
1879 }
1880 $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1881 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
1882 } else {
1883 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1884 }
1885 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
1886 return $returnObject;
1887 }
1888
1889 /**
1890 * Copies a folder between storages.
1891 *
1892 * @param Folder $folderToCopy
1893 * @param Folder $targetParentFolder
1894 * @param string $newFolderName
1895 *
1896 * @return bool
1897 * @throws \RuntimeException
1898 */
1899 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
1900 throw new \RuntimeException('Not yet implemented.');
1901 }
1902
1903 /**
1904 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1905 *
1906 * @param Folder $folderObject
1907 * @param string $newName
1908 * @throws \Exception
1909 * @throws \InvalidArgumentException
1910 * @return Folder
1911 */
1912 public function renameFolder($folderObject, $newName) {
1913
1914 // Renaming the folder should check if the parent folder is writable
1915 // We cannot do this however because we cannot extract the parent folder from a folder currently
1916 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1917 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1918 }
1919
1920 $sanitizedNewName = $this->driver->sanitizeFileName($newName);
1921 $returnObject = NULL;
1922 if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
1923 throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1924 }
1925
1926 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
1927
1928 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1929 $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
1930 // Update the identifier of all file objects
1931 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1932 $newIdentifier = $fileMappings[$oldIdentifier];
1933 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1934 $this->getIndexer()->updateIndexEntry($fileObject);
1935 }
1936 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1937
1938 $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
1939
1940 return $returnObject;
1941 }
1942
1943 /**
1944 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1945 *
1946 * @param Folder $folderObject
1947 * @param bool $deleteRecursively
1948 * @throws \RuntimeException
1949 * @throws Exception\InsufficientFolderAccessPermissionsException
1950 * @throws Exception\InsufficientUserPermissionsException
1951 * @throws Exception\FileOperationErrorException
1952 * @throws Exception\InvalidPathException
1953 * @return bool
1954 */
1955 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1956 $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
1957 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
1958 if (!$isEmpty && !$deleteRecursively) {
1959 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1960 }
1961
1962 $this->emitPreFolderDeleteSignal($folderObject);
1963
1964 $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
1965
1966 $this->emitPostFolderDeleteSignal($folderObject);
1967
1968 return $result;
1969 }
1970
1971 /**
1972 * @param Folder $folder
1973 * @param int $start
1974 * @param int $maxNumberOfItems
1975 * @param bool $useFilters
1976 * @param bool $recursive
1977 * @param string $sort Property name used to sort the items.
1978 * Among them may be: '' (empty, no sorting), name,
1979 * fileext, size, tstamp and rw.
1980 * If a driver does not support the given property, it
1981 * should fall back to "name".
1982 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1983 * @return Folder[]
1984 */
1985 public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
1986 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1987
1988 $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
1989
1990 // Exclude processing folders
1991 foreach ($this->getProcessingFolders() as $processingFolder) {
1992 $processingIdentifier = $processingFolder->getIdentifier();
1993 if (isset($folderIdentifiers[$processingIdentifier])) {
1994 unset($folderIdentifiers[$processingIdentifier]);
1995 }
1996 }
1997 $folders = array();
1998 foreach ($folderIdentifiers as $folderIdentifier) {
1999 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, TRUE);
2000 }
2001 return $folders;
2002 }
2003
2004 /**
2005 * @param Folder $folder
2006 * @param bool $useFilters
2007 * @param bool $recursive
2008 * @return integer Number of subfolders
2009 */
2010 public function countFoldersInFolder(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
2011 $this->assureFolderReadPermission($folder);
2012 $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
2013 return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2014 }
2015
2016 /**
2017 * Returns TRUE if the specified folder exists.
2018 *
2019 * @param string $identifier
2020 * @return bool
2021 */
2022 public function hasFolder($identifier) {
2023 $this->assureFolderReadPermission();
2024 return $this->driver->folderExists($identifier);
2025 }
2026
2027 /**
2028 * Checks if the given file exists in the given folder
2029 *
2030 * @param string $folderName
2031 * @param Folder $folder
2032 * @return bool
2033 */
2034 public function hasFolderInFolder($folderName, Folder $folder) {
2035 $this->assureFolderReadPermission($folder);
2036 return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2037 }
2038
2039 /**
2040 * Creates a new folder.
2041 *
2042 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2043 *
2044 * @param string $folderName The new folder name
2045 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2046 *
2047 * @throws Exception\InsufficientFolderWritePermissionsException
2048 * @throws \InvalidArgumentException
2049 * @return Folder The new folder object
2050 */
2051 public function createFolder($folderName, Folder $parentFolder = NULL) {
2052 if ($parentFolder === NULL) {
2053 $parentFolder = $this->getRootLevelFolder();
2054 } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2055 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2056 }
2057 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2058 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2059 }
2060
2061 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2062
2063 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), TRUE);
2064 $newFolder = $this->getFolder($newFolder);
2065
2066 $this->emitPostFolderAddSignal($newFolder);
2067
2068 return $newFolder;
2069 }
2070
2071 /**
2072 * Returns the default folder where new files are stored if no other folder is given.
2073 *
2074 * @return Folder
2075 */
2076 public function getDefaultFolder() {
2077 return $this->getFolder($this->driver->getDefaultFolder());
2078 }
2079
2080 /**
2081 * @param string $identifier
2082 * @param bool $returnInaccessibleFolderObject
2083 *
2084 * @return Folder
2085 * @throws \Exception
2086 * @throws Exception\InsufficientFolderAccessPermissionsException
2087 */
2088 public function getFolder($identifier, $returnInaccessibleFolderObject = FALSE) {
2089 $data = $this->driver->getFolderInfoByIdentifier($identifier);
2090 $folder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2091
2092 try {
2093 $this->assureFolderReadPermission($folder);
2094 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2095 $folder = NULL;
2096 if ($returnInaccessibleFolderObject) {
2097 // if parent folder is readable return inaccessible folder object
2098 $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2099 if ($parentPermissions['r']) {
2100 $folder = GeneralUtility::makeInstance(
2101 \TYPO3\CMS\Core\Resource\InaccessibleFolder::class, $this, $data['identifier'], $data['name']
2102 );
2103 }
2104 }
2105
2106 if ($folder === NULL) {
2107 throw $e;
2108 }
2109 }
2110 return $folder;
2111 }
2112
2113 /**
2114 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2115 *
2116 * @param string $identifier
2117 * @return bool
2118 */
2119 public function isWithinProcessingFolder($identifier) {
2120 $inProcessingFolder = FALSE;
2121 foreach ($this->getProcessingFolders() as $processingFolder) {
2122 if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2123 $inProcessingFolder = TRUE;
2124 break;
2125 }
2126 }
2127 return $inProcessingFolder;
2128 }
2129
2130 /**
2131 * Checks if a resource (file or folder) is within the given folder
2132 *
2133 * @param Folder $folder
2134 * @param ResourceInterface $resource
2135 * @return bool
2136 * @throws \InvalidArgumentException
2137 */
2138 public function isWithinFolder(Folder $folder, ResourceInterface $resource) {
2139 if ($folder->getStorage() !== $this) {
2140 throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2141 }
2142 if ($folder->getStorage() !== $resource->getStorage()) {
2143 return FALSE;
2144 }
2145 return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2146 }
2147
2148 /**
2149 * Returns the folders on the root level of the storage
2150 * or the first mount point of this storage for this user.
2151 *
2152 * @return Folder
2153 */
2154 public function getRootLevelFolder() {
2155 if (count($this->fileMounts)) {
2156 $mount = reset($this->fileMounts);
2157 return $mount['folder'];
2158 } else {
2159 return ResourceFactory::getInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2160 }
2161 }
2162
2163 /**
2164 * Emits file pre-add signal.
2165 *
2166 * @param string $targetFileName
2167 * @param Folder $targetFolder
2168 * @param string $sourceFilePath
2169 * @return string Modified target file name
2170 */
2171 protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath) {
2172 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileAdd, array(&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver));
2173 return $targetFileName;
2174 }
2175
2176 /**
2177 * Emits the file post-add signal.
2178 *
2179 * @param FileInterface $file
2180 * @param Folder $targetFolder
2181 * @return void
2182 */
2183 protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder) {
2184 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileAdd, array($file, $targetFolder));
2185 }
2186
2187 /**
2188 * Emits file pre-copy signal.
2189 *
2190 * @param FileInterface $file
2191 * @param Folder $targetFolder
2192 * @return void
2193 */
2194 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder) {
2195 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileCopy, array($file, $targetFolder));
2196 }
2197
2198 /**
2199 * Emits the file post-copy signal.
2200 *
2201 * @param FileInterface $file
2202 * @param Folder $targetFolder
2203 * @return void
2204 */
2205 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder) {
2206 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCopy, array($file, $targetFolder));
2207 }
2208
2209 /**
2210 * Emits the file pre-move signal.
2211 *
2212 * @param FileInterface $file
2213 * @param Folder $targetFolder
2214 * @return void
2215 */
2216 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder) {
2217 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileMove, array($file, $targetFolder));
2218 }
2219
2220 /**
2221 * Emits the file post-move signal.
2222 *
2223 * @param FileInterface $file
2224 * @param Folder $targetFolder
2225 * @param Folder $originalFolder
2226 * @return void
2227 */
2228 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, Folder $originalFolder) {
2229 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileMove, array($file, $targetFolder, $originalFolder));
2230 }
2231
2232 /**
2233 * Emits the file pre-rename signal
2234 *
2235 * @param FileInterface $file
2236 * @param $targetFolder
2237 * @return void
2238 */
2239 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder) {
2240 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileRename, array($file, $targetFolder));
2241 }
2242
2243 /**
2244 * Emits the file post-rename signal.
2245 *
2246 * @param FileInterface $file
2247 * @param $targetFolder
2248 * @return void
2249 */
2250 protected function emitPostFileRenameSignal(FileInterface $file, $targetFolder) {
2251 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileRename, array($file, $targetFolder));
2252 }
2253
2254 /**
2255 * Emits the file pre-replace signal.
2256 *
2257 * @param FileInterface $file
2258 * @param $localFilePath
2259 * @return void
2260 */
2261 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath) {
2262 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileReplace, array($file, $localFilePath));
2263 }
2264
2265 /**
2266 * Emits the file post-replace signal
2267 *
2268 * @param FileInterface $file
2269 * @param string $localFilePath
2270 * @return void
2271 */
2272 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath) {
2273 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileReplace, array($file, $localFilePath));
2274 }
2275
2276 /**
2277 * Emits the file post-create signal
2278 *
2279 * @param string $newFileIdentifier
2280 * @param Folder $targetFolder
2281 */
2282 protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder) {
2283 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileCreate, array($newFileIdentifier, $targetFolder));
2284 }
2285
2286 /**
2287 * Emits the file pre-deletion signal.
2288 *
2289 * @param FileInterface $file
2290 * @return void
2291 */
2292 protected function emitPreFileDeleteSignal(FileInterface $file) {
2293 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFileDelete, array($file));
2294 }
2295
2296 /**
2297 * Emits the file post-deletion signal
2298 *
2299 * @param FileInterface $file
2300 * @return void
2301 */
2302 protected function emitPostFileDeleteSignal(FileInterface $file) {
2303 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileDelete, array($file));
2304 }
2305
2306 /**
2307 * Emits the file post-set-contents signal
2308 *
2309 * @param FileInterface $file
2310 * @param mixed $content
2311 * @return void
2312 */
2313 protected function emitPostFileSetContentsSignal(FileInterface $file, $content) {
2314 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFileSetContents, array($file, $content));
2315 }
2316
2317 /**
2318 * Emits the folder pre-add signal.
2319 *
2320 * @param Folder $targetFolder
2321 * @param string $name
2322 * @return void
2323 */
2324 protected function emitPreFolderAddSignal(Folder $targetFolder, $name) {
2325 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderAdd, array($targetFolder, $name));
2326 }
2327
2328 /**
2329 * Emits the folder post-add signal.
2330 *
2331 * @param Folder $folder
2332 * @return void
2333 */
2334 protected function emitPostFolderAddSignal(Folder $folder) {
2335 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderAdd, array($folder));
2336 }
2337
2338 /**
2339 * Emits the folder pre-copy signal.
2340 *
2341 * @param Folder $folder
2342 * @param Folder $targetFolder
2343 * @param $newName
2344 * @return void
2345 */
2346 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2347 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderCopy, array($folder, $targetFolder, $newName));
2348 }
2349
2350 /**
2351 * Emits the folder post-copy signal.
2352 *
2353 * @param Folder $folder
2354 * @param Folder $targetFolder
2355 * @param $newName
2356 * @return void
2357 */
2358 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2359 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderCopy, array($folder, $targetFolder, $newName));
2360 }
2361
2362 /**
2363 * Emits the folder pre-move signal.
2364 *
2365 * @param Folder $folder
2366 * @param Folder $targetFolder
2367 * @param $newName
2368 * @return void
2369 */
2370 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName) {
2371 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderMove, array($folder, $targetFolder, $newName));
2372 }
2373
2374 /**
2375 * Emits the folder post-move signal.
2376 *
2377 * @param Folder $folder
2378 * @param Folder $targetFolder
2379 * @param $newName
2380 * @param Folder $originalFolder
2381 * @return void
2382 */
2383 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder) {
2384 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderMove, array($folder, $targetFolder, $newName, $originalFolder));
2385 }
2386
2387 /**
2388 * Emits the folder pre-rename signal.
2389 *
2390 * @param Folder $folder
2391 * @param $newName
2392 * @return void
2393 */
2394 protected function emitPreFolderRenameSignal(Folder $folder, $newName) {
2395 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderRename, array($folder, $newName));
2396 }
2397
2398 /**
2399 * Emits the folder post-rename signal.
2400 *
2401 * @param Folder $folder
2402 * @param $newName
2403 * @return void
2404 */
2405 protected function emitPostFolderRenameSignal(Folder $folder, $newName) {
2406 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderRename, array($folder, $newName));
2407 }
2408
2409 /**
2410 * Emits the folder pre-deletion signal.
2411 *
2412 * @param Folder $folder
2413 * @return void
2414 */
2415 protected function emitPreFolderDeleteSignal(Folder $folder) {
2416 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreFolderDelete, array($folder));
2417 }
2418
2419 /**
2420 * Emits folder post-deletion signal..
2421 *
2422 * @param Folder $folder
2423 * @return void
2424 */
2425 protected function emitPostFolderDeleteSignal(Folder $folder) {
2426 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PostFolderDelete, array($folder));
2427 }
2428
2429 /**
2430 * Emits file pre-processing signal when generating a public url for a file or folder.
2431 *
2432 * @param ResourceInterface $resourceObject
2433 * @param bool $relativeToCurrentScript
2434 * @param array $urlData
2435 */
2436 protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData) {
2437 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\ResourceStorage::class, self::SIGNAL_PreGeneratePublicUrl, array($this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData));
2438 }
2439
2440 /**
2441 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2442 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2443 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2444 *
2445 * @param Folder $folder
2446 * @param string $theFile The input fileName to check
2447 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2448 *
2449 * @throws \RuntimeException
2450 * @return string A unique fileName inside $folder, based on $theFile.
2451 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2452 */
2453 protected function getUniqueName(Folder $folder, $theFile, $dontCheckForUnique = FALSE) {
2454 static $maxNumber = 99, $uniqueNamePrefix = '';
2455 // Fetches info about path, name, extension of $theFile
2456 $origFileInfo = PathUtility::pathinfo($theFile);
2457 // Adds prefix
2458 if ($uniqueNamePrefix) {
2459 $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2460 $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2461 }
2462 // Check if the file exists and if not - return the fileName...
2463 // The destinations file
2464 $theDestFile = $origFileInfo['basename'];
2465 // If the file does NOT exist we return this fileName
2466 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2467 return $theDestFile;
2468 }
2469 // Well the fileName in its pure form existed. Now we try to append
2470 // numbers / unique-strings and see if we can find an available fileName
2471 // This removes _xx if appended to the file
2472 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2473 $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2474 for ($a = 1; $a <= $maxNumber + 1; $a++) {
2475 // First we try to append numbers
2476 if ($a <= $maxNumber) {
2477 $insert = '_' . sprintf('%02d', $a);
2478 } else {
2479 $insert = '_' . substr(md5(uniqid('', TRUE)), 0, 6);
2480 }
2481 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2482 // The destinations file
2483 $theDestFile = $theTestFile;
2484 // If the file does NOT exist we return this fileName
2485 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2486 return $theDestFile;
2487 }
2488 }
2489 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2490 }
2491
2492 /**
2493 * Get the SignalSlot dispatcher.
2494 *
2495 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2496 */
2497 protected function getSignalSlotDispatcher() {
2498 if (!isset($this->signalSlotDispatcher)) {
2499 $this->signalSlotDispatcher = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
2500 }
2501 return $this->signalSlotDispatcher;
2502 }
2503
2504 /**
2505 * Gets the ObjectManager.
2506 *
2507 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2508 */
2509 protected function getObjectManager() {
2510 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
2511 }
2512
2513 /**
2514 * @return ResourceFactory
2515 */
2516 protected function getFileFactory() {
2517 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
2518 }
2519
2520 /**
2521 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
2522 */
2523 protected function getFileIndexRepository() {
2524 return FileIndexRepository::getInstance();
2525 }
2526
2527 /**
2528 * @return Service\FileProcessingService
2529 */
2530 protected function getFileProcessingService() {
2531 if (!$this->fileProcessingService) {
2532 $this->fileProcessingService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Service\FileProcessingService::class, $this, $this->driver);
2533 }
2534 return $this->fileProcessingService;
2535 }
2536
2537 /**
2538 * Gets the role of a folder.
2539 *
2540 * @param FolderInterface $folder Folder object to get the role from
2541 * @return string The role the folder has
2542 */
2543 public function getRole(FolderInterface $folder) {
2544 $folderRole = FolderInterface::ROLE_DEFAULT;
2545 $identifier = $folder->getIdentifier();
2546 if (method_exists($this->driver, 'getRole')) {
2547 $folderRole = $this->driver->getRole($folder->getIdentifier());
2548 }
2549 if (isset($this->fileMounts[$identifier])) {
2550 $folderRole = FolderInterface::ROLE_MOUNT;
2551
2552 if (!empty($this->fileMounts[$identifier]['read_only'])) {
2553 $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
2554 }
2555 if ($this->fileMounts[$identifier]['user_mount']) {
2556 $folderRole = FolderInterface::ROLE_USER_MOUNT;
2557 }
2558 }
2559 if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2560 $folderRole = FolderInterface::ROLE_PROCESSING;
2561 }
2562
2563 return $folderRole;
2564 }
2565
2566 /**
2567 * Getter function to return the folder where the files can
2568 * be processed. Does not check for access rights here.
2569 *
2570 * @return Folder
2571 */
2572 public function getProcessingFolder() {
2573 if (!isset($this->processingFolder)) {
2574 $processingFolder = self::DEFAULT_ProcessingFolder;
2575 if (!empty($this->storageRecord['processingfolder'])) {
2576 $processingFolder = $this->storageRecord['processingfolder'];
2577 }
2578 try {
2579 if (strpos($processingFolder, ':') !== FALSE) {
2580 $this->processingFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($processingFolder);
2581 } else {
2582 if ($this->driver->folderExists($processingFolder) === FALSE) {
2583 $this->processingFolder = $this->createFolder($processingFolder);
2584 } else {
2585 $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
2586 $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2587 }
2588 }
2589 } catch(Exception\InsufficientFolderWritePermissionsException $e) {
2590 $this->processingFolder = GeneralUtility::makeInstance(
2591 InaccessibleFolder::class, $this, $processingFolder, $processingFolder
2592 );
2593 }
2594 }
2595 return $this->processingFolder;
2596 }
2597
2598 /**
2599 * Gets the driver Type configured for this storage.
2600 *
2601 * @return string
2602 */
2603 public function getDriverType() {
2604 return $this->storageRecord['driver'];
2605 }
2606
2607 /**
2608 * Gets the Indexer.
2609 *
2610 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
2611 */
2612 protected function getIndexer() {
2613 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $this);
2614 }
2615
2616 /**
2617 * @param bool $isDefault
2618 * @return void
2619 */
2620 public function setDefault($isDefault) {
2621 $this->isDefault = (bool)$isDefault;
2622 }
2623
2624 /**
2625 * @return bool
2626 */
2627 public function isDefault() {
2628 return $this->isDefault;
2629 }
2630
2631 }