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