TYPO3  7.6
LocalDriver.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Driver;
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 = array('sha1', 'md5');
48 
55  protected $baseUri = null;
56 
60  protected $charsetConversion;
61 
63  protected $mappingFolderNameToRole = array(
64  '_recycler_' => FolderInterface::ROLE_RECYCLER,
66  'user_upload' => FolderInterface::ROLE_USERUPLOAD,
67  );
68 
72  public function __construct(array $configuration = array())
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 
96 
102  public function processConfiguration()
103  {
104  $this->absoluteBasePath = $this->calculateBasePath($this->configuration);
105  $this->determineBaseUrl();
106  if ($this->baseUri === null) {
107  // remove public flag
108  $this->capabilities &= ~ResourceStorage::CAPABILITY_PUBLIC;
109  }
110  }
111 
118  public function initialize()
119  {
120  }
121 
128  protected function determineBaseUrl()
129  {
130  // only calculate baseURI if the storage does not enforce jumpUrl Script
132  if (GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
133  // use site-relative URLs
134  $temporaryBaseUri = rtrim(PathUtility::stripPathSitePrefix($this->absoluteBasePath), '/');
135  if ($temporaryBaseUri !== '') {
136  $uriParts = explode('/', $temporaryBaseUri);
137  $uriParts = array_map('rawurlencode', $uriParts);
138  $temporaryBaseUri = implode('/', $uriParts) . '/';
139  }
140  $this->baseUri = $temporaryBaseUri;
141  } elseif (isset($this->configuration['baseUri']) && GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
142  $this->baseUri = rtrim($this->configuration['baseUri'], '/') . '/';
143  }
144  }
145  }
146 
154  protected function calculateBasePath(array $configuration)
155  {
156  if (!array_key_exists('basePath', $configuration) || empty($configuration['basePath'])) {
157  throw new Exception\InvalidConfigurationException(
158  'Configuration must contain base path.',
159  1346510477
160  );
161  }
162 
163  if ($configuration['pathType'] === 'relative') {
164  $relativeBasePath = $configuration['basePath'];
165  $absoluteBasePath = PATH_site . $relativeBasePath;
166  } else {
167  $absoluteBasePath = $configuration['basePath'];
168  }
170  $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
171  if (!is_dir($absoluteBasePath)) {
172  throw new Exception\InvalidConfigurationException(
173  'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
174  1299233097
175  );
176  }
177  return $absoluteBasePath;
178  }
179 
188  public function getPublicUrl($identifier)
189  {
190  $publicUrl = null;
191  if ($this->baseUri !== null) {
192  $uriParts = explode('/', ltrim($identifier, '/'));
193  $uriParts = array_map('rawurlencode', $uriParts);
194  $identifier = implode('/', $uriParts);
195  $publicUrl = $this->baseUri . $identifier;
196  }
197  return $publicUrl;
198  }
199 
205  public function getRootLevelFolder()
206  {
207  return '/';
208  }
209 
215  public function getDefaultFolder()
216  {
217  $identifier = '/user_upload/';
218  $createFolder = !$this->folderExists($identifier);
219  if ($createFolder === true) {
220  $identifier = $this->createFolder('user_upload');
221  }
222  return $identifier;
223  }
224 
234  public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false)
235  {
236  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
237  $newFolderName = trim($newFolderName, '/');
238  if ($recursive == false) {
239  $newFolderName = $this->sanitizeFileName($newFolderName);
240  $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
241  GeneralUtility::mkdir($this->getAbsolutePath($newIdentifier));
242  } else {
243  $parts = GeneralUtility::trimExplode('/', $newFolderName);
244  $parts = array_map(array($this, 'sanitizeFileName'), $parts);
245  $newFolderName = implode('/', $parts);
246  $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
247  GeneralUtility::mkdir_deep($this->getAbsolutePath($parentFolderIdentifier) . '/', $newFolderName);
248  }
249  return $newIdentifier;
250  }
251 
260  public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = array())
261  {
262  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
263  // don't use $this->fileExists() because we need the absolute path to the file anyways, so we can directly
264  // use PHP's filesystem method.
265  if (!file_exists($absoluteFilePath) || !is_file($absoluteFilePath)) {
266  throw new \InvalidArgumentException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
267  }
268 
269  $dirPath = PathUtility::dirname($fileIdentifier);
270  $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
271  return $this->extractFileInformation($absoluteFilePath, $dirPath, $propertiesToExtract);
272  }
273 
281  public function getFolderInfoByIdentifier($folderIdentifier)
282  {
283  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
284 
285  if (!$this->folderExists($folderIdentifier)) {
286  throw new Exception\FolderDoesNotExistException(
287  'Folder "' . $folderIdentifier . '" does not exist.',
288  1314516810
289  );
290  }
291  return array(
292  'identifier' => $folderIdentifier,
293  'name' => PathUtility::basename($folderIdentifier),
294  'storage' => $this->storageUid
295  );
296  }
297 
310  public function sanitizeFileName($fileName, $charset = '')
311  {
312  // Handle UTF-8 characters
313  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
314  // Allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
315  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . ']/u', '_', trim($fileName));
316  } else {
317  // Define character set
318  if (!$charset) {
319  if (TYPO3_MODE === 'FE') {
320  $charset = $GLOBALS['TSFE']->renderCharset;
321  } else {
322  // default for Backend
323  $charset = 'utf-8';
324  }
325  }
326  // If a charset was found, convert fileName
327  if ($charset) {
328  $fileName = $this->getCharsetConversion()->specCharsToASCII($charset, $fileName);
329  }
330  // Replace unwanted characters by underscores
331  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . '\\xC0-\\xFF]/', '_', trim($fileName));
332  }
333  // Strip trailing dots and return
334  $cleanFileName = rtrim($cleanFileName, '.');
335  if ($cleanFileName === '') {
336  throw new Exception\InvalidFileNameException(
337  'File name ' . $fileName . ' is invalid.',
338  1320288991
339  );
340  }
341  return $cleanFileName;
342  }
343 
363  protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = true, $includeDirs = true, $recursive = false, $sort = '', $sortRev = false)
364  {
365  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
366  $realPath = $this->getAbsolutePath($folderIdentifier);
367  if (!is_dir($realPath)) {
368  throw new \InvalidArgumentException(
369  'Cannot list items in directory ' . $folderIdentifier . ' - does not exist or is no directory',
370  1314349666
371  );
372  }
373 
374  if ($start > 0) {
375  $start--;
376  }
377 
378  $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs, $sort, $sortRev);
379  $iterator = new \ArrayIterator($items);
380  if ($iterator->count() === 0) {
381  return array();
382  }
383  $iterator->seek($start);
384 
385  // $c is the counter for how many items we still have to fetch (-1 is unlimited)
386  $c = $numberOfItems > 0 ? $numberOfItems : - 1;
387  $items = array();
388  while ($iterator->valid() && ($numberOfItems === 0 || $c > 0)) {
389  // $iteratorItem is the file or folder name
390  $iteratorItem = $iterator->current();
391  // go on to the next iterator item now as we might skip this one early
392  $iterator->next();
393 
394  if (
396  $filterMethods,
397  $iteratorItem['name'],
398  $iteratorItem['identifier'],
399  $this->getParentFolderIdentifierOfIdentifier($iteratorItem['identifier'])
400  )
401  ) {
402  continue;
403  }
404 
405  $items[$iteratorItem['identifier']] = $iteratorItem['identifier'];
406  // Decrement item counter to make sure we only return $numberOfItems
407  // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
408  // item here
409  --$c;
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, array(), $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 = array(), $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 = array())
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 = array(), $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 = array())
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  );
546  } else {
547  $iterator = new \RecursiveDirectoryIterator($path, $iteratorMode);
548  }
549 
550  $directoryEntries = array();
551  while ($iterator->valid()) {
553  $entry = $iterator->current();
554  // skip non-files/non-folders, and empty entries
555  if ((!$entry->isFile() && !$entry->isDir()) || $entry->getFilename() == '' ||
556  ($entry->isFile() && !$includeFiles) || ($entry->isDir() && !$includeDirs)) {
557  $iterator->next();
558  continue;
559  }
560  $entryIdentifier = '/' . substr($entry->getPathname(), $pathLength);
561  $entryName = PathUtility::basename($entryIdentifier);
562  if ($entry->isDir()) {
563  $entryIdentifier .= '/';
564  }
565  $entryArray = array(
566  'identifier' => $entryIdentifier,
567  'name' => $entryName,
568  'type' => $entry->isDir() ? 'dir' : 'file'
569  );
570  $directoryEntries[$entryIdentifier] = $entryArray;
571  $iterator->next();
572  }
573  return $this->sortDirectoryEntries($directoryEntries, $sort, $sortRev);
574  }
575 
589  protected function sortDirectoryEntries($directoryEntries, $sort = '', $sortRev = false)
590  {
591  $entriesToSort = array();
592  foreach ($directoryEntries as $entryArray) {
593  $dir = pathinfo($entryArray['name'], PATHINFO_DIRNAME) . '/';
594  $fullPath = $this->getAbsoluteBasePath() . $entryArray['identifier'];
595  switch ($sort) {
596  case 'size':
597  if ($entryArray['type'] === 'file') {
598  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'size');
599  } else {
600  $sortingKey = '0';
601  }
602  break;
603  case 'rw':
604  $perms = $this->getPermissions($entryArray['identifier']);
605  $sortingKey = ($perms['r'] ? 'R' : '')
606  . ($perms['w'] ? 'W' : '');
607  break;
608  case 'fileext':
609  $sortingKey = pathinfo($entryArray['name'], PATHINFO_EXTENSION);
610  break;
611  case 'tstamp':
612  if ($entryArray['type'] === 'file') {
613  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'mtime');
614  } else {
615  $sortingKey = '0';
616  }
617  break;
618  case 'name':
619  case 'file':
620  default:
621  $sortingKey = $entryArray['name'];
622  }
623  $i = 0;
624  while (isset($entriesToSort[$sortingKey . $i])) {
625  $i++;
626  }
627  $entriesToSort[$sortingKey . $i] = $entryArray;
628  }
629  uksort($entriesToSort, 'strnatcasecmp');
630 
631  if ($sortRev) {
632  $entriesToSort = array_reverse($entriesToSort);
633  }
634 
635  return $entriesToSort;
636  }
637 
646  protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array())
647  {
648  if (empty($propertiesToExtract)) {
649  $propertiesToExtract = array(
650  'size', 'atime', 'atime', 'mtime', 'ctime', 'mimetype', 'name',
651  'identifier', 'identifier_hash', 'storage', 'folder_hash'
652  );
653  }
654  $fileInformation = array();
655  foreach ($propertiesToExtract as $property) {
656  $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
657  }
658  return $fileInformation;
659  }
660 
661 
672  public function getSpecificFileInformation($fileIdentifier, $containerPath, $property)
673  {
674  $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
675 
677  $fileInfo = GeneralUtility::makeInstance(FileInfo::class, $fileIdentifier);
678  switch ($property) {
679  case 'size':
680  return $fileInfo->getSize();
681  case 'atime':
682  return $fileInfo->getATime();
683  case 'mtime':
684  return $fileInfo->getMTime();
685  case 'ctime':
686  return $fileInfo->getCTime();
687  case 'name':
688  return PathUtility::basename($fileIdentifier);
689  case 'mimetype':
690  return (string)$fileInfo->getMimeType();
691  case 'identifier':
692  return $identifier;
693  case 'storage':
694  return $this->storageUid;
695  case 'identifier_hash':
696  return $this->hashIdentifier($identifier);
697  case 'folder_hash':
698  return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
699  default:
700  throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
701  }
702  }
703 
709  protected function getAbsoluteBasePath()
710  {
712  }
713 
721  protected function getAbsolutePath($fileIdentifier)
722  {
723  $relativeFilePath = ltrim($this->canonicalizeAndCheckFileIdentifier($fileIdentifier), '/');
724  $path = $this->absoluteBasePath . $relativeFilePath;
725  return $path;
726  }
727 
737  public function hash($fileIdentifier, $hashAlgorithm)
738  {
739  if (!in_array($hashAlgorithm, $this->supportedHashAlgorithms)) {
740  throw new \InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
741  }
742  switch ($hashAlgorithm) {
743  case 'sha1':
744  $hash = sha1_file($this->getAbsolutePath($fileIdentifier));
745  break;
746  case 'md5':
747  $hash = md5_file($this->getAbsolutePath($fileIdentifier));
748  break;
749  default:
750  throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
751  }
752  return $hash;
753  }
754 
768  public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true)
769  {
770  $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
771  // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
772  // thus, it is not checked here
773  // @todo is check in storage
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);
776  }
777  $newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath));
778  $newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName;
779  $targetPath = $this->getAbsolutePath($newFileIdentifier);
780 
781  if ($removeOriginal) {
782  if (is_uploaded_file($localFilePath)) {
783  $result = move_uploaded_file($localFilePath, $targetPath);
784  } else {
785  $result = rename($localFilePath, $targetPath);
786  }
787  } else {
788  $result = copy($localFilePath, $targetPath);
789  }
790  if ($result === false || !file_exists($targetPath)) {
791  throw new \RuntimeException('Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.');
792  }
793  clearstatcache();
794  // Change the permissions of the file
795  GeneralUtility::fixPermissions($targetPath);
796  return $newFileIdentifier;
797  }
798 
806  public function fileExists($fileIdentifier)
807  {
808  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
809  return is_file($absoluteFilePath);
810  }
811 
819  public function fileExistsInFolder($fileName, $folderIdentifier)
820  {
821  $identifier = $folderIdentifier . '/' . $fileName;
822  $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
823  return $this->fileExists($identifier);
824  }
825 
833  public function folderExists($folderIdentifier)
834  {
835  $absoluteFilePath = $this->getAbsolutePath($folderIdentifier);
836  return is_dir($absoluteFilePath);
837  }
838 
846  public function folderExistsInFolder($folderName, $folderIdentifier)
847  {
848  $identifier = $folderIdentifier . '/' . $folderName;
849  $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
850  return $this->folderExists($identifier);
851  }
852 
860  public function getFolderInFolder($folderName, $folderIdentifier)
861  {
862  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
863  return $folderIdentifier;
864  }
865 
874  public function replaceFile($fileIdentifier, $localFilePath)
875  {
876  $filePath = $this->getAbsolutePath($fileIdentifier);
877  $result = rename($localFilePath, $filePath);
879  if ($result === false) {
880  throw new \RuntimeException('Replacing file ' . $fileIdentifier . ' with ' . $localFilePath . ' failed.', 1315314711);
881  }
882  return $result;
883  }
884 
895  public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
896  {
897  $sourcePath = $this->getAbsolutePath($fileIdentifier);
898  $newIdentifier = $targetFolderIdentifier . '/' . $fileName;
899  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
900 
901  $absoluteFilePath = $this->getAbsolutePath($newIdentifier);
902  copy($sourcePath, $absoluteFilePath);
903  GeneralUtility::fixPermissions($absoluteFilePath);
904  return $newIdentifier;
905  }
906 
918  public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
919  {
920  $sourcePath = $this->getAbsolutePath($fileIdentifier);
921  $targetIdentifier = $targetFolderIdentifier . '/' . $newFileName;
922  $targetIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetIdentifier);
923  $result = rename($sourcePath, $this->getAbsolutePath($targetIdentifier));
924  if ($result === false) {
925  throw new \RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
926  }
927  return $targetIdentifier;
928  }
929 
937  protected function copyFileToTemporaryPath($fileIdentifier)
938  {
939  $sourcePath = $this->getAbsolutePath($fileIdentifier);
940  $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
941  $result = copy($sourcePath, $temporaryPath);
942  touch($temporaryPath, filemtime($sourcePath));
943  if ($result === false) {
944  throw new \RuntimeException(
945  'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.',
946  1320577649
947  );
948  }
949  return $temporaryPath;
950  }
951 
963  protected function createIdentifierMap(array $filesAndFolders, $sourceFolderIdentifier, $targetFolderIdentifier)
964  {
965  $identifierMap = array();
966  $identifierMap[$sourceFolderIdentifier] = $targetFolderIdentifier;
967  foreach ($filesAndFolders as $oldItem) {
968  if ($oldItem['type'] == 'dir') {
969  $oldIdentifier = $oldItem['identifier'];
970  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier(
971  str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
972  );
973  } else {
974  $oldIdentifier = $oldItem['identifier'];
975  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier(
976  str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
977  );
978  }
979  if (!file_exists($this->getAbsolutePath($newIdentifier))) {
980  throw new Exception\FileOperationErrorException(
981  sprintf('File "%1$s" was not found (should have been copied/moved from "%2$s").', $newIdentifier, $oldIdentifier),
982  1330119453
983  );
984  }
985  $identifierMap[$oldIdentifier] = $newIdentifier;
986  }
987  return $identifierMap;
988  }
989 
1000  public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
1001  {
1002  $sourcePath = $this->getAbsolutePath($sourceFolderIdentifier);
1003  $relativeTargetPath = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
1004  $targetPath = $this->getAbsolutePath($relativeTargetPath);
1005  // get all files and folders we are going to move, to have a map for updating later.
1006  $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, true);
1007  $result = rename($sourcePath, $targetPath);
1008  if ($result === false) {
1009  throw new \RuntimeException('Moving folder ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320711817);
1010  }
1011  // Create a mapping from old to new identifiers
1012  $identifierMap = $this->createIdentifierMap($filesAndFolders, $sourceFolderIdentifier, $relativeTargetPath);
1013  return $identifierMap;
1014  }
1015 
1026  public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
1027  {
1028  // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
1029  // We can thus rely on this folder being present and just create the subfolder we want to copy to.
1030  $newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
1031  $sourceFolderPath = $this->getAbsolutePath($sourceFolderIdentifier);
1032  $targetFolderPath = $this->getAbsolutePath($newFolderIdentifier);
1033 
1034  mkdir($targetFolderPath);
1036  $iterator = new \RecursiveIteratorIterator(
1037  new \RecursiveDirectoryIterator($sourceFolderPath),
1038  \RecursiveIteratorIterator::SELF_FIRST
1039  );
1040  // Rewind the iterator as this is important for some systems e.g. Windows
1041  $iterator->rewind();
1042  while ($iterator->valid()) {
1044  $current = $iterator->current();
1045  $fileName = $current->getFilename();
1046  $itemSubPath = GeneralUtility::fixWindowsFilePath($iterator->getSubPathname());
1047  if ($current->isDir() && !($fileName === '..' || $fileName === '.')) {
1048  GeneralUtility::mkdir($targetFolderPath . '/' . $itemSubPath);
1049  } elseif ($current->isFile()) {
1050  $result = copy($sourceFolderPath . '/' . $itemSubPath, $targetFolderPath . '/' . $itemSubPath);
1051  if ($result === false) {
1052  // rollback
1053  GeneralUtility::rmdir($targetFolderIdentifier, true);
1054  throw new Exception\FileOperationErrorException(
1055  'Copying file "' . $sourceFolderPath . $itemSubPath . '" to "' . $targetFolderPath . $itemSubPath . '" failed.',
1056  1330119452
1057  );
1058  }
1059  }
1060  $iterator->next();
1061  }
1062  GeneralUtility::fixPermissions($targetFolderPath, true);
1063  return true;
1064  }
1065 
1075  public function renameFile($fileIdentifier, $newName)
1076  {
1077  // Makes sure the Path given as parameter is valid
1078  $newName = $this->sanitizeFileName($newName);
1079  $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(PathUtility::dirname($fileIdentifier)), '/') . '/' . $newName;
1080  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
1081  // The target should not exist already
1082  if ($this->fileExists($newIdentifier)) {
1083  throw new Exception\ExistingTargetFileNameException(
1084  'The target file "' . $newIdentifier . '" already exists.',
1085  1320291063
1086  );
1087  }
1088  $sourcePath = $this->getAbsolutePath($fileIdentifier);
1089  $targetPath = $this->getAbsolutePath($newIdentifier);
1090  $result = rename($sourcePath, $targetPath);
1091  if ($result === false) {
1092  throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
1093  }
1094  return $newIdentifier;
1095  }
1096 
1097 
1106  public function renameFolder($folderIdentifier, $newName)
1107  {
1108  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
1109  $newName = $this->sanitizeFileName($newName);
1110 
1111  $newIdentifier = PathUtility::dirname($folderIdentifier) . '/' . $newName;
1112  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier($newIdentifier);
1113 
1114  $sourcePath = $this->getAbsolutePath($folderIdentifier);
1115  $targetPath = $this->getAbsolutePath($newIdentifier);
1116  // get all files and folders we are going to move, to have a map for updating later.
1117  $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, true);
1118  $result = rename($sourcePath, $targetPath);
1119  if ($result === false) {
1120  throw new \RuntimeException(sprintf('Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
1121  }
1122  try {
1123  // Create a mapping from old to new identifiers
1124  $identifierMap = $this->createIdentifierMap($filesAndFolders, $folderIdentifier, $newIdentifier);
1125  } catch (\Exception $e) {
1126  rename($targetPath, $sourcePath);
1127  throw new \RuntimeException(
1128  sprintf(
1129  'Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"',
1130  $sourcePath, $targetPath, $e->getMessage()
1131  ),
1132  1334160746
1133  );
1134  }
1135  return $identifierMap;
1136  }
1137 
1147  public function deleteFile($fileIdentifier)
1148  {
1149  $filePath = $this->getAbsolutePath($fileIdentifier);
1150  $result = unlink($filePath);
1151  if ($result === false) {
1152  throw new \RuntimeException('Deletion of file ' . $fileIdentifier . ' failed.', 1320855304);
1153  }
1154  return $result;
1155  }
1156 
1166  public function deleteFolder($folderIdentifier, $deleteRecursively = false)
1167  {
1168  $folderPath = $this->getAbsolutePath($folderIdentifier);
1169  $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
1170  if ($result === false) {
1171  throw new Exception\FileOperationErrorException(
1172  'Deleting folder "' . $folderIdentifier . '" failed.',
1173  1330119451
1174  );
1175  }
1176  return $result;
1177  }
1178 
1185  public function isFolderEmpty($folderIdentifier)
1186  {
1187  $path = $this->getAbsolutePath($folderIdentifier);
1188  $dirHandle = opendir($path);
1189  while ($entry = readdir($dirHandle)) {
1190  if ($entry !== '.' && $entry !== '..') {
1191  closedir($dirHandle);
1192  return false;
1193  }
1194  }
1195  closedir($dirHandle);
1196  return true;
1197  }
1198 
1209  public function getFileForLocalProcessing($fileIdentifier, $writable = true)
1210  {
1211  if ($writable === false) {
1212  return $this->getAbsolutePath($fileIdentifier);
1213  } else {
1214  return $this->copyFileToTemporaryPath($fileIdentifier);
1215  }
1216  }
1217 
1218 
1226  public function getPermissions($identifier)
1227  {
1228  $path = $this->getAbsolutePath($identifier);
1229  $permissionBits = fileperms($path);
1230  if ($permissionBits === false) {
1231  throw new Exception\ResourcePermissionsUnavailableException('Error while fetching permissions for ' . $path, 1319455097);
1232  }
1233  return array(
1234  'r' => (bool)is_readable($path),
1235  'w' => (bool)is_writable($path)
1236  );
1237  }
1238 
1248  public function isWithin($folderIdentifier, $identifier)
1249  {
1250  $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
1251  $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
1252  if ($folderIdentifier === $entryIdentifier) {
1253  return true;
1254  }
1255  // File identifier canonicalization will not modify a single slash so
1256  // we must not append another slash in that case.
1257  if ($folderIdentifier !== '/') {
1258  $folderIdentifier .= '/';
1259  }
1260  return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
1261  }
1262 
1272  public function createFile($fileName, $parentFolderIdentifier)
1273  {
1274  if (!$this->isValidFilename($fileName)) {
1275  throw new Exception\InvalidFileNameException(
1276  'Invalid characters in fileName "' . $fileName . '"',
1277  1320572272
1278  );
1279  }
1280  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
1281  $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier(
1282  $parentFolderIdentifier . $this->sanitizeFileName(ltrim($fileName, '/'))
1283  );
1284  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
1285  $result = touch($absoluteFilePath);
1286  GeneralUtility::fixPermissions($absoluteFilePath);
1287  clearstatcache();
1288  if ($result !== true) {
1289  throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854);
1290  }
1291  return $fileIdentifier;
1292  }
1293 
1303  public function getFileContents($fileIdentifier)
1304  {
1305  $filePath = $this->getAbsolutePath($fileIdentifier);
1306  return file_get_contents($filePath);
1307  }
1308 
1317  public function setFileContents($fileIdentifier, $contents)
1318  {
1319  $filePath = $this->getAbsolutePath($fileIdentifier);
1320  $result = file_put_contents($filePath, $contents);
1321 
1322  // Make sure later calls to filesize() etc. return correct values.
1323  clearstatcache(true, $filePath);
1324 
1325  if ($result === false) {
1326  throw new \RuntimeException('Setting contents of file "' . $fileIdentifier . '" failed.', 1325419305);
1327  }
1328  return $result;
1329  }
1330 
1336  protected function getCharsetConversion()
1337  {
1338  if (!isset($this->charsetConversion)) {
1339  if (TYPO3_MODE === 'FE') {
1340  $this->charsetConversion = $GLOBALS['TSFE']->csConvObj;
1341  } elseif (is_object($GLOBALS['LANG'])) {
1342  // BE assumed:
1343  $this->charsetConversion = $GLOBALS['LANG']->csConvObj;
1344  } else {
1345  // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
1346  $this->charsetConversion = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
1347  }
1348  }
1349  return $this->charsetConversion;
1350  }
1351 
1358  public function getRole($folderIdentifier)
1359  {
1360  $name = PathUtility::basename($folderIdentifier);
1361  $role = $this->mappingFolderNameToRole[$name];
1362  if (empty($role)) {
1364  }
1365  return $role;
1366  }
1367 
1376  public function dumpFileContents($identifier)
1377  {
1378  readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
1379  }
1380 }