2 namespace TYPO3\CMS\Core\
Resource;
4 /***************************************************************
7 * (c) 2011-2013 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 use TYPO3\CMS\Core\Utility\GeneralUtility
;
31 use TYPO3\CMS\Core\Utility\PathUtility
;
34 * A "mount point" inside the TYPO3 file handling.
36 * A "storage" object handles
37 * - abstraction to the driver
38 * - permissions (from the driver, and from the user, + capabilities)
39 * - an entry point for files, folders, and for most other operations
41 * == Driver entry point
42 * The driver itself, that does the actual work on the file system,
43 * is inside the storage but completely shadowed by
44 * the storage, as the storage also handles the abstraction to the
47 * The storage can be on the local system, but can also be on a remote
48 * system. The combination of driver + configurable capabilities (storage
49 * is read-only e.g.) allows for flexible uses.
52 * == Permission system
53 * As all requests have to run through the storage, the storage knows about the
54 * permissions of a BE/FE user, the file permissions / limitations of the driver
55 * and has some configurable capabilities.
56 * Additionally, a BE user can use "filemounts" (known from previous installations)
57 * to limit his/her work-zone to only a subset (identifier and its subfolders/subfolders)
60 * Check 1: "User Permissions" [is the user allowed to write a file) [is the user allowed to write a file]
61 * Check 2: "File Mounts" of the User (act as subsets / filters to the identifiers) [is the user allowed to do something in this folder?]
62 * Check 3: "Capabilities" of Storage (then: of Driver) [is the storage/driver writable?]
63 * Check 4: "File permissions" of the Driver [is the folder writable?]
65 * @author Andreas Wolf <andreas.wolf@typo3.org>
66 * @author Ingmar Schlecht <ingmar@typo3.org>
68 class ResourceStorage
{
70 const SIGNAL_PreProcessConfiguration
= 'preProcessConfiguration';
71 const SIGNAL_PostProcessConfiguration
= 'postProcessConfiguration';
72 const SIGNAL_PreFileCopy
= 'preFileCopy';
73 const SIGNAL_PostFileCopy
= 'postFileCopy';
74 const SIGNAL_PreFileMove
= 'preFileMove';
75 const SIGNAL_PostFileMove
= 'postFileMove';
76 const SIGNAL_PreFileDelete
= 'preFileDelete';
77 const SIGNAL_PostFileDelete
= 'postFileDelete';
78 const SIGNAL_PreFileRename
= 'preFileRename';
79 const SIGNAL_PostFileRename
= 'postFileRename';
80 const SIGNAL_PreFileReplace
= 'preFileReplace';
81 const SIGNAL_PostFileReplace
= 'postFileReplace';
82 const SIGNAL_PreFolderCopy
= 'preFolderCopy';
83 const SIGNAL_PostFolderCopy
= 'postFolderCopy';
84 const SIGNAL_PreFolderMove
= 'preFolderMove';
85 const SIGNAL_PostFolderMove
= 'postFolderMove';
86 const SIGNAL_PreFolderDelete
= 'preFolderDelete';
87 const SIGNAL_PostFolderDelete
= 'postFolderDelete';
88 const SIGNAL_PreFolderRename
= 'preFolderRename';
89 const SIGNAL_PostFolderRename
= 'postFolderRename';
90 const SIGNAL_PreGeneratePublicUrl
= 'preGeneratePublicUrl';
92 * The storage driver instance belonging to this storage.
94 * @var Driver\AbstractDriver
99 * The database record for this storage
103 protected $storageRecord;
106 * The configuration belonging to this storage (decoded from the configuration field).
110 protected $configuration;
113 * The base URI to this storage.
120 * @var Service\FileProcessingService
122 protected $fileProcessingService;
125 * Whether to check if file or folder is in user mounts
126 * and the action is allowed for a user
127 * Default is FALSE so that resources are accessible for
128 * front end rendering or admins.
132 protected $evaluatePermissions = FALSE;
135 * User filemounts, added as an array, and used as filters
139 protected $fileMounts = array();
142 * The file permissions of the user (and their group) merged together and
143 * available as an array
147 protected $userPermissions = array();
150 * The capabilities of this storage as defined in the storage record.
151 * Also see the CAPABILITY_* constants below
155 protected $capabilities;
158 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
160 protected $signalSlotDispatcher;
163 * Capability for being browsable by (backend) users
165 const CAPABILITY_BROWSABLE
= 1;
167 * Capability for publicly accessible storages (= accessible from the web)
169 const CAPABILITY_PUBLIC
= 2;
171 * Capability for writable storages. This only signifies writability in
172 * general - this might also be further limited by configuration.
174 const CAPABILITY_WRITABLE
= 4;
176 * Name of the default processing folder
178 const DEFAULT_ProcessingFolder
= '_processed_';
182 protected $processingFolder;
185 * whether this storage is online or offline in this request
189 protected $isOnline = NULL;
192 * The filters used for the files and folder names.
196 protected $fileAndFolderNameFilters = array();
199 * Constructor for a storage object.
201 * @param Driver\AbstractDriver $driver
202 * @param array $storageRecord The storage record row from the database
204 public function __construct(Driver\AbstractDriver
$driver, array $storageRecord) {
205 $this->storageRecord
= $storageRecord;
206 $this->configuration
= ResourceFactory
::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
207 $this->driver
= $driver;
208 $this->driver
->setStorage($this);
210 $this->driver
->processConfiguration();
211 } catch (Exception\InvalidConfigurationException
$e) {
212 // configuration error
213 // mark this storage as permanently unusable
214 $this->markAsPermanentlyOffline();
216 $this->driver
->initialize();
217 $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);
218 // TODO do not set the "public" capability if no public URIs can be generated
219 $this->processConfiguration();
220 $this->resetFileAndFolderNameFiltersToDefault();
224 * Gets the configuration
228 public function getConfiguration() {
229 return $this->configuration
;
233 * Sets the configuration.
235 * @param array $configuration
237 public function setConfiguration(array $configuration) {
238 $this->configuration
= $configuration;
242 * Gets the storage record.
246 public function getStorageRecord() {
247 return $this->storageRecord
;
251 * Processes the configuration of this storage.
253 * @throws \InvalidArgumentException If a required configuration option is not set or has an invalid value.
256 protected function processConfiguration() {
257 $this->emitPreProcessConfigurationSignal();
258 if (isset($this->configuration
['baseUri'])) {
259 $this->baseUri
= rtrim($this->configuration
['baseUri'], '/') . '/';
261 $this->emitPostProcessConfigurationSignal();
265 * Returns the base URI of this storage; all files are reachable via URLs
266 * beginning with this string.
270 public function getBaseUri() {
271 return $this->baseUri
;
275 * Sets the storage that belongs to this storage.
277 * @param Driver\AbstractDriver $driver
278 * @return ResourceStorage
280 public function setDriver(Driver\AbstractDriver
$driver) {
281 $this->driver
= $driver;
286 * Returns the driver object belonging to this storage.
288 * @return Driver\AbstractDriver
290 protected function getDriver() {
291 return $this->driver
;
295 * Deprecated function, don't use it. Will be removed in some later revision.
297 * @param string $identifier
299 * @throws \BadMethodCallException
301 public function getFolderByIdentifier($identifier) {
302 throw new \
BadMethodCallException('Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFolderByIdentifier() has been renamed to just getFolder(). Please fix the method call.', 1333754514);
306 * Deprecated function, don't use it. Will be removed in some later revision.
308 * @param string $identifier
310 * @throws \BadMethodCallException
312 public function getFileByIdentifier($identifier) {
313 throw new \
BadMethodCallException('Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFileByIdentifier() has been renamed to just getFileInfoByIdentifier(). ' . 'Please fix the method call.', 1333754533);
317 * Returns the name of this storage.
321 public function getName() {
322 return $this->storageRecord
['name'];
326 * Returns the uid of this storage.
330 public function getUid() {
331 return (int) $this->storageRecord
['uid'];
335 * Tells whether there are children in this storage
339 public function hasChildren() {
343 /*********************************
345 ********************************/
347 * Returns the capabilities of this storage.
350 * @see CAPABILITY_* constants
352 public function getCapabilities() {
353 return (int) $this->capabilities
;
357 * Returns TRUE if this storage has the given capability.
359 * @param integer $capability A capability, as defined in a CAPABILITY_* constant
362 protected function hasCapability($capability) {
363 return ($this->capabilities
& $capability) == $capability;
367 * Returns TRUE if this storage is publicly available. This is just a
368 * configuration option and does not mean that it really *is* public. OTOH
369 * a storage that is marked as not publicly available will trigger the file
370 * publishing mechanisms of TYPO3.
374 public function isPublic() {
375 return $this->hasCapability(self
::CAPABILITY_PUBLIC
);
379 * Returns TRUE if this storage is writable. This is determined by the
380 * driver and the storage configuration; user permissions are not taken into account.
384 public function isWritable() {
385 return $this->hasCapability(self
::CAPABILITY_WRITABLE
);
389 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
393 public function isBrowsable() {
394 return $this->isOnline() && $this->hasCapability(self
::CAPABILITY_BROWSABLE
);
398 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
402 public function isOnline() {
403 if ($this->isOnline
=== NULL) {
404 if ($this->getUid() === 0) {
405 $this->isOnline
= TRUE;
407 // the storage is not marked as online for a longer time
408 if ($this->storageRecord
['is_online'] == 0) {
409 $this->isOnline
= FALSE;
411 if ($this->isOnline
!== FALSE) {
412 // all files are ALWAYS available in the frontend
413 if (TYPO3_MODE
=== 'FE') {
414 $this->isOnline
= TRUE;
416 // check if the storage is disabled temporary for now
417 $registryObject = GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Registry');
418 $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
419 if ($offlineUntil && $offlineUntil > time()) {
420 $this->isOnline
= FALSE;
422 $this->isOnline
= TRUE;
427 return $this->isOnline
;
431 * blow the "fuse" and mark the storage as offline
432 * can only be modified by an admin
433 * typically this is only done if the configuration is wrong
435 public function markAsPermanentlyOffline() {
436 if ($this->getUid() > 0) {
437 // @todo: move this to the storage repository
438 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_file_storage', 'uid=' . intval($this->getUid()), array('is_online' => 0));
440 $this->storageRecord
['is_online'] = 0;
441 $this->isOnline
= FALSE;
445 * mark this storage as offline
447 * non-permanent: this typically happens for remote storages
448 * that are "flaky" and not available all the time
449 * mark this storage as offline for the next 5 minutes
453 public function markAsTemporaryOffline() {
454 $registryObject = GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Registry');
455 $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() +
60 * 5);
456 $this->storageRecord
['is_online'] = 0;
457 $this->isOnline
= FALSE;
460 /*********************************
461 * User Permissions / File Mounts
462 ********************************/
464 * Adds a filemount as a "filter" for users to only work on a subset of a
467 * @param string $folderIdentifier
468 * @param array $additionalData
470 * @throws Exception\FolderDoesNotExistException
473 public function addFileMount($folderIdentifier, $additionalData = array()) {
474 // check for the folder before we add it as a filemount
475 if ($this->driver
->folderExists($folderIdentifier) === FALSE) {
476 // if there is an error, this is important and should be handled
477 // as otherwise the user would see the whole storage without any restrictions for the filemounts
478 throw new Exception\
FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
480 $folderObject = $this->driver
->getFolder($folderIdentifier);
481 if (empty($additionalData)) {
482 $additionalData = array(
483 'path' => $folderIdentifier,
484 'title' => $folderIdentifier,
485 'folder' => $folderObject
488 $additionalData['folder'] = $folderObject;
489 if (!isset($additionalData['title'])) {
490 $additionalData['title'] = $folderIdentifier;
493 $this->fileMounts
[$folderIdentifier] = $additionalData;
497 * Returns all file mounts that are registered with this storage.
501 public function getFileMounts() {
502 return $this->fileMounts
;
506 * Checks if the given subject is within one of the registered user
507 * filemounts. If not, working with the file is not permitted for the user.
512 public function isWithinFileMountBoundaries($subject) {
513 if (!$this->evaluatePermissions
) {
516 $isWithinFilemount = FALSE;
518 $subject = $this->getRootLevelFolder();
520 $identifier = $subject->getIdentifier();
522 // Allow access to processing folder
523 if ($this->driver
->isWithin($this->getProcessingFolder(), $identifier)) {
524 $isWithinFilemount = TRUE;
526 // Check if the identifier of the subject is within at
527 // least one of the file mounts
528 foreach ($this->fileMounts
as $fileMount) {
529 if ($this->driver
->isWithin($fileMount['folder'], $identifier)) {
530 $isWithinFilemount = TRUE;
535 return $isWithinFilemount;
539 * @param $evaluatePermissions
541 public function setEvaluatePermissions($evaluatePermissions) {
542 $this->evaluatePermissions
= (boolean
) $evaluatePermissions;
546 * Sets the user permissions of the storage
548 * @param array $userPermissions
551 public function setUserPermissions(array $userPermissions) {
552 $this->userPermissions
= $userPermissions;
556 * Check if the ACL settings allow for a certain action
557 * (is a user allowed to read a file or copy a folder)
559 * @param string $action
560 * @param string $type either File or Folder
563 public function checkUserActionPermission($action, $type) {
564 if (!$this->evaluatePermissions
) {
569 if (!empty($this->userPermissions
[strtolower($action) . ucfirst(strtolower($type))])) {
577 * Check if a file operation (= action) is allowed on a
578 * File/Folder/Storage (= subject).
580 * This method, by design, does not throw exceptions or do logging.
581 * Besides the usage from other methods in this class, it is also used by
582 * the File List UI to check whether an action is allowed and whether action
583 * related UI elements should thus be shown (move icon, edit icon, etc.)
585 * @param string $action, can be read, write, delete
586 * @param FileInterface $file
589 public function checkFileActionPermission($action, FileInterface
$file) {
590 $isProcessedFile = $file instanceof ProcessedFile
;
591 // Check 1: Does the user have permission to perform the action? e.g. "readFile"
592 if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === FALSE) {
595 // Check 2: No action allowed on files for denied file extensions
596 if (!$this->checkFileExtensionPermission($file->getName())) {
599 // Check 3: Does the user have the right to perform the action?
600 // (= is he within the file mount borders)
601 if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file)) {
604 $isReadCheck = FALSE;
605 if (in_array($action, array('read', 'copy', 'move'), TRUE)) {
608 $isWriteCheck = FALSE;
609 if (in_array($action, array('add', 'write', 'move', 'rename', 'unzip', 'delete'), TRUE)) {
610 $isWriteCheck = TRUE;
614 if (!$isProcessedFile) {
615 $isMissing = $file->isMissing();
618 // Check 4: Check the capabilities of the storage (and the driver)
619 if ($isWriteCheck && ($isMissing ||
!$this->isWritable())) {
622 // Check 5: "File permissions" of the driver (only when file isn't marked as missing)
624 $filePermissions = $this->driver
->getFilePermissions($file);
625 if ($isReadCheck && !$filePermissions['r']) {
628 if ($isWriteCheck && !$filePermissions['w']) {
636 * Check if a folder operation (= action) is allowed on a Folder
638 * This method, by design, does not throw exceptions or do logging.
639 * See the checkFileActionPermission() method above for the reasons.
641 * @param string $action
642 * @param Folder $folder
645 public function checkFolderActionPermission($action, Folder
$folder = NULL) {
646 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
647 if ($this->checkUserActionPermission($action, 'Folder') === FALSE) {
651 // If we do not have a folder here, we cannot do further checks
652 if ($folder === NULL) {
656 // Check 2: Does the user has the right to perform the action?
657 // (= is he within the file mount borders)
658 if (!$this->isWithinFileMountBoundaries($folder)) {
661 $isReadCheck = FALSE;
662 if (in_array($action, array('read', 'copy'), TRUE)) {
665 $isWriteCheck = FALSE;
666 if (in_array($action, array('add', 'move', 'write', 'delete', 'rename'), TRUE)) {
667 $isWriteCheck = TRUE;
669 // Check 3: Check the capabilities of the storage (and the driver)
670 if ($isReadCheck && !$this->isBrowsable()) {
673 if ($isWriteCheck && !$this->isWritable()) {
676 // Check 4: "Folder permissions" of the driver
677 $folderPermissions = $this->driver
->getFolderPermissions($folder);
678 if ($isReadCheck && !$folderPermissions['r']) {
681 if ($isWriteCheck && !$folderPermissions['w']) {
688 * If the fileName is given, check it against the
689 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed
691 * @param string $fileName Full filename
692 * @return boolean TRUE if extension/filename is allowed
694 protected function checkFileExtensionPermission($fileName) {
695 if (!$this->evaluatePermissions
) {
698 $fileName = $this->driver
->sanitizeFileName($fileName);
699 $isAllowed = GeneralUtility
::verifyFilenameAgainstDenyPattern($fileName);
701 $fileInfo = GeneralUtility
::split_fileref($fileName);
702 // Set up the permissions for the file extension
703 $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
704 $fileExtensionPermissions['allow'] = GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['allow']));
705 $fileExtensionPermissions['deny'] = GeneralUtility
::uniqueList(strtolower($fileExtensionPermissions['deny']));
706 $fileExtension = strtolower($fileInfo['fileext']);
707 if ($fileExtension !== '') {
708 // If the extension is found amongst the allowed types, we return TRUE immediately
709 if ($fileExtensionPermissions['allow'] === '*' || GeneralUtility
::inList($fileExtensionPermissions['allow'], $fileExtension)) {
712 // If the extension is found amongst the denied types, we return FALSE immediately
713 if ($fileExtensionPermissions['deny'] === '*' || GeneralUtility
::inList($fileExtensionPermissions['deny'], $fileExtension)) {
716 // If no match we return TRUE
719 if ($fileExtensionPermissions['allow'] === '*') {
722 if ($fileExtensionPermissions['deny'] === '*') {
732 * @param Folder $folder If a folder is given, mountpoits are checked. If not only user folder read permissions are checked.
734 * @throws Exception\InsufficientFolderAccessPermissionsException
736 protected function assureFolderReadPermission(Folder
$folder = NULL) {
737 if (!$this->checkFolderActionPermission('read', $folder)) {
738 throw new Exception\
InsufficientFolderAccessPermissionsException('You are not allowed to access the given folder', 1375955684);
743 * @param Folder $folder If a folder is given, mountpoits are checked. If not only user folder delete permissions are checked.
744 * @param boolean $checkDeleteRecursively
746 * @throws Exception\InsufficientFolderAccessPermissionsException
747 * @throws Exception\InsufficientFolderWritePermissionsException
748 * @throws Exception\InsufficientUserPermissionsException
750 protected function assureFolderDeletePermission(Folder
$folder, $checkDeleteRecursively) {
751 // Check user permissions for recursive deletion if it is requested
752 if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
753 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
755 // Check user action permission
756 if (!$this->checkFolderActionPermission('delete', $folder)) {
757 throw new Exception\
InsufficientFolderAccessPermissionsException('You are not allowed to delete the given folder', 1377779039);
759 // Check if the user has write permissions to folders
760 // Would be good if we could check for actual write permissions in the containig folder
761 // but we cannot since we have no access to the containing folder of this file.
762 if (!$this->checkUserActionPermission('write', 'Folder')) {
763 throw new Exception\
InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
768 * @param FileInterface $file
770 * @throws Exception\InsufficientFileAccessPermissionsException
771 * @throws Exception\IllegalFileExtensionException
773 protected function assureFileReadPermission(FileInterface
$file) {
774 if (!$this->checkFileActionPermission('read', $file)) {
775 throw new Exception\
InsufficientFileAccessPermissionsException('You are not allowed to access that file.', 1375955429);
777 if (!$this->checkFileExtensionPermission($file->getName())) {
778 throw new Exception\
IllegalFileExtensionException('You are not allowed to use that file extension', 1375955430);
783 * @param FileInterface $file
785 * @throws Exception\IllegalFileExtensionException
786 * @throws Exception\InsufficientFileWritePermissionsException
787 * @throws Exception\InsufficientUserPermissionsException
789 protected function assureFileWritePermissions(FileInterface
$file) {
790 // Check if user is allowed to write the file and $file is writable
791 if (!$this->checkFileActionPermission('write', $file)) {
792 throw new Exception\
InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
794 if (!$this->checkFileExtensionPermission($file->getName())) {
795 throw new Exception\
IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
800 * @param FileInterface $file
802 * @throws Exception\IllegalFileExtensionException
803 * @throws Exception\InsufficientFileWritePermissionsException
804 * @throws Exception\InsufficientFolderWritePermissionsException
806 protected function assureFileDeletePermissions(FileInterface
$file) {
807 // Check for disallowed file extensions
808 if (!$this->checkFileExtensionPermission($file->getName())) {
809 throw new Exception\
IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
811 // Check further permissions if file is not a processed file
812 if (!$file instanceof ProcessedFile
) {
813 // Check if user is allowed to delete the file and $file is writable
814 if (!$this->checkFileActionPermission('delete', $file)) {
815 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '\'', 1319550425);
817 // Check if the user has write permissions to folders
818 // Would be good if we could check for actual write permissions in the containig folder
819 // but we cannot since we have no access to the containing folder of this file.
820 if (!$this->checkUserActionPermission('write', 'Folder')) {
821 throw new Exception\
InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
827 * Check if a file has the permission to be uploaded to a Folder/Storage,
828 * if not throw an exception
830 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
831 * @param Folder $targetFolder
832 * @param string $targetFileName the destination file name $_FILES['file1']['name']
834 * @throws Exception\InsufficientFolderWritePermissionsException
835 * @throws Exception\UploadException
836 * @throws Exception\IllegalFileExtensionException
837 * @throws Exception\UploadSizeException
838 * @throws Exception\InsufficientUserPermissionsException
841 protected function assureFileAddPermissions($localFilePath, $targetFolder, $targetFileName) {
842 // Check for a valid file extension
843 if (!$this->checkFileExtensionPermission($targetFileName) ||
($localFilePath && !$this->checkFileExtensionPermission($localFilePath))) {
844 throw new Exception\
IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
846 // Makes sure the user is allowed to upload
847 if (!$this->checkUserActionPermission('add', 'File')) {
848 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
850 // Check if targetFolder is writable
851 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
852 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
857 * Check if a file has the permission to be uploaded to a Folder/Storage,
858 * if not throw an exception
860 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
861 * @param Folder $targetFolder
862 * @param string $targetFileName the destination file name $_FILES['file1']['name']
863 * @param integer $uploadedFileSize
865 * @throws Exception\InsufficientFolderWritePermissionsException
866 * @throws Exception\UploadException
867 * @throws Exception\IllegalFileExtensionException
868 * @throws Exception\UploadSizeException
869 * @throws Exception\InsufficientUserPermissionsException
872 protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
873 // Makes sure this is an uploaded file
874 if (!is_uploaded_file($localFilePath)) {
875 throw new Exception\
UploadException('The upload has failed, no uploaded file found!', 1322110455);
877 // Max upload size (kb) for files.
878 $maxUploadFileSize = GeneralUtility
::getMaxUploadFileSize() * 1024;
879 if ($uploadedFileSize >= $maxUploadFileSize) {
880 unlink($localFilePath);
881 throw new Exception\
UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
883 $this->assureFileAddPermissions($localFilePath, $targetFolder, $targetFileName);
887 * Checks for permissions to move a file.
889 * @throws \RuntimeException
890 * @throws Exception\InsufficientFileReadPermissionsException
891 * @throws Exception\InsufficientFileWritePermissionsException
892 * @throws Exception\InsufficientFolderAccessPermissionsException
893 * @throws Exception\InsufficientUserPermissionsException
894 * @param FileInterface $file
895 * @param Folder $targetFolder
896 * @param string $targetFileName
899 protected function assureFileMovePermissions(FileInterface
$file, Folder
$targetFolder, $targetFileName) {
900 // Check if targetFolder is within this storage
901 if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
902 throw new \
RuntimeException();
904 // Check for a valid file extension
905 if (!$this->checkFileExtensionPermission($targetFileName)) {
906 throw new Exception\
IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
908 // Check if user is allowed to move and $file is readable and writable
909 if (!$this->checkFileActionPermission('move', $file)) {
910 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
912 // Check if target folder is writable
913 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
914 throw new Exception\
InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219349);
919 * Checks for permissions to rename a file.
921 * @param FileInterface $file
922 * @param string $targetFileName
923 * @throws Exception\InsufficientFileWritePermissionsException
924 * @throws Exception\IllegalFileExtensionException
925 * @throws Exception\InsufficientFileReadPermissionsException
926 * @throws Exception\InsufficientUserPermissionsException
929 protected function assureFileRenamePermissions(FileInterface
$file, $targetFileName) {
930 // Check if file extension is allowed
931 if (!$this->checkFileExtensionPermission($targetFileName) ||
!$this->checkFileExtensionPermission($file->getName())) {
932 throw new Exception\
IllegalFileExtensionException('You are not allowed to rename a file with to this extension', 1371466663);
934 // Check if user is allowed to rename
935 if (!$this->checkFileActionPermission('rename', $file)) {
936 throw new Exception\
InsufficientUserPermissionsException('You are not allowed to rename files."', 1319219349);
938 // Check if the user is allowed to write to folders
939 // Although it would be good to check, we cannot check here if the folder actually is writable
940 // because we do not know in which folder the file resides.
941 // So we rely on the driver to throw an exception in case the renaming failed.
942 if (!$this->checkFolderActionPermission('write')) {
943 throw new Exception\
InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219349);
948 * Check if a file has the permission to be copied on a File/Folder/Storage,
949 * if not throw an exception
951 * @param FileInterface $file
952 * @param Folder $targetFolder
953 * @param string $targetFileName
956 * @throws Exception\InsufficientFolderWritePermissionsException
957 * @throws Exception\IllegalFileExtensionException
958 * @throws Exception\InsufficientFileReadPermissionsException
959 * @throws Exception\InsufficientUserPermissionsException
962 protected function assureFileCopyPermissions(FileInterface
$file, Folder
$targetFolder, $targetFileName) {
963 // Check if targetFolder is within this storage, this should never happen
964 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
965 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
967 // Check if user is allowed to copy
968 if (!$this->checkFileActionPermission('copy', $file)) {
969 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '\'', 1319550425);
971 // Check if targetFolder is writable
972 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
973 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
975 // Check for a valid file extension
976 if (!$this->checkFileExtensionPermission($targetFileName) ||
!$this->checkFileExtensionPermission($file->getName())) {
977 throw new Exception\
IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
982 * Check if a file has the permission to be copied on a File/Folder/Storage,
983 * if not throw an exception
985 * @param FolderInterface $folderToCopy
986 * @param FolderInterface $targetParentFolder
989 * @throws Exception\InsufficientFolderWritePermissionsException
990 * @throws Exception\IllegalFileExtensionException
991 * @throws Exception\InsufficientFileReadPermissionsException
992 * @throws Exception\InsufficientUserPermissionsException
995 protected function assureFolderCopyPermissions(FolderInterface
$folderToCopy, FolderInterface
$targetParentFolder) {
996 // Check if targetFolder is within this storage, this should never happen
997 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
998 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
1000 // Check if user is allowed to copy and the folder is readable
1001 if (!$this->checkFolderActionPermission('copy', $folderToCopy)) {
1002 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '\'', 1377777629);
1004 // Check if targetFolder is writable
1005 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1006 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
1011 * Check if a file has the permission to be copied on a File/Folder/Storage,
1012 * if not throw an exception
1014 * @param FolderInterface $folderToMove
1015 * @param FolderInterface $targetParentFolder
1017 * @throws \InvalidArgumentException
1018 * @throws Exception\InsufficientFolderWritePermissionsException
1019 * @throws Exception\IllegalFileExtensionException
1020 * @throws Exception\InsufficientFileReadPermissionsException
1021 * @throws Exception\InsufficientUserPermissionsException
1024 protected function assureFolderMovePermissions(FolderInterface
$folderToMove, FolderInterface
$targetParentFolder) {
1025 // Check if targetFolder is within this storage, this should never happen
1026 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1027 throw new \
InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1029 // Check if user is allowed to move and the folder is writable
1030 // In fact we would need to check if the parent folder of the folder to move is writable also
1031 // But as of now we cannot extract the parent folder from this folder
1032 if (!$this->checkFolderActionPermission('move', $folderToMove)) {
1033 throw new Exception\
InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '\'', 1377778045);
1035 // Check if targetFolder is writable
1036 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1037 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1041 /********************
1043 ********************/
1045 * Moves a file from the local filesystem to this storage.
1047 * @param string $localFilePath The file on the server's hard disk to add.
1048 * @param Folder $targetFolder The target path, without the fileName
1049 * @param string $targetFileName The fileName. If not set, the local file name is used.
1050 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
1052 * @throws \InvalidArgumentException
1053 * @throws Exception\ExistingTargetFileNameException
1054 * @return FileInterface
1056 public function addFile($localFilePath, Folder
$targetFolder, $targetFileName = '', $conflictMode = 'changeName') {
1057 $localFilePath = PathUtility
::getCanonicalPath($localFilePath);
1058 if (!file_exists($localFilePath)) {
1059 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1061 $this->assureFileAddPermissions($localFilePath, $targetFolder, $targetFileName);
1062 $targetFolder = $targetFolder ?
$targetFolder : $this->getDefaultFolder();
1063 $targetFileName = $targetFileName ?
$targetFileName : PathUtility
::basename($localFilePath);
1064 if ($conflictMode === 'cancel' && $this->driver
->fileExistsInFolder($targetFileName, $targetFolder)) {
1065 throw new Exception\
ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1066 } elseif ($conflictMode === 'changeName') {
1067 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1069 // We do not care whether the file exists if $conflictMode is "replace",
1070 // so just use the name as is in that case
1071 return $this->driver
->addFile($localFilePath, $targetFolder, $targetFileName);
1075 * Updates a processed file with a new file from the local filesystem.
1077 * @param $localFilePath
1078 * @param ProcessedFile $processedFile
1079 * @return FileInterface
1080 * @throws \InvalidArgumentException
1081 * @internal use only
1083 public function updateProcessedFile($localFilePath, ProcessedFile
$processedFile) {
1084 if (!file_exists($localFilePath)) {
1085 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1087 $file = $this->driver
->addFile($localFilePath, $this->getProcessingFolder(), $processedFile->getName());
1088 $file->setIndexable(FALSE);
1093 * Creates a (cryptographic) hash for a file.
1095 * @param FileInterface $fileObject
1099 public function hashFile(FileInterface
$fileObject, $hash) {
1100 return $this->driver
->hash($fileObject, $hash);
1104 * Returns a publicly accessible URL for a file.
1106 * WARNING: Access to the file may be restricted by further means, e.g.
1107 * some web-based authentication. You have to take care of this yourself.
1109 * @param ResourceInterface $resourceObject The file or folder object
1110 * @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)
1113 public function getPublicUrl(ResourceInterface
$resourceObject, $relativeToCurrentScript = FALSE) {
1115 if ($this->isOnline()) {
1116 // Pre-process the public URL by an accordant slot
1117 $this->emitPreGeneratePublicUrl($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
1118 // If slot did not handle the signal, use the default way to determine public URL
1119 if ($publicUrl === NULL) {
1120 $publicUrl = $this->driver
->getPublicUrl($resourceObject, $relativeToCurrentScript);
1127 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1129 * @param FileInterface $fileObject The file object
1130 * @param string $context
1131 * @param array $configuration
1133 * @return ProcessedFile
1134 * @throws \InvalidArgumentException
1136 public function processFile(FileInterface
$fileObject, $context, array $configuration) {
1137 if ($fileObject->getStorage() !== $this) {
1138 throw new \
InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1140 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1142 return $processedFile;
1146 * Copies a file from the storage for local processing.
1148 * @param FileInterface $fileObject
1149 * @param bool $writable
1150 * @return string Path to local file (either original or copied to some temporary local location)
1152 public function getFileForLocalProcessing(FileInterface
$fileObject, $writable = TRUE) {
1153 $filePath = $this->driver
->getFileForLocalProcessing($fileObject, $writable);
1154 // @todo: shouldn't this go in the driver? this function is called from the indexing service
1155 // @todo: and recursively calls itself over and over again, this is left out for now with getModificationTime()
1156 // touch($filePath, $fileObject->getModificationTime());
1161 * Get file by identifier
1163 * @param string $identifier
1164 * @return FileInterface
1166 public function getFile($identifier) {
1167 return $this->driver
->getFile($identifier);
1171 * Get information about a file
1173 * @param FileInterface $fileObject
1176 public function getFileInfo(FileInterface
$fileObject) {
1177 return $this->driver
->getFileInfo($fileObject);
1181 * Get information about a file by its identifier
1183 * @param string $identifier
1185 * @throws \BadMethodCallException
1188 public function getFileInfoByIdentifier($identifier) {
1189 throw new \
BadMethodCallException('The method ResourceStorage::getFileInfoByIdentifier() has been deprecated. Please fix your method call and use getFileInfo with the file object instead.', 1346577887);
1193 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
1197 public function unsetFileAndFolderNameFilters() {
1198 $this->fileAndFolderNameFilters
= array();
1202 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1206 public function resetFileAndFolderNameFiltersToDefault() {
1207 $this->fileAndFolderNameFilters
= $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1211 * Returns the file and folder name filters used by this storage.
1215 public function getFileAndFolderNameFilters() {
1216 return $this->fileAndFolderNameFilters
;
1219 public function setFileAndFolderNameFilters(array $filters) {
1220 $this->fileAndFolderNameFilters
= $filters;
1224 public function addFileAndFolderNameFilter($filter) {
1225 $this->fileAndFolderNameFilters
[] = $filter;
1229 * Returns a list of files in a given path, filtered by some custom filter methods.
1231 * @see getUnfilteredFileList(), getFileListWithDefaultFilters()
1232 * @param string $path The path to list
1233 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
1234 * @param integer $numberOfItems The number of items to list; if not set, return all items
1235 * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
1236 * @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.
1237 * @param boolean $recursive
1238 * @return array Information about the files found.
1240 // TODO check if we should use a folder object instead of $path
1241 // TODO add unit test for $loadIndexRecords
1242 public function getFileList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE, $loadIndexRecords = TRUE, $recursive = FALSE) {
1243 // This also checks for read permissions on folder
1244 $folder = $this->getFolder($path);
1246 if ($loadIndexRecords) {
1247 $rows = $this->getFileRepository()->getFileIndexRecordsForFolder($folder);
1249 $filters = $useFilters == TRUE ?
$this->fileAndFolderNameFilters
: array();
1250 $items = $this->driver
->getFileList($path, $start, $numberOfItems, $filters, $rows, $recursive);
1252 // We should not sort when fetching a recursive list, as these are indexed numerically
1253 if ($recursive === FALSE) {
1254 uksort($items, 'strnatcasecmp');
1261 * Returns TRUE if the specified file exists.
1263 * @param string $identifier
1266 public function hasFile($identifier) {
1267 $this->assureFolderReadPermission();
1268 return $this->driver
->fileExists($identifier);
1272 * Checks if the queried file in the given folder exists.
1274 * @param string $fileName
1275 * @param Folder $folder
1278 public function hasFileInFolder($fileName, Folder
$folder) {
1279 $this->assureFolderReadPermission($folder);
1280 return $this->driver
->fileExistsInFolder($fileName, $folder);
1284 * Get contents of a file object
1286 * @param FileInterface $file
1288 * @throws Exception\InsufficientFileReadPermissionsException
1291 public function getFileContents($file) {
1292 $this->assureFileReadPermission($file);
1293 return $this->driver
->getFileContents($file);
1297 * Set contents of a file object.
1299 * @param AbstractFile $file
1300 * @param string $contents
1302 * @throws \Exception|\RuntimeException
1303 * @throws Exception\InsufficientFileWritePermissionsException
1304 * @throws Exception\InsufficientUserPermissionsException
1305 * @return integer The number of bytes written to the file
1307 public function setFileContents(AbstractFile
$file, $contents) {
1308 // Check if user is allowed to edit
1309 $this->assureFileWritePermissions($file);
1310 // Call driver method to update the file and update file properties afterwards
1311 $result = $this->driver
->setFileContents($file, $contents);
1312 $fileInfo = $this->driver
->getFileInfo($file);
1313 $fileInfo['sha1'] = $this->driver
->hash($file, 'sha1');
1314 $file->updateProperties($fileInfo);
1315 $this->getFileRepository()->update($file);
1320 * Creates a new file
1322 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1324 * @param string $fileName
1325 * @param Folder $targetFolderObject
1327 * @throws Exception\IllegalFileExtensionException
1328 * @throws Exception\InsufficientFolderWritePermissionsException
1329 * @return FileInterface The file object
1331 public function createFile($fileName, Folder
$targetFolderObject) {
1332 $this->assureFileAddPermissions('', $targetFolderObject, $fileName);
1333 return $this->driver
->createFile($fileName, $targetFolderObject);
1337 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1339 * @param $fileObject FileInterface
1340 * @throws Exception\InsufficientFileAccessPermissionsException
1341 * @throws Exception\FileOperationErrorException
1342 * @return boolean TRUE if deletion succeeded
1344 public function deleteFile($fileObject) {
1345 $this->assureFileDeletePermissions($fileObject);
1347 $this->emitPreFileDeleteSignal($fileObject);
1349 $result = $this->driver
->deleteFile($fileObject);
1350 if ($result === FALSE) {
1351 throw new Exception\
FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1353 // Mark the file object as deleted
1354 $fileObject->setDeleted();
1356 $this->emitPostFileDeleteSignal($fileObject);
1362 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1363 * copies a source file (from any location) in to the target
1364 * folder, the latter has to be part of this storage
1366 * @param FileInterface $file
1367 * @param Folder $targetFolder
1368 * @param string $targetFileName an optional destination fileName
1369 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1371 * @throws \Exception|Exception\AbstractFileOperationException
1372 * @throws Exception\ExistingTargetFileNameException
1373 * @return FileInterface
1375 public function copyFile(FileInterface
$file, Folder
$targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1376 if ($targetFileName === NULL) {
1377 $targetFileName = $file->getName();
1379 $this->assureFileCopyPermissions($file, $targetFolder, $targetFileName);
1380 $this->emitPreFileCopySignal($file, $targetFolder);
1381 // File exists and we should abort, let's abort
1382 if ($conflictMode === 'cancel' && $targetFolder->hasFile($targetFileName)) {
1383 throw new Exception\
ExistingTargetFileNameException('The target file already exists.', 1320291063);
1385 // File exists and we should find another name, let's find another one
1386 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($targetFileName)) {
1387 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1389 $sourceStorage = $file->getStorage();
1390 // Call driver method to create a new file from an existing file object,
1391 // and return the new file object
1392 if ($sourceStorage === $this) {
1393 $newFileObject = $this->driver
->copyFileWithinStorage($file, $targetFolder, $targetFileName);
1395 $tempPath = $file->getForLocalProcessing();
1396 $newFileObject = $this->driver
->addFile($tempPath, $targetFolder, $targetFileName);
1398 $this->emitPostFileCopySignal($file, $targetFolder);
1399 return $newFileObject;
1403 * Moves a $file into a $targetFolder
1404 * the target folder has to be part of this storage
1406 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1408 * @param FileInterface $file
1409 * @param Folder $targetFolder
1410 * @param string $targetFileName an optional destination fileName
1411 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1413 * @throws Exception\ExistingTargetFileNameException
1414 * @return FileInterface
1416 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1417 if ($targetFileName === NULL) {
1418 $targetFileName = $file->getName();
1420 $this->assureFileMovePermissions($file, $targetFolder, $targetFileName);
1421 if ($targetFolder->hasFile($targetFileName)) {
1422 // File exists and we should abort, let's abort
1423 if ($conflictMode === 'renameNewFile') {
1424 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1425 } elseif ($conflictMode === 'cancel') {
1426 throw new Exception\
ExistingTargetFileNameException('The target file already exists', 1329850997);
1429 $this->emitPreFileMoveSignal($file, $targetFolder);
1430 $sourceStorage = $file->getStorage();
1431 // Call driver method to move the file that also updates the file
1432 // object properties
1434 if ($sourceStorage === $this) {
1435 $newIdentifier = $this->driver
->moveFileWithinStorage($file, $targetFolder, $targetFileName);
1436 $this->updateFile($file, $newIdentifier);
1438 $tempPath = $file->getForLocalProcessing();
1439 $newIdentifier = $this->driver
->addFileRaw($tempPath, $targetFolder, $targetFileName);
1440 $sourceStorage->driver
->deleteFileRaw($file->getIdentifier());
1441 $this->updateFile($file, $newIdentifier, $this);
1443 } catch (\TYPO3\CMS\Core\Exception
$e) {
1444 echo $e->getMessage();
1446 $this->emitPostFileMoveSignal($file, $targetFolder);
1451 * Updates the properties of a file object with some that are freshly
1452 * fetched from the driver.
1454 * @param AbstractFile $file
1455 * @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)
1456 * @param ResourceStorage $storage
1459 protected function updateFile(AbstractFile
$file, $identifier = '', $storage = NULL) {
1460 if ($identifier === '') {
1461 $identifier = $file->getIdentifier();
1463 $fileInfo = $this->driver
->getFileInfoByIdentifier($identifier);
1464 // TODO extend mapping
1465 $newProperties = array(
1466 'storage' => $fileInfo['storage'],
1467 'identifier' => $fileInfo['identifier'],
1468 'tstamp' => $fileInfo['mtime'],
1469 'crdate' => $fileInfo['ctime'],
1470 'mime_type' => $fileInfo['mimetype'],
1471 'size' => $fileInfo['size'],
1472 'name' => $fileInfo['name']
1474 if ($storage !== NULL) {
1475 $newProperties['storage'] = $storage->getUid();
1477 $file->updateProperties($newProperties);
1478 $this->getFileRepository()->update($file);
1482 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1484 * @param FileInterface $file
1485 * @param string $targetFileName
1487 * @throws Exception\InsufficientFileWritePermissionsException
1488 * @throws Exception\InsufficientFileReadPermissionsException
1489 * @throws Exception\InsufficientUserPermissionsException
1490 * @return FileInterface
1492 // TODO add $conflictMode setting
1493 public function renameFile($file, $targetFileName) {
1494 // The name should be different from the current.
1495 if ($file->getName() === $targetFileName) {
1498 $this->assureFileRenamePermissions($file, $targetFileName);
1499 $this->emitPreFileRenameSignal($file, $targetFileName);
1501 // Call driver method to rename the file that also updates the file
1502 // object properties
1504 $newIdentifier = $this->driver
->renameFile($file, $targetFileName);
1505 $this->updateFile($file, $newIdentifier);
1506 $this->getFileRepository()->update($file);
1507 } catch (\RuntimeException
$e) {
1511 $this->emitPostFileRenameSignal($file, $targetFileName);
1517 * Replaces a file with a local file (e.g. a freshly uploaded file)
1519 * @param FileInterface $file
1520 * @param string $localFilePath
1522 * @throws \InvalidArgumentException
1523 * @return FileInterface
1525 public function replaceFile(FileInterface
$file, $localFilePath) {
1526 $this->assureFileWritePermissions($file);
1527 if (!$this->checkFileExtensionPermission($localFilePath)) {
1528 throw new Exception\
IllegalFileExtensionException('Sorce file extension not allowed', 1378132239);
1530 if (!file_exists($localFilePath)) {
1531 throw new \
InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1533 // TODO check permissions
1534 $this->emitPreFileReplaceSignal($file, $localFilePath);
1535 $result = $this->driver
->replaceFile($file, $localFilePath);
1536 $this->emitPostFileReplaceSignal($file, $localFilePath);
1541 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1543 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1544 * @param Folder $targetFolder the target folder
1545 * @param string $targetFileName the file name to be written
1546 * @param string $conflictMode possible value are 'cancel', 'replace'
1547 * @return FileInterface The file object
1549 public function addUploadedFile(array $uploadedFileData, Folder
$targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1550 $localFilePath = $uploadedFileData['tmp_name'];
1551 if ($targetFolder === NULL) {
1552 $targetFolder = $this->getDefaultFolder();
1554 if ($targetFileName === NULL) {
1555 $targetFileName = $uploadedFileData['name'];
1557 // Handling $conflictMode is delegated to addFile()
1558 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1559 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1560 return $resultObject;
1563 /********************
1565 ********************/
1567 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1569 * @param Folder $folder
1572 protected function getAllFileObjectsInFolder(Folder
$folder) {
1574 $folderQueue = array($folder);
1575 while (!empty($folderQueue)) {
1576 $folder = array_shift($folderQueue);
1577 foreach ($folder->getSubfolders() as $subfolder) {
1578 $folderQueue[] = $subfolder;
1580 foreach ($folder->getFiles() as $file) {
1581 $files[$file->getIdentifier()] = $file;
1588 * Moves a folder. If you want to move a folder from this storage to another
1589 * one, call this method on the target storage, otherwise you will get an exception.
1591 * @param Folder $folderToMove The folder to move.
1592 * @param Folder $targetParentFolder The target parent folder
1593 * @param string $newFolderName
1594 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1596 * @throws \Exception|\TYPO3\CMS\Core\Exception
1597 * @throws \InvalidArgumentException
1601 public function moveFolder(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1602 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1603 $sourceStorage = $folderToMove->getStorage();
1604 $returnObject = NULL;
1605 $newFolderName = $newFolderName ?
$newFolderName : $folderToMove->getName();
1606 // TODO check if folder already exists in $targetParentFolder, handle this conflict then
1607 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1608 // Get all file objects now so we are able to update them after moving the folder
1609 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1610 if ($sourceStorage === $this) {
1611 $fileMappings = $this->driver
->moveFolderWithinStorage($folderToMove, $targetParentFolder, $newFolderName);
1613 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1615 // Update the identifier and storage of all file objects
1616 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1617 $newIdentifier = $fileMappings[$oldIdentifier];
1618 $fileObject->updateProperties(array('storage' => $this, 'identifier' => $newIdentifier));
1619 $this->getFileRepository()->update($fileObject);
1621 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1622 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1623 return $returnObject;
1627 * Moves the given folder from a different storage to the target folder in this storage.
1629 * @param Folder $folderToMove
1630 * @param Folder $targetParentFolder
1631 * @param string $newFolderName
1635 protected function moveFolderBetweenStorages(Folder
$folderToMove, Folder
$targetParentFolder, $newFolderName) {
1636 return $this->getDriver()->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1642 * @param FolderInterface $folderToCopy The folder to copy
1643 * @param FolderInterface $targetParentFolder The target folder
1644 * @param string $newFolderName
1645 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1646 * @return Folder The new (copied) folder object
1648 public function copyFolder(FolderInterface
$folderToCopy, FolderInterface
$targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1649 // TODO implement the $conflictMode handling
1650 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
1651 $returnObject = NULL;
1652 $newFolderName = $newFolderName ?
$newFolderName : $folderToCopy->getName();
1653 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1654 $sourceStorage = $folderToCopy->getStorage();
1655 // call driver method to move the file
1656 // that also updates the file object properties
1658 if ($sourceStorage === $this) {
1659 $this->driver
->copyFolderWithinStorage($folderToCopy, $targetParentFolder, $newFolderName);
1660 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($newFolderName)->getIdentifier());
1662 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1664 } catch (\TYPO3\CMS\Core\Exception
$e) {
1665 echo $e->getMessage();
1667 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1668 return $returnObject;
1672 * Copy folders between storages
1674 * @param Folder $folderToCopy
1675 * @param Folder $targetParentFolder
1676 * @param string $newFolderName
1680 protected function copyFolderBetweenStorages(Folder
$folderToCopy, Folder
$targetParentFolder, $newFolderName) {
1681 return $this->getDriver()->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1685 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1687 * @param Folder $folderObject
1688 * @param string $newName
1689 * @throws \Exception
1690 * @throws \InvalidArgumentException
1693 public function renameFolder($folderObject, $newName) {
1696 // Renaming the folder should check if the parent folder is writable
1697 // We cannot do this however because we cannot extract the parent folder from a folder currently
1698 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1699 throw new \TYPO3\CMS\Core\
Resource\Exception\
InsufficientFileAccessPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1702 $returnObject = NULL;
1703 if ($this->driver
->folderExistsInFolder($newName, $folderObject)) {
1704 throw new \
InvalidArgumentException('The folder ' . $newName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1707 $this->emitPreFolderRenameSignal($folderObject, $newName);
1709 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1710 $fileMappings = $this->driver
->renameFolder($folderObject, $newName);
1711 // Update the identifier of all file objects
1712 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1713 $newIdentifier = $fileMappings[$oldIdentifier];
1714 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1715 $this->getFileRepository()->update($fileObject);
1717 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1719 $this->emitPostFolderRenameSignal($folderObject, $newName);
1721 return $returnObject;
1725 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1727 * @param Folder $folderObject
1728 * @param boolean $deleteRecursively
1729 * @throws \RuntimeException
1732 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1733 $isEmpty = $this->driver
->isFolderEmpty($folderObject);
1734 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
1735 if (!$isEmpty && !$deleteRecursively) {
1736 throw new \
RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1739 $this->emitPreFolderDeleteSignal($folderObject);
1741 $result = $this->driver
->deleteFolder($folderObject, $deleteRecursively);
1743 $this->emitPostFolderDeleteSignal($folderObject);
1749 * Returns a list of folders in a given path.
1751 * @param string $path The path to list
1752 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
1753 * @param integer $numberOfItems The number of items to list; if not set, return all items
1754 * @param boolean $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
1755 * @return array Information about the folders found.
1757 public function getFolderList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE) {
1758 // Permissions are checked in $this->fetchFolderListFromDriver()
1759 $filters = $useFilters === TRUE ?
$this->fileAndFolderNameFilters
: array();
1760 return $this->fetchFolderListFromDriver($path, $start, $numberOfItems, $filters);
1765 * @param integer $start
1766 * @param integer $numberOfItems
1767 * @param array $folderFilterCallbacks
1768 * @param boolean $recursive
1771 public function fetchFolderListFromDriver($path, $start = 0, $numberOfItems = 0, array $folderFilterCallbacks = array(), $recursive = FALSE) {
1772 // This also checks for access to that path and throws exceptions accordingly
1773 if ($this->getFolder($path) === NULL) {
1776 $items = $this->driver
->getFolderList($path, $start, $numberOfItems, $folderFilterCallbacks, $recursive);
1777 if (!empty($items)) {
1778 // Exclude the _processed_ folder, so it won't get indexed etc
1779 // The processed folder might be any sub folder in storage
1780 $processingFolder = $this->getProcessingFolder();
1781 if ($processingFolder) {
1782 $processedFolderIdentifier = $this->processingFolder
->getIdentifier();
1783 $processedFolderIdentifier = trim($processedFolderIdentifier, '/');
1784 $processedFolderIdentifierParts = explode('/', $processedFolderIdentifier);
1785 $processedFolderName = array_pop($processedFolderIdentifierParts);
1786 $processedFolderParent = implode('/', $processedFolderIdentifierParts);
1787 if ($processedFolderParent === trim($path, '/') && isset($items[$processedFolderName])) {
1788 unset($items[$processedFolderName]);
1791 uksort($items, 'strnatcasecmp');
1797 * Returns TRUE if the specified folder exists.
1799 * @param string $identifier
1802 public function hasFolder($identifier) {
1803 $this->assureFolderReadPermission();
1804 return $this->driver
->folderExists($identifier);
1808 * Checks if the given file exists in the given folder
1810 * @param string $folderName
1811 * @param Folder $folder
1814 public function hasFolderInFolder($folderName, Folder
$folder) {
1815 $this->assureFolderReadPermission($folder);
1816 return $this->driver
->folderExistsInFolder($folderName, $folder);
1820 * Creates a new folder.
1822 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
1824 * @param string $folderName The new folder name
1825 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
1827 * @throws Exception\InsufficientFolderWritePermissionsException
1828 * @throws \InvalidArgumentException
1829 * @return Folder The new folder object
1831 public function createFolder($folderName, Folder
$parentFolder = NULL) {
1832 if ($parentFolder === NULL) {
1833 $parentFolder = $this->getRootLevelFolder();
1835 if (!$this->driver
->folderExists($parentFolder->getIdentifier())) {
1836 throw new \
InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
1838 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
1839 throw new Exception\
InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
1841 // TODO this only works with hirachical file systems
1842 $folderParts = GeneralUtility
::trimExplode('/', $folderName, TRUE);
1843 foreach ($folderParts as $folder) {
1844 // TODO check if folder creation succeeded
1845 if ($this->hasFolderInFolder($folder, $parentFolder)) {
1846 $parentFolder = $this->driver
->getFolderInFolder($folder, $parentFolder);
1848 $parentFolder = $this->driver
->createFolder($folder, $parentFolder);
1851 return $parentFolder;
1855 * Returns the default folder where new files are stored if no other folder is given.
1859 public function getDefaultFolder() {
1860 return $this->driver
->getDefaultFolder();
1864 * @param string $identifier
1866 * @throws Exception\NotInMountPointException
1867 * @throws Exception\FolderDoesNotExistException
1870 public function getFolder($identifier) {
1871 if (!$this->driver
->folderExists($identifier)) {
1872 throw new Exception\
FolderDoesNotExistException('Folder ' . $identifier . ' does not exist.', 1320575630);
1874 $folder = $this->driver
->getFolder($identifier);
1875 $this->assureFolderReadPermission($folder);
1881 * Returns the folders on the root level of the storage
1882 * or the first mount point of this storage for this user
1886 public function getRootLevelFolder() {
1887 if (count($this->fileMounts
)) {
1888 $mount = reset($this->fileMounts
);
1889 return $mount['folder'];
1891 return $this->driver
->getRootLevelFolder();
1896 * Emits the configuration pre-processing signal
1900 protected function emitPreProcessConfigurationSignal() {
1901 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreProcessConfiguration
, array($this));
1905 * Emits the configuration post-processing signal
1909 protected function emitPostProcessConfigurationSignal() {
1910 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostProcessConfiguration
, array($this));
1914 * Emits file pre-copy signal
1916 * @param FileInterface $file
1917 * @param Folder $targetFolder
1920 protected function emitPreFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
1921 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileCopy
, array($file, $targetFolder));
1925 * Emits the file post-copy signal
1927 * @param FileInterface $file
1928 * @param Folder $targetFolder
1931 protected function emitPostFileCopySignal(FileInterface
$file, Folder
$targetFolder) {
1932 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileCopy
, array($file, $targetFolder));
1936 * Emits the file pre-move signal
1938 * @param FileInterface $file
1939 * @param Folder $targetFolder
1942 protected function emitPreFileMoveSignal(FileInterface
$file, Folder
$targetFolder) {
1943 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileMove
, array($file, $targetFolder));
1947 * Emits the file post-move signal
1949 * @param FileInterface $file
1950 * @param Folder $targetFolder
1953 protected function emitPostFileMoveSignal(FileInterface
$file, Folder
$targetFolder) {
1954 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileMove
, array($file, $targetFolder));
1958 * Emits the file pre-rename signal
1960 * @param FileInterface $file
1961 * @param $targetFolder
1964 protected function emitPreFileRenameSignal(FileInterface
$file, $targetFolder) {
1965 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileRename
, array($file, $targetFolder));
1969 * Emits the file post-rename signal
1971 * @param FileInterface $file
1972 * @param $targetFolder
1975 protected function emitPostFileRenameSignal(FileInterface
$file, $targetFolder) {
1976 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileRename
, array($file, $targetFolder));
1980 * Emits the file pre-replace signal
1982 * @param FileInterface $file
1983 * @param $localFilePath
1986 protected function emitPreFileReplaceSignal(FileInterface
$file, $localFilePath) {
1987 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileReplace
, array($file, $localFilePath));
1991 * Emits the file post-replace signal
1993 * @param FileInterface $file
1994 * @param $localFilePath
1997 protected function emitPostFileReplaceSignal(FileInterface
$file, $localFilePath) {
1998 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileReplace
, array($file, $localFilePath));
2002 * Emits the file pre-deletion signal
2004 * @param FileInterface $file
2007 protected function emitPreFileDeleteSignal(FileInterface
$file) {
2008 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreFileDelete
, array($file));
2012 * Emits the file post-deletion signal
2014 * @param FileInterface $file
2017 protected function emitPostFileDeleteSignal(FileInterface
$file) {
2018 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PostFileDelete
, array($file));
2022 * Emits the folder pre-copy signal
2024 * @param Folder $folder
2025 * @param Folder $targetFolder
2029 protected function emitPreFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
2030 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderCopy
, array($folder, $targetFolder));
2034 * Emits the folder post-copy signal
2036 * @param Folder $folder
2037 * @param Folder $targetFolder
2041 protected function emitPostFolderCopySignal(Folder
$folder, Folder
$targetFolder, $newName) {
2042 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderCopy
, array($folder, $targetFolder));
2046 * Emits the folder pre-move signal
2048 * @param Folder $folder
2049 * @param Folder $targetFolder
2053 protected function emitPreFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName) {
2054 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderMove
, array($folder, $targetFolder));
2058 * Emits the folder post-move signal
2060 * @param Folder $folder
2061 * @param Folder $targetFolder
2065 protected function emitPostFolderMoveSignal(Folder
$folder, Folder
$targetFolder, $newName) {
2066 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderMove
, array($folder, $targetFolder));
2070 * Emits the folder pre-rename signal
2072 * @param Folder $folder
2076 protected function emitPreFolderRenameSignal(Folder
$folder, $newName) {
2077 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderRename
, array($folder, $newName));
2081 * Emits the folder post-rename signal
2083 * @param Folder $folder
2087 protected function emitPostFolderRenameSignal(Folder
$folder, $newName) {
2088 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderRename
, array($folder, $newName));
2092 * Emits the folder pre-deletion signal
2094 * @param Folder $folder
2097 protected function emitPreFolderDeleteSignal(Folder
$folder) {
2098 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PreFolderDelete
, array($folder));
2102 * Emits folder postdeletion signal.
2104 * @param Folder $folder
2107 protected function emitPostFolderDeleteSignal(Folder
$folder) {
2108 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self
::SIGNAL_PostFolderDelete
, array($folder));
2112 * Emits file pre-processing signal when generating a public url for a file or folder.
2114 * @param ResourceInterface $resourceObject
2115 * @param boolean $relativeToCurrentScript
2116 * @param array $urlData
2118 protected function emitPreGeneratePublicUrl(ResourceInterface
$resourceObject, $relativeToCurrentScript, array $urlData) {
2119 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self
::SIGNAL_PreGeneratePublicUrl
, array($this, $this->driver
, $resourceObject, $relativeToCurrentScript, $urlData));
2123 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2124 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2125 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2127 * @param Folder $folder
2128 * @param string $theFile The input fileName to check
2129 * @param boolean $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2131 * @throws \RuntimeException
2132 * @return string A unique fileName inside $folder, based on $theFile.
2133 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2135 // TODO check if this should be moved back to Folder
2136 protected function getUniqueName(Folder
$folder, $theFile, $dontCheckForUnique = FALSE) {
2137 static $maxNumber = 99, $uniqueNamePrefix = '';
2138 // Fetches info about path, name, extention of $theFile
2139 $origFileInfo = GeneralUtility
::split_fileref($theFile);
2141 if ($uniqueNamePrefix) {
2142 $origFileInfo['file'] = $uniqueNamePrefix . $origFileInfo['file'];
2143 $origFileInfo['filebody'] = $uniqueNamePrefix . $origFileInfo['filebody'];
2145 // Check if the file exists and if not - return the fileName...
2146 $fileInfo = $origFileInfo;
2147 // The destinations file
2148 $theDestFile = $fileInfo['file'];
2149 // If the file does NOT exist we return this fileName
2150 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder) ||
$dontCheckForUnique) {
2151 return $theDestFile;
2153 // Well the fileName in its pure form existed. Now we try to append
2154 // numbers / unique-strings and see if we can find an available fileName
2155 // This removes _xx if appended to the file
2156 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']);
2157 $theOrigExt = $origFileInfo['realFileext'] ?
'.' . $origFileInfo['realFileext'] : '';
2158 for ($a = 1; $a <= $maxNumber +
1; $a++
) {
2159 // First we try to append numbers
2160 if ($a <= $maxNumber) {
2161 $insert = '_' . sprintf('%02d', $a);
2163 // TODO remove constant 6
2164 $insert = '_' . substr(md5(uniqId('')), 0, 6);
2166 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2167 // The destinations file
2168 $theDestFile = $theTestFile;
2169 // If the file does NOT exist we return this fileName
2170 if (!$this->driver
->fileExistsInFolder($theDestFile, $folder)) {
2171 return $theDestFile;
2174 throw new \
RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2178 * Get the SignalSlot dispatcher
2180 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2182 protected function getSignalSlotDispatcher() {
2183 if (!isset($this->signalSlotDispatcher
)) {
2184 $this->signalSlotDispatcher
= $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
2186 return $this->signalSlotDispatcher
;
2190 * Get the ObjectManager
2192 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2194 protected function getObjectManager() {
2195 return GeneralUtility
::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
2199 * @return ResourceFactory
2201 protected function getFileFactory() {
2202 return GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
2206 * @return \TYPO3\CMS\Core\Resource\FileRepository
2208 protected function getFileRepository() {
2209 return GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileRepository');
2213 * @return Service\FileProcessingService
2215 protected function getFileProcessingService() {
2216 if (!$this->fileProcessingService
) {
2217 $this->fileProcessingService
= GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\Service\\FileProcessingService', $this, $this->driver
);
2219 return $this->fileProcessingService
;
2223 * Gets the role of a folder
2225 * @param FolderInterface $folder Folder object to get the role from
2226 * @return string The role the folder has
2228 public function getRole(FolderInterface
$folder) {
2229 $folderRole = FolderInterface
::ROLE_DEFAULT
;
2231 if (method_exists($this->driver
, 'getRole')) {
2232 $folderRole = $this->driver
->getRole($folder);
2235 if ($folder->getIdentifier() === $this->getProcessingFolder()->getIdentifier()) {
2236 $folderRole = FolderInterface
::ROLE_PROCESSING
;
2243 * Getter function to return the folder where the files can
2244 * be processed. does not check for access rights here
2246 * @todo check if we need to implement "is writable" capability
2247 * @return Folder the processing folder, can be empty as well, if the storage doesn't have a processing folder
2249 public function getProcessingFolder() {
2250 if (!isset($this->processingFolder
)) {
2251 $processingFolder = self
::DEFAULT_ProcessingFolder
;
2252 if (!empty($this->storageRecord
['processingfolder'])) {
2253 $processingFolder = $this->storageRecord
['processingfolder'];
2255 $processingFolder = '/' . trim($processingFolder, '/') . '/';
2256 // this way, we also worry about deeplinked folders like typo3temp/_processed_
2257 if ($this->driver
->folderExists($processingFolder) === FALSE) {
2258 $processingFolderParts = explode('/', $processingFolder);
2259 $parentFolder = $this->driver
->getRootLevelFolder();
2260 foreach ($processingFolderParts as $folderPart) {
2261 if ($folderPart === '') {
2264 if (!$this->driver
->folderExistsInFolder($folderPart, $parentFolder)) {
2265 $parentFolder = $this->driver
->createFolder($folderPart, $parentFolder);
2267 $parentFolder = $parentFolder->getSubfolder($folderPart);
2271 $this->processingFolder
= $this->driver
->getFolder($processingFolder);
2273 return $this->processingFolder
;