17 use Psr\Http\Message\ResponseInterface;
104 if ($this->baseUri ===
null) {
129 if ($temporaryBaseUri !==
'') {
130 $uriParts = explode(
'/', $temporaryBaseUri);
131 $uriParts = array_map(
'rawurlencode', $uriParts);
132 $temporaryBaseUri = implode(
'/', $uriParts) .
'/';
134 $this->baseUri = $temporaryBaseUri;
135 } elseif (isset($this->configuration[
'baseUri']) && GeneralUtility::isValidUrl($this->configuration[
'baseUri'])) {
136 $this->baseUri = rtrim($this->configuration[
'baseUri'],
'/') .
'/';
152 throw new Exception\InvalidConfigurationException(
153 'Configuration must contain base path.',
167 throw new Exception\InvalidConfigurationException(
186 if ($this->baseUri !==
null) {
187 $uriParts = explode(
'/', ltrim($identifier,
'/'));
188 $uriParts = array_map(
'rawurlencode', $uriParts);
189 $identifier = implode(
'/', $uriParts);
190 $publicUrl = $this->baseUri . $identifier;
212 $identifier =
'/user_upload/';
214 if ($createFolder ===
true) {
229 public function createFolder($newFolderName, $parentFolderIdentifier =
'', $recursive =
false)
232 $newFolderName = trim($newFolderName,
'/');
233 if ($recursive ===
false) {
238 $parts = GeneralUtility::trimExplode(
'/', $newFolderName);
239 $parts = array_map([$this,
'sanitizeFileName'], $parts);
240 $newFolderName = implode(
'/', $parts);
242 $parentFolderIdentifier . $newFolderName .
'/'
246 return $newIdentifier;
262 if (!file_exists($absoluteFilePath) || !is_file($absoluteFilePath)) {
263 throw new \InvalidArgumentException(
'File ' . $fileIdentifier .
' does not exist.', 1314516809);
283 throw new Exception\FolderDoesNotExistException(
284 'Folder "' . $folderIdentifier .
'" does not exist.',
290 'identifier' => $folderIdentifier,
292 'mtime' => filemtime($absolutePath),
293 'ctime' => filectime($absolutePath),
313 if (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SYS'][
'UTF8filesystem']) {
315 $cleanFileName = preg_replace(
'/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION .
']/u',
'_', trim($fileName));
317 $fileName = GeneralUtility::makeInstance(CharsetConverter::class)->specCharsToASCII($charset, $fileName);
319 $cleanFileName = preg_replace(
'/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION .
'\\xC0-\\xFF]/',
'_', trim($fileName));
322 $cleanFileName = rtrim($cleanFileName,
'.');
323 if ($cleanFileName ===
'') {
324 throw new Exception\InvalidFileNameException(
325 'File name ' . $fileName .
' is invalid.',
329 return $cleanFileName;
351 protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles =
true, $includeDirs =
true, $recursive =
false, $sort =
'', $sortRev =
false)
355 if (!is_dir($realPath)) {
356 throw new \InvalidArgumentException(
357 'Cannot list items in directory ' . $folderIdentifier .
' - does not exist or is no directory',
363 $iterator = new \ArrayIterator($items);
364 if ($iterator->count() === 0) {
369 $c = $numberOfItems > 0 ? $numberOfItems : - 1;
371 while ($iterator->valid() && ($numberOfItems === 0 || $c > 0)) {
373 $iteratorItem = $iterator->current();
381 $iteratorItem[
'name'],
382 $iteratorItem[
'identifier'],
391 $items[$iteratorItem[
'identifier']] = $iteratorItem[
'identifier'];
397 }
catch (Exception\InvalidPathException $e) {
416 foreach ($filterMethods as $filter) {
417 if (is_callable($filter)) {
418 $result = call_user_func($filter, $itemName, $itemIdentifier, $parentIdentifier, [], $this);
421 if ($result === -1) {
424 if ($result ===
false) {
425 throw new \RuntimeException(
426 'Could not apply file/folder name filter ' . $filter[0] .
'::' . $filter[1],
463 public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive =
false, array $filenameFilterCallbacks = [], $sort =
'', $sortRev =
false)
465 return $this->
getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks,
true,
false, $recursive, $sort, $sortRev);
476 public function countFilesInFolder($folderIdentifier, $recursive =
false, array $filenameFilterCallbacks = [])
478 return count($this->
getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
497 public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive =
false, array $folderNameFilterCallbacks = [], $sort =
'', $sortRev =
false)
499 return $this->
getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks,
false,
true, $recursive, $sort, $sortRev);
510 public function countFoldersInFolder($folderIdentifier, $recursive =
false, array $folderNameFilterCallbacks = [])
512 return count($this->
getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $folderNameFilterCallbacks));
530 protected function retrieveFileAndFoldersInPath($path, $recursive =
false, $includeFiles =
true, $includeDirs =
true, $sort =
'', $sortRev =
false)
533 $iteratorMode = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::FOLLOW_SYMLINKS;
535 $iterator = new \RecursiveIteratorIterator(
536 new \RecursiveDirectoryIterator($path, $iteratorMode),
537 \RecursiveIteratorIterator::SELF_FIRST,
538 \RecursiveIteratorIterator::CATCH_GET_CHILD
541 $iterator = new \RecursiveDirectoryIterator($path, $iteratorMode);
544 $directoryEntries = [];
545 while ($iterator->valid()) {
547 $entry = $iterator->current();
548 $isFile = $entry->isFile();
549 $isDirectory = $isFile ? false : $entry->isDir();
551 (!$isFile && !$isDirectory)
552 || ($isFile && !$includeFiles)
553 || ($isDirectory && !$includeDirs)
554 || $entry->getFilename() ===
''
555 || !$entry->isReadable()
560 $entryIdentifier =
'/' . substr($entry->getPathname(), $pathLength);
563 $entryIdentifier .=
'/';
566 'identifier' => $entryIdentifier,
567 'name' => $entryName,
568 'type' => $isDirectory ?
'dir' :
'file'
570 $directoryEntries[$entryIdentifier] = $entryArray;
592 foreach ($directoryEntries as $entryArray) {
593 $dir = pathinfo($entryArray[
'name'], PATHINFO_DIRNAME) .
'/';
598 if ($entryArray[
'type'] ===
'file') {
606 $sortingKey = ($perms[
'r'] ?
'R' :
'')
607 . ($perms[
'w'] ?
'W' :
'');
610 $sortingKey = pathinfo($entryArray[
'name'], PATHINFO_EXTENSION);
620 $sortingKey = $entryArray[
'name'];
623 while (isset($entriesToSort[$sortingKey . $i])) {
626 $entriesToSort[$sortingKey . $i] = $entryArray;
628 uksort($entriesToSort,
'strnatcasecmp');
631 $entriesToSort = array_reverse($entriesToSort);
634 return $entriesToSort;
647 if (empty($propertiesToExtract)) {
648 $propertiesToExtract = [
649 'size',
'atime',
'mtime',
'ctime',
'mimetype',
'name',
'extension',
650 'identifier',
'identifier_hash',
'storage',
'folder_hash'
653 $fileInformation = [];
654 foreach ($propertiesToExtract as $property) {
657 return $fileInformation;
675 $fileInfo = GeneralUtility::makeInstance(FileInfo::class, $fileIdentifier);
678 return $fileInfo->getSize();
680 return $fileInfo->getATime();
682 return $fileInfo->getMTime();
684 return $fileInfo->getCTime();
690 return (
string)$fileInfo->getMimeType();
695 case 'identifier_hash':
700 throw new \InvalidArgumentException(sprintf(
'The information "%s" is not available.', $property), 1476047422);
724 $path = $this->absoluteBasePath . $relativeFilePath;
737 public function hash($fileIdentifier, $hashAlgorithm)
739 if (!in_array($hashAlgorithm, $this->supportedHashAlgorithms)) {
740 throw new \InvalidArgumentException(
'Hash algorithm "' . $hashAlgorithm .
'" is not supported.', 1304964032);
742 switch ($hashAlgorithm) {
750 throw new \RuntimeException(
'Hash algorithm ' . $hashAlgorithm .
' is not implemented.', 1329644451);
768 public function addFile($localFilePath, $targetFolderIdentifier, $newFileName =
'', $removeOriginal =
true)
774 if (GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storageUid > 0) {
775 throw new \InvalidArgumentException(
'Cannot add a file that is already part of this storage.', 1314778269);
781 if ($removeOriginal) {
782 if (is_uploaded_file($localFilePath)) {
783 $result = move_uploaded_file($localFilePath, $targetPath);
785 $result = rename($localFilePath, $targetPath);
788 $result = copy($localFilePath, $targetPath);
790 if ($result ===
false || !file_exists($targetPath)) {
791 throw new \RuntimeException(
792 'Adding file ' . $localFilePath .
' at ' . $newFileIdentifier .
' failed.',
798 GeneralUtility::fixPermissions($targetPath);
799 return $newFileIdentifier;
812 return is_file($absoluteFilePath);
824 $identifier = $folderIdentifier .
'/' . $fileName;
839 return is_dir($absoluteFilePath);
851 $identifier = $folderIdentifier .
'/' . $folderName;
866 return $folderIdentifier;
877 public function replaceFile($fileIdentifier, $localFilePath)
880 if (is_uploaded_file($localFilePath)) {
881 $result = move_uploaded_file($localFilePath, $filePath);
883 $result = rename($localFilePath, $filePath);
885 GeneralUtility::fixPermissions($filePath);
886 if ($result ===
false) {
887 throw new \RuntimeException(
'Replacing file ' . $fileIdentifier .
' with ' . $localFilePath .
' failed.', 1315314711);
905 $newIdentifier = $targetFolderIdentifier .
'/' . $fileName;
909 copy($sourcePath, $absoluteFilePath);
910 GeneralUtility::fixPermissions($absoluteFilePath);
911 return $newIdentifier;
928 $targetIdentifier = $targetFolderIdentifier .
'/' . $newFileName;
931 if ($result ===
false) {
932 throw new \RuntimeException(
'Moving file ' . $sourcePath .
' to ' . $targetIdentifier .
' failed.', 1315314712);
934 return $targetIdentifier;
948 $result = copy($sourcePath, $temporaryPath);
949 touch($temporaryPath, filemtime($sourcePath));
950 if ($result ===
false) {
951 throw new \RuntimeException(
952 'Copying file "' . $fileIdentifier .
'" to temporary path "' . $temporaryPath .
'" failed.',
956 return $temporaryPath;
970 if (file_exists($destinationFile)) {
971 $timeStamp = \DateTimeImmutable::createFromFormat(
'U.u', microtime(
true))->format(
'YmdHisu');
974 $result = rename($filePath, $destinationFile);
978 touch($destinationFile);
994 protected function createIdentifierMap(array $filesAndFolders, $sourceFolderIdentifier, $targetFolderIdentifier)
997 $identifierMap[$sourceFolderIdentifier] = $targetFolderIdentifier;
998 foreach ($filesAndFolders as $oldItem) {
999 if ($oldItem[
'type'] ===
'dir') {
1000 $oldIdentifier = $oldItem[
'identifier'];
1002 str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem[
'identifier'])
1005 $oldIdentifier = $oldItem[
'identifier'];
1007 str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem[
'identifier'])
1011 throw new Exception\FileOperationErrorException(
1012 sprintf(
'File "%1$s" was not found (should have been copied/moved from "%2$s").', $newIdentifier, $oldIdentifier),
1016 $identifierMap[$oldIdentifier] = $newIdentifier;
1018 return $identifierMap;
1038 $result = rename($sourcePath, $targetPath);
1039 if ($result ===
false) {
1040 throw new \RuntimeException(
'Moving folder ' . $sourcePath .
' to ' . $targetPath .
' failed.', 1320711817);
1043 $identifierMap = $this->
createIdentifierMap($filesAndFolders, $sourceFolderIdentifier, $relativeTargetPath);
1044 return $identifierMap;
1065 mkdir($targetFolderPath);
1067 $iterator = new \RecursiveIteratorIterator(
1068 new \RecursiveDirectoryIterator($sourceFolderPath),
1069 \RecursiveIteratorIterator::SELF_FIRST,
1070 \RecursiveIteratorIterator::CATCH_GET_CHILD
1073 $iterator->rewind();
1074 while ($iterator->valid()) {
1076 $current = $iterator->current();
1077 $fileName = $current->getFilename();
1078 $itemSubPath = GeneralUtility::fixWindowsFilePath($iterator->getSubPathname());
1079 if ($current->isDir() && !($fileName ===
'..' || $fileName ===
'.')) {
1080 GeneralUtility::mkdir($targetFolderPath .
'/' . $itemSubPath);
1081 } elseif ($current->isFile()) {
1082 $copySourcePath = $sourceFolderPath .
'/' . $itemSubPath;
1083 $copyTargetPath = $targetFolderPath .
'/' . $itemSubPath;
1084 $result = copy($copySourcePath, $copyTargetPath);
1085 if ($result ===
false) {
1087 GeneralUtility::rmdir($targetFolderIdentifier,
true);
1088 throw new Exception\FileOperationErrorException(
1089 'Copying resource "' . $copySourcePath .
'" to "' . $copyTargetPath .
'" failed.',
1096 GeneralUtility::fixPermissions($targetFolderPath,
true);
1109 public function renameFile($fileIdentifier, $newName)
1113 $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(
PathUtility::dirname($fileIdentifier)),
'/') .
'/' . $newName;
1117 throw new Exception\ExistingTargetFileNameException(
1118 'The target file "' . $newIdentifier .
'" already exists.',
1124 $result = rename($sourcePath, $targetPath);
1125 if ($result ===
false) {
1126 throw new \RuntimeException(
'Renaming file ' . $sourcePath .
' to ' . $targetPath .
' failed.', 1320375115);
1128 return $newIdentifier;
1151 $result = rename($sourcePath, $targetPath);
1152 if ($result ===
false) {
1153 throw new \RuntimeException(sprintf(
'Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
1157 $identifierMap = $this->
createIdentifierMap($filesAndFolders, $folderIdentifier, $newIdentifier);
1158 }
catch (\Exception $e) {
1159 rename($targetPath, $sourcePath);
1160 throw new \RuntimeException(
1162 'Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"',
1170 return $identifierMap;
1185 $result = unlink($filePath);
1187 if ($result ===
false) {
1188 throw new \RuntimeException(
'Deletion of file ' . $fileIdentifier .
' failed.', 1320855304);
1202 public function deleteFolder($folderIdentifier, $deleteRecursively =
false)
1206 if (!empty($recycleDirectory) && $folderPath !== $recycleDirectory) {
1209 $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
1211 if ($result ===
false) {
1212 throw new Exception\FileOperationErrorException(
1213 'Deleting folder "' . $folderIdentifier .
'" failed.',
1229 $dirHandle = opendir($path);
1230 while ($entry = readdir($dirHandle)) {
1231 if ($entry !==
'.' && $entry !==
'..') {
1232 closedir($dirHandle);
1236 closedir($dirHandle);
1252 if ($writable ===
false) {
1268 $permissionBits = fileperms($path);
1269 if ($permissionBits ===
false) {
1270 throw new Exception\ResourcePermissionsUnavailableException(
'Error while fetching permissions for ' . $path, 1319455097);
1273 'r' => (bool)is_readable($path),
1274 'w' => (bool)is_writable($path)
1287 public function isWithin($folderIdentifier, $identifier)
1291 if ($folderIdentifier === $entryIdentifier) {
1296 if ($folderIdentifier !==
'/') {
1297 $folderIdentifier .=
'/';
1299 return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
1310 public function createFile($fileName, $parentFolderIdentifier)
1315 $parentFolderIdentifier . $fileName
1318 $result = touch($absoluteFilePath);
1319 GeneralUtility::fixPermissions($absoluteFilePath);
1321 if ($result !==
true) {
1322 throw new \RuntimeException(
'Creating file ' . $fileIdentifier .
' failed.', 1320569854);
1324 return $fileIdentifier;
1339 return file_get_contents($filePath);
1353 $result = file_put_contents($filePath, $contents);
1356 clearstatcache(
true, $filePath);
1358 if ($result ===
false) {
1359 throw new \RuntimeException(
'Setting contents of file "' . $fileIdentifier .
'" failed.', 1325419305);
1370 public function getRole($folderIdentifier)
1396 public function streamFile(
string $identifier, array $properties): ResponseInterface
1399 $downloadName = $properties[
'filename_overwrite'] ?? $fileInfo[
'name'] ??
'';
1400 $mimeType = $properties[
'mimetype_overwrite'] ?? $fileInfo[
'mimetype'] ??
'';
1401 $contentDisposition = ($properties[
'as_download'] ??
false) ?
'attachment' :
'inline';
1405 return new Response(
1406 new SelfEmittableLazyOpenStream($filePath),
1409 'Content-Disposition' => $contentDisposition .
'; filename="' . $downloadName .
'"',
1410 'Content-Type' => $mimeType,
1411 'Content-Length' => (
string)$fileInfo[
'size'],
1412 'Last-Modified' => gmdate(
'D, d M Y H:i:s', $fileInfo[
'mtime']) .
' GMT',
1415 'Cache-Control' =>
'',
1430 if ($recyclerSubdirectory ===
false) {
1439 if ($searchDirectory == $rootDirectory) {
1445 while ($searchDirectory) {
1446 $recycleDirectory = $searchDirectory .
'/' . $recyclerSubdirectory;
1447 if (is_dir($recycleDirectory)) {
1448 return $recycleDirectory;
1450 if ($searchDirectory === $rootDirectory) {