2 namespace TYPO3\CMS\Core\
Resource;
4 /***************************************************************
7 * (c) 2011 Andreas Wolf <andreas.wolf@typo3.org>
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
30 * A "mount point" inside the TYPO3 file handling.
32 * A "storage" object handles
33 * - abstraction to the driver
34 * - permissions (from the driver, and from the user, + capabilities)
35 * - an entry point for files, folders, and for most other operations
37 * == Driver entry point
38 * The driver itself, that does the actual work on the file system,
39 * is inside the storage but completely shadowed by
40 * the storage, as the storage also handles the abstraction to the
43 * The storage can be on the local system, but can also be on a remote
44 * system. The combination of driver + configurable capabilities (storage
45 * is read-only e.g.) allows for flexible uses.
48 * == Permission system
49 * As all requests have to run through the storage, the storage knows about the
50 * permissions of a BE/FE user, the file permissions / limitations of the driver
51 * and has some configurable capabilities.
52 * Additionally, a BE user can use "filemounts" (known from previous installations)
53 * to limit his/her work-zone to only a subset (identifier and its subfolders/subfolders)
56 * Check 1: "User Permissions" [is the user allowed to write a file) [is the user allowed to write a file]
57 * Check 2: "File Mounts" of the User (act as subsets / filters to the identifiers) [is the user allowed to do something in this folder?]
58 * Check 3: "Capabilities" of Storage (then: of Driver) [is the storage/driver writable?]
59 * Check 4: "File permissions" of the Driver [is the folder writable?]
64 * @author Andreas Wolf <andreas.wolf@typo3.org>
65 * @author Ingmar Schlecht <ingmar@typo3.org>
67 class ResourceStorage
{
69 const SIGNAL_PreProcessConfiguration
= 'preProcessConfiguration';
70 const SIGNAL_PostProcessConfiguration
= 'postProcessConfiguration';
71 const SIGNAL_PreFileCopy
= 'preFileCopy';
72 const SIGNAL_PostFileCopy
= 'postFileCopy';
73 const SIGNAL_PreFileMove
= 'preFileMove';
74 const SIGNAL_PostFileMove
= 'postFileMove';
75 const SIGNAL_PreFileDelete
= 'preFileDelete';
76 const SIGNAL_PostFileDelete
= 'postFileDelete';
77 const SIGNAL_PreFileRename
= 'preFileRename';
78 const SIGNAL_PostFileRename
= 'postFileRename';
79 const SIGNAL_PreFileReplace
= 'preFileReplace';
80 const SIGNAL_PostFileReplace
= 'postFileReplace';
81 const SIGNAL_PreFolderCopy
= 'preFolderCopy';
82 const SIGNAL_PostFolderCopy
= 'postFolderCopy';
83 const SIGNAL_PreFolderMove
= 'preFolderMove';
84 const SIGNAL_PostFolderMove
= 'postFolderMove';
85 const SIGNAL_PreFolderDelete
= 'preFolderDelete';
86 const SIGNAL_PostFolderDelete
= 'postFolderDelete';
87 const SIGNAL_PreFolderRename
= 'preFolderRename';
88 const SIGNAL_PostFolderRename
= 'postFolderRename';
89 const SIGNAL_PreGeneratePublicUrl
= 'preGeneratePublicUrl';
91 * The storage driver instance belonging to this storage.
93 * @var Driver\AbstractDriver
98 * The database record for this storage
102 protected $storageRecord;
105 * The configuration belonging to this storage (decoded from the configuration field).
109 protected $configuration;
112 * The base URI to this storage.
119 * @var Service\FileProcessingService
121 protected $fileProcessingService;
124 * User filemounts, added as an array, and used as filters
128 protected $fileMounts = array();
131 * The file permissions of the user (and their group) merged together and
132 * available as an array
136 protected $userPermissions = array();
139 * The capabilities of this storage as defined in the storage record.
140 * Also see the CAPABILITY_* constants below
144 protected $capabilities;
147 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
149 protected $signalSlotDispatcher;
152 * Capability for being browsable by (backend) users
154 const CAPABILITY_BROWSABLE
= 1;
156 * Capability for publicly accessible storages (= accessible from the web)
158 const CAPABILITY_PUBLIC
= 2;
160 * Capability for writable storages. This only signifies writability in
161 * general - this might also be further limited by configuration.
163 const CAPABILITY_WRITABLE
= 4;
165 * Name of the default processing folder
167 const DEFAULT_ProcessingFolder
= '_processed_';
171 protected $processingFolder;
174 * whether this storage is online or offline in this request
178 protected $isOnline = NULL;
181 * The filters used for the files and folder names.
185 protected $fileAndFolderNameFilters = array();
188 * Constructor for a storage object.
190 * @param Driver\AbstractDriver $driver
191 * @param array $storageRecord The storage record row from the database
193 public function __construct(Driver\AbstractDriver
$driver, array $storageRecord) {
194 $this->storageRecord
= $storageRecord;
195 $this->configuration
= ResourceFactory
::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
196 $this->driver
= $driver;
197 $this->driver
->setStorage($this);
199 $this->driver
->processConfiguration();
200 } catch (Exception\InvalidConfigurationException
$e) {
201 // configuration error
202 // mark this storage as permanently unusable
203 $this->markAsPermanentlyOffline();
205 $this->driver
->initialize();
206 $this->capabilities
= ($this->storageRecord
['is_browsable'] && $this->driver
->hasCapability(self
::CAPABILITY_BROWSABLE
) ? self
::CAPABILITY_BROWSABLE
: 0) +
($this->storageRecord
['is_public'] && $this->driver
->hasCapability(self
::CAPABILITY_PUBLIC
) ? self
::CAPABILITY_PUBLIC
: 0) +
($this->storageRecord
['is_writable'] && $this->driver
->hasCapability(self
::CAPABILITY_WRITABLE
) ? self
::CAPABILITY_WRITABLE
: 0);
207 // TODO do not set the "public" capability if no public URIs can be generated
208 $this->processConfiguration();
209 $this->resetFileAndFolderNameFiltersToDefault();
213 * Gets the configuration
217 public function getConfiguration() {
218 return $this->configuration
;
222 * Sets the configuration.
224 * @param array $configuration
226 public function setConfiguration(array $configuration) {
227 $this->configuration
= $configuration;
231 * Gets the storage record.
235 public function getStorageRecord() {
236 return $this->storageRecord
;
240 * Processes the configuration of this storage.
242 * @throws \InvalidArgumentException If a required configuration option is not set or has an invalid value.
245 protected function processConfiguration() {
246 $this->emitPreProcessConfigurationSignal();
247 if (isset($this->configuration
['baseUri'])) {
248 $this->baseUri
= rtrim($this->configuration
['baseUri'], '/') . '/';
250 $this->emitPostProcessConfigurationSignal();
254 * Returns the base URI of this storage; all files are reachable via URLs
255 * beginning with this string.
259 public function getBaseUri() {
260 return $this->baseUri
;
264 * Sets the storage that belongs to this storage.
266 * @param Driver\AbstractDriver $driver
267 * @return ResourceStorage
269 public function setDriver(Driver\AbstractDriver
$driver) {
270 $this->driver
= $driver;
275 * Returns the driver object belonging to this storage.
277 * @return Driver\AbstractDriver
279 protected function getDriver() {
280 return $this->driver
;
284 * Deprecated function, don't use it. Will be removed in some later revision.
286 * @param string $identifier
288 * @throws \BadMethodCallException
290 public function getFolderByIdentifier($identifier) {
291 throw new \
BadMethodCallException('Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFolderByIdentifier() has been renamed to just getFolder(). Please fix the method call.', 1333754514);
295 * Deprecated function, don't use it. Will be removed in some later revision.
297 * @param string $identifier
299 * @throws \BadMethodCallException
301 public function getFileByIdentifier($identifier) {
302 throw new \
BadMethodCallException('Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFileByIdentifier() has been renamed to just getFileInfoByIdentifier(). ' . 'Please fix the method call.', 1333754533);
306 * Returns the name of this storage.
310 public function getName() {
311 return $this->storageRecord
['name'];
315 * Returns the uid of this storage.
319 public function getUid() {
320 return (int) $this->storageRecord
['uid'];
324 * Tells whether there are children in this storage
328 public function hasChildren() {
332 /*********************************
334 ********************************/
336 * Returns the capabilities of this storage.
339 * @see CAPABILITY_* constants
341 public function getCapabilities() {
342 return (int) $this->capabilities
;
346 * Returns TRUE if this storage has the given capability.
348 * @param int $capability A capability, as defined in a CAPABILITY_* constant
351 protected function hasCapability($capability) {
352 return ($this->capabilities
& $capability) == $capability;
356 * Returns TRUE if this storage is publicly available. This is just a
357 * configuration option and does not mean that it really *is* public. OTOH
358 * a storage that is marked as not publicly available will trigger the file
359 * publishing mechanisms of TYPO3.
363 public function isPublic() {
364 return $this->hasCapability(self
::CAPABILITY_PUBLIC
);
368 * Returns TRUE if this storage is writable. This is determined by the
369 * driver and the storage configuration; user permissions are not taken into account.
373 public function isWritable() {
374 return $this->hasCapability(self
::CAPABILITY_WRITABLE
);
378 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
382 public function isBrowsable() {
383 return $this->isOnline() && $this->hasCapability(self
::CAPABILITY_BROWSABLE
);
387 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
391 public function isOnline() {
392 if ($this->isOnline
=== NULL) {
393 if ($this->getUid() === 0) {
394 $this->isOnline
= TRUE;
396 // the storage is not marked as online for a longer time
397 if ($this->storageRecord
['is_online'] == 0) {
398 $this->isOnline
= FALSE;
400 if ($this->isOnline
!== FALSE) {
401 // all files are ALWAYS available in the frontend
402 if (TYPO3_MODE
=== 'FE') {
403 $this->isOnline
= TRUE;
405 // check if the storage is disabled temporary for now
406 $registryObject = \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Registry');
407 $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
408 if ($offlineUntil && $offlineUntil > time()) {
409 $this->isOnline
= FALSE;
411 $this->isOnline
= TRUE;
416 return $this->isOnline
;
420 * blow the "fuse" and mark the storage as offline
421 * can only be modified by an admin
422 * typically this is only done if the configuration is wrong
424 public function markAsPermanentlyOffline() {
425 if ($this->getUid() > 0) {
426 // @todo: move this to the storage repository
427 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_file_storage', 'uid=' . intval($this->getUid()), array('is_online' => 0));
429 $this->storageRecord
['is_online'] = 0;
430 $this->isOnline
= FALSE;
434 * mark this storage as offline
436 * non-permanent: this typically happens for remote storages
437 * that are "flaky" and not available all the time
438 * mark this storage as offline for the next 5 minutes
442 public function markAsTemporaryOffline() {
443 $registryObject = \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Registry');
444 $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() +
60 * 5);
445 $this->storageRecord
['is_online'] = 0;
446 $this->isOnline
= FALSE;
449 /*********************************
450 * User Permissions / File Mounts
451 ********************************/
453 * Adds a filemount as a "filter" for users to only work on a subset of a
456 * @param string $folderIdentifier
457 * @param array $additionalData
459 * @throws Exception\FolderDoesNotExistException
462 public function addFileMount($folderIdentifier, $additionalData = array()) {
463 // check for the folder before we add it as a filemount
464 if ($this->driver
->folderExists($folderIdentifier) === FALSE) {
465 // if there is an error, this is important and should be handled
466 // as otherwise the user would see the whole storage without any restrictions for the filemounts
467 throw new Exception\
FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
469 $folderObject = $this->driver
->getFolder($folderIdentifier);
470 if (empty($additionalData)) {
471 $additionalData = array(
472 'path' => $folderIdentifier,
473 'title' => $folderIdentifier,
474 'folder' => $folderObject
477 $additionalData['folder'] = $folderObject;
478 if (!isset($additionalData['title'])) {
479 $additionalData['title'] = $folderIdentifier;
482 $this->fileMounts
[$folderIdentifier] = $additionalData;
486 * Returns all file mounts that are registered with this storage.
490 public function getFileMounts() {
491 return $this->fileMounts
;
495 * Checks if the given subject is within one of the registered user
496 * filemounts. If not, working with the file is not permitted for the user.
501 public function isWithinFileMountBoundaries($subject) {
502 $isWithinFilemount = TRUE;
503 if (is_array($this->fileMounts
)) {
504 $isWithinFilemount = FALSE;
506 $subject = $this->getRootLevelFolder();
508 $identifier = $subject->getIdentifier();
510 // Allow access to processing folder
511 if ($this->driver
->isWithin($this->getProcessingFolder(), $identifier)) {
512 $isWithinFilemount = TRUE;
514 // Check if the identifier of the subject is within at
515 // least one of the file mounts
516 foreach ($this->fileMounts
as $fileMount) {
517 if ($this->driver
->isWithin($fileMount['folder'], $identifier)) {
518 $isWithinFilemount = TRUE;
524 return $isWithinFilemount;
528 * Sets the user permissions of the storage
530 * @param array $userPermissions
533 public function setUserPermissions(array $userPermissions) {
534 $this->userPermissions
= $userPermissions;
538 * Check if the ACL settings allow for a certain action
539 * (is a user allowed to read a file or copy a folder)
541 * @param string $action
542 * @param string $type either File or Folder
545 public function checkUserActionPermission($action, $type) {
546 // TODO decide if we should return TRUE if no permissions are set
547 if (!empty($this->userPermissions
)) {
548 $action = strtolower($action);
549 $type = ucfirst(strtolower($type));
550 if ($this->userPermissions
[$action . $type] == 0) {
556 // TODO should the default be really TRUE?
561 * Check if a file operation (= action) is allowed on a
562 * File/Folder/Storage (= subject).
564 * This method, by design, does not throw exceptions or do logging.
565 * Besides the usage from other methods in this class, it is also used by
566 * the File List UI to check whether an action is allowed and whether action
567 * related UI elements should thus be shown (move icon, edit icon, etc.)
569 * @param string $action, can be read, write, delete
570 * @param FileInterface $file
573 public function checkFileActionPermission($action, FileInterface
$file) {
574 // Check 1: Does the user have permission to perform the action? e.g. "readFile"
575 if ($this->checkUserActionPermission($action, 'File') === FALSE) {
578 // Check 2: Does the user has the right to perform the action?
579 // (= is he within the file mount borders)
580 if (is_array($this->fileMounts
) && count($this->fileMounts
) && !$this->isWithinFileMountBoundaries($file)) {
583 $isReadCheck = FALSE;
584 if ($action === 'read') {
587 $isWriteCheck = FALSE;
588 if (in_array($action, array('add', 'edit', 'write', 'upload', 'move', 'rename', 'unzip', 'remove'))) {
589 $isWriteCheck = TRUE;
591 // Check 3: Check the capabilities of the storage (and the driver)
592 if ($isReadCheck && !$this->isBrowsable()) {
595 if ($isWriteCheck && !$this->isWritable()) {
598 // Check 4: "File permissions" of the driver
599 $filePermissions = $this->driver
->getFilePermissions($file);
600 if ($isReadCheck && !$filePermissions['r']) {
603 if ($isWriteCheck && !$filePermissions['w']) {
610 * Check if a folder operation (= action) is allowed on a Folder
612 * This method, by design, does not throw exceptions or do logging.
613 * See the checkFileActionPermission() method above for the reasons.
615 * @param string $action
616 * @param Folder $folder
619 public function checkFolderActionPermission($action, Folder
$folder = NULL) {
620 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
621 if ($this->checkUserActionPermission($action, 'Folder') === FALSE) {
624 // Check 2: Does the user has the right to perform the action?
625 // (= is he within the file mount borders)
626 if (is_array($this->fileMounts
) && count($this->fileMounts
) && !$this->isWithinFileMountBoundaries($folder)) {
629 $isReadCheck = FALSE;
630 if ($action === 'read') {
633 $isWriteCheck = FALSE;
634 if (in_array($action, array('add', 'move', 'write', 'remove', 'rename'))) {
635 $isWriteCheck = TRUE;
637 // Check 3: Check the capabilities of the storage (and the driver)
638 if ($isReadCheck && !$this->isBrowsable()) {
641 if ($isWriteCheck && !$this->isWritable()) {
644 // Check 4: "Folder permissions" of the driver
645 $folderPermissions = $this->driver
->getFolderPermissions($folder);
646 if ($isReadCheck && !$folderPermissions['r']) {
649 if ($isWriteCheck && !$folderPermissions['w']) {
656 * If the fileName is given, check it against the
657 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed
659 * @param string $fileName Full filename
660 * @return boolean TRUE if extension/filename is allowed
662 protected function checkFileExtensionPermission($fileName) {
663 $isAllowed = \TYPO3\CMS\Core\Utility\GeneralUtility
::verifyFilenameAgainstDenyPattern($fileName);
665 $fileInfo = \TYPO3\CMS\Core\Utility\GeneralUtility
::split_fileref($fileName);
666 // Set up the permissions for the file extension
667 $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
668 $fileExtensionPermissions['allow'] = \TYPO3\CMS\Core\Utility\GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['allow']));
669 $fileExtensionPermissions['deny'] = \TYPO3\CMS\Core\Utility\GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['deny']));
670 $fileExtension = strtolower($fileInfo['fileext']);
671 if ($fileExtension !== '') {
672 // If the extension is found amongst the allowed types, we return TRUE immediately
673 if ($fileExtensionPermissions['allow'] === '*' || \TYPO3\CMS\Core\Utility\GeneralUtility
::inList($fileExtensionPermissions['allow'], $fileExtension)) {
676 // If the extension is found amongst the denied types, we return FALSE immediately
677 if ($fileExtensionPermissions['deny'] === '*' || \TYPO3\CMS\Core\Utility\GeneralUtility
::inList($fileExtensionPermissions['deny'], $fileExtension)) {
680 // If no match we return TRUE
683 if ($fileExtensionPermissions['allow'] === '*') {
686 if ($fileExtensionPermissions['deny'] === '*') {
695 /********************
697 ********************/
699 * Moves a file from the local filesystem to this storage.
701 * @param string $localFilePath The file on the server's hard disk to add.
702 * @param Folder $targetFolder The target path, without the fileName
703 * @param string $fileName The fileName. If not set, the local file name is used.
704 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
706 * @throws \InvalidArgumentException
707 * @throws Exception\ExistingTargetFileNameException
708 * @return FileInterface
710 public function addFile($localFilePath, Folder
$targetFolder, $fileName = '', $conflictMode = 'changeName') {
711 // TODO check permissions (write on target, upload, ...)
712 if (!file_exists($localFilePath)) {
713 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
715 $targetFolder = $targetFolder ?
$targetFolder : $this->getDefaultFolder();
716 $fileName = $fileName ?
$fileName : basename($localFilePath);
717 if ($conflictMode === 'cancel' && $this->driver
->fileExistsInFolder($fileName, $targetFolder)) {
718 throw new Exception\
ExistingTargetFileNameException('File "' . $fileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
719 } elseif ($conflictMode === 'changeName') {
720 $fileName = $this->getUniqueName($targetFolder, $fileName);
722 // We do not care whether the file exists if $conflictMode is "replace",
723 // so just use the name as is in that case
724 return $this->driver
->addFile($localFilePath, $targetFolder, $fileName);
728 * Creates a (cryptographic) hash for a file.
730 * @param FileInterface $fileObject
734 public function hashFile(FileInterface
$fileObject, $hash) {
735 return $this->driver
->hash($fileObject, $hash);
739 * Returns a publicly accessible URL for a file.
741 * WARNING: Access to the file may be restricted by further means, e.g.
742 * some web-based authentication. You have to take care of this yourself.
744 * @param ResourceInterface $resourceObject The file or folder object
745 * @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)
748 public function getPublicUrl(ResourceInterface
$resourceObject, $relativeToCurrentScript = FALSE) {
750 // Pre-process the public URL by an accordant slot
751 $this->emitPreGeneratePublicUrl($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
752 // If slot did not handle the signal, use the default way to determine public URL
753 if ($publicUrl === NULL) {
754 $publicUrl = $this->driver
->getPublicUrl($resourceObject, $relativeToCurrentScript);
760 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
762 * @param FileInterface $fileObject The file object
763 * @param string $context
764 * @param array $configuration
766 * @return ProcessedFile
767 * @throws \InvalidArgumentException
769 public function processFile(FileInterface
$fileObject, $context, array $configuration) {
770 if ($fileObject->getStorage() !== $this) {
771 throw new \
InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
773 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
775 return $processedFile;
779 * Copies a file from the storage for local processing.
781 * @param FileInterface $fileObject
782 * @param bool $writable
783 * @return string Path to local file (either original or copied to some temporary local location)
785 public function getFileForLocalProcessing(FileInterface
$fileObject, $writable = TRUE) {
786 $filePath = $this->driver
->getFileForLocalProcessing($fileObject, $writable);
787 // @todo: shouldn't this go in the driver? this function is called from the indexing service
788 // @todo: and recursively calls itself over and over again, this is left out for now with getModificationTime()
789 // touch($filePath, $fileObject->getModificationTime());
794 * Get file by identifier
796 * @param string $identifier
797 * @return FileInterface
799 public function getFile($identifier) {
800 return $this->driver
->getFile($identifier);
804 * Get information about a file
806 * @param FileInterface $fileObject
809 public function getFileInfo(FileInterface
$fileObject) {
810 return $this->driver
->getFileInfo($fileObject);
814 * Get information about a file by its identifier
816 * @param string $identifier
818 * @throws \BadMethodCallException
821 public function getFileInfoByIdentifier($identifier) {
822 throw new \
BadMethodCallException("The method ResourceStorage::getFileInfoByIdentifier() has been deprecated. Please fix your method call and use getFileInfo with the file object instead.", 1346577887);
826 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
830 public function unsetFileAndFolderNameFilters() {
831 $this->fileAndFolderNameFilters
= array();
835 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
839 public function resetFileAndFolderNameFiltersToDefault() {
840 $this->fileAndFolderNameFilters
= $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
844 * Returns the file and folder name filters used by this storage.
848 public function getFileAndFolderNameFilters() {
849 return $this->fileAndFolderNameFilters
;
852 public function setFileAndFolderNameFilters(array $filters) {
853 $this->fileAndFolderNameFilters
= $filters;
857 public function addFileAndFolderNameFilter($filter) {
858 $this->fileAndFolderNameFilters
[] = $filter;
862 * Returns a list of files in a given path, filtered by some custom filter methods.
864 * @see getUnfilteredFileList(), getFileListWithDefaultFilters()
865 * @param string $path The path to list
866 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
867 * @param integer $numberOfItems The number of items to list; if not set, return all items
868 * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
869 * @param bool $loadIndexRecords If set to TRUE, the index records for all files are loaded from the database. This can greatly improve performance of this method, especially with a lot of files.
870 * @param boolean $recursive
871 * @return array Information about the files found.
873 // TODO check if we should use a folder object instead of $path
874 // TODO add unit test for $loadIndexRecords
875 public function getFileList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE, $loadIndexRecords = TRUE, $recursive = FALSE) {
877 if ($loadIndexRecords) {
878 $rows = $this->getFileRepository()->getFileIndexRecordsForFolder($this->getFolder($path));
880 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
881 $items = $this->driver
->getFileList($path, $start, $numberOfItems, $filters, $rows, $recursive);
882 uksort($items, 'strnatcasecmp');
887 * Returns TRUE if the specified file exists.
889 * @param string $identifier
892 public function hasFile($identifier) {
893 // @todo: access check?
894 return $this->driver
->fileExists($identifier);
898 * Checks if the queried file in the given folder exists.
900 * @param string $fileName
901 * @param Folder $folder
904 public function hasFileInFolder($fileName, Folder
$folder) {
905 return $this->driver
->fileExistsInFolder($fileName, $folder);
909 * Get contents of a file object
911 * @param FileInterface $file
913 * @throws Exception\InsufficientFileReadPermissionsException
916 public function getFileContents($file) {
917 // Check if $file is readable
918 if (!$this->checkFileActionPermission('read', $file)) {
919 throw new Exception\
InsufficientFileReadPermissionsException('Reading file "' . $file->getIdentifier() . '" is not allowed.', 1330121089);
921 return $this->driver
->getFileContents($file);
925 * Set contents of a file object.
927 * @param AbstractFile $file
928 * @param string $contents
930 * @throws \Exception|\RuntimeException
931 * @throws Exception\InsufficientFileWritePermissionsException
932 * @throws Exception\InsufficientUserPermissionsException
933 * @return integer The number of bytes written to the file
935 public function setFileContents(AbstractFile
$file, $contents) {
936 // Check if user is allowed to edit
937 if (!$this->checkUserActionPermission('edit', 'File')) {
938 throw new Exception\
InsufficientUserPermissionsException(('Updating file "' . $file->getIdentifier()) . '" not allowed for user.', 1330121117);
940 // Check if $file is writable
941 if (!$this->checkFileActionPermission('write', $file)) {
942 throw new Exception\
InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
944 // Call driver method to update the file and update file properties afterwards
946 $result = $this->driver
->setFileContents($file, $contents);
947 $fileInfo = $this->driver
->getFileInfo($file);
948 $fileInfo['sha1'] = $this->driver
->hash($file, 'sha1');
949 $file->updateProperties($fileInfo);
950 $this->getFileRepository()->update($file);
951 } catch (\RuntimeException
$e) {
960 * previously in t3lib_extFileFunc::func_newfile()
962 * @param string $fileName
963 * @param Folder $targetFolderObject
965 * @throws Exception\InsufficientFolderWritePermissionsException
966 * @return FileInterface The file object
968 public function createFile($fileName, Folder
$targetFolderObject) {
969 if (!$this->checkFolderActionPermission('add', $targetFolderObject)) {
970 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to create directories on this storage "' . $targetFolderObject->getIdentifier() . '"', 1323059807);
972 return $this->driver
->createFile($fileName, $targetFolderObject);
976 * Previously in t3lib_extFileFunc::deleteFile()
978 * @param $fileObject FileInterface
980 * @throws Exception\InsufficientFileAccessPermissionsException
981 * @throws Exception\FileOperationErrorException
982 * @return bool TRUE if deletion succeeded
984 public function deleteFile($fileObject) {
985 if (!$this->checkFileActionPermission('remove', $fileObject)) {
986 throw new Exception\
InsufficientFileAccessPermissionsException('You are not allowed to delete the file "' . $fileObject->getIdentifier() . '\'', 1319550425);
989 $this->emitPreFileDeleteSignal($fileObject);
991 $result = $this->driver
->deleteFile($fileObject);
992 if ($result === FALSE) {
993 throw new Exception\
FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
995 // Mark the file object as deleted
996 $fileObject->setDeleted();
998 $this->emitPostFileDeleteSignal($fileObject);
1004 * Previously in t3lib_extFileFunc::func_copy()
1005 * copies a source file (from any location) in to the target
1006 * folder, the latter has to be part of this storage
1008 * @param FileInterface $file
1009 * @param Folder $targetFolder
1010 * @param string $targetFileName an optional destination fileName
1011 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1013 * @throws \Exception|Exception\AbstractFileOperationException
1014 * @throws Exception\ExistingTargetFileNameException
1015 * @return FileInterface
1017 public function copyFile(FileInterface
$file, Folder
$targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1018 $this->emitPreFileCopySignal($file, $targetFolder);
1019 $this->checkFileCopyPermissions($file, $targetFolder, $targetFileName);
1020 if ($targetFileName === NULL) {
1021 $targetFileName = $file->getName();
1023 // File exists and we should abort, let's abort
1024 if ($conflictMode === 'cancel' && $targetFolder->hasFile($targetFileName)) {
1025 throw new Exception\
ExistingTargetFileNameException('The target file already exists.', 1320291063);
1027 // File exists and we should find another name, let's find another one
1028 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($targetFileName)) {
1029 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1031 $sourceStorage = $file->getStorage();
1032 // Call driver method to create a new file from an existing file object,
1033 // and return the new file object
1035 if ($sourceStorage == $this) {
1036 $newFileObject = $this->driver
->copyFileWithinStorage($file, $targetFolder, $targetFileName);
1038 $tempPath = $file->getForLocalProcessing();
1039 $newFileObject = $this->driver
->addFile($tempPath, $targetFolder, $targetFileName);
1041 } catch (Exception\AbstractFileOperationException
$e) {
1044 $this->emitPostFileCopySignal($file, $targetFolder);
1045 return $newFileObject;
1049 * Check if a file has the permission to be uploaded to a Folder/Storage,
1050 * if not throw an exception
1052 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
1053 * @param Folder $targetFolder
1054 * @param string $targetFileName the destination file name $_FILES['file1']['name']
1055 * @param int $uploadedFileSize
1057 * @throws Exception\InsufficientFolderWritePermissionsException
1058 * @throws Exception\UploadException
1059 * @throws Exception\IllegalFileExtensionException
1060 * @throws Exception\UploadSizeException
1061 * @throws Exception\InsufficientUserPermissionsException
1064 protected function checkFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
1065 // Makes sure the user is allowed to upload
1066 if (!$this->checkUserActionPermission('upload', 'File')) {
1067 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to upload files to this storage "' . $this->getUid() . '"', 1322112430);
1069 // Makes sure this is an uploaded file
1070 if (!is_uploaded_file($localFilePath)) {
1071 throw new Exception\
UploadException('The upload has failed, no uploaded file found!', 1322110455);
1073 // Max upload size (kb) for files.
1074 $maxUploadFileSize = \TYPO3\CMS\Core\Utility\GeneralUtility
::getMaxUploadFileSize() * 1024;
1075 if ($uploadedFileSize >= $maxUploadFileSize) {
1076 throw new Exception\
UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
1078 // Check if targetFolder is writable
1079 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1080 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
1082 // Check for a valid file extension
1083 if (!$this->checkFileExtensionPermission($targetFileName)) {
1084 throw new Exception\
IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
1089 * Check if a file has the permission to be copied on a File/Folder/Storage,
1090 * if not throw an exception
1092 * @param FileInterface $file
1093 * @param Folder $targetFolder
1094 * @param string $targetFileName
1097 * @throws Exception\InsufficientFolderWritePermissionsException
1098 * @throws Exception\IllegalFileExtensionException
1099 * @throws Exception\InsufficientFileReadPermissionsException
1100 * @throws Exception\InsufficientUserPermissionsException
1103 protected function checkFileCopyPermissions(FileInterface
$file, Folder
$targetFolder, $targetFileName) {
1104 // Check if targetFolder is within this storage, this should never happen
1105 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1106 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
1108 // Check if user is allowed to copy
1109 if (!$this->checkUserActionPermission('copy', 'File')) {
1110 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to copy files to this storage "' . $this->getUid() . '"', 1319550415);
1112 // Check if $file is readable
1113 if (!$this->checkFileActionPermission('read', $file)) {
1114 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319550425);
1116 // Check if targetFolder is writable
1117 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1118 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
1120 // Check for a valid file extension
1121 if (!$this->checkFileExtensionPermission($targetFileName)) {
1122 throw new Exception\
IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
1127 * Moves a $file into a $targetFolder
1128 * the target folder has to be part of this storage
1130 * previously in t3lib_extFileFunc::func_move()
1132 * @param FileInterface $file
1133 * @param Folder $targetFolder
1134 * @param string $targetFileName an optional destination fileName
1135 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1137 * @throws Exception\ExistingTargetFileNameException
1138 * @return FileInterface
1140 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1141 $this->checkFileMovePermissions($file, $targetFolder);
1142 if ($targetFileName === NULL) {
1143 $targetFileName = $file->getName();
1145 if ($targetFolder->hasFile($targetFileName)) {
1146 // File exists and we should abort, let's abort
1147 if ($conflictMode === 'renameNewFile') {
1148 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1149 } elseif ($conflictMode === 'cancel') {
1150 throw new Exception\
ExistingTargetFileNameException('The target file already exists', 1329850997);
1153 $this->emitPreFileMoveSignal($file, $targetFolder);
1154 $sourceStorage = $file->getStorage();
1155 // Call driver method to move the file that also updates the file
1156 // object properties
1158 if ($sourceStorage == $this) {
1159 $newIdentifier = $this->driver
->moveFileWithinStorage($file, $targetFolder, $targetFileName);
1160 $this->updateFile($file, $newIdentifier);
1162 $tempPath = $file->getForLocalProcessing();
1163 $newIdentifier = $this->driver
->addFileRaw($tempPath, $targetFolder, $targetFileName);
1164 $sourceStorage->driver
->deleteFileRaw($file->getIdentifier());
1165 $this->updateFile($file, $newIdentifier, $this);
1167 } catch (\TYPO3\CMS\Core\Exception
$e) {
1168 echo $e->getMessage();
1170 $this->emitPostFileMoveSignal($file, $targetFolder);
1175 * Updates the properties of a file object with some that are freshly
1176 * fetched from the driver.
1178 * @param AbstractFile $file
1179 * @param string $identifier The identifier of the file. If set, this will overwrite the file object's identifier (use e.g. after moving a file)
1180 * @param ResourceStorage $storage
1183 protected function updateFile(AbstractFile
$file, $identifier = '', $storage = NULL) {
1184 if ($identifier === '') {
1185 $identifier = $file->getIdentifier();
1187 $fileInfo = $this->driver
->getFileInfoByIdentifier($identifier);
1188 // TODO extend mapping
1189 $newProperties = array(
1190 'storage' => $fileInfo['storage'],
1191 'identifier' => $fileInfo['identifier'],
1192 'tstamp' => $fileInfo['mtime'],
1193 'crdate' => $fileInfo['ctime'],
1194 'mime_type' => $fileInfo['mimetype'],
1195 'size' => $fileInfo['size'],
1196 'name' => $fileInfo['name']
1198 if ($storage !== NULL) {
1199 $newProperties['storage'] = $storage->getUid();
1201 $file->updateProperties($newProperties);
1202 $this->getFileRepository()->update($file);
1206 * Checks for permissions to move a file.
1208 * @throws \RuntimeException
1209 * @throws Exception\InsufficientFileReadPermissionsException
1210 * @throws Exception\InsufficientFileWritePermissionsException
1211 * @throws Exception\InsufficientFolderAccessPermissionsException
1212 * @throws Exception\InsufficientUserPermissionsException
1213 * @param FileInterface $file
1214 * @param Folder $targetFolder
1217 protected function checkFileMovePermissions(FileInterface
$file, Folder
$targetFolder) {
1218 // Check if targetFolder is within this storage
1219 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1220 throw new \
RuntimeException();
1222 // Check if user is allowed to move
1223 if (!$this->checkUserActionPermission('move', 'File')) {
1224 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
1226 // Check if $file is readable
1227 if (!$this->checkFileActionPermission('read', $file)) {
1228 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319219349);
1230 // Check if $file is writable
1231 if (!$this->checkFileActionPermission('write', $file)) {
1232 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to move the file "' . $file->getIdentifier() . '\'', 1319219349);
1234 // Check if targetFolder is writable
1235 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1236 throw new Exception\
InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219349);
1241 * Previously in t3lib_extFileFunc::func_rename()
1243 * @param FileInterface $file
1244 * @param string $targetFileName
1246 * @throws Exception\InsufficientFileWritePermissionsException
1247 * @throws Exception\InsufficientFileReadPermissionsException
1248 * @throws Exception\InsufficientUserPermissionsException
1249 * @return FileInterface
1251 // TODO add $conflictMode setting
1252 public function renameFile($file, $targetFileName) {
1253 // The name should be different from the current.
1254 if ($file->getIdentifier() == $targetFileName) {
1257 // Check if user is allowed to rename
1258 if (!$this->checkUserActionPermission('rename', 'File')) {
1259 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to rename files."', 1319219349);
1261 // Check if $file is readable
1262 if (!$this->checkFileActionPermission('read', $file)) {
1263 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319219349);
1265 // Check if $file is writable
1266 if (!$this->checkFileActionPermission('write', $file)) {
1267 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to rename the file "' . $file->getIdentifier() . '\'', 1319219349);
1270 $this->emitPreFileRenameSignal($file, $targetFileName);
1272 // Call driver method to rename the file that also updates the file
1273 // object properties
1275 $newIdentifier = $this->driver
->renameFile($file, $targetFileName);
1276 $this->updateFile($file, $newIdentifier);
1277 $this->getFileRepository()->update($file);
1278 } catch (\RuntimeException
$e) {
1282 $this->emitPostFileRenameSignal($file, $targetFileName);
1288 * Replaces a file with a local file (e.g. a freshly uploaded file)
1290 * @param FileInterface $file
1291 * @param string $localFilePath
1293 * @throws \InvalidArgumentException
1294 * @return FileInterface
1296 public function replaceFile(FileInterface
$file, $localFilePath) {
1297 if (!file_exists($localFilePath)) {
1298 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1300 // TODO check permissions
1301 $this->emitPreFileReplaceSignal($file, $localFilePath);
1302 $result = $this->driver
->replaceFile($file, $localFilePath);
1303 $this->emitPostFileReplaceSignal($file, $localFilePath);
1308 * Adds an uploaded file into the Storage. Previously in t3lib_extFileFunc::file_upload()
1310 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1311 * @param Folder $targetFolder the target folder
1312 * @param string $targetFileName the file name to be written
1313 * @param string $conflictMode possible value are 'cancel', 'replace'
1314 * @return FileInterface The file object
1316 public function addUploadedFile(array $uploadedFileData, Folder
$targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1317 $localFilePath = $uploadedFileData['tmp_name'];
1318 if ($targetFolder === NULL) {
1319 $targetFolder = $this->getDefaultFolder();
1321 if ($targetFileName === NULL) {
1322 $targetFileName = $uploadedFileData['name'];
1324 // Handling $conflictMode is delegated to addFile()
1325 $this->checkFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1326 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1327 return $resultObject;
1330 /********************
1332 ********************/
1334 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1336 * @param Folder $folder
1339 protected function getAllFileObjectsInFolder(Folder
$folder) {
1341 $folderQueue = array($folder);
1342 while (!empty($folderQueue)) {
1343 $folder = array_shift($folderQueue);
1344 foreach ($folder->getSubfolders() as $subfolder) {
1345 $folderQueue[] = $subfolder;
1347 foreach ($folder->getFiles() as $file) {
1348 $files[$file->getIdentifier()] = $file;
1355 * Moves a folder. If you want to move a folder from this storage to another
1356 * one, call this method on the target storage, otherwise you will get an exception.
1358 * @param Folder $folderToMove The folder to move.
1359 * @param Folder $targetParentFolder The target parent folder
1360 * @param string $newFolderName
1361 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1363 * @throws \Exception|\TYPO3\CMS\Core\Exception
1364 * @throws \InvalidArgumentException
1368 public function moveFolder(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1369 $sourceStorage = $folderToMove->getStorage();
1370 $returnObject = NULL;
1371 if (!$targetParentFolder->getStorage() == $this) {
1372 throw new \
InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1374 $newFolderName = $newFolderName ?
$newFolderName : $folderToMove->getName();
1375 // TODO check if folder already exists in $targetParentFolder, handle this conflict then
1376 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1377 // Get all file objects now so we are able to update them after moving the folder
1378 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1380 if ($sourceStorage == $this) {
1381 $fileMappings = $this->driver
->moveFolderWithinStorage($folderToMove, $targetParentFolder, $newFolderName);
1383 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1385 // Update the identifier and storage of all file objects
1386 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1387 $newIdentifier = $fileMappings[$oldIdentifier];
1388 $fileObject->updateProperties(array('storage' => $this, 'identifier' => $newIdentifier));
1389 $this->getFileRepository()->update($fileObject);
1391 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1392 } catch (\TYPO3\CMS\Core\Exception
$e) {
1395 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1396 return $returnObject;
1400 * Moves the given folder from a different storage to the target folder in this storage.
1402 * @param Folder $folderToMove
1403 * @param Folder $targetParentFolder
1404 * @param string $newFolderName
1408 protected function moveFolderBetweenStorages(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName) {
1409 return $this->getDriver()->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1415 * @param Folder $folderToCopy The folder to copy
1416 * @param Folder $targetParentFolder The target folder
1417 * @param string $newFolderName
1418 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1419 * @return Folder The new (copied) folder object
1421 public function copyFolder(Folder
$folderToCopy, Folder
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1422 // TODO implement the $conflictMode handling
1423 // TODO permission checks
1424 $returnObject = NULL;
1425 $newFolderName = $newFolderName ?
$newFolderName : $folderToCopy->getName();
1426 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1427 $sourceStorage = $folderToCopy->getStorage();
1428 // call driver method to move the file
1429 // that also updates the file object properties
1431 if ($sourceStorage == $this) {
1432 $this->driver
->copyFolderWithinStorage($folderToCopy, $targetParentFolder, $newFolderName);
1433 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($newFolderName)->getIdentifier());
1435 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1437 } catch (\TYPO3\CMS\Core\Exception
$e) {
1438 echo $e->getMessage();
1440 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1441 return $returnObject;
1445 * Copy folders between storages
1447 * @param Folder $folderToCopy
1448 * @param Folder $targetParentFolder
1449 * @param string $newFolderName
1453 protected function copyFolderBetweenStorages(Folder
$folderToCopy, Folder
$targetParentFolder, $newFolderName) {
1454 return $this->getDriver()->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1458 * Previously in t3lib_extFileFunc::folder_move()
1460 * @param Folder $folderObject
1461 * @param string $newName
1462 * @throws \Exception
1463 * @throws \InvalidArgumentException
1466 public function renameFolder($folderObject, $newName) {
1469 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1470 throw new \TYPO3\CMS\Core\
Resource\Exception\
InsufficientFileAccessPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1473 $returnObject = NULL;
1474 if ($this->driver
->folderExistsInFolder($newName, $folderObject)) {
1475 throw new \
InvalidArgumentException('The folder ' . $newName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1478 $this->emitPreFolderRenameSignal($folderObject, $newName);
1480 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1482 $fileMappings = $this->driver
->renameFolder($folderObject, $newName);
1483 // Update the identifier of all file objects
1484 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1485 $newIdentifier = $fileMappings[$oldIdentifier];
1486 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1487 $this->getFileRepository()->update($fileObject);
1489 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1490 } catch (\Exception
$e) {
1494 $this->emitPostFolderRenameSignal($folderObject, $newName);
1496 return $returnObject;
1500 * Previously in t3lib_extFileFunc::folder_delete()
1502 * @param Folder $folderObject
1503 * @param bool $deleteRecursively
1504 * @throws \RuntimeException
1505 * @throws Exception\InsufficientFileAccessPermissionsException
1508 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1509 if (!$this->checkFolderActionPermission('remove', $folderObject)) {
1510 throw new Exception\
InsufficientFileAccessPermissionsException('You are not allowed to access the folder "' . $folderObject->getIdentifier() . '\'', 1323423953);
1512 if ($this->driver
->isFolderEmpty($folderObject) && !$deleteRecursively) {
1513 throw new \
RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1516 $this->emitPreFolderDeleteSignal($folderObject);
1518 $result = $this->driver
->deleteFolder($folderObject, $deleteRecursively);
1520 $this->emitPostFolderDeleteSignal($folderObject);
1526 * Returns a list of folders in a given path.
1528 * @param string $path The path to list
1529 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
1530 * @param integer $numberOfItems The number of items to list; if not set, return all items
1531 * @param boolean $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
1532 * @return array Information about the folders found.
1534 public function getFolderList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE) {
1535 $filters = $useFilters === TRUE ?
$this->fileAndFolderNameFilters
: array();
1536 return $this->fetchFolderListFromDriver($path, $start, $numberOfItems, $filters);
1542 * @param int $numberOfItems
1543 * @param array $folderFilterCallbacks
1544 * @param boolean $recursive
1547 public function fetchFolderListFromDriver($path, $start = 0, $numberOfItems = 0, array $folderFilterCallbacks = array(), $recursive = FALSE) {
1548 $items = $this->driver
->getFolderList($path, $start, $numberOfItems, $folderFilterCallbacks, $recursive);
1549 // Exclude the _processed_ folder, so it won't get indexed etc
1550 $processingFolder = $this->getProcessingFolder();
1551 if ($processingFolder && $path == '/') {
1552 $processedFolderIdentifier = $this->processingFolder
->getIdentifier();
1553 $processedFolderIdentifier = trim($processedFolderIdentifier, '/');
1554 if (isset($items[$processedFolderIdentifier])) {
1555 unset($items[$processedFolderIdentifier]);
1558 uksort($items, 'strnatcasecmp');
1563 * Returns TRUE if the specified folder exists.
1565 * @param string $identifier
1568 public function hasFolder($identifier) {
1569 return $this->driver
->folderExists($identifier);
1573 * Checks if the given file exists in the given folder
1575 * @param string $folderName
1576 * @param Folder $folder
1579 public function hasFolderInFolder($folderName, Folder
$folder) {
1580 return $this->driver
->folderExistsInFolder($folderName, $folder);
1584 * Creates a new folder.
1586 * previously in t3lib_extFileFunc::func_newfolder()
1588 * @param string $folderName The new folder name
1589 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
1591 * @throws Exception\InsufficientFolderWritePermissionsException
1592 * @throws \InvalidArgumentException
1593 * @return Folder The new folder object
1595 public function createFolder($folderName, Folder
$parentFolder = NULL) {
1596 if ($parentFolder === NULL) {
1597 $parentFolder = $this->getRootLevelFolder();
1599 if (!$this->driver
->folderExists($parentFolder->getIdentifier())) {
1600 throw new \
InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
1602 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
1603 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
1605 $folderParts = \TYPO3\CMS\Core\Utility\GeneralUtility
::trimExplode('/', $folderName, TRUE);
1606 foreach ($folderParts as $folder) {
1607 // TODO check if folder creation succeeded
1608 if ($this->hasFolderInFolder($folder, $parentFolder)) {
1609 $parentFolder = $this->driver
->getFolderInFolder($folder, $parentFolder);
1611 $parentFolder = $this->driver
->createFolder($folder, $parentFolder);
1614 return $parentFolder;
1618 * Returns the default folder where new files are stored if no other folder is given.
1622 public function getDefaultFolder() {
1623 return $this->driver
->getDefaultFolder();
1627 * @param string $identifier
1629 * @throws Exception\NotInMountPointException
1630 * @throws Exception\FolderDoesNotExistException
1633 public function getFolder($identifier) {
1634 if (!$this->driver
->folderExists($identifier)) {
1635 throw new Exception\
FolderDoesNotExistException('Folder ' . $identifier . ' does not exist.', 1320575630);
1637 $folderObject = $this->driver
->getFolder($identifier);
1638 if ($this->fileMounts
&& !$this->isWithinFileMountBoundaries($folderObject)) {
1639 throw new Exception\
NotInMountPointException('Folder "' . $identifier . '" is not within your mount points.', 1330120649);
1641 return $folderObject;
1646 * Returns the folders on the root level of the storage
1647 * or the first mount point of this storage for this user
1651 public function getRootLevelFolder() {
1652 if (count($this->fileMounts
)) {
1653 $mount = reset($this->fileMounts
);
1654 return $mount['folder'];
1656 return $this->driver
->getRootLevelFolder();
1661 * Emits the configuration pre-processing signal
1665 protected function emitPreProcessConfigurationSignal() {
1666 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreProcessConfiguration
, array($this));
1670 * Emits the configuration post-processing signal
1674 protected function emitPostProcessConfigurationSignal() {
1675 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostProcessConfiguration
, array($this));
1679 * Emits file pre-copy signal
1681 * @param FileInterface $file
1682 * @param Folder $targetFolder
1685 protected function emitPreFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
1686 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileCopy
, array($file, $targetFolder));
1690 * Emits the file post-copy signal
1692 * @param FileInterface $file
1693 * @param Folder $targetFolder
1696 protected function emitPostFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
1697 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileCopy
, array($file, $targetFolder));
1701 * Emits the file pre-move signal
1703 * @param FileInterface $file
1704 * @param Folder $targetFolder
1707 protected function emitPreFileMoveSignal(FileInterface
$file, Folder
$targetFolder) {
1708 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileMove
, array($file, $targetFolder));
1712 * Emits the file post-move signal
1714 * @param FileInterface $file
1715 * @param Folder $targetFolder
1718 protected function emitPostFileMoveSignal(FileInterface
$file, Folder
$targetFolder) {
1719 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileMove
, array($file, $targetFolder));
1723 * Emits the file pre-rename signal
1725 * @param FileInterface $file
1726 * @param $targetFolder
1729 protected function emitPreFileRenameSignal(FileInterface
$file, $targetFolder) {
1730 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileRename
, array($file, $targetFolder));
1734 * Emits the file post-rename signal
1736 * @param FileInterface $file
1737 * @param $targetFolder
1740 protected function emitPostFileRenameSignal(FileInterface
$file, $targetFolder) {
1741 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileRename
, array($file, $targetFolder));
1745 * Emits the file pre-replace signal
1747 * @param FileInterface $file
1748 * @param $localFilePath
1751 protected function emitPreFileReplaceSignal(FileInterface
$file, $localFilePath) {
1752 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileReplace
, array($file, $localFilePath));
1756 * Emits the file post-replace signal
1758 * @param FileInterface $file
1759 * @param $localFilePath
1762 protected function emitPostFileReplaceSignal(FileInterface
$file, $localFilePath) {
1763 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileReplace
, array($file, $localFilePath));
1767 * Emits the file pre-deletion signal
1769 * @param FileInterface $file
1772 protected function emitPreFileDeleteSignal(FileInterface
$file) {
1773 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileDelete
, array($file));
1777 * Emits the file post-deletion signal
1779 * @param FileInterface $file
1782 protected function emitPostFileDeleteSignal(FileInterface
$file) {
1783 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileDelete
, array($file));
1787 * Emits the folder pre-copy signal
1789 * @param Folder $folder
1790 * @param Folder $targetFolder
1794 protected function emitPreFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
1795 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderCopy
, array($folder, $targetFolder));
1799 * Emits the folder post-copy signal
1801 * @param Folder $folder
1802 * @param Folder $targetFolder
1806 protected function emitPostFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
1807 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderCopy
, array($folder, $targetFolder));
1811 * Emits the folder pre-move signal
1813 * @param Folder $folder
1814 * @param Folder $targetFolder
1818 protected function emitPreFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName) {
1819 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderMove
, array($folder, $targetFolder));
1823 * Emits the folder post-move signal
1825 * @param Folder $folder
1826 * @param Folder $targetFolder
1830 protected function emitPostFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName) {
1831 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderMove
, array($folder, $targetFolder));
1835 * Emits the folder pre-rename signal
1837 * @param Folder $folder
1841 protected function emitPreFolderRenameSignal(Folder
$folder, $newName) {
1842 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderRename
, array($folder, $newName));
1846 * Emits the folder post-rename signal
1848 * @param Folder $folder
1852 protected function emitPostFolderRenameSignal(Folder
$folder, $newName) {
1853 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderRename
, array($folder, $newName));
1857 * Emits the folder pre-deletion signal
1859 * @param Folder $folder
1862 protected function emitPreFolderDeleteSignal(Folder
$folder) {
1863 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderDelete
, array($folder));
1867 * Emits folder postdeletion signal.
1869 * @param Folder $folder
1872 protected function emitPostFolderDeleteSignal(Folder
$folder) {
1873 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderDelete
, array($folder));
1877 * Emits file pre-processing signal when generating a public url for a file or folder.
1879 * @param ResourceInterface $resourceObject
1880 * @param boolean $relativeToCurrentScript
1881 * @param array $urlData
1883 protected function emitPreGeneratePublicUrl(ResourceInterface
$resourceObject, $relativeToCurrentScript, array $urlData) {
1884 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreGeneratePublicUrl
, array($this, $this->driver
, $resourceObject, $relativeToCurrentScript, $urlData));
1888 * Returns the destination path/fileName of a unique fileName/foldername in that path.
1889 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
1890 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
1892 * @param Folder $folder
1893 * @param string $theFile The input fileName to check
1894 * @param boolean $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
1896 * @throws \RuntimeException
1897 * @return string A unique fileName inside $folder, based on $theFile.
1898 * @see t3lib_basicFileFunc::getUniqueName()
1900 // TODO check if this should be moved back to Folder
1901 protected function getUniqueName(Folder
$folder, $theFile, $dontCheckForUnique = FALSE) {
1902 static $maxNumber = 99, $uniqueNamePrefix = '';
1903 // Fetches info about path, name, extention of $theFile
1904 $origFileInfo = \TYPO3\CMS\Core\Utility\GeneralUtility
::split_fileref($theFile);
1906 if ($uniqueNamePrefix) {
1907 $origFileInfo['file'] = $uniqueNamePrefix . $origFileInfo['file'];
1908 $origFileInfo['filebody'] = $uniqueNamePrefix . $origFileInfo['filebody'];
1910 // Check if the file exists and if not - return the fileName...
1911 $fileInfo = $origFileInfo;
1912 // The destinations file
1913 $theDestFile = $fileInfo['file'];
1914 // If the file does NOT exist we return this fileName
1915 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder) ||
$dontCheckForUnique) {
1916 return $theDestFile;
1918 // Well the fileName in its pure form existed. Now we try to append
1919 // numbers / unique-strings and see if we can find an available fileName
1920 // This removes _xx if appended to the file
1921 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']);
1922 $theOrigExt = $origFileInfo['realFileext'] ?
'.' . $origFileInfo['realFileext'] : '';
1923 for ($a = 1; $a <= $maxNumber +
1; $a++
) {
1924 // First we try to append numbers
1925 if ($a <= $maxNumber) {
1926 $insert = '_' . sprintf('%02d', $a);
1928 // TODO remove constant 6
1929 $insert = '_' . substr(md5(uniqId('')), 0, 6);
1931 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
1932 // The destinations file
1933 $theDestFile = $theTestFile;
1934 // If the file does NOT exist we return this fileName
1935 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder)) {
1936 return $theDestFile;
1939 throw new \
RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
1943 * Get the SignalSlot dispatcher
1945 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
1947 protected function getSignalSlotDispatcher() {
1948 if (!isset($this->signalSlotDispatcher
)) {
1949 $this->signalSlotDispatcher
= $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
1951 return $this->signalSlotDispatcher
;
1955 * Get the ObjectManager
1957 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
1959 protected function getObjectManager() {
1960 return \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
1964 * @return ResourceFactory
1966 protected function getFileFactory() {
1967 return \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
1971 * @return \TYPO3\CMS\Core\Resource\FileRepository
1973 protected function getFileRepository() {
1974 return \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileRepository');
1978 * @return Service\FileProcessingService
1980 protected function getFileProcessingService() {
1981 if (!$this->fileProcessingService
) {
1982 $this->fileProcessingService
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\Service\\FileProcessingService', $this, $this->driver
);
1984 return $this->fileProcessingService
;
1988 * Getter function to return the folder where the files can
1989 * be processed. does not check for access rights here
1991 * @todo check if we need to implement "is writable" capability
1992 * @return Folder the processing folder, can be empty as well, if the storage doesn't have a processing folder
1994 public function getProcessingFolder() {
1995 if (!isset($this->processingFolder
)) {
1996 $processingFolder = self
::DEFAULT_ProcessingFolder
;
1997 if (!empty($this->storageRecord
['processingfolder'])) {
1998 $processingFolder = $this->storageRecord
['processingfolder'];
2000 $processingFolder = '/' . trim($processingFolder, '/') . '/';
2001 // this way, we also worry about deeplinked folders like typo3temp/_processed_
2002 if ($this->driver
->folderExists($processingFolder) === FALSE) {
2003 $processingFolderParts = explode('/', $processingFolder);
2004 $parentFolder = $this->driver
->getRootLevelFolder();
2005 foreach ($processingFolderParts as $folderPart) {
2006 if (!$this->driver
->folderExistsInFolder($folderPart, $parentFolder)) {
2007 $parentFolder = $this->driver
->createFolder($folderPart, $parentFolder);
2009 $parentFolder = $parentFolder->getSubfolder($folderPart);
2013 $this->processingFolder
= $this->driver
->getFolder($processingFolder);
2015 return $this->processingFolder
;