TYPO3 CMS  TYPO3_8-7
ResourceStorage.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
28 
62 {
68  protected $driver;
69 
75  protected $storageRecord;
76 
82  protected $configuration;
83 
88 
97  protected $evaluatePermissions = false;
98 
104  protected $fileMounts = [];
105 
112  protected $userPermissions = [];
113 
120  protected $capabilities;
121 
126 
130  protected $processingFolder;
131 
138 
144  protected $isOnline = null;
145 
149  protected $isDefault = false;
150 
157 
162 
169  public function __construct(Driver\DriverInterface $driver, array $storageRecord)
170  {
171  $this->storageRecord = $storageRecord;
172  $this->configuration = $this->getResourceFactoryInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
173  $this->capabilities =
174  ($this->storageRecord['is_browsable'] ? self::CAPABILITY_BROWSABLE : 0) |
175  ($this->storageRecord['is_public'] ? self::CAPABILITY_PUBLIC : 0) |
176  ($this->storageRecord['is_writable'] ? self::CAPABILITY_WRITABLE : 0);
177 
178  $this->driver = $driver;
179  $this->driver->setStorageUid($storageRecord['uid']);
180  $this->driver->mergeConfigurationCapabilities($this->capabilities);
181  try {
182  $this->driver->processConfiguration();
183  } catch (Exception\InvalidConfigurationException $e) {
184  // Configuration error
185  $this->isOnline = false;
186 
187  $message = sprintf(
188  'Failed initializing storage [%d] "%s", error: %s',
189  $this->getUid(),
190  $this->getName(),
191  $e->getMessage()
192  );
193 
194  $this->getLogger()->error($message);
195  }
196  $this->driver->initialize();
197  $this->capabilities = $this->driver->getCapabilities();
198 
199  $this->isDefault = (isset($storageRecord['is_default']) && $storageRecord['is_default'] == 1);
201  }
202 
208  public function getConfiguration()
209  {
210  return $this->configuration;
211  }
212 
218  public function setConfiguration(array $configuration)
219  {
220  $this->configuration = $configuration;
221  }
222 
228  public function getStorageRecord()
229  {
230  return $this->storageRecord;
231  }
232 
239  public function setDriver(Driver\DriverInterface $driver)
240  {
241  $this->driver = $driver;
242  return $this;
243  }
244 
250  protected function getDriver()
251  {
252  return $this->driver;
253  }
254 
260  public function getName()
261  {
262  return $this->storageRecord['name'];
263  }
264 
270  public function getUid()
271  {
272  return (int)$this->storageRecord['uid'];
273  }
274 
280  public function hasChildren()
281  {
282  return true;
283  }
284 
285  /*********************************
286  * Capabilities
287  ********************************/
294  public function getCapabilities()
295  {
296  return (int)$this->capabilities;
297  }
298 
305  protected function hasCapability($capability)
306  {
307  return ($this->capabilities & $capability) == $capability;
308  }
309 
318  public function isPublic()
319  {
320  return $this->hasCapability(self::CAPABILITY_PUBLIC);
321  }
322 
329  public function isWritable()
330  {
331  return $this->hasCapability(self::CAPABILITY_WRITABLE);
332  }
333 
339  public function isBrowsable()
340  {
341  return $this->isOnline() && $this->hasCapability(self::CAPABILITY_BROWSABLE);
342  }
343 
350  {
351  return $this->driver->isCaseSensitiveFileSystem();
352  }
353 
359  public function isOnline()
360  {
361  if ($this->isOnline === null) {
362  if ($this->getUid() === 0) {
363  $this->isOnline = true;
364  }
365  // the storage is not marked as online for a longer time
366  if ($this->storageRecord['is_online'] == 0) {
367  $this->isOnline = false;
368  }
369  if ($this->isOnline !== false) {
370  // all files are ALWAYS available in the frontend
371  if (TYPO3_MODE === 'FE') {
372  $this->isOnline = true;
373  } else {
374  // check if the storage is disabled temporary for now
375  $registryObject = GeneralUtility::makeInstance(Registry::class);
376  $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
377  if ($offlineUntil && $offlineUntil > time()) {
378  $this->isOnline = false;
379  } else {
380  $this->isOnline = true;
381  }
382  }
383  }
384  }
385  return $this->isOnline;
386  }
387 
393  public function autoExtractMetadataEnabled()
394  {
395  return !empty($this->storageRecord['auto_extract_metadata']);
396  }
397 
405  public function markAsPermanentlyOffline()
406  {
407  if ($this->getUid() > 0) {
408  // @todo: move this to the storage repository
409  GeneralUtility::makeInstance(ConnectionPool::class)
410  ->getConnectionForTable('sys_file_storage')
411  ->update(
412  'sys_file_storage',
413  ['is_online' => 0],
414  ['uid' => (int)$this->getUid()]
415  );
416  }
417  $this->storageRecord['is_online'] = 0;
418  $this->isOnline = false;
419  }
420 
427  public function markAsTemporaryOffline()
428  {
429  $registryObject = GeneralUtility::makeInstance(Registry::class);
430  $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() + 60 * 5);
431  $this->storageRecord['is_online'] = 0;
432  $this->isOnline = false;
433  }
434 
435  /*********************************
436  * User Permissions / File Mounts
437  ********************************/
447  public function addFileMount($folderIdentifier, $additionalData = [])
448  {
449  // check for the folder before we add it as a filemount
450  if ($this->driver->folderExists($folderIdentifier) === false) {
451  // if there is an error, this is important and should be handled
452  // as otherwise the user would see the whole storage without any restrictions for the filemounts
453  throw new Exception\FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
454  }
455  $data = $this->driver->getFolderInfoByIdentifier($folderIdentifier);
456  $folderObject = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
457  // Use the canonical identifier instead of the user provided one!
458  $folderIdentifier = $folderObject->getIdentifier();
459  if (
460  !empty($this->fileMounts[$folderIdentifier])
461  && empty($this->fileMounts[$folderIdentifier]['read_only'])
462  && !empty($additionalData['read_only'])
463  ) {
464  // Do not overwrite a regular mount with a read only mount
465  return;
466  }
467  if (empty($additionalData)) {
468  $additionalData = [
469  'path' => $folderIdentifier,
470  'title' => $folderIdentifier,
471  'folder' => $folderObject
472  ];
473  } else {
474  $additionalData['folder'] = $folderObject;
475  if (!isset($additionalData['title'])) {
476  $additionalData['title'] = $folderIdentifier;
477  }
478  }
479  $this->fileMounts[$folderIdentifier] = $additionalData;
480  }
481 
487  public function getFileMounts()
488  {
489  return $this->fileMounts;
490  }
491 
500  public function isWithinFileMountBoundaries($subject, $checkWriteAccess = false)
501  {
502  if (!$this->evaluatePermissions) {
503  return true;
504  }
505  $isWithinFileMount = false;
506  if (!$subject) {
507  $subject = $this->getRootLevelFolder();
508  }
509  $identifier = $subject->getIdentifier();
510 
511  // Allow access to processing folder
512  if ($this->isWithinProcessingFolder($identifier)) {
513  $isWithinFileMount = true;
514  } else {
515  // Check if the identifier of the subject is within at
516  // least one of the file mounts
517  $writableFileMountAvailable = false;
518  foreach ($this->fileMounts as $fileMount) {
520  $folder = $fileMount['folder'];
521  if ($this->driver->isWithin($folder->getIdentifier(), $identifier)) {
522  $isWithinFileMount = true;
523  if (!$checkWriteAccess) {
524  break;
525  }
526  if (empty($fileMount['read_only'])) {
527  $writableFileMountAvailable = true;
528  break;
529  }
530  }
531  }
532  $isWithinFileMount = $checkWriteAccess ? $writableFileMountAvailable : $isWithinFileMount;
533  }
534  return $isWithinFileMount;
535  }
536 
544  {
545  $this->evaluatePermissions = (bool)$evaluatePermissions;
546  }
547 
554  public function getEvaluatePermissions()
555  {
557  }
558 
564  public function setUserPermissions(array $userPermissions)
565  {
566  $this->userPermissions = $userPermissions;
567  }
568 
577  public function checkUserActionPermission($action, $type)
578  {
579  if (!$this->evaluatePermissions) {
580  return true;
581  }
582 
583  $allow = false;
584  if (!empty($this->userPermissions[strtolower($action) . ucfirst(strtolower($type))])) {
585  $allow = true;
586  }
587 
588  return $allow;
589  }
590 
604  public function checkFileActionPermission($action, FileInterface $file)
605  {
606  $isProcessedFile = $file instanceof ProcessedFile;
607  // Check 1: Allow editing meta data of a file if it is in mount boundaries of a writable file mount
608  if ($action === 'editMeta') {
609  return !$isProcessedFile && $this->isWithinFileMountBoundaries($file, true);
610  }
611  // Check 2: Does the user have permission to perform the action? e.g. "readFile"
612  if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === false) {
613  return false;
614  }
615  // Check 3: No action allowed on files for denied file extensions
616  if (!$this->checkFileExtensionPermission($file->getName())) {
617  return false;
618  }
619  $isReadCheck = false;
620  if (in_array($action, ['read', 'copy', 'move', 'replace'], true)) {
621  $isReadCheck = true;
622  }
623  $isWriteCheck = false;
624  if (in_array($action, ['add', 'write', 'move', 'rename', 'replace', 'delete'], true)) {
625  $isWriteCheck = true;
626  }
627  // Check 4: Does the user have the right to perform the action?
628  // (= is he within the file mount borders)
629  if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file, $isWriteCheck)) {
630  return false;
631  }
632 
633  $isMissing = false;
634  if (!$isProcessedFile && $file instanceof File) {
635  $isMissing = $file->isMissing();
636  }
637 
638  if ($this->driver->fileExists($file->getIdentifier()) === false) {
639  $file->setMissing(true);
640  $isMissing = true;
641  }
642 
643  // Check 5: Check the capabilities of the storage (and the driver)
644  if ($isWriteCheck && ($isMissing || !$this->isWritable())) {
645  return false;
646  }
647 
648  // Check 6: "File permissions" of the driver (only when file isn't marked as missing)
649  if (!$isMissing) {
650  $filePermissions = $this->driver->getPermissions($file->getIdentifier());
651  if ($isReadCheck && !$filePermissions['r']) {
652  return false;
653  }
654  if ($isWriteCheck && !$filePermissions['w']) {
655  return false;
656  }
657  }
658  return true;
659  }
660 
671  public function checkFolderActionPermission($action, Folder $folder = null)
672  {
673  // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
674  if ($this->checkUserActionPermission($action, 'Folder') === false) {
675  return false;
676  }
677 
678  // If we do not have a folder here, we cannot do further checks
679  if ($folder === null) {
680  return true;
681  }
682 
683  $isReadCheck = false;
684  if (in_array($action, ['read', 'copy'], true)) {
685  $isReadCheck = true;
686  }
687  $isWriteCheck = false;
688  if (in_array($action, ['add', 'move', 'write', 'delete', 'rename'], true)) {
689  $isWriteCheck = true;
690  }
691  // Check 2: Does the user has the right to perform the action?
692  // (= is he within the file mount borders)
693  if (!$this->isWithinFileMountBoundaries($folder, $isWriteCheck)) {
694  return false;
695  }
696  // Check 3: Check the capabilities of the storage (and the driver)
697  if ($isReadCheck && !$this->isBrowsable()) {
698  return false;
699  }
700  if ($isWriteCheck && !$this->isWritable()) {
701  return false;
702  }
703 
704  // Check 4: "Folder permissions" of the driver
705  $folderPermissions = $this->driver->getPermissions($folder->getIdentifier());
706  if ($isReadCheck && !$folderPermissions['r']) {
707  return false;
708  }
709  if ($isWriteCheck && !$folderPermissions['w']) {
710  return false;
711  }
712  return true;
713  }
714 
719  public function checkFileAndFolderNameFilters(ResourceInterface $fileOrFolder)
720  {
721  foreach ($this->fileAndFolderNameFilters as $filter) {
722  if (is_callable($filter)) {
723  $result = call_user_func($filter, $fileOrFolder->getName(), $fileOrFolder->getIdentifier(), $fileOrFolder->getParentFolder()->getIdentifier(), [], $this->driver);
724  // We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
725  // If calling the method succeeded and thus we can't use that as a return value.
726  if ($result === -1) {
727  return false;
728  }
729  if ($result === false) {
730  throw new \RuntimeException(
731  'Could not apply file/folder name filter ' . $filter[0] . '::' . $filter[1],
732  1525342106
733  );
734  }
735  }
736  }
737 
738  return true;
739  }
740 
748  protected function checkFileExtensionPermission($fileName)
749  {
750  $fileName = $this->driver->sanitizeFileName($fileName);
751  $isAllowed = GeneralUtility::verifyFilenameAgainstDenyPattern($fileName);
752  if ($isAllowed && $this->evaluatePermissions) {
753  $fileExtension = strtolower(PathUtility::pathinfo($fileName, PATHINFO_EXTENSION));
754  // Set up the permissions for the file extension
755  $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
756  $fileExtensionPermissions['allow'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['allow']));
757  $fileExtensionPermissions['deny'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['deny']));
758  if ($fileExtension !== '') {
759  // If the extension is found amongst the allowed types, we return TRUE immediately
760  if ($fileExtensionPermissions['allow'] === '*' || GeneralUtility::inList($fileExtensionPermissions['allow'], $fileExtension)) {
761  return true;
762  }
763  // If the extension is found amongst the denied types, we return FALSE immediately
764  if ($fileExtensionPermissions['deny'] === '*' || GeneralUtility::inList($fileExtensionPermissions['deny'], $fileExtension)) {
765  return false;
766  }
767  // If no match we return TRUE
768  return true;
769  }
770  if ($fileExtensionPermissions['allow'] === '*') {
771  return true;
772  }
773  if ($fileExtensionPermissions['deny'] === '*') {
774  return false;
775  }
776  return true;
777  }
778  return $isAllowed;
779  }
780 
787  protected function assureFolderReadPermission(Folder $folder = null)
788  {
789  if (!$this->checkFolderActionPermission('read', $folder)) {
790  if ($folder === null) {
791  throw new Exception\InsufficientFolderAccessPermissionsException(
792  'You are not allowed to read folders',
793  1430657869
794  );
795  }
796  throw new Exception\InsufficientFolderAccessPermissionsException(
797  'You are not allowed to access the given folder: "' . $folder->getName() . '"',
798  1375955684
799  );
800  }
801  }
802 
812  protected function assureFolderDeletePermission(Folder $folder, $checkDeleteRecursively)
813  {
814  // Check user permissions for recursive deletion if it is requested
815  if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
816  throw new Exception\InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
817  }
818  // Check user action permission
819  if (!$this->checkFolderActionPermission('delete', $folder)) {
820  throw new Exception\InsufficientFolderAccessPermissionsException(
821  'You are not allowed to delete the given folder: "' . $folder->getName() . '"',
822  1377779039
823  );
824  }
825  // Check if the user has write permissions to folders
826  // Would be good if we could check for actual write permissions in the containig folder
827  // but we cannot since we have no access to the containing folder of this file.
828  if (!$this->checkUserActionPermission('write', 'Folder')) {
829  throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
830  }
831  }
832 
840  protected function assureFileReadPermission(FileInterface $file)
841  {
842  if (!$this->checkFileActionPermission('read', $file)) {
843  throw new Exception\InsufficientFileAccessPermissionsException(
844  'You are not allowed to access that file: "' . $file->getName() . '"',
845  1375955429
846  );
847  }
848  if (!$this->checkFileExtensionPermission($file->getName())) {
849  throw new Exception\IllegalFileExtensionException(
850  'You are not allowed to use that file extension. File: "' . $file->getName() . '"',
851  1375955430
852  );
853  }
854  }
855 
864  protected function assureFileWritePermissions(FileInterface $file)
865  {
866  // Check if user is allowed to write the file and $file is writable
867  if (!$this->checkFileActionPermission('write', $file)) {
868  throw new Exception\InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
869  }
870  if (!$this->checkFileExtensionPermission($file->getName())) {
871  throw new Exception\IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
872  }
873  }
874 
882  protected function assureFileReplacePermissions(FileInterface $file)
883  {
884  // Check if user is allowed to replace the file and $file is writable
885  if (!$this->checkFileActionPermission('replace', $file)) {
886  throw new Exception\InsufficientFileWritePermissionsException('Replacing file "' . $file->getIdentifier() . '" is not allowed.', 1436899571);
887  }
888  // Check if parentFolder is writable for the user
889  if (!$this->checkFolderActionPermission('write', $file->getParentFolder())) {
890  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $file->getIdentifier() . '"', 1436899572);
891  }
892  }
893 
902  protected function assureFileDeletePermissions(FileInterface $file)
903  {
904  // Check for disallowed file extensions
905  if (!$this->checkFileExtensionPermission($file->getName())) {
906  throw new Exception\IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
907  }
908  // Check further permissions if file is not a processed file
909  if (!$file instanceof ProcessedFile) {
910  // Check if user is allowed to delete the file and $file is writable
911  if (!$this->checkFileActionPermission('delete', $file)) {
912  throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '"', 1319550425);
913  }
914  // Check if the user has write permissions to folders
915  // Would be good if we could check for actual write permissions in the containig folder
916  // but we cannot since we have no access to the containing folder of this file.
917  if (!$this->checkUserActionPermission('write', 'Folder')) {
918  throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
919  }
920  }
921  }
922 
934  protected function assureFileAddPermissions($targetFolder, $targetFileName)
935  {
936  // Check for a valid file extension
937  if (!$this->checkFileExtensionPermission($targetFileName)) {
938  throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
939  }
940  // Makes sure the user is allowed to upload
941  if (!$this->checkUserActionPermission('add', 'File')) {
942  throw new Exception\InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
943  }
944  // Check if targetFolder is writable
945  if (!$this->checkFolderActionPermission('write', $targetFolder)) {
946  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
947  }
948  }
949 
965  protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize)
966  {
967  // Makes sure this is an uploaded file
968  if (!is_uploaded_file($localFilePath)) {
969  throw new Exception\UploadException('The upload has failed, no uploaded file found!', 1322110455);
970  }
971  // Max upload size (kb) for files.
972  $maxUploadFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
973  if ($maxUploadFileSize > 0 && $uploadedFileSize >= $maxUploadFileSize) {
974  unlink($localFilePath);
975  throw new Exception\UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
976  }
977  $this->assureFileAddPermissions($targetFolder, $targetFileName);
978  }
979 
991  protected function assureFileMovePermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
992  {
993  // Check if targetFolder is within this storage
994  if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
995  throw new \RuntimeException('The target folder is not in the same storage. Target folder given: "' . $targetFolder->getIdentifier() . '"', 1422553107);
996  }
997  // Check for a valid file extension
998  if (!$this->checkFileExtensionPermission($targetFileName)) {
999  throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
1000  }
1001  // Check if user is allowed to move and $file is readable and writable
1002  if (!$file->getStorage()->checkFileActionPermission('move', $file)) {
1003  throw new Exception\InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
1004  }
1005  // Check if target folder is writable
1006  if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1007  throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219350);
1008  }
1009  }
1010 
1021  protected function assureFileRenamePermissions(FileInterface $file, $targetFileName)
1022  {
1023  // Check if file extension is allowed
1024  if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
1025  throw new Exception\IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663);
1026  }
1027  // Check if user is allowed to rename
1028  if (!$this->checkFileActionPermission('rename', $file)) {
1029  throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename files. File given: "' . $file->getName() . '"', 1319219351);
1030  }
1031  // Check if the user is allowed to write to folders
1032  // Although it would be good to check, we cannot check here if the folder actually is writable
1033  // because we do not know in which folder the file resides.
1034  // So we rely on the driver to throw an exception in case the renaming failed.
1035  if (!$this->checkFolderActionPermission('write')) {
1036  throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219352);
1037  }
1038  }
1039 
1054  protected function assureFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
1055  {
1056  // Check if targetFolder is within this storage, this should never happen
1057  if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1058  throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
1059  }
1060  // Check if user is allowed to copy
1061  if (!$file->getStorage()->checkFileActionPermission('copy', $file)) {
1062  throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '"', 1319550426);
1063  }
1064  // Check if targetFolder is writable
1065  if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1066  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
1067  }
1068  // Check for a valid file extension
1069  if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
1070  throw new Exception\IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
1071  }
1072  }
1073 
1088  protected function assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder)
1089  {
1090  // Check if targetFolder is within this storage, this should never happen
1091  if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1092  throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
1093  }
1094  if (!$folderToCopy instanceof Folder) {
1095  throw new \RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type folder.', 1384209020);
1096  }
1097  // Check if user is allowed to copy and the folder is readable
1098  if (!$folderToCopy->getStorage()->checkFolderActionPermission('copy', $folderToCopy)) {
1099  throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
1100  }
1101  if (!$targetParentFolder instanceof Folder) {
1102  throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type folder.', 1384209021);
1103  }
1104  // Check if targetFolder is writable
1105  if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1106  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
1107  }
1108  }
1109 
1124  protected function assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder)
1125  {
1126  // Check if targetFolder is within this storage, this should never happen
1127  if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1128  throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1129  }
1130  if (!$folderToMove instanceof Folder) {
1131  throw new \RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
1132  }
1133  // Check if user is allowed to move and the folder is writable
1134  // In fact we would need to check if the parent folder of the folder to move is writable also
1135  // But as of now we cannot extract the parent folder from this folder
1136  if (!$folderToMove->getStorage()->checkFolderActionPermission('move', $folderToMove)) {
1137  throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
1138  }
1139  if (!$targetParentFolder instanceof Folder) {
1140  throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
1141  }
1142  // Check if targetFolder is writable
1143  if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1144  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1145  }
1146  }
1147 
1158  public function sanitizeFileName($fileName, Folder $targetFolder = null)
1159  {
1160  $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1161  $fileName = $this->driver->sanitizeFileName($fileName);
1162 
1163  // The file name could be changed by an external slot
1164  $fileName = $this->emitSanitizeFileNameSignal($fileName, $targetFolder);
1165 
1166  return $fileName;
1167  }
1168 
1169  /********************
1170  * FILE ACTIONS
1171  ********************/
1185  public function addFile($localFilePath, Folder $targetFolder, $targetFileName = '', $conflictMode = DuplicationBehavior::RENAME, $removeOriginal = true)
1186  {
1187  $localFilePath = PathUtility::getCanonicalPath($localFilePath);
1188  // File is not available locally NOR is it an uploaded file
1189  if (!is_uploaded_file($localFilePath) && !file_exists($localFilePath)) {
1190  throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1191  }
1192  $conflictMode = DuplicationBehavior::cast($conflictMode);
1193  $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1194  $targetFileName = $this->sanitizeFileName($targetFileName ?: PathUtility::basename($localFilePath), $targetFolder);
1195 
1196  $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1197 
1198  $this->assureFileAddPermissions($targetFolder, $targetFileName);
1199 
1200  $replaceExisting = false;
1201  if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1202  throw new Exception\ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1203  }
1204  if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1205  $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1206  } elseif ($conflictMode->equals(DuplicationBehavior::REPLACE) && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1207  $replaceExisting = true;
1208  }
1209 
1210  $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName, $removeOriginal);
1211  $file = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1212 
1213  if ($replaceExisting && $file instanceof File) {
1214  $this->getIndexer()->updateIndexEntry($file);
1215  }
1216  if ($this->autoExtractMetadataEnabled()) {
1217  $this->getIndexer()->extractMetaData($file);
1218  }
1219 
1220  $this->emitPostFileAddSignal($file, $targetFolder);
1221 
1222  return $file;
1223  }
1224 
1235  public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = null)
1236  {
1237  if (!file_exists($localFilePath)) {
1238  throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1239  }
1240  if ($processingFolder === null) {
1241  $processingFolder = $this->getProcessingFolder($processedFile->getOriginalFile());
1242  }
1243  $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1244  // @todo check if we have to update the processed file other then the identifier
1245  $processedFile->setIdentifier($fileIdentifier);
1246  return $processedFile;
1247  }
1248 
1256  public function hashFile(FileInterface $fileObject, $hash)
1257  {
1258  return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1259  }
1260 
1269  public function hashFileByIdentifier($fileIdentifier, $hash)
1270  {
1271  return $this->driver->hash($fileIdentifier, $hash);
1272  }
1273 
1282  public function hashFileIdentifier($file)
1283  {
1284  if (is_object($file) && $file instanceof FileInterface) {
1286  $file = $file->getIdentifier();
1287  }
1288  return $this->driver->hashIdentifier($file);
1289  }
1290 
1301  public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = false)
1302  {
1303  $publicUrl = null;
1304  if ($this->isOnline()) {
1305  // Pre-process the public URL by an accordant slot
1306  $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, ['publicUrl' => &$publicUrl]);
1307 
1308  if (
1309  $publicUrl === null
1310  && $resourceObject instanceof File
1311  && ($helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($resourceObject)) !== false
1312  ) {
1313  $publicUrl = $helper->getPublicUrl($resourceObject, $relativeToCurrentScript);
1314  }
1315 
1316  // If slot did not handle the signal, use the default way to determine public URL
1317  if ($publicUrl === null) {
1318  if ($this->hasCapability(self::CAPABILITY_PUBLIC)) {
1319  $publicUrl = $this->driver->getPublicUrl($resourceObject->getIdentifier());
1320  }
1321 
1322  if ($publicUrl === null && $resourceObject instanceof FileInterface) {
1323  $queryParameterArray = ['eID' => 'dumpFile', 't' => ''];
1324  if ($resourceObject instanceof File) {
1325  $queryParameterArray['f'] = $resourceObject->getUid();
1326  $queryParameterArray['t'] = 'f';
1327  } elseif ($resourceObject instanceof ProcessedFile) {
1328  $queryParameterArray['p'] = $resourceObject->getUid();
1329  $queryParameterArray['t'] = 'p';
1330  }
1331 
1332  $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1333  $publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(PATH_site . 'index.php'));
1334  $publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
1335  }
1336 
1337  // If requested, make the path relative to the current script in order to make it possible
1338  // to use the relative file
1339  if ($publicUrl !== null && $relativeToCurrentScript && !GeneralUtility::isValidUrl($publicUrl)) {
1340  $absolutePathToContainingFolder = PathUtility::dirname(PATH_site . $publicUrl);
1341  $pathPart = PathUtility::getRelativePathTo($absolutePathToContainingFolder);
1342  $filePart = substr(PATH_site . $publicUrl, strlen($absolutePathToContainingFolder) + 1);
1343  $publicUrl = $pathPart . $filePart;
1344  }
1345  }
1346  }
1347  return $publicUrl;
1348  }
1349 
1360  public function processFile(FileInterface $fileObject, $context, array $configuration)
1361  {
1362  if ($fileObject->getStorage() !== $this) {
1363  throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1364  }
1365  $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1366 
1367  return $processedFile;
1368  }
1369 
1377  public function getFileForLocalProcessing(FileInterface $fileObject, $writable = true)
1378  {
1379  $filePath = $this->driver->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1380  return $filePath;
1381  }
1382 
1389  public function getFile($identifier)
1390  {
1391  $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1392  if (!$this->driver->fileExists($identifier)) {
1393  $file->setMissing(true);
1394  }
1395  return $file;
1396  }
1397 
1405  public function getFileInfo(FileInterface $fileObject)
1406  {
1407  return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1408  }
1409 
1418  public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = [])
1419  {
1420  return $this->driver->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1421  }
1422 
1427  {
1428  $this->fileAndFolderNameFilters = [];
1429  }
1430 
1435  {
1436  $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1437  }
1438 
1445  {
1447  }
1448 
1453  public function setFileAndFolderNameFilters(array $filters)
1454  {
1455  $this->fileAndFolderNameFilters = $filters;
1456  return $this;
1457  }
1458 
1462  public function addFileAndFolderNameFilter($filter)
1463  {
1464  $this->fileAndFolderNameFilters[] = $filter;
1465  }
1466 
1472  public function getFolderIdentifierFromFileIdentifier($fileIdentifier)
1473  {
1474  return $this->driver->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1475  }
1476 
1484  public function getFileInFolder($fileName, Folder $folder)
1485  {
1486  $identifier = $this->driver->getFileInFolder($fileName, $folder->getIdentifier());
1487  return $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1488  }
1489 
1505  public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
1506  {
1507  $this->assureFolderReadPermission($folder);
1508 
1509  $rows = $this->getFileIndexRepository()->findByFolder($folder);
1510 
1511  $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1512  $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
1513 
1514  $items = [];
1515  foreach ($fileIdentifiers as $identifier) {
1516  if (isset($rows[$identifier])) {
1517  $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1518  } else {
1519  $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1520  }
1521  if ($fileObject instanceof FileInterface) {
1522  $key = $fileObject->getName();
1523  while (isset($items[$key])) {
1524  $key .= 'z';
1525  }
1526  $items[$key] = $fileObject;
1527  }
1528  }
1529 
1530  return $items;
1531  }
1532 
1539  public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1540  {
1541  $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1542  return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1543  }
1544 
1552  public function countFilesInFolder(Folder $folder, $useFilters = true, $recursive = false)
1553  {
1554  $this->assureFolderReadPermission($folder);
1555  $filters = $useFilters ? $this->fileAndFolderNameFilters : [];
1556  return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters);
1557  }
1558 
1565  public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false)
1566  {
1567  $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
1568  return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1569  }
1570 
1577  public function hasFile($identifier)
1578  {
1579  // Allow if identifier is in processing folder
1580  if (!$this->isWithinProcessingFolder($identifier)) {
1581  $this->assureFolderReadPermission();
1582  }
1583  return $this->driver->fileExists($identifier);
1584  }
1585 
1591  public function getProcessingFolders()
1592  {
1593  if ($this->processingFolders === null) {
1594  $this->processingFolders = [];
1595  $this->processingFolders[] = $this->getProcessingFolder();
1597  $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
1598  $allStorages = $storageRepository->findAll();
1599  foreach ($allStorages as $storage) {
1600  // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1601  // See #66695 for details
1602  list($storageUid, $processingFolderIdentifier) = GeneralUtility::trimExplode(':', $storage->getStorageRecord()['processingfolder']);
1603  if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1604  continue;
1605  }
1606  $potentialProcessingFolder = $this->getResourceFactoryInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1607  if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1608  $this->processingFolders[] = $potentialProcessingFolder;
1609  }
1610  }
1611  }
1612 
1613  return $this->processingFolders;
1614  }
1615 
1623  public function isProcessingFolder(Folder $folder)
1624  {
1625  $isProcessingFolder = false;
1626  foreach ($this->getProcessingFolders() as $processingFolder) {
1627  if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1628  $isProcessingFolder = true;
1629  break;
1630  }
1631  }
1632  return $isProcessingFolder;
1633  }
1634 
1642  public function hasFileInFolder($fileName, Folder $folder)
1643  {
1644  $this->assureFolderReadPermission($folder);
1645  return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1646  }
1647 
1656  public function getFileContents($file)
1657  {
1658  $this->assureFileReadPermission($file);
1659  return $this->driver->getFileContents($file->getIdentifier());
1660  }
1661 
1671  public function dumpFileContents(FileInterface $file, $asDownload = false, $alternativeFilename = null, $overrideMimeType = null)
1672  {
1673  $downloadName = $alternativeFilename ?: $file->getName();
1674  $contentDisposition = $asDownload ? 'attachment' : 'inline';
1675  header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1676  header('Content-Type: ' . ($overrideMimeType ?: $file->getMimeType()));
1677  header('Content-Length: ' . $file->getSize());
1678 
1679  // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1680  // See for more information: http://support.microsoft.com/kb/323308
1681  header("Cache-Control: ''");
1682  header(
1683  'Last-Modified: ' .
1684  gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), ['mtime']))) . ' GMT',
1685  true,
1686  200
1687  );
1688  ob_clean();
1689  flush();
1690  while (ob_get_level() > 0) {
1691  ob_end_clean();
1692  }
1693  $this->driver->dumpFileContents($file->getIdentifier());
1694  }
1695 
1707  public function setFileContents(AbstractFile $file, $contents)
1708  {
1709  // Check if user is allowed to edit
1710  $this->assureFileWritePermissions($file);
1711  $this->emitPreFileSetContentsSignal($file, $contents);
1712  // Call driver method to update the file and update file index entry afterwards
1713  $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1714  if ($file instanceof File) {
1715  $this->getIndexer()->updateIndexEntry($file);
1716  }
1717  $this->emitPostFileSetContentsSignal($file, $contents);
1718  return $result;
1719  }
1720 
1733  public function createFile($fileName, Folder $targetFolderObject)
1734  {
1735  $this->assureFileAddPermissions($targetFolderObject, $fileName);
1736  $this->emitPreFileCreateSignal($fileName, $targetFolderObject);
1737  $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1738  $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1739  return $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1740  }
1741 
1750  public function deleteFile($fileObject)
1751  {
1752  $this->assureFileDeletePermissions($fileObject);
1753 
1754  $this->emitPreFileDeleteSignal($fileObject);
1755  $deleted = true;
1756 
1757  if ($this->driver->fileExists($fileObject->getIdentifier())) {
1758  // Disable permission check to find nearest recycler and move file without errors
1759  $currentPermissions = $this->evaluatePermissions;
1760  $this->evaluatePermissions = false;
1761 
1762  $recyclerFolder = $this->getNearestRecyclerFolder($fileObject);
1763  if ($recyclerFolder === null) {
1764  $result = $this->driver->deleteFile($fileObject->getIdentifier());
1765  } else {
1766  $result = $this->moveFile($fileObject, $recyclerFolder);
1767  $deleted = false;
1768  }
1769 
1770  $this->evaluatePermissions = $currentPermissions;
1771 
1772  if (!$result) {
1773  throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1774  }
1775  }
1776  // Mark the file object as deleted
1777  if ($deleted && $fileObject instanceof AbstractFile) {
1778  $fileObject->setDeleted();
1779  }
1780 
1781  $this->emitPostFileDeleteSignal($fileObject);
1782 
1783  return true;
1784  }
1785 
1800  public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1801  {
1802  $conflictMode = DuplicationBehavior::cast($conflictMode);
1803  if ($targetFileName === null) {
1804  $targetFileName = $file->getName();
1805  }
1806  $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1807  $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1808  $this->emitPreFileCopySignal($file, $targetFolder);
1809  // File exists and we should abort, let's abort
1810  if ($conflictMode->equals(DuplicationBehavior::CANCEL) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1811  throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1812  }
1813  // File exists and we should find another name, let's find another one
1814  if ($conflictMode->equals(DuplicationBehavior::RENAME) && $targetFolder->hasFile($sanitizedTargetFileName)) {
1815  $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1816  }
1817  $sourceStorage = $file->getStorage();
1818  // Call driver method to create a new file from an existing file object,
1819  // and return the new file object
1820  if ($sourceStorage === $this) {
1821  $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1822  } else {
1823  $tempPath = $file->getForLocalProcessing();
1824  $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1825  }
1826  $newFileObject = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1827  $this->emitPostFileCopySignal($file, $targetFolder);
1828  return $newFileObject;
1829  }
1830 
1846  public function moveFile($file, $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
1847  {
1848  $conflictMode = DuplicationBehavior::cast($conflictMode);
1849  if ($targetFileName === null) {
1850  $targetFileName = $file->getName();
1851  }
1852  $originalFolder = $file->getParentFolder();
1853  $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1854  $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1855  if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1856  // File exists and we should abort, let's abort
1857  if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1858  $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1859  } elseif ($conflictMode->equals(DuplicationBehavior::CANCEL)) {
1860  throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1861  }
1862  }
1863  $this->emitPreFileMoveSignal($file, $targetFolder, $sanitizedTargetFileName);
1864  $sourceStorage = $file->getStorage();
1865  // Call driver method to move the file and update the index entry
1866  try {
1867  if ($sourceStorage === $this) {
1868  $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1869  if (!$file instanceof AbstractFile) {
1870  throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1871  }
1872  $file->updateProperties(['identifier' => $newIdentifier]);
1873  } else {
1874  $tempPath = $file->getForLocalProcessing();
1875  $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1876 
1877  // Disable permission check to find nearest recycler and move file without errors
1878  $currentPermissions = $sourceStorage->evaluatePermissions;
1879  $sourceStorage->evaluatePermissions = false;
1880 
1881  $recyclerFolder = $sourceStorage->getNearestRecyclerFolder($file);
1882  if ($recyclerFolder === null) {
1883  $sourceStorage->driver->deleteFile($file->getIdentifier());
1884  } else {
1885  $sourceStorage->moveFile($file, $recyclerFolder);
1886  }
1887  $sourceStorage->evaluatePermissions = $currentPermissions;
1888  if ($file instanceof File) {
1889  $file->updateProperties(['storage' => $this->getUid(), 'identifier' => $newIdentifier]);
1890  }
1891  }
1892  $this->getIndexer()->updateIndexEntry($file);
1893  } catch (\TYPO3\CMS\Core\Exception $e) {
1894  echo $e->getMessage();
1895  }
1896  $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1897  return $file;
1898  }
1899 
1909  public function renameFile($file, $targetFileName, $conflictMode = DuplicationBehavior::RENAME)
1910  {
1911  // The name should be different from the current.
1912  if ($file->getName() === $targetFileName) {
1913  return $file;
1914  }
1915  $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1916  $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1917  $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1918 
1919  $conflictMode = DuplicationBehavior::cast($conflictMode);
1920 
1921  // Call driver method to rename the file and update the index entry
1922  try {
1923  $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1924  if ($file instanceof File) {
1925  $file->updateProperties(['identifier' => $newIdentifier]);
1926  }
1927  $this->getIndexer()->updateIndexEntry($file);
1928  } catch (ExistingTargetFileNameException $exception) {
1929  if ($conflictMode->equals(DuplicationBehavior::RENAME)) {
1930  $newName = $this->getUniqueName($file->getParentFolder(), $sanitizedTargetFileName);
1931  $file = $this->renameFile($file, $newName);
1932  } elseif ($conflictMode->equals(DuplicationBehavior::CANCEL)) {
1933  throw $exception;
1934  } elseif ($conflictMode->equals(DuplicationBehavior::REPLACE)) {
1935  $sourceFileIdentifier = substr($file->getCombinedIdentifier(), 0, strrpos($file->getCombinedIdentifier(), '/') + 1) . $targetFileName;
1936  $sourceFile = $this->getResourceFactoryInstance()->getFileObjectFromCombinedIdentifier($sourceFileIdentifier);
1937  $file = $this->replaceFile($sourceFile, PATH_site . $file->getPublicUrl());
1938  }
1939  } catch (\RuntimeException $e) {
1940  }
1941 
1942  $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1943 
1944  return $file;
1945  }
1946 
1958  public function replaceFile(FileInterface $file, $localFilePath)
1959  {
1960  $this->assureFileReplacePermissions($file);
1961  if (!file_exists($localFilePath)) {
1962  throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1963  }
1964  $this->emitPreFileReplaceSignal($file, $localFilePath);
1965  $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
1966  if ($file instanceof File) {
1967  $this->getIndexer()->updateIndexEntry($file);
1968  }
1969  if ($this->autoExtractMetadataEnabled()) {
1970  $this->getIndexer()->extractMetaData($file);
1971  }
1972  $this->emitPostFileReplaceSignal($file, $localFilePath);
1973 
1974  return $file;
1975  }
1976 
1986  public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = null, $targetFileName = null, $conflictMode = DuplicationBehavior::CANCEL)
1987  {
1988  $conflictMode = DuplicationBehavior::cast($conflictMode);
1989  $localFilePath = $uploadedFileData['tmp_name'];
1990  if ($targetFolder === null) {
1991  $targetFolder = $this->getDefaultFolder();
1992  }
1993  if ($targetFileName === null) {
1994  $targetFileName = $uploadedFileData['name'];
1995  }
1996  $targetFileName = $this->driver->sanitizeFileName($targetFileName);
1997 
1998  $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1999  if ($this->hasFileInFolder($targetFileName, $targetFolder) && $conflictMode->equals(DuplicationBehavior::REPLACE)) {
2000  $file = $this->getFileInFolder($targetFileName, $targetFolder);
2001  $resultObject = $this->replaceFile($file, $localFilePath);
2002  } else {
2003  $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, (string)$conflictMode);
2004  }
2005  return $resultObject;
2006  }
2007 
2008  /********************
2009  * FOLDER ACTIONS
2010  ********************/
2017  protected function getAllFileObjectsInFolder(Folder $folder)
2018  {
2019  $files = [];
2020  $folderQueue = [$folder];
2021  while (!empty($folderQueue)) {
2022  $folder = array_shift($folderQueue);
2023  foreach ($folder->getSubfolders() as $subfolder) {
2024  $folderQueue[] = $subfolder;
2025  }
2026  foreach ($folder->getFiles() as $file) {
2028  $files[$file->getIdentifier()] = $file;
2029  }
2030  }
2031 
2032  return $files;
2033  }
2034 
2049  public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
2050  {
2051  // @todo add tests
2052  $originalFolder = $folderToMove->getParentFolder();
2053  $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
2054  $sourceStorage = $folderToMove->getStorage();
2055  $returnObject = null;
2056  $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
2057  // @todo check if folder already exists in $targetParentFolder, handle this conflict then
2058  $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
2059  // Get all file objects now so we are able to update them after moving the folder
2060  $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
2061  if ($sourceStorage === $this) {
2062  if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
2063  throw new InvalidTargetFolderException(
2064  sprintf(
2065  'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
2066  $folderToMove->getName(),
2067  $targetParentFolder->getName()
2068  ),
2069  1422723050
2070  );
2071  }
2072  $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2073  } else {
2074  $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
2075  }
2076  // Update the identifier and storage of all file objects
2077  foreach ($fileObjects as $oldIdentifier => $fileObject) {
2078  $newIdentifier = $fileMappings[$oldIdentifier];
2079  $fileObject->updateProperties(['storage' => $this->getUid(), 'identifier' => $newIdentifier]);
2080  $this->getIndexer()->updateIndexEntry($fileObject);
2081  }
2082  $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
2083  $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
2084  return $returnObject;
2085  }
2086 
2097  protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName)
2098  {
2099  throw new NotImplementedMethodException('Not yet implemented', 1476046361);
2100  }
2101 
2112  public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
2113  {
2114  // @todo implement the $conflictMode handling
2115  $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
2116  $returnObject = null;
2117  $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
2118  if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
2119  $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2120  }
2121  $sourceStorage = $folderToCopy->getStorage();
2122  // call driver method to move the file
2123  // that also updates the file object properties
2124  if ($sourceStorage === $this) {
2125  if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
2126  throw new InvalidTargetFolderException(
2127  sprintf(
2128  'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
2129  $folderToCopy->getName(),
2130  $targetParentFolder->getName()
2131  ),
2132  1422723059
2133  );
2134  }
2135  $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
2136  $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
2137  } else {
2138  $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
2139  }
2140  $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
2141  return $returnObject;
2142  }
2143 
2154  protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName)
2155  {
2156  throw new NotImplementedMethodException('Not yet implemented.', 1476046386);
2157  }
2158 
2168  public function renameFolder($folderObject, $newName)
2169  {
2170 
2171  // Renaming the folder should check if the parent folder is writable
2172  // We cannot do this however because we cannot extract the parent folder from a folder currently
2173  if (!$this->checkFolderActionPermission('rename', $folderObject)) {
2174  throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
2175  }
2176 
2177  $sanitizedNewName = $this->driver->sanitizeFileName($newName);
2178  $returnObject = null;
2179  if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
2180  throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
2181  }
2182 
2183  $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
2184 
2185  $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
2186  $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
2187  // Update the identifier of all file objects
2188  foreach ($fileObjects as $oldIdentifier => $fileObject) {
2189  $newIdentifier = $fileMappings[$oldIdentifier];
2190  $fileObject->updateProperties(['identifier' => $newIdentifier]);
2191  $this->getIndexer()->updateIndexEntry($fileObject);
2192  }
2193  $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
2194 
2195  $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
2196 
2197  return $returnObject;
2198  }
2199 
2212  public function deleteFolder($folderObject, $deleteRecursively = false)
2213  {
2214  $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
2215  $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
2216  if (!$isEmpty && !$deleteRecursively) {
2217  throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
2218  }
2219 
2220  $this->emitPreFolderDeleteSignal($folderObject);
2221 
2222  foreach ($this->getFilesInFolder($folderObject, 0, 0, false, $deleteRecursively) as $file) {
2223  $this->deleteFile($file);
2224  }
2225 
2226  $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
2227 
2228  $this->emitPostFolderDeleteSignal($folderObject);
2229 
2230  return $result;
2231  }
2232 
2243  public function getFolderInFolder($folderName, Folder $parentFolder, $returnInaccessibleFolderObject = false)
2244  {
2245  $folderIdentifier = $this->driver->getFolderInFolder($folderName, $parentFolder->getIdentifier());
2246  return $this->getFolder($folderIdentifier, $returnInaccessibleFolderObject);
2247  }
2248 
2263  public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false)
2264  {
2265  $filters = $useFilters == true ? $this->fileAndFolderNameFilters : [];
2266 
2267  $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
2268 
2269  // Exclude processing folders
2270  foreach ($this->getProcessingFolders() as $processingFolder) {
2271  $processingIdentifier = $processingFolder->getIdentifier();
2272  if (isset($folderIdentifiers[$processingIdentifier])) {
2273  unset($folderIdentifiers[$processingIdentifier]);
2274  }
2275  }
2276  $folders = [];
2277  foreach ($folderIdentifiers as $folderIdentifier) {
2278  $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, true);
2279  }
2280  return $folders;
2281  }
2282 
2290  public function countFoldersInFolder(Folder $folder, $useFilters = true, $recursive = false)
2291  {
2292  $this->assureFolderReadPermission($folder);
2293  $filters = $useFilters ? $this->fileAndFolderNameFilters : [];
2294  return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters);
2295  }
2296 
2303  public function hasFolder($identifier)
2304  {
2305  $this->assureFolderReadPermission();
2306  return $this->driver->folderExists($identifier);
2307  }
2308 
2316  public function hasFolderInFolder($folderName, Folder $folder)
2317  {
2318  $this->assureFolderReadPermission($folder);
2319  return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2320  }
2321 
2335  public function createFolder($folderName, Folder $parentFolder = null)
2336  {
2337  if ($parentFolder === null) {
2338  $parentFolder = $this->getRootLevelFolder();
2339  } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2340  throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2341  }
2342  if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2343  throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2344  }
2345  if ($this->driver->folderExistsInFolder($folderName, $parentFolder->getIdentifier())) {
2346  throw new Exception\ExistingTargetFolderException('Folder "' . $folderName . '" already exists.', 1423347324);
2347  }
2348 
2349  $this->emitPreFolderAddSignal($parentFolder, $folderName);
2350 
2351  $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), true);
2352  $newFolder = $this->getFolder($newFolder);
2353 
2354  $this->emitPostFolderAddSignal($newFolder);
2355 
2356  return $newFolder;
2357  }
2358 
2365  public function getFolderInfo(Folder $folder)
2366  {
2367  return $this->driver->getFolderInfoByIdentifier($folder->getIdentifier());
2368  }
2369 
2375  public function getDefaultFolder()
2376  {
2377  return $this->getFolder($this->driver->getDefaultFolder());
2378  }
2379 
2388  public function getFolder($identifier, $returnInaccessibleFolderObject = false)
2389  {
2390  $data = $this->driver->getFolderInfoByIdentifier($identifier);
2391  $folder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'] ?? null, $data['name'] ?? null);
2392 
2393  try {
2394  $this->assureFolderReadPermission($folder);
2395  } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2396  $folder = null;
2397  if ($returnInaccessibleFolderObject) {
2398  // if parent folder is readable return inaccessible folder object
2399  $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2400  if ($parentPermissions['r']) {
2401  $folder = GeneralUtility::makeInstance(
2402  InaccessibleFolder::class,
2403  $this,
2404  $data['identifier'],
2405  $data['name']
2406  );
2407  }
2408  }
2409 
2410  if ($folder === null) {
2411  throw $e;
2412  }
2413  }
2414  return $folder;
2415  }
2416 
2423  public function isWithinProcessingFolder($identifier)
2424  {
2425  $inProcessingFolder = false;
2426  foreach ($this->getProcessingFolders() as $processingFolder) {
2427  if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2428  $inProcessingFolder = true;
2429  break;
2430  }
2431  }
2432  return $inProcessingFolder;
2433  }
2434 
2443  public function isWithinFolder(Folder $folder, ResourceInterface $resource)
2444  {
2445  if ($folder->getStorage() !== $this) {
2446  throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2447  }
2448  if ($folder->getStorage() !== $resource->getStorage()) {
2449  return false;
2450  }
2451  return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2452  }
2453 
2462  public function getRootLevelFolder($respectFileMounts = true)
2463  {
2464  if ($respectFileMounts && !empty($this->fileMounts)) {
2465  $mount = reset($this->fileMounts);
2466  return $mount['folder'];
2467  }
2468  return $this->getResourceFactoryInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2469  }
2470 
2478  protected function emitSanitizeFileNameSignal($fileName, Folder $targetFolder)
2479  {
2480  list($fileName) = $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_SanitizeFileName, [$fileName, $targetFolder, $this, $this->driver]);
2481  return $fileName;
2482  }
2483 
2492  protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath)
2493  {
2494  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileAdd, [&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver]);
2495  return $targetFileName;
2496  }
2497 
2504  protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder)
2505  {
2506  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileAdd, [$file, $targetFolder]);
2507  }
2508 
2515  protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder)
2516  {
2517  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileCopy, [$file, $targetFolder]);
2518  }
2519 
2526  protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder)
2527  {
2528  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileCopy, [$file, $targetFolder]);
2529  }
2530 
2538  protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder, string $targetFileName)
2539  {
2540  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileMove, [$file, $targetFolder, $targetFileName]);
2541  }
2542 
2550  protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, FolderInterface $originalFolder)
2551  {
2552  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileMove, [$file, $targetFolder, $originalFolder]);
2553  }
2554 
2561  protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder)
2562  {
2563  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileRename, [$file, $targetFolder]);
2564  }
2565 
2572  protected function emitPostFileRenameSignal(FileInterface $file, $sanitizedTargetFileName)
2573  {
2574  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileRename, [$file, $sanitizedTargetFileName]);
2575  }
2576 
2583  protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath)
2584  {
2585  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileReplace, [$file, $localFilePath]);
2586  }
2587 
2594  protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath)
2595  {
2596  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileReplace, [$file, $localFilePath]);
2597  }
2598 
2605  protected function emitPreFileCreateSignal(string $fileName, Folder $targetFolder)
2606  {
2607  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileCreate, [$fileName, $targetFolder]);
2608  }
2609 
2616  protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder)
2617  {
2618  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileCreate, [$newFileIdentifier, $targetFolder]);
2619  }
2620 
2626  protected function emitPreFileDeleteSignal(FileInterface $file)
2627  {
2628  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileDelete, [$file]);
2629  }
2630 
2636  protected function emitPostFileDeleteSignal(FileInterface $file)
2637  {
2638  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileDelete, [$file]);
2639  }
2640 
2647  protected function emitPreFileSetContentsSignal(FileInterface $file, $content)
2648  {
2649  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFileSetContents, [$file, $content]);
2650  }
2651 
2658  protected function emitPostFileSetContentsSignal(FileInterface $file, $content)
2659  {
2660  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFileSetContents, [$file, $content]);
2661  }
2662 
2669  protected function emitPreFolderAddSignal(Folder $targetFolder, $name)
2670  {
2671  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderAdd, [$targetFolder, $name]);
2672  }
2673 
2679  protected function emitPostFolderAddSignal(Folder $folder)
2680  {
2681  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderAdd, [$folder]);
2682  }
2683 
2691  protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2692  {
2693  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderCopy, [$folder, $targetFolder, $newName]);
2694  }
2695 
2703  protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
2704  {
2705  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderCopy, [$folder, $targetFolder, $newName]);
2706  }
2707 
2715  protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName)
2716  {
2717  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderMove, [$folder, $targetFolder, $newName]);
2718  }
2719 
2728  protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder)
2729  {
2730  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderMove, [$folder, $targetFolder, $newName, $originalFolder]);
2731  }
2732 
2739  protected function emitPreFolderRenameSignal(Folder $folder, $newName)
2740  {
2741  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderRename, [$folder, $newName]);
2742  }
2743 
2750  protected function emitPostFolderRenameSignal(Folder $folder, $newName)
2751  {
2752  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderRename, [$folder, $newName]);
2753  }
2754 
2760  protected function emitPreFolderDeleteSignal(Folder $folder)
2761  {
2762  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreFolderDelete, [$folder]);
2763  }
2764 
2770  protected function emitPostFolderDeleteSignal(Folder $folder)
2771  {
2772  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PostFolderDelete, [$folder]);
2773  }
2774 
2782  protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData)
2783  {
2784  $this->getSignalSlotDispatcher()->dispatch(self::class, self::SIGNAL_PreGeneratePublicUrl, [$this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData]);
2785  }
2786 
2800  protected function getUniqueName(FolderInterface $folder, $theFile, $dontCheckForUnique = false)
2801  {
2802  static $maxNumber = 99, $uniqueNamePrefix = '';
2803  // Fetches info about path, name, extension of $theFile
2804  $origFileInfo = PathUtility::pathinfo($theFile);
2805  // Adds prefix
2806  if ($uniqueNamePrefix) {
2807  $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2808  $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2809  }
2810  // Check if the file exists and if not - return the fileName...
2811  // The destinations file
2812  $theDestFile = $origFileInfo['basename'];
2813  // If the file does NOT exist we return this fileName
2814  if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2815  return $theDestFile;
2816  }
2817  // Well the fileName in its pure form existed. Now we try to append
2818  // numbers / unique-strings and see if we can find an available fileName
2819  // This removes _xx if appended to the file
2820  $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2821  $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2822  for ($a = 1; $a <= $maxNumber + 1; $a++) {
2823  // First we try to append numbers
2824  if ($a <= $maxNumber) {
2825  $insert = '_' . sprintf('%02d', $a);
2826  } else {
2827  $insert = '_' . substr(md5(uniqid('', true)), 0, 6);
2828  }
2829  $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2830  // The destinations file
2831  $theDestFile = $theTestFile;
2832  // If the file does NOT exist we return this fileName
2833  if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2834  return $theDestFile;
2835  }
2836  }
2837  throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2838  }
2839 
2845  protected function getSignalSlotDispatcher()
2846  {
2847  if (!isset($this->signalSlotDispatcher)) {
2848  $this->signalSlotDispatcher = $this->getObjectManager()->get(Dispatcher::class);
2849  }
2851  }
2852 
2858  protected function getObjectManager()
2859  {
2860  return GeneralUtility::makeInstance(ObjectManager::class);
2861  }
2862 
2866  protected function getFileFactory()
2867  {
2868  return GeneralUtility::makeInstance(ResourceFactory::class);
2869  }
2870 
2874  protected function getFileIndexRepository()
2875  {
2877  }
2878 
2882  protected function getFileProcessingService()
2883  {
2884  if (!$this->fileProcessingService) {
2885  $this->fileProcessingService = GeneralUtility::makeInstance(Service\FileProcessingService::class, $this, $this->driver);
2886  }
2888  }
2889 
2896  public function getRole(FolderInterface $folder)
2897  {
2898  $folderRole = FolderInterface::ROLE_DEFAULT;
2899  $identifier = $folder->getIdentifier();
2900  if (method_exists($this->driver, 'getRole')) {
2901  $folderRole = $this->driver->getRole($folder->getIdentifier());
2902  }
2903  if (isset($this->fileMounts[$identifier])) {
2904  $folderRole = FolderInterface::ROLE_MOUNT;
2905 
2906  if (!empty($this->fileMounts[$identifier]['read_only'])) {
2908  }
2909  if ($this->fileMounts[$identifier]['user_mount']) {
2910  $folderRole = FolderInterface::ROLE_USER_MOUNT;
2911  }
2912  }
2913  if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2914  $folderRole = FolderInterface::ROLE_PROCESSING;
2915  }
2916 
2917  return $folderRole;
2918  }
2919 
2927  public function getProcessingFolder(File $file = null)
2928  {
2929  // If a file is given, make sure to return the processing folder of the correct storage
2930  if ($file !== null && $file->getStorage()->getUid() !== $this->getUid()) {
2931  return $file->getStorage()->getProcessingFolder($file);
2932  }
2933  if (!isset($this->processingFolder)) {
2934  $processingFolder = self::DEFAULT_ProcessingFolder;
2935  if (!empty($this->storageRecord['processingfolder'])) {
2936  $processingFolder = $this->storageRecord['processingfolder'];
2937  }
2938  try {
2939  if (strpos($processingFolder, ':') !== false) {
2940  list($storageUid, $processingFolderIdentifier) = explode(':', $processingFolder, 2);
2941  $storage = $this->getResourceFactoryInstance()->getStorageObject($storageUid);
2942  if ($storage->hasFolder($processingFolderIdentifier)) {
2943  $this->processingFolder = $storage->getFolder($processingFolderIdentifier);
2944  } else {
2945  $rootFolder = $storage->getRootLevelFolder(false);
2946  $currentEvaluatePermissions = $storage->getEvaluatePermissions();
2947  $storage->setEvaluatePermissions(false);
2948  $this->processingFolder = $storage->createFolder(
2949  ltrim($processingFolderIdentifier, '/'),
2950  $rootFolder
2951  );
2952  $storage->setEvaluatePermissions($currentEvaluatePermissions);
2953  }
2954  } else {
2955  if ($this->driver->folderExists($processingFolder) === false) {
2956  $rootFolder = $this->getRootLevelFolder(false);
2957  try {
2958  $currentEvaluatePermissions = $this->evaluatePermissions;
2959  $this->evaluatePermissions = false;
2960  $this->processingFolder = $this->createFolder(
2962  $rootFolder
2963  );
2964  $this->evaluatePermissions = $currentEvaluatePermissions;
2965  } catch (\InvalidArgumentException $e) {
2966  $this->processingFolder = GeneralUtility::makeInstance(
2967  InaccessibleFolder::class,
2968  $this,
2971  );
2972  }
2973  } else {
2974  $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
2975  $this->processingFolder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2976  }
2977  }
2978  } catch (Exception\InsufficientFolderWritePermissionsException $e) {
2979  $this->processingFolder = GeneralUtility::makeInstance(
2980  InaccessibleFolder::class,
2981  $this,
2984  );
2985  } catch (Exception\ResourcePermissionsUnavailableException $e) {
2986  $this->processingFolder = GeneralUtility::makeInstance(
2987  InaccessibleFolder::class,
2988  $this,
2991  );
2992  }
2993  }
2994 
2996  if (!empty($file)) {
2998  }
2999  return $processingFolder;
3000  }
3001 
3011  protected function getNestedProcessingFolder(File $file, Folder $rootProcessingFolder)
3012  {
3013  $processingFolder = $rootProcessingFolder;
3014  $nestedFolderNames = $this->getNamesForNestedProcessingFolder(
3015  $file->getIdentifier(),
3016  self::PROCESSING_FOLDER_LEVELS
3017  );
3018 
3019  try {
3020  foreach ($nestedFolderNames as $folderName) {
3021  if ($processingFolder->hasFolder($folderName)) {
3022  $processingFolder = $processingFolder->getSubfolder($folderName);
3023  } else {
3024  $currentEvaluatePermissions = $processingFolder->getStorage()->getEvaluatePermissions();
3025  $processingFolder->getStorage()->setEvaluatePermissions(false);
3026  $processingFolder = $processingFolder->createFolder($folderName);
3027  $processingFolder->getStorage()->setEvaluatePermissions($currentEvaluatePermissions);
3028  }
3029  }
3030  } catch (Exception\FolderDoesNotExistException $e) {
3031  }
3032 
3033  return $processingFolder;
3034  }
3035 
3043  protected function getNamesForNestedProcessingFolder($fileIdentifier, $levels)
3044  {
3045  $names = [];
3046  if ($levels === 0) {
3047  return $names;
3048  }
3049  $hash = md5($fileIdentifier);
3050  for ($i = 1; $i <= $levels; $i++) {
3051  $names[] = substr($hash, $i, 1);
3052  }
3053  return $names;
3054  }
3055 
3061  public function getDriverType()
3062  {
3063  return $this->storageRecord['driver'];
3064  }
3065 
3071  protected function getIndexer()
3072  {
3073  return GeneralUtility::makeInstance(Index\Indexer::class, $this);
3074  }
3075 
3079  public function setDefault($isDefault)
3080  {
3081  $this->isDefault = (bool)$isDefault;
3082  }
3083 
3087  public function isDefault()
3088  {
3089  return $this->isDefault;
3090  }
3091 
3096  {
3098  }
3099 
3105  protected function getBackendUser()
3106  {
3107  return $GLOBALS['BE_USER'];
3108  }
3109 
3121  protected function getNearestRecyclerFolder(FileInterface $file)
3122  {
3123  if ($file instanceof ProcessedFile) {
3124  return null;
3125  }
3126  // if the storage is not browsable we cannot fetch the parent folder of the file so no recycler handling is possible
3127  if (!$this->isBrowsable()) {
3128  return null;
3129  }
3130 
3131  $recyclerFolder = null;
3132  $folder = $file->getParentFolder();
3133 
3134  do {
3135  if ($folder->getRole() === FolderInterface::ROLE_RECYCLER) {
3136  break;
3137  }
3138 
3139  foreach ($folder->getSubfolders() as $subFolder) {
3140  if ($subFolder->getRole() === FolderInterface::ROLE_RECYCLER) {
3141  $recyclerFolder = $subFolder;
3142  break;
3143  }
3144  }
3145 
3146  $parentFolder = $folder->getParentFolder();
3147  $isFolderLoop = $folder->getIdentifier() === $parentFolder->getIdentifier();
3148  $folder = $parentFolder;
3149  } while ($recyclerFolder === null && !$isFolderLoop);
3150 
3151  return $recyclerFolder;
3152  }
3153 
3157  protected function getLogger()
3158  {
3160  $logManager = GeneralUtility::makeInstance(
3161  \TYPO3\CMS\Core\Log\LogManager::class
3162  );
3163  return $logManager->getLogger(get_class($this));
3164  }
3165 }
assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder)
moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName=null, $conflictMode=DuplicationBehavior::RENAME)
replaceFile(FileInterface $file, $localFilePath)
moveFile($file, $targetFolder, $targetFileName=null, $conflictMode=DuplicationBehavior::RENAME)
emitPreFileReplaceSignal(FileInterface $file, $localFilePath)
isWithinFolder(Folder $folder, ResourceInterface $resource)
emitPreFileCreateSignal(string $fileName, Folder $targetFolder)
checkFileActionPermission($action, FileInterface $file)
assureFileReplacePermissions(FileInterface $file)
emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, FolderInterface $originalFolder)
setFileContents(AbstractFile $file, $contents)
hasFileInFolder($fileName, Folder $folder)
emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName)
processFile(FileInterface $fileObject, $context, array $configuration)
addFileMount($folderIdentifier, $additionalData=[])
static getRelativePathTo($targetPath)
Definition: PathUtility.php:29
__construct(Driver\DriverInterface $driver, array $storageRecord)
emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder)
getUniqueName(FolderInterface $folder, $theFile, $dontCheckForUnique=false)
getFileInfoByIdentifier($identifier, array $propertiesToExtract=[])
getFolderInFolder($folderName, Folder $parentFolder, $returnInaccessibleFolderObject=false)
assureFileAddPermissions($targetFolder, $targetFileName)
getFolderIdentifiersInFolder($folderIdentifier, $useFilters=true, $recursive=false)
emitPreFileCopySignal(FileInterface $file, Folder $targetFolder)
hashFileByIdentifier($fileIdentifier, $hash)
emitPreFolderRenameSignal(Folder $folder, $newName)
dumpFileContents(FileInterface $file, $asDownload=false, $alternativeFilename=null, $overrideMimeType=null)
emitPreFileRenameSignal(FileInterface $file, $targetFolder)
static getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:40
static hmac($input, $additionalSecret='')
checkFolderActionPermission($action, Folder $folder=null)
emitPostFolderRenameSignal(Folder $folder, $newName)
assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder)
setUserPermissions(array $userPermissions)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
emitPostFileReplaceSignal(FileInterface $file, $localFilePath)
copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName)
emitPostFileRenameSignal(FileInterface $file, $sanitizedTargetFileName)
static verifyFilenameAgainstDenyPattern($filename)
getFilesInFolder(Folder $folder, $start=0, $maxNumberOfItems=0, $useFilters=true, $recursive=false, $sort='', $sortRev=false)
hashFile(FileInterface $fileObject, $hash)
getFolder($identifier, $returnInaccessibleFolderObject=false)
getNestedProcessingFolder(File $file, Folder $rootProcessingFolder)
static makeInstance($className,... $constructorArguments)
getRootLevelFolder($respectFileMounts=true)
emitPostFileSetContentsSignal(FileInterface $file, $content)
emitPostFileCopySignal(FileInterface $file, Folder $targetFolder)
getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript=false)
getFileInFolder($fileName, Folder $folder)
sanitizeFileName($fileName, Folder $targetFolder=null)
emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData)
addUploadedFile(array $uploadedFileData, Folder $targetFolder=null, $targetFileName=null, $conflictMode=DuplicationBehavior::CANCEL)
emitPreFileSetContentsSignal(FileInterface $file, $content)
static pathinfo($path, $options=null)
addFile($localFilePath, Folder $targetFolder, $targetFileName='', $conflictMode=DuplicationBehavior::RENAME, $removeOriginal=true)
assureFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName)
assureFolderDeletePermission(Folder $folder, $checkDeleteRecursively)
emitPostFileAddSignal(FileInterface $file, Folder $targetFolder)
getFileForLocalProcessing(FileInterface $fileObject, $writable=true)
createFolder($folderName, Folder $parentFolder=null)
getFiles($start=0, $numberOfItems=0, $filterMode=self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive=false, $sort='', $sortRev=false)
Definition: Folder.php:217
emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder, string $targetFileName)
assureFileDeletePermissions(FileInterface $file)
setDriver(Driver\DriverInterface $driver)
emitPreFolderAddSignal(Folder $targetFolder, $name)
deleteFolder($folderObject, $deleteRecursively=false)
createFile($fileName, Folder $targetFolderObject)
countFilesInFolder(Folder $folder, $useFilters=true, $recursive=false)
emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder)
getSubfolders($start=0, $numberOfItems=0, $filterMode=self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive=false)
Definition: Folder.php:272
assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize)
checkFileAndFolderNameFilters(ResourceInterface $fileOrFolder)
getFoldersInFolder(Folder $folder, $start=0, $maxNumberOfItems=0, $useFilters=true, $recursive=false, $sort='', $sortRev=false)
emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath)
getNamesForNestedProcessingFolder($fileIdentifier, $levels)
emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName)
hasFolderInFolder($folderName, Folder $folder)
emitSanitizeFileNameSignal($fileName, Folder $targetFolder)
getFileIdentifiersInFolder($folderIdentifier, $useFilters=true, $recursive=false)
updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder=null)
assureFileRenamePermissions(FileInterface $file, $targetFileName)
copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName=null, $conflictMode=DuplicationBehavior::RENAME)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
countFoldersInFolder(Folder $folder, $useFilters=true, $recursive=false)
assureFileMovePermissions(FileInterface $file, Folder $targetFolder, $targetFileName)
getFileInfo(FileInterface $fileObject)
static uniqueList($in_list, $secondParameter=null)
renameFile($file, $targetFileName, $conflictMode=DuplicationBehavior::RENAME)