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