TYPO3 CMS  TYPO3_7-6
LocalDriver.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 
23 
29 {
33  const UNSAFE_FILENAME_CHARACTER_EXPRESSION = '\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF';
34 
40  protected $absoluteBasePath;
41 
47  protected $supportedHashAlgorithms = ['sha1', 'md5'];
48 
55  protected $baseUri = null;
56 
60  protected $charsetConversion;
61 
64  '_recycler_' => FolderInterface::ROLE_RECYCLER,
66  'user_upload' => FolderInterface::ROLE_USERUPLOAD,
67  ];
68 
72  public function __construct(array $configuration = [])
73  {
74  parent::__construct($configuration);
75  // The capabilities default of this driver. See CAPABILITY_* constants for possible values
76  $this->capabilities =
80  }
81 
91  {
92  $this->capabilities &= $capabilities;
93  return $this->capabilities;
94  }
95 
101  public function processConfiguration()
102  {
103  $this->absoluteBasePath = $this->calculateBasePath($this->configuration);
104  $this->determineBaseUrl();
105  if ($this->baseUri === null) {
106  // remove public flag
107  $this->capabilities &= ~ResourceStorage::CAPABILITY_PUBLIC;
108  }
109  }
110 
117  public function initialize()
118  {
119  }
120 
127  protected function determineBaseUrl()
128  {
129  // only calculate baseURI if the storage does not enforce jumpUrl Script
131  if (GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
132  // use site-relative URLs
133  $temporaryBaseUri = rtrim(PathUtility::stripPathSitePrefix($this->absoluteBasePath), '/');
134  if ($temporaryBaseUri !== '') {
135  $uriParts = explode('/', $temporaryBaseUri);
136  $uriParts = array_map('rawurlencode', $uriParts);
137  $temporaryBaseUri = implode('/', $uriParts) . '/';
138  }
139  $this->baseUri = $temporaryBaseUri;
140  } elseif (isset($this->configuration['baseUri']) && GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
141  $this->baseUri = rtrim($this->configuration['baseUri'], '/') . '/';
142  }
143  }
144  }
145 
153  protected function calculateBasePath(array $configuration)
154  {
155  if (!array_key_exists('basePath', $configuration) || empty($configuration['basePath'])) {
156  throw new Exception\InvalidConfigurationException(
157  'Configuration must contain base path.',
158  1346510477
159  );
160  }
161 
162  if ($configuration['pathType'] === 'relative') {
163  $relativeBasePath = $configuration['basePath'];
164  $absoluteBasePath = PATH_site . $relativeBasePath;
165  } else {
166  $absoluteBasePath = $configuration['basePath'];
167  }
169  $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
170  if (!is_dir($absoluteBasePath)) {
171  throw new Exception\InvalidConfigurationException(
172  'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
173  1299233097
174  );
175  }
176  return $absoluteBasePath;
177  }
178 
187  public function getPublicUrl($identifier)
188  {
189  $publicUrl = null;
190  if ($this->baseUri !== null) {
191  $uriParts = explode('/', ltrim($identifier, '/'));
192  $uriParts = array_map('rawurlencode', $uriParts);
193  $identifier = implode('/', $uriParts);
194  $publicUrl = $this->baseUri . $identifier;
195  }
196  return $publicUrl;
197  }
198 
204  public function getRootLevelFolder()
205  {
206  return '/';
207  }
208 
214  public function getDefaultFolder()
215  {
216  $identifier = '/user_upload/';
217  $createFolder = !$this->folderExists($identifier);
218  if ($createFolder === true) {
219  $identifier = $this->createFolder('user_upload');
220  }
221  return $identifier;
222  }
223 
233  public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false)
234  {
235  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
236  $newFolderName = trim($newFolderName, '/');
237  if ($recursive == false) {
238  $newFolderName = $this->sanitizeFileName($newFolderName);
239  $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
240  GeneralUtility::mkdir($this->getAbsolutePath($newIdentifier));
241  } else {
242  $parts = GeneralUtility::trimExplode('/', $newFolderName);
243  $parts = array_map([$this, 'sanitizeFileName'], $parts);
244  $newFolderName = implode('/', $parts);
245  $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
246  GeneralUtility::mkdir_deep($this->getAbsolutePath($parentFolderIdentifier) . '/', $newFolderName);
247  }
248  return $newIdentifier;
249  }
250 
259  public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = [])
260  {
261  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
262  // don't use $this->fileExists() because we need the absolute path to the file anyways, so we can directly
263  // use PHP's filesystem method.
264  if (!file_exists($absoluteFilePath) || !is_file($absoluteFilePath)) {
265  throw new \InvalidArgumentException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
266  }
267 
268  $dirPath = PathUtility::dirname($fileIdentifier);
269  $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
270  return $this->extractFileInformation($absoluteFilePath, $dirPath, $propertiesToExtract);
271  }
272 
280  public function getFolderInfoByIdentifier($folderIdentifier)
281  {
282  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
283 
284  if (!$this->folderExists($folderIdentifier)) {
285  throw new Exception\FolderDoesNotExistException(
286  'Folder "' . $folderIdentifier . '" does not exist.',
287  1314516810
288  );
289  }
290  return [
291  'identifier' => $folderIdentifier,
292  'name' => PathUtility::basename($folderIdentifier),
293  'storage' => $this->storageUid
294  ];
295  }
296 
309  public function sanitizeFileName($fileName, $charset = '')
310  {
311  // Handle UTF-8 characters
312  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
313  // Allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
314  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . ']/u', '_', trim($fileName));
315  } else {
316  // Define character set
317  if (!$charset) {
318  if (TYPO3_MODE === 'FE') {
319  $charset = $GLOBALS['TSFE']->renderCharset;
320  } else {
321  // default for Backend
322  $charset = 'utf-8';
323  }
324  }
325  // If a charset was found, convert fileName
326  if ($charset) {
327  $fileName = $this->getCharsetConversion()->specCharsToASCII($charset, $fileName);
328  }
329  // Replace unwanted characters by underscores
330  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . '\\xC0-\\xFF]/', '_', trim($fileName));
331  }
332  // Strip trailing dots and return
333  $cleanFileName = rtrim($cleanFileName, '.');
334  if ($cleanFileName === '') {
335  throw new Exception\InvalidFileNameException(
336  'File name ' . $fileName . ' is invalid.',
337  1320288991
338  );
339  }
340  return $cleanFileName;
341  }
342 
362  protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = true, $includeDirs = true, $recursive = false, $sort = '', $sortRev = false)
363  {
364  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
365  $realPath = $this->getAbsolutePath($folderIdentifier);
366  if (!is_dir($realPath)) {
367  throw new \InvalidArgumentException(
368  'Cannot list items in directory ' . $folderIdentifier . ' - does not exist or is no directory',
369  1314349666
370  );
371  }
372 
373  $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs, $sort, $sortRev);
374  $iterator = new \ArrayIterator($items);
375  if ($iterator->count() === 0) {
376  return [];
377  }
378 
379  // $c is the counter for how many items we still have to fetch (-1 is unlimited)
380  $c = $numberOfItems > 0 ? $numberOfItems : - 1;
381  $items = [];
382  while ($iterator->valid() && ($numberOfItems === 0 || $c > 0)) {
383  // $iteratorItem is the file or folder name
384  $iteratorItem = $iterator->current();
385  // go on to the next iterator item now as we might skip this one early
386  $iterator->next();
387 
388  try {
389  if (
391  $filterMethods,
392  $iteratorItem['name'],
393  $iteratorItem['identifier'],
394  $this->getParentFolderIdentifierOfIdentifier($iteratorItem['identifier'])
395  )
396  ) {
397  continue;
398  }
399  if ($start > 0) {
400  $start--;
401  } else {
402  $items[$iteratorItem['identifier']] = $iteratorItem['identifier'];
403  // Decrement item counter to make sure we only return $numberOfItems
404  // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
405  // item here
406  --$c;
407  }
408  } catch (Exception\InvalidPathException $e) {
409  }
410  }
411  return $items;
412  }
413 
425  protected function applyFilterMethodsToDirectoryItem(array $filterMethods, $itemName, $itemIdentifier, $parentIdentifier)
426  {
427  foreach ($filterMethods as $filter) {
428  if (is_callable($filter)) {
429  $result = call_user_func($filter, $itemName, $itemIdentifier, $parentIdentifier, [], $this);
430  // We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
431  // If calling the method succeeded and thus we can't use that as a return value.
432  if ($result === -1) {
433  return false;
434  } elseif ($result === false) {
435  throw new \RuntimeException('Could not apply file/folder name filter ' . $filter[0] . '::' . $filter[1]);
436  }
437  }
438  }
439  return true;
440  }
441 
449  public function getFileInFolder($fileName, $folderIdentifier)
450  {
451  return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
452  }
453 
470  public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $filenameFilterCallbacks = [], $sort = '', $sortRev = false)
471  {
472  return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks, true, false, $recursive, $sort, $sortRev);
473  }
474 
483  public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = [])
484  {
485  return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
486  }
487 
504  public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $folderNameFilterCallbacks = [], $sort = '', $sortRev = false)
505  {
506  return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks, false, true, $recursive, $sort, $sortRev);
507  }
508 
517  public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = [])
518  {
519  return count($this->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $folderNameFilterCallbacks));
520  }
521 
537  protected function retrieveFileAndFoldersInPath($path, $recursive = false, $includeFiles = true, $includeDirs = true, $sort = '', $sortRev = false)
538  {
539  $pathLength = strlen($this->getAbsoluteBasePath());
540  $iteratorMode = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::FOLLOW_SYMLINKS;
541  if ($recursive) {
542  $iterator = new \RecursiveIteratorIterator(
543  new \RecursiveDirectoryIterator($path, $iteratorMode),
544  \RecursiveIteratorIterator::SELF_FIRST,
545  \RecursiveIteratorIterator::CATCH_GET_CHILD
546  );
547  } else {
548  $iterator = new \RecursiveDirectoryIterator($path, $iteratorMode);
549  }
550 
551  $directoryEntries = [];
552  while ($iterator->valid()) {
554  $entry = $iterator->current();
555  // skip non-files/non-folders, and empty entries
556  if ((!$entry->isFile() && !$entry->isDir()) || $entry->getFilename() == '' ||
557  ($entry->isFile() && !$includeFiles) || ($entry->isDir() && !$includeDirs)) {
558  $iterator->next();
559  continue;
560  }
561  $entryIdentifier = '/' . substr($entry->getPathname(), $pathLength);
562  $entryName = PathUtility::basename($entryIdentifier);
563  if ($entry->isDir()) {
564  $entryIdentifier .= '/';
565  }
566  $entryArray = [
567  'identifier' => $entryIdentifier,
568  'name' => $entryName,
569  'type' => $entry->isDir() ? 'dir' : 'file'
570  ];
571  $directoryEntries[$entryIdentifier] = $entryArray;
572  $iterator->next();
573  }
574  return $this->sortDirectoryEntries($directoryEntries, $sort, $sortRev);
575  }
576 
590  protected function sortDirectoryEntries($directoryEntries, $sort = '', $sortRev = false)
591  {
592  $entriesToSort = [];
593  foreach ($directoryEntries as $entryArray) {
594  $dir = pathinfo($entryArray['name'], PATHINFO_DIRNAME) . '/';
595  $fullPath = $this->getAbsoluteBasePath() . $entryArray['identifier'];
596  switch ($sort) {
597  case 'size':
598  $sortingKey = '0';
599  if ($entryArray['type'] === 'file') {
600  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'size');
601  }
602  // Add a character for a natural order sorting
603  $sortingKey .= 's';
604  break;
605  case 'rw':
606  $perms = $this->getPermissions($entryArray['identifier']);
607  $sortingKey = ($perms['r'] ? 'R' : '')
608  . ($perms['w'] ? 'W' : '');
609  break;
610  case 'fileext':
611  $sortingKey = pathinfo($entryArray['name'], PATHINFO_EXTENSION);
612  break;
613  case 'tstamp':
614  $sortingKey = '0';
615  if ($entryArray['type'] === 'file') {
616  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'mtime');
617  }
618  // Add a character for a natural order sorting
619  $sortingKey .= 't';
620  break;
621  case 'name':
622  case 'file':
623  default:
624  $sortingKey = $entryArray['name'];
625  }
626  $i = 0;
627  while (isset($entriesToSort[$sortingKey . $i])) {
628  $i++;
629  }
630  $entriesToSort[$sortingKey . $i] = $entryArray;
631  }
632  uksort($entriesToSort, 'strnatcasecmp');
633 
634  if ($sortRev) {
635  $entriesToSort = array_reverse($entriesToSort);
636  }
637 
638  return $entriesToSort;
639  }
640 
649  protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = [])
650  {
651  if (empty($propertiesToExtract)) {
652  $propertiesToExtract = [
653  'size', 'atime', 'atime', 'mtime', 'ctime', 'mimetype', 'name',
654  'identifier', 'identifier_hash', 'storage', 'folder_hash'
655  ];
656  }
657  $fileInformation = [];
658  foreach ($propertiesToExtract as $property) {
659  $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
660  }
661  return $fileInformation;
662  }
663 
674  public function getSpecificFileInformation($fileIdentifier, $containerPath, $property)
675  {
676  $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
677 
679  $fileInfo = GeneralUtility::makeInstance(FileInfo::class, $fileIdentifier);
680  switch ($property) {
681  case 'size':
682  return $fileInfo->getSize();
683  case 'atime':
684  return $fileInfo->getATime();
685  case 'mtime':
686  return $fileInfo->getMTime();
687  case 'ctime':
688  return $fileInfo->getCTime();
689  case 'name':
690  return PathUtility::basename($fileIdentifier);
691  case 'mimetype':
692  return (string)$fileInfo->getMimeType();
693  case 'identifier':
694  return $identifier;
695  case 'storage':
696  return $this->storageUid;
697  case 'identifier_hash':
698  return $this->hashIdentifier($identifier);
699  case 'folder_hash':
700  return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
701  default:
702  throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
703  }
704  }
705 
711  protected function getAbsoluteBasePath()
712  {
714  }
715 
723  protected function getAbsolutePath($fileIdentifier)
724  {
725  $relativeFilePath = ltrim($this->canonicalizeAndCheckFileIdentifier($fileIdentifier), '/');
726  $path = $this->absoluteBasePath . $relativeFilePath;
727  return $path;
728  }
729 
739  public function hash($fileIdentifier, $hashAlgorithm)
740  {
741  if (!in_array($hashAlgorithm, $this->supportedHashAlgorithms)) {
742  throw new \InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
743  }
744  switch ($hashAlgorithm) {
745  case 'sha1':
746  $hash = sha1_file($this->getAbsolutePath($fileIdentifier));
747  break;
748  case 'md5':
749  $hash = md5_file($this->getAbsolutePath($fileIdentifier));
750  break;
751  default:
752  throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
753  }
754  return $hash;
755  }
756 
770  public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true)
771  {
772  $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
773  // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
774  // thus, it is not checked here
775  // @todo is check in storage
776  if (GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storageUid > 0) {
777  throw new \InvalidArgumentException('Cannot add a file that is already part of this storage.', 1314778269);
778  }
779  $newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath));
780  $newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName;
781  $targetPath = $this->getAbsolutePath($newFileIdentifier);
782 
783  if ($removeOriginal) {
784  if (is_uploaded_file($localFilePath)) {
785  $result = move_uploaded_file($localFilePath, $targetPath);
786  } else {
787  $result = rename($localFilePath, $targetPath);
788  }
789  } else {
790  $result = copy($localFilePath, $targetPath);
791  }
792  if ($result === false || !file_exists($targetPath)) {
793  throw new \RuntimeException('Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.');
794  }
795  clearstatcache();
796  // Change the permissions of the file
797  GeneralUtility::fixPermissions($targetPath);
798  return $newFileIdentifier;
799  }
800 
808  public function fileExists($fileIdentifier)
809  {
810  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
811  return is_file($absoluteFilePath);
812  }
813 
821  public function fileExistsInFolder($fileName, $folderIdentifier)
822  {
823  $identifier = $folderIdentifier . '/' . $fileName;
824  $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
825  return $this->fileExists($identifier);
826  }
827 
835  public function folderExists($folderIdentifier)
836  {
837  $absoluteFilePath = $this->getAbsolutePath($folderIdentifier);
838  return is_dir($absoluteFilePath);
839  }
840 
848  public function folderExistsInFolder($folderName, $folderIdentifier)
849  {
850  $identifier = $folderIdentifier . '/' . $folderName;
851  $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
852  return $this->folderExists($identifier);
853  }
854 
862  public function getFolderInFolder($folderName, $folderIdentifier)
863  {
864  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
865  return $folderIdentifier;
866  }
867 
876  public function replaceFile($fileIdentifier, $localFilePath)
877  {
878  $filePath = $this->getAbsolutePath($fileIdentifier);
879  if (is_uploaded_file($localFilePath)) {
880  $result = move_uploaded_file($localFilePath, $filePath);
881  } else {
882  $result = rename($localFilePath, $filePath);
883  }
885  if ($result === false) {
886  throw new \RuntimeException('Replacing file ' . $fileIdentifier . ' with ' . $localFilePath . ' failed.', 1315314711);
887  }
888  return $result;
889  }
890 
901  public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
902  {
903  $sourcePath = $this->getAbsolutePath($fileIdentifier);
904  $newIdentifier = $targetFolderIdentifier . '/' . $fileName;
905  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
906 
907  $absoluteFilePath = $this->getAbsolutePath($newIdentifier);
908  copy($sourcePath, $absoluteFilePath);
909  GeneralUtility::fixPermissions($absoluteFilePath);
910  return $newIdentifier;
911  }
912 
924  public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
925  {
926  $sourcePath = $this->getAbsolutePath($fileIdentifier);
927  $targetIdentifier = $targetFolderIdentifier . '/' . $newFileName;
928  $targetIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetIdentifier);
929  $result = rename($sourcePath, $this->getAbsolutePath($targetIdentifier));
930  if ($result === false) {
931  throw new \RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
932  }
933  return $targetIdentifier;
934  }
935 
943  protected function copyFileToTemporaryPath($fileIdentifier)
944  {
945  $sourcePath = $this->getAbsolutePath($fileIdentifier);
946  $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
947  $result = copy($sourcePath, $temporaryPath);
948  touch($temporaryPath, filemtime($sourcePath));
949  if ($result === false) {
950  throw new \RuntimeException(
951  'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.',
952  1320577649
953  );
954  }
955  return $temporaryPath;
956  }
957 
966  protected function recycleFileOrFolder($filePath, $recycleDirectory)
967  {
968  $destinationFile = $recycleDirectory . '/' . PathUtility::basename($filePath);
969  if (file_exists($destinationFile)) {
970  $timeStamp = \DateTimeImmutable::createFromFormat('U.u', microtime(true))->format('YmdHisu');
971  $destinationFile = $recycleDirectory . '/' . $timeStamp . '_' . PathUtility::basename($filePath);
972  }
973  $result = rename($filePath, $destinationFile);
974  return $result;
975  }
976 
988  protected function createIdentifierMap(array $filesAndFolders, $sourceFolderIdentifier, $targetFolderIdentifier)
989  {
990  $identifierMap = [];
991  $identifierMap[$sourceFolderIdentifier] = $targetFolderIdentifier;
992  foreach ($filesAndFolders as $oldItem) {
993  if ($oldItem['type'] == 'dir') {
994  $oldIdentifier = $oldItem['identifier'];
995  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier(
996  str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
997  );
998  } else {
999  $oldIdentifier = $oldItem['identifier'];
1000  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier(
1001  str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
1002  );
1003  }
1004  if (!file_exists($this->getAbsolutePath($newIdentifier))) {
1005  throw new Exception\FileOperationErrorException(
1006  sprintf('File "%1$s" was not found (should have been copied/moved from "%2$s").', $newIdentifier, $oldIdentifier),
1007  1330119453
1008  );
1009  }
1010  $identifierMap[$oldIdentifier] = $newIdentifier;
1011  }
1012  return $identifierMap;
1013  }
1014 
1025  public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
1026  {
1027  $sourcePath = $this->getAbsolutePath($sourceFolderIdentifier);
1028  $relativeTargetPath = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
1029  $targetPath = $this->getAbsolutePath($relativeTargetPath);
1030  // get all files and folders we are going to move, to have a map for updating later.
1031  $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, true);
1032  $result = rename($sourcePath, $targetPath);
1033  if ($result === false) {
1034  throw new \RuntimeException('Moving folder ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320711817);
1035  }
1036  // Create a mapping from old to new identifiers
1037  $identifierMap = $this->createIdentifierMap($filesAndFolders, $sourceFolderIdentifier, $relativeTargetPath);
1038  return $identifierMap;
1039  }
1040 
1051  public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
1052  {
1053  // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
1054  // We can thus rely on this folder being present and just create the subfolder we want to copy to.
1055  $newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
1056  $sourceFolderPath = $this->getAbsolutePath($sourceFolderIdentifier);
1057  $targetFolderPath = $this->getAbsolutePath($newFolderIdentifier);
1058 
1059  mkdir($targetFolderPath);
1061  $iterator = new \RecursiveIteratorIterator(
1062  new \RecursiveDirectoryIterator($sourceFolderPath),
1063  \RecursiveIteratorIterator::SELF_FIRST,
1064  \RecursiveIteratorIterator::CATCH_GET_CHILD
1065  );
1066  // Rewind the iterator as this is important for some systems e.g. Windows
1067  $iterator->rewind();
1068  while ($iterator->valid()) {
1070  $current = $iterator->current();
1071  $fileName = $current->getFilename();
1072  $itemSubPath = GeneralUtility::fixWindowsFilePath($iterator->getSubPathname());
1073  if ($current->isDir() && !($fileName === '..' || $fileName === '.')) {
1074  GeneralUtility::mkdir($targetFolderPath . '/' . $itemSubPath);
1075  } elseif ($current->isFile()) {
1076  $result = copy($sourceFolderPath . '/' . $itemSubPath, $targetFolderPath . '/' . $itemSubPath);
1077  if ($result === false) {
1078  // rollback
1079  GeneralUtility::rmdir($targetFolderIdentifier, true);
1080  throw new Exception\FileOperationErrorException(
1081  'Copying file "' . $sourceFolderPath . $itemSubPath . '" to "' . $targetFolderPath . $itemSubPath . '" failed.',
1082  1330119452
1083  );
1084  }
1085  }
1086  $iterator->next();
1087  }
1088  GeneralUtility::fixPermissions($targetFolderPath, true);
1089  return true;
1090  }
1091 
1101  public function renameFile($fileIdentifier, $newName)
1102  {
1103  // Makes sure the Path given as parameter is valid
1104  $newName = $this->sanitizeFileName($newName);
1105  $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(PathUtility::dirname($fileIdentifier)), '/') . '/' . $newName;
1106  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
1107  // The target should not exist already
1108  if ($this->fileExists($newIdentifier)) {
1109  throw new Exception\ExistingTargetFileNameException(
1110  'The target file "' . $newIdentifier . '" already exists.',
1111  1320291063
1112  );
1113  }
1114  $sourcePath = $this->getAbsolutePath($fileIdentifier);
1115  $targetPath = $this->getAbsolutePath($newIdentifier);
1116  $result = rename($sourcePath, $targetPath);
1117  if ($result === false) {
1118  throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
1119  }
1120  return $newIdentifier;
1121  }
1122 
1131  public function renameFolder($folderIdentifier, $newName)
1132  {
1133  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
1134  $newName = $this->sanitizeFileName($newName);
1135 
1136  $newIdentifier = PathUtility::dirname($folderIdentifier) . '/' . $newName;
1137  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier($newIdentifier);
1138 
1139  $sourcePath = $this->getAbsolutePath($folderIdentifier);
1140  $targetPath = $this->getAbsolutePath($newIdentifier);
1141  // get all files and folders we are going to move, to have a map for updating later.
1142  $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, true);
1143  $result = rename($sourcePath, $targetPath);
1144  if ($result === false) {
1145  throw new \RuntimeException(sprintf('Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
1146  }
1147  try {
1148  // Create a mapping from old to new identifiers
1149  $identifierMap = $this->createIdentifierMap($filesAndFolders, $folderIdentifier, $newIdentifier);
1150  } catch (\Exception $e) {
1151  rename($targetPath, $sourcePath);
1152  throw new \RuntimeException(
1153  sprintf(
1154  'Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"',
1155  $sourcePath, $targetPath, $e->getMessage()
1156  ),
1157  1334160746
1158  );
1159  }
1160  return $identifierMap;
1161  }
1162 
1172  public function deleteFile($fileIdentifier)
1173  {
1174  $filePath = $this->getAbsolutePath($fileIdentifier);
1175  $recycleDirectory = $this->getRecycleDirectory($filePath);
1176  if (!empty($recycleDirectory)) {
1177  $result = $this->recycleFileOrFolder($filePath, $recycleDirectory);
1178  } else {
1179  $result = unlink($filePath);
1180  }
1181  if ($result === false) {
1182  throw new \RuntimeException('Deletion of file ' . $fileIdentifier . ' failed.', 1320855304);
1183  }
1184  return $result;
1185  }
1186 
1196  public function deleteFolder($folderIdentifier, $deleteRecursively = false)
1197  {
1198  $folderPath = $this->getAbsolutePath($folderIdentifier);
1199  $recycleDirectory = $this->getRecycleDirectory($folderPath);
1200  if (!empty($recycleDirectory) && $folderPath !== $recycleDirectory) {
1201  $result = $this->recycleFileOrFolder($folderPath, $recycleDirectory);
1202  } else {
1203  $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
1204  }
1205  if ($result === false) {
1206  throw new Exception\FileOperationErrorException(
1207  'Deleting folder "' . $folderIdentifier . '" failed.',
1208  1330119451
1209  );
1210  }
1211  return $result;
1212  }
1213 
1220  public function isFolderEmpty($folderIdentifier)
1221  {
1222  $path = $this->getAbsolutePath($folderIdentifier);
1223  $dirHandle = opendir($path);
1224  while ($entry = readdir($dirHandle)) {
1225  if ($entry !== '.' && $entry !== '..') {
1226  closedir($dirHandle);
1227  return false;
1228  }
1229  }
1230  closedir($dirHandle);
1231  return true;
1232  }
1233 
1244  public function getFileForLocalProcessing($fileIdentifier, $writable = true)
1245  {
1246  if ($writable === false) {
1247  return $this->getAbsolutePath($fileIdentifier);
1248  } else {
1249  return $this->copyFileToTemporaryPath($fileIdentifier);
1250  }
1251  }
1252 
1260  public function getPermissions($identifier)
1261  {
1262  $path = $this->getAbsolutePath($identifier);
1263  $permissionBits = fileperms($path);
1264  if ($permissionBits === false) {
1265  throw new Exception\ResourcePermissionsUnavailableException('Error while fetching permissions for ' . $path, 1319455097);
1266  }
1267  return [
1268  'r' => (bool)is_readable($path),
1269  'w' => (bool)is_writable($path)
1270  ];
1271  }
1272 
1282  public function isWithin($folderIdentifier, $identifier)
1283  {
1284  $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
1285  $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
1286  if ($folderIdentifier === $entryIdentifier) {
1287  return true;
1288  }
1289  // File identifier canonicalization will not modify a single slash so
1290  // we must not append another slash in that case.
1291  if ($folderIdentifier !== '/') {
1292  $folderIdentifier .= '/';
1293  }
1294  return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
1295  }
1296 
1306  public function createFile($fileName, $parentFolderIdentifier)
1307  {
1308  if (!$this->isValidFilename($fileName)) {
1309  throw new Exception\InvalidFileNameException(
1310  'Invalid characters in fileName "' . $fileName . '"',
1311  1320572272
1312  );
1313  }
1314  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
1315  $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier(
1316  $parentFolderIdentifier . $this->sanitizeFileName(ltrim($fileName, '/'))
1317  );
1318  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
1319  $result = touch($absoluteFilePath);
1320  GeneralUtility::fixPermissions($absoluteFilePath);
1321  clearstatcache();
1322  if ($result !== true) {
1323  throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854);
1324  }
1325  return $fileIdentifier;
1326  }
1327 
1337  public function getFileContents($fileIdentifier)
1338  {
1339  $filePath = $this->getAbsolutePath($fileIdentifier);
1340  return file_get_contents($filePath);
1341  }
1342 
1351  public function setFileContents($fileIdentifier, $contents)
1352  {
1353  $filePath = $this->getAbsolutePath($fileIdentifier);
1354  $result = file_put_contents($filePath, $contents);
1355 
1356  // Make sure later calls to filesize() etc. return correct values.
1357  clearstatcache(true, $filePath);
1358 
1359  if ($result === false) {
1360  throw new \RuntimeException('Setting contents of file "' . $fileIdentifier . '" failed.', 1325419305);
1361  }
1362  return $result;
1363  }
1364 
1370  protected function getCharsetConversion()
1371  {
1372  if (!isset($this->charsetConversion)) {
1373  if (TYPO3_MODE === 'FE') {
1374  $this->charsetConversion = $GLOBALS['TSFE']->csConvObj;
1375  } elseif (is_object($GLOBALS['LANG'])) {
1376  // BE assumed:
1377  $this->charsetConversion = $GLOBALS['LANG']->csConvObj;
1378  } else {
1379  // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
1380  $this->charsetConversion = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
1381  }
1382  }
1383  return $this->charsetConversion;
1384  }
1385 
1392  public function getRole($folderIdentifier)
1393  {
1394  $name = PathUtility::basename($folderIdentifier);
1395  $role = $this->mappingFolderNameToRole[$name];
1396  if (empty($role)) {
1398  }
1399  return $role;
1400  }
1401 
1410  public function dumpFileContents($identifier)
1411  {
1412  readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
1413  }
1414 
1422  protected function getRecycleDirectory($path)
1423  {
1424  $recyclerSubdirectory = array_search(FolderInterface::ROLE_RECYCLER, $this->mappingFolderNameToRole, true);
1425  if ($recyclerSubdirectory === false) {
1426  return '';
1427  }
1428  $rootDirectory = rtrim($this->getAbsolutePath($this->getRootLevelFolder()), '/');
1429  $searchDirectory = PathUtility::dirname($path);
1430  // Check if file or folder to be deleted is inside a recycler directory
1431  if ($this->getRole($searchDirectory) === FolderInterface::ROLE_RECYCLER) {
1432  $searchDirectory = PathUtility::dirname($searchDirectory);
1433  // Check if file or folder to be deleted is inside the root recycler
1434  if ($searchDirectory == $rootDirectory) {
1435  return '';
1436  }
1437  $searchDirectory = PathUtility::dirname($searchDirectory);
1438  }
1439  // Search for the closest recycler directory
1440  while ($searchDirectory) {
1441  $recycleDirectory = $searchDirectory . '/' . $recyclerSubdirectory;
1442  if (is_dir($recycleDirectory)) {
1443  return $recycleDirectory;
1444  } elseif ($searchDirectory === $rootDirectory) {
1445  return '';
1446  } else {
1447  $searchDirectory = PathUtility::dirname($searchDirectory);
1448  }
1449  }
1450 
1451  return '';
1452  }
1453 }
getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract=[])
getFilesInFolder($folderIdentifier, $start=0, $numberOfItems=0, $recursive=false, array $filenameFilterCallbacks=[], $sort='', $sortRev=false)
deleteFolder($folderIdentifier, $deleteRecursively=false)
static mkdir_deep($directory, $deepDirectory='')
applyFilterMethodsToDirectoryItem(array $filterMethods, $itemName, $itemIdentifier, $parentIdentifier)
static isFirstPartOfStr($str, $partStr)
copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
addFile($localFilePath, $targetFolderIdentifier, $newFileName='', $removeOriginal=true)
sortDirectoryEntries($directoryEntries, $sort='', $sortRev=false)
getFoldersInFolder($folderIdentifier, $start=0, $numberOfItems=0, $recursive=false, array $folderNameFilterCallbacks=[], $sort='', $sortRev=false)
__construct(array $configuration=[])
Definition: LocalDriver.php:72
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
hash($fileIdentifier, $hashAlgorithm)
moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
renameFolder($folderIdentifier, $newName)
moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
extractFileInformation($filePath, $containerPath, array $propertiesToExtract=[])
recycleFileOrFolder($filePath, $recycleDirectory)
static fixPermissions($path, $recursive=false)
createFile($fileName, $parentFolderIdentifier)
folderExistsInFolder($folderName, $folderIdentifier)
setFileContents($fileIdentifier, $contents)
countFoldersInFolder($folderIdentifier, $recursive=false, array $folderNameFilterCallbacks=[])
static rmdir($path, $removeNonEmpty=false)
getFolderInFolder($folderName, $folderIdentifier)
createFolder($newFolderName, $parentFolderIdentifier='', $recursive=false)
replaceFile($fileIdentifier, $localFilePath)
getFileForLocalProcessing($fileIdentifier, $writable=true)
copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
getFileInFolder($fileName, $folderIdentifier)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
renameFile($fileIdentifier, $newName)
fileExistsInFolder($fileName, $folderIdentifier)
isWithin($folderIdentifier, $identifier)
sanitizeFileName($fileName, $charset='')
countFilesInFolder($folderIdentifier, $recursive=false, array $filenameFilterCallbacks=[])
createIdentifierMap(array $filesAndFolders, $sourceFolderIdentifier, $targetFolderIdentifier)
getDirectoryItemList($folderIdentifier, $start=0, $numberOfItems=0, array $filterMethods, $includeFiles=true, $includeDirs=true, $recursive=false, $sort='', $sortRev=false)