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