2 namespace TYPO3\CMS\Core\
Resource;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
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
;
23 * A "mount point" inside the TYPO3 file handling.
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
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
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.
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)
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?]
54 * @author Andreas Wolf <andreas.wolf@typo3.org>
55 * @author Ingmar Schlecht <ingmar@typo3.org>
57 class ResourceStorage
implements ResourceStorageInterface
{
60 * The storage driver instance belonging to this storage.
62 * @var Driver\DriverInterface
67 * The database record for this storage
71 protected $storageRecord;
74 * The configuration belonging to this storage (decoded from the configuration field).
78 protected $configuration;
81 * @var Service\FileProcessingService
83 protected $fileProcessingService;
86 * Whether to check if file or folder is in user mounts
87 * and the action is allowed for a user
88 * Default is FALSE so that resources are accessible for
89 * front end rendering or admins.
93 protected $evaluatePermissions = FALSE;
96 * User filemounts, added as an array, and used as filters
100 protected $fileMounts = array();
103 * The file permissions of the user (and their group) merged together and
104 * available as an array
108 protected $userPermissions = array();
111 * The capabilities of this storage as defined in the storage record.
112 * Also see the CAPABILITY_* constants below
116 protected $capabilities;
119 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
121 protected $signalSlotDispatcher;
126 protected $processingFolder;
129 * All processing folders of this storage used in any storage
133 protected $processingFolders;
136 * whether this storage is online or offline in this request
140 protected $isOnline = NULL;
145 protected $isDefault = FALSE;
148 * The filters used for the files and folder names.
152 protected $fileAndFolderNameFilters = array();
155 * Constructor for a storage object.
157 * @param Driver\DriverInterface $driver
158 * @param array $storageRecord The storage record row from the database
160 public function __construct(Driver\DriverInterface
$driver, array $storageRecord) {
161 $this->storageRecord
= $storageRecord;
162 $this->configuration
= ResourceFactory
::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
163 $this->capabilities
=
164 ($this->storageRecord
['is_browsable'] ? self
::CAPABILITY_BROWSABLE
: 0) |
165 ($this->storageRecord
['is_public'] ? self
::CAPABILITY_PUBLIC
: 0) |
166 ($this->storageRecord
['is_writable'] ? self
::CAPABILITY_WRITABLE
: 0);
168 $this->driver
= $driver;
169 $this->driver
->setStorageUid($storageRecord['uid']);
170 $this->driver
->mergeConfigurationCapabilities($this->capabilities
);
172 $this->driver
->processConfiguration();
173 } catch (Exception\InvalidConfigurationException
$e) {
174 // configuration error
175 // mark this storage as permanently unusable
176 $this->markAsPermanentlyOffline();
178 $this->driver
->initialize();
179 $this->capabilities
= $this->driver
->getCapabilities();
181 $this->isDefault
= (isset($storageRecord['is_default']) && $storageRecord['is_default'] == 1);
182 $this->resetFileAndFolderNameFiltersToDefault();
186 * Gets the configuration.
190 public function getConfiguration() {
191 return $this->configuration
;
195 * Sets the configuration.
197 * @param array $configuration
199 public function setConfiguration(array $configuration) {
200 $this->configuration
= $configuration;
204 * Gets the storage record.
208 public function getStorageRecord() {
209 return $this->storageRecord
;
213 * Sets the storage that belongs to this storage.
215 * @param Driver\DriverInterface $driver
216 * @return ResourceStorage
218 public function setDriver(Driver\DriverInterface
$driver) {
219 $this->driver
= $driver;
224 * Returns the driver object belonging to this storage.
226 * @return Driver\DriverInterface
228 protected function getDriver() {
229 return $this->driver
;
233 * Returns the name of this storage.
237 public function getName() {
238 return $this->storageRecord
['name'];
242 * Returns the UID of this storage.
246 public function getUid() {
247 return (int)$this->storageRecord
['uid'];
251 * Tells whether there are children in this storage.
255 public function hasChildren() {
259 /*********************************
261 ********************************/
263 * Returns the capabilities of this storage.
266 * @see CAPABILITY_* constants
268 public function getCapabilities() {
269 return (int)$this->capabilities
;
273 * Returns TRUE if this storage has the given capability.
275 * @param int $capability A capability, as defined in a CAPABILITY_* constant
278 protected function hasCapability($capability) {
279 return ($this->capabilities
& $capability) == $capability;
283 * Returns TRUE if this storage is publicly available. This is just a
284 * configuration option and does not mean that it really *is* public. OTOH
285 * a storage that is marked as not publicly available will trigger the file
286 * publishing mechanisms of TYPO3.
290 public function isPublic() {
291 return $this->hasCapability(self
::CAPABILITY_PUBLIC
);
295 * Returns TRUE if this storage is writable. This is determined by the
296 * driver and the storage configuration; user permissions are not taken into account.
300 public function isWritable() {
301 return $this->hasCapability(self
::CAPABILITY_WRITABLE
);
305 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
309 public function isBrowsable() {
310 return $this->isOnline() && $this->hasCapability(self
::CAPABILITY_BROWSABLE
);
314 * Returns TRUE if the identifiers used by this storage are case-sensitive.
318 public function usesCaseSensitiveIdentifiers() {
319 return $this->driver
->isCaseSensitiveFileSystem();
323 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
327 public function isOnline() {
328 if ($this->isOnline
=== NULL) {
329 if ($this->getUid() === 0) {
330 $this->isOnline
= TRUE;
332 // the storage is not marked as online for a longer time
333 if ($this->storageRecord
['is_online'] == 0) {
334 $this->isOnline
= FALSE;
336 if ($this->isOnline
!== FALSE) {
337 // all files are ALWAYS available in the frontend
338 if (TYPO3_MODE
=== 'FE') {
339 $this->isOnline
= TRUE;
341 // check if the storage is disabled temporary for now
342 $registryObject = GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Registry
::class);
343 $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
344 if ($offlineUntil && $offlineUntil > time()) {
345 $this->isOnline
= FALSE;
347 $this->isOnline
= TRUE;
352 return $this->isOnline
;
356 * Blows the "fuse" and marks the storage as offline.
358 * Can only be modified by an admin.
360 * Typically, this is only done if the configuration is wrong.
364 public function markAsPermanentlyOffline() {
365 if ($this->getUid() > 0) {
366 // @todo: move this to the storage repository
367 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_file_storage', 'uid=' . (int)$this->getUid(), array('is_online' => 0));
369 $this->storageRecord
['is_online'] = 0;
370 $this->isOnline
= FALSE;
374 * Marks this storage as offline for the next 5 minutes.
376 * Non-permanent: This typically happens for remote storages
377 * that are "flaky" and not available all the time.
381 public function markAsTemporaryOffline() {
382 $registryObject = GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Registry
::class);
383 $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() +
60 * 5);
384 $this->storageRecord
['is_online'] = 0;
385 $this->isOnline
= FALSE;
388 /*********************************
389 * User Permissions / File Mounts
390 ********************************/
392 * Adds a filemount as a "filter" for users to only work on a subset of a
395 * @param string $folderIdentifier
396 * @param array $additionalData
398 * @throws Exception\FolderDoesNotExistException
401 public function addFileMount($folderIdentifier, $additionalData = array()) {
402 // check for the folder before we add it as a filemount
403 if ($this->driver
->folderExists($folderIdentifier) === FALSE) {
404 // if there is an error, this is important and should be handled
405 // as otherwise the user would see the whole storage without any restrictions for the filemounts
406 throw new Exception\
FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
408 $data = $this->driver
->getFolderInfoByIdentifier($folderIdentifier);
409 $folderObject = ResourceFactory
::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
410 // Use the canonical identifier instead of the user provided one!
411 $folderIdentifier = $folderObject->getIdentifier();
413 !empty($this->fileMounts
[$folderIdentifier])
414 && empty($this->fileMounts
[$folderIdentifier]['read_only'])
415 && !empty($additionalData['read_only'])
417 // Do not overwrite a regular mount with a read only mount
420 if (empty($additionalData)) {
421 $additionalData = array(
422 'path' => $folderIdentifier,
423 'title' => $folderIdentifier,
424 'folder' => $folderObject
427 $additionalData['folder'] = $folderObject;
428 if (!isset($additionalData['title'])) {
429 $additionalData['title'] = $folderIdentifier;
432 $this->fileMounts
[$folderIdentifier] = $additionalData;
436 * Returns all file mounts that are registered with this storage.
440 public function getFileMounts() {
441 return $this->fileMounts
;
445 * Checks if the given subject is within one of the registered user
446 * file mounts. If not, working with the file is not permitted for the user.
448 * @param ResourceInterface $subject file or folder
449 * @param bool $checkWriteAccess If true, it is not only checked if the subject is within the file mount but also whether it isn't a read only file mount
452 public function isWithinFileMountBoundaries($subject, $checkWriteAccess = FALSE) {
453 if (!$this->evaluatePermissions
) {
456 $isWithinFileMount = FALSE;
458 $subject = $this->getRootLevelFolder();
460 $identifier = $subject->getIdentifier();
462 // Allow access to processing folder
463 if ($this->isWithinProcessingFolder($identifier)) {
464 $isWithinFileMount = TRUE;
466 // Check if the identifier of the subject is within at
467 // least one of the file mounts
468 $writableFileMountAvailable = FALSE;
469 foreach ($this->fileMounts
as $fileMount) {
470 if ($this->driver
->isWithin($fileMount['folder']->getIdentifier(), $identifier)) {
471 $isWithinFileMount = TRUE;
472 if (!$checkWriteAccess) {
474 } elseif (empty($fileMount['read_only'])) {
475 $writableFileMountAvailable = TRUE;
480 $isWithinFileMount = $checkWriteAccess ?
$writableFileMountAvailable : $isWithinFileMount;
482 return $isWithinFileMount;
486 * Sets whether the permissions to access or write
487 * into this storage should be checked or not.
489 * @param bool $evaluatePermissions
491 public function setEvaluatePermissions($evaluatePermissions) {
492 $this->evaluatePermissions
= (bool)$evaluatePermissions;
496 * Gets whether the permissions to access or write
497 * into this storage should be checked or not.
499 * @return bool $evaluatePermissions
501 public function getEvaluatePermissions() {
502 return $this->evaluatePermissions
;
506 * Sets the user permissions of the storage.
508 * @param array $userPermissions
511 public function setUserPermissions(array $userPermissions) {
512 $this->userPermissions
= $userPermissions;
516 * Checks if the ACL settings allow for a certain action
517 * (is a user allowed to read a file or copy a folder).
519 * @param string $action
520 * @param string $type either File or Folder
523 public function checkUserActionPermission($action, $type) {
524 if (!$this->evaluatePermissions
) {
529 if (!empty($this->userPermissions
[strtolower($action) . ucfirst(strtolower($type))])) {
537 * Checks if a file operation (= action) is allowed on a
538 * File/Folder/Storage (= subject).
540 * This method, by design, does not throw exceptions or do logging.
541 * Besides the usage from other methods in this class, it is also used by
542 * the File List UI to check whether an action is allowed and whether action
543 * related UI elements should thus be shown (move icon, edit icon, etc.)
545 * @param string $action action, can be read, write, delete
546 * @param FileInterface $file
549 public function checkFileActionPermission($action, FileInterface
$file) {
550 $isProcessedFile = $file instanceof ProcessedFile
;
551 // Check 1: Does the user have permission to perform the action? e.g. "readFile"
552 if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === FALSE) {
555 // Check 2: No action allowed on files for denied file extensions
556 if (!$this->checkFileExtensionPermission($file->getName())) {
559 $isReadCheck = FALSE;
560 if (in_array($action, array('read', 'copy', 'move'), TRUE)) {
563 $isWriteCheck = FALSE;
564 if (in_array($action, array('add', 'write', 'move', 'rename', 'unzip', 'delete'), TRUE)) {
565 $isWriteCheck = TRUE;
567 // Check 3: Does the user have the right to perform the action?
568 // (= is he within the file mount borders)
569 if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file, $isWriteCheck)) {
574 if (!$isProcessedFile && $file instanceof File
) {
575 $isMissing = $file->isMissing();
578 // Check 4: Check the capabilities of the storage (and the driver)
579 if ($isWriteCheck && ($isMissing ||
!$this->isWritable())) {
582 // Check 5: "File permissions" of the driver (only when file isn't marked as missing)
584 $filePermissions = $this->driver
->getPermissions($file->getIdentifier());
585 if ($isReadCheck && !$filePermissions['r']) {
588 if ($isWriteCheck && !$filePermissions['w']) {
596 * Checks if a folder operation (= action) is allowed on a Folder.
598 * This method, by design, does not throw exceptions or do logging.
599 * See the checkFileActionPermission() method above for the reasons.
601 * @param string $action
602 * @param Folder $folder
605 public function checkFolderActionPermission($action, Folder
$folder = NULL) {
606 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
607 if ($this->checkUserActionPermission($action, 'Folder') === FALSE) {
611 // If we do not have a folder here, we cannot do further checks
612 if ($folder === NULL) {
616 $isReadCheck = FALSE;
617 if (in_array($action, array('read', 'copy'), TRUE)) {
620 $isWriteCheck = FALSE;
621 if (in_array($action, array('add', 'move', 'write', 'delete', 'rename'), TRUE)) {
622 $isWriteCheck = TRUE;
624 // Check 2: Does the user has the right to perform the action?
625 // (= is he within the file mount borders)
626 if (!$this->isWithinFileMountBoundaries($folder, $isWriteCheck)) {
629 // Check 3: Check the capabilities of the storage (and the driver)
630 if ($isReadCheck && !$this->isBrowsable()) {
633 if ($isWriteCheck && !$this->isWritable()) {
637 // Check 4: "Folder permissions" of the driver
638 $folderPermissions = $this->driver
->getPermissions($folder->getIdentifier());
639 if ($isReadCheck && !$folderPermissions['r']) {
642 if ($isWriteCheck && !$folderPermissions['w']) {
649 * If the fileName is given, checks it against the
650 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed.
652 * @param string $fileName full filename
653 * @return bool TRUE if extension/filename is allowed
655 protected function checkFileExtensionPermission($fileName) {
656 if (!$this->evaluatePermissions
) {
659 $fileName = $this->driver
->sanitizeFileName($fileName);
660 $isAllowed = GeneralUtility
::verifyFilenameAgainstDenyPattern($fileName);
662 $fileExtension = strtolower(PathUtility
::pathinfo($fileName, PATHINFO_EXTENSION
));
663 // Set up the permissions for the file extension
664 $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
665 $fileExtensionPermissions['allow'] = GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['allow']));
666 $fileExtensionPermissions['deny'] = GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['deny']));
667 if ($fileExtension !== '') {
668 // If the extension is found amongst the allowed types, we return TRUE immediately
669 if ($fileExtensionPermissions['allow'] === '*' || GeneralUtility
::inList($fileExtensionPermissions['allow'], $fileExtension)) {
672 // If the extension is found amongst the denied types, we return FALSE immediately
673 if ($fileExtensionPermissions['deny'] === '*' || GeneralUtility
::inList($fileExtensionPermissions['deny'], $fileExtension)) {
676 // If no match we return TRUE
679 if ($fileExtensionPermissions['allow'] === '*') {
682 if ($fileExtensionPermissions['deny'] === '*') {
692 * Assures read permission for given folder.
694 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder read permissions are checked.
696 * @throws Exception\InsufficientFolderAccessPermissionsException
698 protected function assureFolderReadPermission(Folder
$folder = NULL) {
699 if (!$this->checkFolderActionPermission('read', $folder)) {
700 if ($folder === NULL) {
701 throw new Exception\
InsufficientFolderAccessPermissionsException(
702 'You are not allowed to read folders',
706 throw new Exception\
InsufficientFolderAccessPermissionsException(
707 'You are not allowed to access the given folder: "' . $folder->getName() . '"',
715 * Assures delete permission for given folder.
717 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder delete permissions are checked.
718 * @param bool $checkDeleteRecursively
720 * @throws Exception\InsufficientFolderAccessPermissionsException
721 * @throws Exception\InsufficientFolderWritePermissionsException
722 * @throws Exception\InsufficientUserPermissionsException
724 protected function assureFolderDeletePermission(Folder
$folder, $checkDeleteRecursively) {
725 // Check user permissions for recursive deletion if it is requested
726 if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
727 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
729 // Check user action permission
730 if (!$this->checkFolderActionPermission('delete', $folder)) {
731 throw new Exception\
InsufficientFolderAccessPermissionsException(
732 'You are not allowed to delete the given folder: "' . $folder->getName() . '"',
736 // Check if the user has write permissions to folders
737 // Would be good if we could check for actual write permissions in the containig folder
738 // but we cannot since we have no access to the containing folder of this file.
739 if (!$this->checkUserActionPermission('write', 'Folder')) {
740 throw new Exception\
InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
745 * Assures read permission for given file.
747 * @param FileInterface $file
749 * @throws Exception\InsufficientFileAccessPermissionsException
750 * @throws Exception\IllegalFileExtensionException
752 protected function assureFileReadPermission(FileInterface
$file) {
753 if (!$this->checkFileActionPermission('read', $file)) {
754 throw new Exception\
InsufficientFileAccessPermissionsException(
755 'You are not allowed to access that file: "' . $file->getName() . '"',
759 if (!$this->checkFileExtensionPermission($file->getName())) {
760 throw new Exception\
IllegalFileExtensionException(
761 'You are not allowed to use that file extension. File: "' . $file->getName() . '"',
768 * Assures write permission for given file.
770 * @param FileInterface $file
772 * @throws Exception\IllegalFileExtensionException
773 * @throws Exception\InsufficientFileWritePermissionsException
774 * @throws Exception\InsufficientUserPermissionsException
776 protected function assureFileWritePermissions(FileInterface
$file) {
777 // Check if user is allowed to write the file and $file is writable
778 if (!$this->checkFileActionPermission('write', $file)) {
779 throw new Exception\
InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
781 if (!$this->checkFileExtensionPermission($file->getName())) {
782 throw new Exception\
IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
787 * Assures delete permission for given file.
789 * @param FileInterface $file
791 * @throws Exception\IllegalFileExtensionException
792 * @throws Exception\InsufficientFileWritePermissionsException
793 * @throws Exception\InsufficientFolderWritePermissionsException
795 protected function assureFileDeletePermissions(FileInterface
$file) {
796 // Check for disallowed file extensions
797 if (!$this->checkFileExtensionPermission($file->getName())) {
798 throw new Exception\
IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
800 // Check further permissions if file is not a processed file
801 if (!$file instanceof ProcessedFile
) {
802 // Check if user is allowed to delete the file and $file is writable
803 if (!$this->checkFileActionPermission('delete', $file)) {
804 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '"', 1319550425);
806 // Check if the user has write permissions to folders
807 // Would be good if we could check for actual write permissions in the containig folder
808 // but we cannot since we have no access to the containing folder of this file.
809 if (!$this->checkUserActionPermission('write', 'Folder')) {
810 throw new Exception\
InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
816 * Checks if a file/user has the permission to be written to a Folder/Storage.
817 * If not, throws an exception.
819 * @param Folder $targetFolder The target folder where the file should be written
820 * @param string $targetFileName The file name which should be written into the storage
823 * @throws Exception\InsufficientFolderWritePermissionsException
824 * @throws Exception\IllegalFileExtensionException
825 * @throws Exception\InsufficientUserPermissionsException
827 protected function assureFileAddPermissions($targetFolder, $targetFileName) {
828 // Check for a valid file extension
829 if (!$this->checkFileExtensionPermission($targetFileName)) {
830 throw new Exception\
IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
832 // Makes sure the user is allowed to upload
833 if (!$this->checkUserActionPermission('add', 'File')) {
834 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
836 // Check if targetFolder is writable
837 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
838 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
843 * Checks if a file has the permission to be uploaded to a Folder/Storage.
844 * If not, throws an exception.
846 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
847 * @param Folder $targetFolder The target folder where the file should be uploaded
848 * @param string $targetFileName the destination file name $_FILES['file1']['name']
849 * @param int $uploadedFileSize
852 * @throws Exception\InsufficientFolderWritePermissionsException
853 * @throws Exception\UploadException
854 * @throws Exception\IllegalFileExtensionException
855 * @throws Exception\UploadSizeException
856 * @throws Exception\InsufficientUserPermissionsException
858 protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
859 // Makes sure this is an uploaded file
860 if (!is_uploaded_file($localFilePath)) {
861 throw new Exception\
UploadException('The upload has failed, no uploaded file found!', 1322110455);
863 // Max upload size (kb) for files.
864 $maxUploadFileSize = GeneralUtility
::getMaxUploadFileSize() * 1024;
865 if ($uploadedFileSize >= $maxUploadFileSize) {
866 unlink($localFilePath);
867 throw new Exception\
UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
869 $this->assureFileAddPermissions($targetFolder, $targetFileName);
873 * Checks for permissions to move a file.
875 * @throws \RuntimeException
876 * @throws Exception\InsufficientFolderAccessPermissionsException
877 * @throws Exception\InsufficientUserPermissionsException
878 * @throws Exception\IllegalFileExtensionException
879 * @param FileInterface $file
880 * @param Folder $targetFolder
881 * @param string $targetFileName
884 protected function assureFileMovePermissions(FileInterface
$file, Folder
$targetFolder, $targetFileName) {
885 // Check if targetFolder is within this storage
886 if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
887 throw new \
RuntimeException('The target folder is not in the same storage. Target folder given: "' . $targetFolder . '"', 1422553107);
889 // Check for a valid file extension
890 if (!$this->checkFileExtensionPermission($targetFileName)) {
891 throw new Exception\
IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
893 // Check if user is allowed to move and $file is readable and writable
894 if (!$file->getStorage()->checkFileActionPermission('move', $file)) {
895 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
897 // Check if target folder is writable
898 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
899 throw new Exception\
InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219350);
904 * Checks for permissions to rename a file.
906 * @param FileInterface $file
907 * @param string $targetFileName
908 * @throws Exception\InsufficientFileWritePermissionsException
909 * @throws Exception\IllegalFileExtensionException
910 * @throws Exception\InsufficientFileReadPermissionsException
911 * @throws Exception\InsufficientUserPermissionsException
914 protected function assureFileRenamePermissions(FileInterface
$file, $targetFileName) {
915 // Check if file extension is allowed
916 if (!$this->checkFileExtensionPermission($targetFileName) ||
!$this->checkFileExtensionPermission($file->getName())) {
917 throw new Exception\
IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663);
919 // Check if user is allowed to rename
920 if (!$this->checkFileActionPermission('rename', $file)) {
921 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to rename files. File given: "' . $file->getName() . '"', 1319219351);
923 // Check if the user is allowed to write to folders
924 // Although it would be good to check, we cannot check here if the folder actually is writable
925 // because we do not know in which folder the file resides.
926 // So we rely on the driver to throw an exception in case the renaming failed.
927 if (!$this->checkFolderActionPermission('write')) {
928 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219352);
933 * Check if a file has the permission to be copied on a File/Folder/Storage,
934 * if not throw an exception
936 * @param FileInterface $file
937 * @param Folder $targetFolder
938 * @param string $targetFileName
941 * @throws Exception\InsufficientFolderWritePermissionsException
942 * @throws Exception\IllegalFileExtensionException
943 * @throws Exception\InsufficientFileReadPermissionsException
944 * @throws Exception\InsufficientUserPermissionsException
947 protected function assureFileCopyPermissions(FileInterface
$file, Folder
$targetFolder, $targetFileName) {
948 // Check if targetFolder is within this storage, this should never happen
949 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
950 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
952 // Check if user is allowed to copy
953 if (!$file->getStorage()->checkFileActionPermission('copy', $file)) {
954 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '"', 1319550426);
956 // Check if targetFolder is writable
957 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
958 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
960 // Check for a valid file extension
961 if (!$this->checkFileExtensionPermission($targetFileName) ||
!$this->checkFileExtensionPermission($file->getName())) {
962 throw new Exception\
IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
967 * Check if a file has the permission to be copied on a File/Folder/Storage,
968 * if not throw an exception
970 * @param FolderInterface $folderToCopy
971 * @param FolderInterface $targetParentFolder
975 * @throws Exception\InsufficientFolderWritePermissionsException
976 * @throws Exception\IllegalFileExtensionException
977 * @throws Exception\InsufficientFileReadPermissionsException
978 * @throws Exception\InsufficientUserPermissionsException
979 * @throws \RuntimeException
981 protected function assureFolderCopyPermissions(FolderInterface
$folderToCopy, FolderInterface
$targetParentFolder) {
982 // Check if targetFolder is within this storage, this should never happen
983 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
984 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
986 if (!$folderToCopy instanceof Folder
) {
987 throw new \
RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type folder.', 1384209020);
989 // Check if user is allowed to copy and the folder is readable
990 if (!$folderToCopy->getStorage()->checkFolderActionPermission('copy', $folderToCopy)) {
991 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
993 if (!$targetParentFolder instanceof Folder
) {
994 throw new \
RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type folder.', 1384209021);
996 // Check if targetFolder is writable
997 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
998 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
1003 * Check if a file has the permission to be copied on a File/Folder/Storage,
1004 * if not throw an exception
1006 * @param FolderInterface $folderToMove
1007 * @param FolderInterface $targetParentFolder
1009 * @throws \InvalidArgumentException
1010 * @throws Exception\InsufficientFolderWritePermissionsException
1011 * @throws Exception\IllegalFileExtensionException
1012 * @throws Exception\InsufficientFileReadPermissionsException
1013 * @throws Exception\InsufficientUserPermissionsException
1014 * @throws \RuntimeException
1017 protected function assureFolderMovePermissions(FolderInterface
$folderToMove, FolderInterface
$targetParentFolder) {
1018 // Check if targetFolder is within this storage, this should never happen
1019 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1020 throw new \
InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1022 if (!$folderToMove instanceof Folder
) {
1023 throw new \
RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
1025 // Check if user is allowed to move and the folder is writable
1026 // In fact we would need to check if the parent folder of the folder to move is writable also
1027 // But as of now we cannot extract the parent folder from this folder
1028 if (!$folderToMove->getStorage()->checkFolderActionPermission('move', $folderToMove)) {
1029 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
1031 if (!$targetParentFolder instanceof Folder
) {
1032 throw new \
RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
1034 // Check if targetFolder is writable
1035 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1036 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1040 /********************
1042 ********************/
1044 * Moves a file from the local filesystem to this storage.
1046 * @param string $localFilePath The file on the server's hard disk to add
1047 * @param Folder $targetFolder The target folder where the file should be added
1048 * @param string $targetFileName The name of the file to be add, If not set, the local file name is used
1049 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
1051 * @throws \InvalidArgumentException
1052 * @throws Exception\ExistingTargetFileNameException
1053 * @return FileInterface
1055 public function addFile($localFilePath, Folder
$targetFolder, $targetFileName = '', $conflictMode = 'changeName') {
1056 $localFilePath = PathUtility
::getCanonicalPath($localFilePath);
1057 if (!file_exists($localFilePath)) {
1058 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1060 $targetFolder = $targetFolder ?
: $this->getDefaultFolder();
1061 $targetFileName = $this->driver
->sanitizeFileName($targetFileName ?
: PathUtility
::basename($localFilePath));
1063 $this->assureFileAddPermissions($targetFolder, $targetFileName);
1065 // We do not care whether the file exists yet because $targetFileName may be changed by an
1066 // external slot and only then we should check how to proceed according to $conflictMode
1067 $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1069 if ($conflictMode === 'cancel' && $this->driver
->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1070 throw new Exception\
ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1071 } elseif ($conflictMode === 'changeName') {
1072 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1075 $fileIdentifier = $this->driver
->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName);
1076 $file = ResourceFactory
::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1078 $this->emitPostFileAddSignal($file, $targetFolder);
1084 * Updates a processed file with a new file from the local filesystem.
1086 * @param string $localFilePath
1087 * @param ProcessedFile $processedFile
1088 * @param Folder $processingFolder
1089 * @return FileInterface
1090 * @throws \InvalidArgumentException
1091 * @internal use only
1093 public function updateProcessedFile($localFilePath, ProcessedFile
$processedFile, Folder
$processingFolder = NULL) {
1094 if (!file_exists($localFilePath)) {
1095 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1097 if ($processingFolder === NULL) {
1098 $processingFolder = $this->getProcessingFolder();
1100 $fileIdentifier = $this->driver
->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1102 // @todo check if we have to update the processed file other then the identifier
1103 $processedFile->setIdentifier($fileIdentifier);
1104 return $processedFile;
1108 * Creates a (cryptographic) hash for a file.
1110 * @param FileInterface $fileObject
1111 * @param string $hash
1114 public function hashFile(FileInterface
$fileObject, $hash) {
1115 return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1119 * Creates a (cryptographic) hash for a fileIdentifier.
1121 * @param string $fileIdentifier
1122 * @param string $hash
1126 public function hashFileByIdentifier($fileIdentifier, $hash) {
1127 return $this->driver
->hash($fileIdentifier, $hash);
1131 * Hashes a file identifier, taking the case sensitivity of the file system
1132 * into account. This helps mitigating problems with case-insensitive
1135 * @param string|FileInterface $file
1138 public function hashFileIdentifier($file) {
1139 if (is_object($file) && $file instanceof FileInterface
) {
1140 /** @var FileInterface $file */
1141 $file = $file->getIdentifier();
1143 return $this->driver
->hashIdentifier($file);
1147 * Returns a publicly accessible URL for a file.
1149 * WARNING: Access to the file may be restricted by further means, e.g.
1150 * some web-based authentication. You have to take care of this yourself.
1152 * @param ResourceInterface $resourceObject The file or folder object
1153 * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
1156 public function getPublicUrl(ResourceInterface
$resourceObject, $relativeToCurrentScript = FALSE) {
1158 if ($this->isOnline()) {
1159 // Pre-process the public URL by an accordant slot
1160 $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
1161 // If slot did not handle the signal, use the default way to determine public URL
1162 if ($publicUrl === NULL) {
1164 if ($this->hasCapability(self
::CAPABILITY_PUBLIC
)) {
1165 $publicUrl = $this->driver
->getPublicUrl($resourceObject->getIdentifier());
1168 if ($publicUrl === NULL && $resourceObject instanceof FileInterface
) {
1169 $queryParameterArray = array('eID' => 'dumpFile', 't' => '');
1170 if ($resourceObject instanceof File
) {
1171 $queryParameterArray['f'] = $resourceObject->getUid();
1172 $queryParameterArray['t'] = 'f';
1173 } elseif ($resourceObject instanceof ProcessedFile
) {
1174 $queryParameterArray['p'] = $resourceObject->getUid();
1175 $queryParameterArray['t'] = 'p';
1178 $queryParameterArray['token'] = GeneralUtility
::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1179 $publicUrl = 'index.php?' . str_replace('+', '%20', http_build_query($queryParameterArray));
1182 // If requested, make the path relative to the current script in order to make it possible
1183 // to use the relative file
1184 if ($publicUrl !== NULL && $relativeToCurrentScript && !GeneralUtility
::isValidUrl($publicUrl)) {
1185 $absolutePathToContainingFolder = PathUtility
::dirname(PATH_site
. $publicUrl);
1186 $pathPart = PathUtility
::getRelativePathTo($absolutePathToContainingFolder);
1187 $filePart = substr(PATH_site
. $publicUrl, strlen($absolutePathToContainingFolder) +
1);
1188 $publicUrl = $pathPart . $filePart;
1196 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1198 * @param FileInterface $fileObject The file object
1199 * @param string $context
1200 * @param array $configuration
1202 * @return ProcessedFile
1203 * @throws \InvalidArgumentException
1205 public function processFile(FileInterface
$fileObject, $context, array $configuration) {
1206 if ($fileObject->getStorage() !== $this) {
1207 throw new \
InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1209 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1211 return $processedFile;
1215 * Copies a file from the storage for local processing.
1217 * @param FileInterface $fileObject
1218 * @param bool $writable
1219 * @return string Path to local file (either original or copied to some temporary local location)
1221 public function getFileForLocalProcessing(FileInterface
$fileObject, $writable = TRUE) {
1222 $filePath = $this->driver
->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1227 * Gets a file by identifier.
1229 * @param string $identifier
1230 * @return FileInterface
1232 public function getFile($identifier) {
1233 $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1234 if (!$this->driver
->fileExists($identifier)) {
1235 $file->setMissing(TRUE);
1241 * Gets information about a file.
1243 * @param FileInterface $fileObject
1247 public function getFileInfo(FileInterface
$fileObject) {
1248 return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1252 * Gets information about a file by its identifier.
1254 * @param string $identifier
1255 * @param array $propertiesToExtract
1259 public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array()) {
1260 return $this->driver
->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1264 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
1268 public function unsetFileAndFolderNameFilters() {
1269 $this->fileAndFolderNameFilters
= array();
1273 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1277 public function resetFileAndFolderNameFiltersToDefault() {
1278 $this->fileAndFolderNameFilters
= $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1282 * Returns the file and folder name filters used by this storage.
1286 public function getFileAndFolderNameFilters() {
1287 return $this->fileAndFolderNameFilters
;
1291 * @param array $filters
1294 public function setFileAndFolderNameFilters(array $filters) {
1295 $this->fileAndFolderNameFilters
= $filters;
1300 * @param array $filter
1302 public function addFileAndFolderNameFilter($filter) {
1303 $this->fileAndFolderNameFilters
[] = $filter;
1307 * @param string $fileIdentifier
1311 public function getFolderIdentifierFromFileIdentifier($fileIdentifier) {
1312 return $this->driver
->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1316 * @param Folder $folder
1318 * @param int $maxNumberOfItems
1319 * @param bool $useFilters
1320 * @param bool $recursive
1321 * @param string $sort Property name used to sort the items.
1322 * Among them may be: '' (empty, no sorting), name,
1323 * fileext, size, tstamp and rw.
1324 * If a driver does not support the given property, it
1325 * should fall back to "name".
1326 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1328 * @throws Exception\InsufficientFolderAccessPermissionsException
1330 public function getFilesInFolder(Folder
$folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
1331 $this->assureFolderReadPermission($folder);
1333 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1335 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
1336 $fileIdentifiers = array_values($this->driver
->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1339 foreach ($fileIdentifiers as $identifier) {
1340 if (isset($rows[$identifier])) {
1341 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1343 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1345 if ($fileObject instanceof FileInterface
) {
1346 $key = $fileObject->getName();
1347 while (isset($items[$key])) {
1350 $items[$key] = $fileObject;
1358 * @param string $folderIdentifier
1359 * @param bool $useFilters
1360 * @param bool $recursive
1363 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1364 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
1365 return $this->driver
->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1369 * @param Folder $folder
1370 * @param bool $useFilters
1371 * @param bool $recursive
1372 * @return int Number of files in folder
1373 * @throws Exception\InsufficientFolderAccessPermissionsException
1375 public function countFilesInFolder(Folder
$folder, $useFilters = TRUE, $recursive = FALSE) {
1376 $this->assureFolderReadPermission($folder);
1377 $filters = $useFilters ?
$this->fileAndFolderNameFilters
: array();
1378 return $this->driver
->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1382 * @param string $folderIdentifier
1383 * @param bool $useFilters
1384 * @param bool $recursive
1387 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1388 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
1389 return $this->driver
->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1393 * Returns TRUE if the specified file exists
1395 * @param string $identifier
1398 public function hasFile($identifier) {
1399 // Allow if identifier is in processing folder
1400 if (!$this->isWithinProcessingFolder($identifier)) {
1401 $this->assureFolderReadPermission();
1403 return $this->driver
->fileExists($identifier);
1407 * Get all processing folders that live in this storage
1411 public function getProcessingFolders() {
1412 if ($this->processingFolders
=== NULL) {
1413 $this->processingFolders
= array();
1414 $this->processingFolders
[] = $this->getProcessingFolder();
1415 /** @var $storageRepository StorageRepository */
1416 $storageRepository = GeneralUtility
::makeInstance(\TYPO3\CMS\Core\
Resource\StorageRepository
::class);
1417 $allStorages = $storageRepository->findAll();
1418 foreach ($allStorages as $storage) {
1419 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1420 // See #66695 for details
1421 list($storageUid, $processingFolderIdentifier) = GeneralUtility
::trimExplode(':', $storage->getStorageRecord()['processingfolder']);
1422 if (empty($processingFolderIdentifier) ||
(int)$storageUid !== $this->getUid()) {
1425 $potentialProcessingFolder = ResourceFactory
::getInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1426 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1427 $this->processingFolders
[] = $potentialProcessingFolder;
1432 return $this->processingFolders
;
1436 * Returns TRUE if folder that is in current storage is set as
1437 * processing folder for one of the existing storages
1439 * @param Folder $folder
1442 public function isProcessingFolder(Folder
$folder) {
1443 $isProcessingFolder = FALSE;
1444 foreach ($this->getProcessingFolders() as $processingFolder) {
1445 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1446 $isProcessingFolder = TRUE;
1450 return $isProcessingFolder;
1454 * Checks if the queried file in the given folder exists
1456 * @param string $fileName
1457 * @param Folder $folder
1460 public function hasFileInFolder($fileName, Folder
$folder) {
1461 $this->assureFolderReadPermission($folder);
1462 return $this->driver
->fileExistsInFolder($fileName, $folder->getIdentifier());
1466 * Get contents of a file object
1468 * @param FileInterface $file
1470 * @throws Exception\InsufficientFileReadPermissionsException
1473 public function getFileContents($file) {
1474 $this->assureFileReadPermission($file);
1475 return $this->driver
->getFileContents($file->getIdentifier());
1479 * Outputs file Contents,
1480 * clears output buffer first and sends headers accordingly.
1482 * @param FileInterface $file
1483 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1484 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1487 public function dumpFileContents(FileInterface
$file, $asDownload = FALSE, $alternativeFilename = NULL) {
1488 $downloadName = $alternativeFilename ?
: $file->getName();
1489 $contentDisposition = $asDownload ?
'attachment' : 'inline';
1490 header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1491 header('Content-Type: ' . $file->getMimeType());
1492 header('Content-Length: ' . $file->getSize());
1494 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1495 // See for more information: http://support.microsoft.com/kb/323308
1496 header("Cache-Control: ''");
1497 header('Last-Modified: ' .
1498 gmdate('D, d M Y H:i:s', array_pop($this->driver
->getFileInfoByIdentifier($file->getIdentifier(), array('mtime')))) . ' GMT',
1504 while (ob_get_level() > 0) {
1507 $this->driver
->dumpFileContents($file->getIdentifier());
1511 * Set contents of a file object.
1513 * @param AbstractFile $file
1514 * @param string $contents
1516 * @throws \Exception|\RuntimeException
1517 * @throws Exception\InsufficientFileWritePermissionsException
1518 * @throws Exception\InsufficientUserPermissionsException
1519 * @return int The number of bytes written to the file
1521 public function setFileContents(AbstractFile
$file, $contents) {
1522 // Check if user is allowed to edit
1523 $this->assureFileWritePermissions($file);
1524 // Call driver method to update the file and update file index entry afterwards
1525 $result = $this->driver
->setFileContents($file->getIdentifier(), $contents);
1526 $this->getIndexer()->updateIndexEntry($file);
1527 $this->emitPostFileSetContentsSignal($file, $contents);
1532 * Creates a new file
1534 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1536 * @param string $fileName The name of the file to be created
1537 * @param Folder $targetFolderObject The target folder where the file should be created
1539 * @throws Exception\IllegalFileExtensionException
1540 * @throws Exception\InsufficientFolderWritePermissionsException
1541 * @return FileInterface The file object
1543 public function createFile($fileName, Folder
$targetFolderObject) {
1544 $this->assureFileAddPermissions($targetFolderObject, $fileName);
1545 $newFileIdentifier = $this->driver
->createFile($fileName, $targetFolderObject->getIdentifier());
1546 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1547 return ResourceFactory
::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1551 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1553 * @param $fileObject FileInterface
1554 * @throws Exception\InsufficientFileAccessPermissionsException
1555 * @throws Exception\FileOperationErrorException
1556 * @return bool TRUE if deletion succeeded
1558 public function deleteFile($fileObject) {
1559 $this->assureFileDeletePermissions($fileObject);
1561 $this->emitPreFileDeleteSignal($fileObject);
1563 if ($this->driver
->fileExists($fileObject->getIdentifier())) {
1564 $result = $this->driver
->deleteFile($fileObject->getIdentifier());
1566 throw new Exception\
FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1569 // Mark the file object as deleted
1570 if ($fileObject instanceof File
) {
1571 $fileObject->setDeleted();
1574 $this->emitPostFileDeleteSignal($fileObject);
1580 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1581 * copies a source file (from any location) in to the target
1582 * folder, the latter has to be part of this storage
1584 * @param FileInterface $file
1585 * @param Folder $targetFolder
1586 * @param string $targetFileName an optional destination fileName
1587 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1589 * @throws \Exception|Exception\AbstractFileOperationException
1590 * @throws Exception\ExistingTargetFileNameException
1591 * @return FileInterface
1593 public function copyFile(FileInterface
$file, Folder
$targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1594 if ($targetFileName === NULL) {
1595 $targetFileName = $file->getName();
1597 $sanitizedTargetFileName = $this->driver
->sanitizeFileName($targetFileName);
1598 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1599 $this->emitPreFileCopySignal($file, $targetFolder);
1600 // File exists and we should abort, let's abort
1601 if ($conflictMode === 'cancel' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1602 throw new Exception\
ExistingTargetFileNameException('The target file already exists.', 1320291064);
1604 // File exists and we should find another name, let's find another one
1605 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1606 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1608 $sourceStorage = $file->getStorage();
1609 // Call driver method to create a new file from an existing file object,
1610 // and return the new file object
1611 if ($sourceStorage === $this) {
1612 $newFileObjectIdentifier = $this->driver
->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1614 $tempPath = $file->getForLocalProcessing();
1615 $newFileObjectIdentifier = $this->driver
->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1617 $newFileObject = ResourceFactory
::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1618 $this->emitPostFileCopySignal($file, $targetFolder);
1619 return $newFileObject;
1623 * Moves a $file into a $targetFolder
1624 * the target folder has to be part of this storage
1626 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1628 * @param FileInterface $file
1629 * @param Folder $targetFolder
1630 * @param string $targetFileName an optional destination fileName
1631 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1633 * @throws Exception\ExistingTargetFileNameException
1634 * @throws \RuntimeException
1635 * @return FileInterface
1637 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1638 if ($targetFileName === NULL) {
1639 $targetFileName = $file->getName();
1641 $originalFolder = $file->getParentFolder();
1642 $sanitizedTargetFileName = $this->driver
->sanitizeFileName($targetFileName);
1643 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1644 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1645 // File exists and we should abort, let's abort
1646 if ($conflictMode === 'renameNewFile') {
1647 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1648 } elseif ($conflictMode === 'cancel') {
1649 throw new Exception\
ExistingTargetFileNameException('The target file already exists', 1329850997);
1652 $this->emitPreFileMoveSignal($file, $targetFolder);
1653 $sourceStorage = $file->getStorage();
1654 // Call driver method to move the file and update the index entry
1656 if ($sourceStorage === $this) {
1657 $newIdentifier = $this->driver
->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1658 if (!$file instanceof AbstractFile
) {
1659 throw new \
RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1661 $file->updateProperties(array('identifier' => $newIdentifier));
1663 $tempPath = $file->getForLocalProcessing();
1664 $newIdentifier = $this->driver
->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1665 $sourceStorage->driver
->deleteFile($file->getIdentifier());
1666 if ($file instanceof File
) {
1667 $file->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1670 $this->getIndexer()->updateIndexEntry($file);
1671 } catch (\TYPO3\CMS\Core\Exception
$e) {
1672 echo $e->getMessage();
1674 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1679 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1681 * @param FileInterface $file
1682 * @param string $targetFileName
1684 * @throws Exception\InsufficientFileWritePermissionsException
1685 * @throws Exception\InsufficientFileReadPermissionsException
1686 * @throws Exception\InsufficientUserPermissionsException
1687 * @return FileInterface
1689 public function renameFile($file, $targetFileName) {
1690 // @todo add $conflictMode setting
1692 // The name should be different from the current.
1693 if ($file->getName() === $targetFileName) {
1696 $sanitizedTargetFileName = $this->driver
->sanitizeFileName($targetFileName);
1697 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1698 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1700 // Call driver method to rename the file and update the index entry
1702 $newIdentifier = $this->driver
->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1703 if ($file instanceof File
) {
1704 $file->updateProperties(array('identifier' => $newIdentifier));
1706 $this->getIndexer()->updateIndexEntry($file);
1707 } catch (\RuntimeException
$e) {
1711 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1717 * Replaces a file with a local file (e.g. a freshly uploaded file)
1719 * @param FileInterface $file
1720 * @param string $localFilePath
1722 * @return FileInterface
1724 * @throws Exception\IllegalFileExtensionException
1725 * @throws \InvalidArgumentException
1727 public function replaceFile(FileInterface
$file, $localFilePath) {
1728 $this->assureFileWritePermissions($file);
1729 if (!$this->checkFileExtensionPermission($localFilePath)) {
1730 throw new Exception\
IllegalFileExtensionException('Source file extension not allowed.', 1378132239);
1732 if (!file_exists($localFilePath)) {
1733 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1735 $this->emitPreFileReplaceSignal($file, $localFilePath);
1736 $result = $this->driver
->replaceFile($file->getIdentifier(), $localFilePath);
1737 if ($file instanceof File
) {
1738 $this->getIndexer()->updateIndexEntry($file);
1740 $this->emitPostFileReplaceSignal($file, $localFilePath);
1745 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1747 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1748 * @param Folder $targetFolder the target folder
1749 * @param string $targetFileName the file name to be written
1750 * @param string $conflictMode possible value are 'cancel', 'replace'
1751 * @return FileInterface The file object
1753 public function addUploadedFile(array $uploadedFileData, Folder
$targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1754 $localFilePath = $uploadedFileData['tmp_name'];
1755 if ($targetFolder === NULL) {
1756 $targetFolder = $this->getDefaultFolder();
1758 if ($targetFileName === NULL) {
1759 $targetFileName = $uploadedFileData['name'];
1761 // Handling $conflictMode is delegated to addFile()
1762 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1763 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1764 return $resultObject;
1767 /********************
1769 ********************/
1771 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1772 * @todo check if this is a duplicate
1773 * @param Folder $folder
1776 protected function getAllFileObjectsInFolder(Folder
$folder) {
1778 $folderQueue = array($folder);
1779 while (!empty($folderQueue)) {
1780 $folder = array_shift($folderQueue);
1781 foreach ($folder->getSubfolders() as $subfolder) {
1782 $folderQueue[] = $subfolder;
1784 foreach ($folder->getFiles() as $file) {
1785 /** @var FileInterface $file */
1786 $files[$file->getIdentifier()] = $file;
1794 * Moves a folder. If you want to move a folder from this storage to another
1795 * one, call this method on the target storage, otherwise you will get an exception.
1797 * @param Folder $folderToMove The folder to move.
1798 * @param Folder $targetParentFolder The target parent folder
1799 * @param string $newFolderName
1800 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1802 * @throws \Exception|\TYPO3\CMS\Core\Exception
1803 * @throws \InvalidArgumentException
1804 * @throws InvalidTargetFolderException
1807 public function moveFolder(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1809 $originalFolder = $folderToMove->getParentFolder();
1810 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1811 $sourceStorage = $folderToMove->getStorage();
1812 $returnObject = NULL;
1813 $sanitizedNewFolderName = $this->driver
->sanitizeFileName($newFolderName ?
: $folderToMove->getName());
1814 // @todo check if folder already exists in $targetParentFolder, handle this conflict then
1815 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1816 // Get all file objects now so we are able to update them after moving the folder
1817 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1818 if ($sourceStorage === $this) {
1819 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
1820 throw new InvalidTargetFolderException(
1822 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
1823 $folderToMove->getName(),
1824 $targetParentFolder->getName()
1829 $fileMappings = $this->driver
->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1831 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1833 // Update the identifier and storage of all file objects
1834 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1835 $newIdentifier = $fileMappings[$oldIdentifier];
1836 $fileObject->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1837 $this->getIndexer()->updateIndexEntry($fileObject);
1839 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1840 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
1841 return $returnObject;
1845 * Moves the given folder from a different storage to the target folder in this storage.
1847 * @param Folder $folderToMove
1848 * @param Folder $targetParentFolder
1849 * @param string $newFolderName
1852 * @throws \RuntimeException
1854 protected function moveFolderBetweenStorages(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName) {
1855 throw new \
RuntimeException('Not yet implemented');
1861 * @param FolderInterface $folderToCopy The folder to copy
1862 * @param FolderInterface $targetParentFolder The target folder
1863 * @param string $newFolderName
1864 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1865 * @return Folder The new (copied) folder object
1866 * @throws InvalidTargetFolderException
1868 public function copyFolder(FolderInterface
$folderToCopy, FolderInterface
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1869 // @todo implement the $conflictMode handling
1870 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
1871 $returnObject = NULL;
1872 $sanitizedNewFolderName = $this->driver
->sanitizeFileName($newFolderName ?
: $folderToCopy->getName());
1873 if ($folderToCopy instanceof Folder
&& $targetParentFolder instanceof Folder
) {
1874 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1876 $sourceStorage = $folderToCopy->getStorage();
1877 // call driver method to move the file
1878 // that also updates the file object properties
1879 if ($sourceStorage === $this) {
1880 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
1881 throw new InvalidTargetFolderException(
1883 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
1884 $folderToCopy->getName(),
1885 $targetParentFolder->getName()
1890 $this->driver
->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1891 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
1893 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1895 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
1896 return $returnObject;
1900 * Copies a folder between storages.
1902 * @param Folder $folderToCopy
1903 * @param Folder $targetParentFolder
1904 * @param string $newFolderName
1907 * @throws \RuntimeException
1909 protected function copyFolderBetweenStorages(Folder
$folderToCopy, Folder
$targetParentFolder, $newFolderName) {
1910 throw new \
RuntimeException('Not yet implemented.');
1914 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1916 * @param Folder $folderObject
1917 * @param string $newName
1918 * @throws \Exception
1919 * @throws \InvalidArgumentException
1922 public function renameFolder($folderObject, $newName) {
1924 // Renaming the folder should check if the parent folder is writable
1925 // We cannot do this however because we cannot extract the parent folder from a folder currently
1926 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1927 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1930 $sanitizedNewName = $this->driver
->sanitizeFileName($newName);
1931 $returnObject = NULL;
1932 if ($this->driver
->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
1933 throw new \
InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1936 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
1938 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1939 $fileMappings = $this->driver
->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
1940 // Update the identifier of all file objects
1941 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1942 $newIdentifier = $fileMappings[$oldIdentifier];
1943 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1944 $this->getIndexer()->updateIndexEntry($fileObject);
1946 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1948 $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
1950 return $returnObject;
1954 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1956 * @param Folder $folderObject
1957 * @param bool $deleteRecursively
1958 * @throws \RuntimeException
1959 * @throws Exception\InsufficientFolderAccessPermissionsException
1960 * @throws Exception\InsufficientUserPermissionsException
1961 * @throws Exception\FileOperationErrorException
1962 * @throws Exception\InvalidPathException
1965 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1966 $isEmpty = $this->driver
->isFolderEmpty($folderObject->getIdentifier());
1967 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
1968 if (!$isEmpty && !$deleteRecursively) {
1969 throw new \
RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1972 $this->emitPreFolderDeleteSignal($folderObject);
1974 foreach ($this->getFilesInFolder($folderObject, 0, 0, FALSE, $deleteRecursively) as $file) {
1975 $this->deleteFile($file);
1978 $result = $this->driver
->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
1980 $this->emitPostFolderDeleteSignal($folderObject);
1986 * @param Folder $folder
1988 * @param int $maxNumberOfItems
1989 * @param bool $useFilters
1990 * @param bool $recursive
1991 * @param string $sort Property name used to sort the items.
1992 * Among them may be: '' (empty, no sorting), name,
1993 * fileext, size, tstamp and rw.
1994 * If a driver does not support the given property, it
1995 * should fall back to "name".
1996 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
1999 public function getFoldersInFolder(Folder
$folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
2000 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
2002 $folderIdentifiers = $this->driver
->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
2004 // Exclude processing folders
2005 foreach ($this->getProcessingFolders() as $processingFolder) {
2006 $processingIdentifier = $processingFolder->getIdentifier();
2007 if (isset($folderIdentifiers[$processingIdentifier])) {
2008 unset($folderIdentifiers[$processingIdentifier]);
2012 foreach ($folderIdentifiers as $folderIdentifier) {
2013 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, TRUE);
2019 * @param Folder $folder
2020 * @param bool $useFilters
2021 * @param bool $recursive
2022 * @return integer Number of subfolders
2023 * @throws Exception\InsufficientFolderAccessPermissionsException
2025 public function countFoldersInFolder(Folder
$folder, $useFilters = TRUE, $recursive = FALSE) {
2026 $this->assureFolderReadPermission($folder);
2027 $filters = $useFilters ?
$this->fileAndFolderNameFilters
: array();
2028 return $this->driver
->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2032 * Returns TRUE if the specified folder exists.
2034 * @param string $identifier
2037 public function hasFolder($identifier) {
2038 $this->assureFolderReadPermission();
2039 return $this->driver
->folderExists($identifier);
2043 * Checks if the given file exists in the given folder
2045 * @param string $folderName
2046 * @param Folder $folder
2049 public function hasFolderInFolder($folderName, Folder
$folder) {
2050 $this->assureFolderReadPermission($folder);
2051 return $this->driver
->folderExistsInFolder($folderName, $folder->getIdentifier());
2055 * Creates a new folder.
2057 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2059 * @param string $folderName The new folder name
2060 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2062 * @throws Exception\InsufficientFolderWritePermissionsException
2063 * @throws \InvalidArgumentException
2064 * @return Folder The new folder object
2066 public function createFolder($folderName, Folder
$parentFolder = NULL) {
2067 if ($parentFolder === NULL) {
2068 $parentFolder = $this->getRootLevelFolder();
2069 } elseif (!$this->driver
->folderExists($parentFolder->getIdentifier())) {
2070 throw new \
InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2072 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2073 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2076 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2078 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), TRUE);
2079 $newFolder = $this->getFolder($newFolder);
2081 $this->emitPostFolderAddSignal($newFolder);
2087 * Returns the default folder where new files are stored if no other folder is given.
2091 public function getDefaultFolder() {
2092 return $this->getFolder($this->driver
->getDefaultFolder());
2096 * @param string $identifier
2097 * @param bool $returnInaccessibleFolderObject
2100 * @throws \Exception
2101 * @throws Exception\InsufficientFolderAccessPermissionsException
2103 public function getFolder($identifier, $returnInaccessibleFolderObject = FALSE) {
2104 $data = $this->driver
->getFolderInfoByIdentifier($identifier);
2105 $folder = ResourceFactory
::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2108 $this->assureFolderReadPermission($folder);
2109 } catch (Exception\InsufficientFolderAccessPermissionsException
$e) {
2111 if ($returnInaccessibleFolderObject) {
2112 // if parent folder is readable return inaccessible folder object
2113 $parentPermissions = $this->driver
->getPermissions($this->driver
->getParentFolderIdentifierOfIdentifier($identifier));
2114 if ($parentPermissions['r']) {
2115 $folder = GeneralUtility
::makeInstance(
2116 \TYPO3\CMS\Core\
Resource\InaccessibleFolder
::class, $this, $data['identifier'], $data['name']
2121 if ($folder === NULL) {
2129 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2131 * @param string $identifier
2134 public function isWithinProcessingFolder($identifier) {
2135 $inProcessingFolder = FALSE;
2136 foreach ($this->getProcessingFolders() as $processingFolder) {
2137 if ($this->driver
->isWithin($processingFolder->getIdentifier(), $identifier)) {
2138 $inProcessingFolder = TRUE;
2142 return $inProcessingFolder;
2146 * Checks if a resource (file or folder) is within the given folder
2148 * @param Folder $folder
2149 * @param ResourceInterface $resource
2151 * @throws \InvalidArgumentException
2153 public function isWithinFolder(Folder
$folder, ResourceInterface
$resource) {
2154 if ($folder->getStorage() !== $this) {
2155 throw new \
InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2157 if ($folder->getStorage() !== $resource->getStorage()) {
2160 return $this->driver
->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2164 * Returns the folders on the root level of the storage
2165 * or the first mount point of this storage for this user
2166 * if $respectFileMounts is set.
2168 * @param bool $respectFileMounts
2171 public function getRootLevelFolder($respectFileMounts = TRUE) {
2172 if ($respectFileMounts && count($this->fileMounts
)) {
2173 $mount = reset($this->fileMounts
);
2174 return $mount['folder'];
2176 return ResourceFactory
::getInstance()->createFolderObject($this, $this->driver
->getRootLevelFolder(), '');
2181 * Emits file pre-add signal.
2183 * @param string $targetFileName
2184 * @param Folder $targetFolder
2185 * @param string $sourceFilePath
2186 * @return string Modified target file name
2188 protected function emitPreFileAddSignal($targetFileName, Folder
$targetFolder, $sourceFilePath) {
2189 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileAdd
, array(&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver
));
2190 return $targetFileName;
2194 * Emits the file post-add signal.
2196 * @param FileInterface $file
2197 * @param Folder $targetFolder
2200 protected function emitPostFileAddSignal(FileInterface
$file, Folder
$targetFolder) {
2201 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileAdd
, array($file, $targetFolder));
2205 * Emits file pre-copy signal.
2207 * @param FileInterface $file
2208 * @param Folder $targetFolder
2211 protected function emitPreFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
2212 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileCopy
, array($file, $targetFolder));
2216 * Emits the file post-copy signal.
2218 * @param FileInterface $file
2219 * @param Folder $targetFolder
2222 protected function emitPostFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
2223 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileCopy
, array($file, $targetFolder));
2227 * Emits the file pre-move signal.
2229 * @param FileInterface $file
2230 * @param Folder $targetFolder
2233 protected function emitPreFileMoveSignal(FileInterface
$file, Folder
$targetFolder) {
2234 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileMove
, array($file, $targetFolder));
2238 * Emits the file post-move signal.
2240 * @param FileInterface $file
2241 * @param Folder $targetFolder
2242 * @param Folder $originalFolder
2245 protected function emitPostFileMoveSignal(FileInterface
$file, Folder
$targetFolder, Folder
$originalFolder) {
2246 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileMove
, array($file, $targetFolder, $originalFolder));
2250 * Emits the file pre-rename signal
2252 * @param FileInterface $file
2253 * @param $targetFolder
2256 protected function emitPreFileRenameSignal(FileInterface
$file, $targetFolder) {
2257 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileRename
, array($file, $targetFolder));
2261 * Emits the file post-rename signal.
2263 * @param FileInterface $file
2264 * @param $targetFolder
2267 protected function emitPostFileRenameSignal(FileInterface
$file, $targetFolder) {
2268 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileRename
, array($file, $targetFolder));
2272 * Emits the file pre-replace signal.
2274 * @param FileInterface $file
2275 * @param $localFilePath
2278 protected function emitPreFileReplaceSignal(FileInterface
$file, $localFilePath) {
2279 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileReplace
, array($file, $localFilePath));
2283 * Emits the file post-replace signal
2285 * @param FileInterface $file
2286 * @param string $localFilePath
2289 protected function emitPostFileReplaceSignal(FileInterface
$file, $localFilePath) {
2290 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileReplace
, array($file, $localFilePath));
2294 * Emits the file post-create signal
2296 * @param string $newFileIdentifier
2297 * @param Folder $targetFolder
2299 protected function emitPostFileCreateSignal($newFileIdentifier, Folder
$targetFolder) {
2300 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileCreate
, array($newFileIdentifier, $targetFolder));
2304 * Emits the file pre-deletion signal.
2306 * @param FileInterface $file
2309 protected function emitPreFileDeleteSignal(FileInterface
$file) {
2310 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFileDelete
, array($file));
2314 * Emits the file post-deletion signal
2316 * @param FileInterface $file
2319 protected function emitPostFileDeleteSignal(FileInterface
$file) {
2320 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileDelete
, array($file));
2324 * Emits the file post-set-contents signal
2326 * @param FileInterface $file
2327 * @param mixed $content
2330 protected function emitPostFileSetContentsSignal(FileInterface
$file, $content) {
2331 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFileSetContents
, array($file, $content));
2335 * Emits the folder pre-add signal.
2337 * @param Folder $targetFolder
2338 * @param string $name
2341 protected function emitPreFolderAddSignal(Folder
$targetFolder, $name) {
2342 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFolderAdd
, array($targetFolder, $name));
2346 * Emits the folder post-add signal.
2348 * @param Folder $folder
2351 protected function emitPostFolderAddSignal(Folder
$folder) {
2352 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFolderAdd
, array($folder));
2356 * Emits the folder pre-copy signal.
2358 * @param Folder $folder
2359 * @param Folder $targetFolder
2363 protected function emitPreFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
2364 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFolderCopy
, array($folder, $targetFolder, $newName));
2368 * Emits the folder post-copy signal.
2370 * @param Folder $folder
2371 * @param Folder $targetFolder
2375 protected function emitPostFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
2376 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFolderCopy
, array($folder, $targetFolder, $newName));
2380 * Emits the folder pre-move signal.
2382 * @param Folder $folder
2383 * @param Folder $targetFolder
2387 protected function emitPreFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName) {
2388 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFolderMove
, array($folder, $targetFolder, $newName));
2392 * Emits the folder post-move signal.
2394 * @param Folder $folder
2395 * @param Folder $targetFolder
2397 * @param Folder $originalFolder
2400 protected function emitPostFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName, Folder
$originalFolder) {
2401 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFolderMove
, array($folder, $targetFolder, $newName, $originalFolder));
2405 * Emits the folder pre-rename signal.
2407 * @param Folder $folder
2411 protected function emitPreFolderRenameSignal(Folder
$folder, $newName) {
2412 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFolderRename
, array($folder, $newName));
2416 * Emits the folder post-rename signal.
2418 * @param Folder $folder
2422 protected function emitPostFolderRenameSignal(Folder
$folder, $newName) {
2423 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFolderRename
, array($folder, $newName));
2427 * Emits the folder pre-deletion signal.
2429 * @param Folder $folder
2432 protected function emitPreFolderDeleteSignal(Folder
$folder) {
2433 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreFolderDelete
, array($folder));
2437 * Emits folder post-deletion signal..
2439 * @param Folder $folder
2442 protected function emitPostFolderDeleteSignal(Folder
$folder) {
2443 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PostFolderDelete
, array($folder));
2447 * Emits file pre-processing signal when generating a public url for a file or folder.
2449 * @param ResourceInterface $resourceObject
2450 * @param bool $relativeToCurrentScript
2451 * @param array $urlData
2453 protected function emitPreGeneratePublicUrlSignal(ResourceInterface
$resourceObject, $relativeToCurrentScript, array $urlData) {
2454 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\
Resource\ResourceStorage
::class, self
::SIGNAL_PreGeneratePublicUrl
, array($this, $this->driver
, $resourceObject, $relativeToCurrentScript, $urlData));
2458 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2459 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2460 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2462 * @param Folder $folder
2463 * @param string $theFile The input fileName to check
2464 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2466 * @throws \RuntimeException
2467 * @return string A unique fileName inside $folder, based on $theFile.
2468 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2470 protected function getUniqueName(Folder
$folder, $theFile, $dontCheckForUnique = FALSE) {
2471 static $maxNumber = 99, $uniqueNamePrefix = '';
2472 // Fetches info about path, name, extension of $theFile
2473 $origFileInfo = PathUtility
::pathinfo($theFile);
2475 if ($uniqueNamePrefix) {
2476 $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2477 $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2479 // Check if the file exists and if not - return the fileName...
2480 // The destinations file
2481 $theDestFile = $origFileInfo['basename'];
2482 // If the file does NOT exist we return this fileName
2483 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder->getIdentifier()) ||
$dontCheckForUnique) {
2484 return $theDestFile;
2486 // Well the fileName in its pure form existed. Now we try to append
2487 // numbers / unique-strings and see if we can find an available fileName
2488 // This removes _xx if appended to the file
2489 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2490 $theOrigExt = $origFileInfo['extension'] ?
'.' . $origFileInfo['extension'] : '';
2491 for ($a = 1; $a <= $maxNumber +
1; $a++
) {
2492 // First we try to append numbers
2493 if ($a <= $maxNumber) {
2494 $insert = '_' . sprintf('%02d', $a);
2496 $insert = '_' . substr(md5(uniqid('', TRUE)), 0, 6);
2498 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2499 // The destinations file
2500 $theDestFile = $theTestFile;
2501 // If the file does NOT exist we return this fileName
2502 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2503 return $theDestFile;
2506 throw new \
RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2510 * Get the SignalSlot dispatcher.
2512 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2514 protected function getSignalSlotDispatcher() {
2515 if (!isset($this->signalSlotDispatcher
)) {
2516 $this->signalSlotDispatcher
= $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher
::class);
2518 return $this->signalSlotDispatcher
;
2522 * Gets the ObjectManager.
2524 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2526 protected function getObjectManager() {
2527 return GeneralUtility
::makeInstance(\TYPO3\CMS\Extbase\
Object\ObjectManager
::class);
2531 * @return ResourceFactory
2533 protected function getFileFactory() {
2534 return GeneralUtility
::makeInstance(\TYPO3\CMS\Core\
Resource\ResourceFactory
::class);
2538 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
2540 protected function getFileIndexRepository() {
2541 return FileIndexRepository
::getInstance();
2545 * @return Service\FileProcessingService
2547 protected function getFileProcessingService() {
2548 if (!$this->fileProcessingService
) {
2549 $this->fileProcessingService
= GeneralUtility
::makeInstance(\TYPO3\CMS\Core\
Resource\Service\FileProcessingService
::class, $this, $this->driver
);
2551 return $this->fileProcessingService
;
2555 * Gets the role of a folder.
2557 * @param FolderInterface $folder Folder object to get the role from
2558 * @return string The role the folder has
2560 public function getRole(FolderInterface
$folder) {
2561 $folderRole = FolderInterface
::ROLE_DEFAULT
;
2562 $identifier = $folder->getIdentifier();
2563 if (method_exists($this->driver
, 'getRole')) {
2564 $folderRole = $this->driver
->getRole($folder->getIdentifier());
2566 if (isset($this->fileMounts
[$identifier])) {
2567 $folderRole = FolderInterface
::ROLE_MOUNT
;
2569 if (!empty($this->fileMounts
[$identifier]['read_only'])) {
2570 $folderRole = FolderInterface
::ROLE_READONLY_MOUNT
;
2572 if ($this->fileMounts
[$identifier]['user_mount']) {
2573 $folderRole = FolderInterface
::ROLE_USER_MOUNT
;
2576 if ($folder instanceof Folder
&& $this->isProcessingFolder($folder)) {
2577 $folderRole = FolderInterface
::ROLE_PROCESSING
;
2584 * Getter function to return the folder where the files can
2585 * be processed. Does not check for access rights here.
2589 public function getProcessingFolder() {
2590 if (!isset($this->processingFolder
)) {
2591 $processingFolder = self
::DEFAULT_ProcessingFolder
;
2592 if (!empty($this->storageRecord
['processingfolder'])) {
2593 $processingFolder = $this->storageRecord
['processingfolder'];
2596 if (strpos($processingFolder, ':') !== FALSE) {
2597 $this->processingFolder
= ResourceFactory
::getInstance()->getFolderObjectFromCombinedIdentifier($processingFolder);
2599 if ($this->driver
->folderExists($processingFolder) === FALSE) {
2600 $this->processingFolder
= $this->createFolder($processingFolder);
2602 $data = $this->driver
->getFolderInfoByIdentifier($processingFolder);
2603 $this->processingFolder
= ResourceFactory
::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2606 } catch(Exception\InsufficientFolderWritePermissionsException
$e) {
2607 $this->processingFolder
= GeneralUtility
::makeInstance(
2608 InaccessibleFolder
::class, $this, $processingFolder, $processingFolder
2612 return $this->processingFolder
;
2616 * Gets the driver Type configured for this storage.
2620 public function getDriverType() {
2621 return $this->storageRecord
['driver'];
2627 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
2629 protected function getIndexer() {
2630 return GeneralUtility
::makeInstance(\TYPO3\CMS\Core\
Resource\Index\Indexer
::class, $this);
2634 * @param bool $isDefault
2637 public function setDefault($isDefault) {
2638 $this->isDefault
= (bool)$isDefault;
2644 public function isDefault() {
2645 return $this->isDefault
;