TYPO3 CMS  TYPO3_8-7
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 
28 {
32  const UNSAFE_FILENAME_CHARACTER_EXPRESSION = '\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF';
33 
39  protected $absoluteBasePath;
40 
46  protected $supportedHashAlgorithms = ['sha1', 'md5'];
47 
54  protected $baseUri = null;
55 
58  '_recycler_' => FolderInterface::ROLE_RECYCLER,
60  'user_upload' => FolderInterface::ROLE_USERUPLOAD,
61  ];
62 
66  public function __construct(array $configuration = [])
67  {
68  parent::__construct($configuration);
69  // The capabilities default of this driver. See CAPABILITY_* constants for possible values
70  $this->capabilities =
74  }
75 
85  {
86  $this->capabilities &= $capabilities;
87  return $this->capabilities;
88  }
89 
93  public function processConfiguration()
94  {
95  $this->absoluteBasePath = $this->calculateBasePath($this->configuration);
96  $this->determineBaseUrl();
97  if ($this->baseUri === null) {
98  // remove public flag
99  $this->capabilities &= ~ResourceStorage::CAPABILITY_PUBLIC;
100  }
101  }
102 
107  public function initialize()
108  {
109  }
110 
115  protected function determineBaseUrl()
116  {
117  // only calculate baseURI if the storage does not enforce jumpUrl Script
119  if (GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
120  // use site-relative URLs
121  $temporaryBaseUri = rtrim(PathUtility::stripPathSitePrefix($this->absoluteBasePath), '/');
122  if ($temporaryBaseUri !== '') {
123  $uriParts = explode('/', $temporaryBaseUri);
124  $uriParts = array_map('rawurlencode', $uriParts);
125  $temporaryBaseUri = implode('/', $uriParts) . '/';
126  }
127  $this->baseUri = $temporaryBaseUri;
128  } elseif (isset($this->configuration['baseUri']) && GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
129  $this->baseUri = rtrim($this->configuration['baseUri'], '/') . '/';
130  }
131  }
132  }
133 
141  protected function calculateBasePath(array $configuration)
142  {
143  if (!array_key_exists('basePath', $configuration) || empty($configuration['basePath'])) {
144  throw new Exception\InvalidConfigurationException(
145  'Configuration must contain base path.',
146  1346510477
147  );
148  }
149 
150  if ($configuration['pathType'] === 'relative') {
151  $relativeBasePath = $configuration['basePath'];
152  $absoluteBasePath = PATH_site . $relativeBasePath;
153  } else {
154  $absoluteBasePath = $configuration['basePath'];
155  }
157  $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
158  if (!is_dir($absoluteBasePath)) {
159  throw new Exception\InvalidConfigurationException(
160  'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
161  1299233097
162  );
163  }
164  return $absoluteBasePath;
165  }
166 
175  public function getPublicUrl($identifier)
176  {
177  $publicUrl = null;
178  if ($this->baseUri !== null) {
179  $uriParts = explode('/', ltrim($identifier, '/'));
180  $uriParts = array_map('rawurlencode', $uriParts);
181  $identifier = implode('/', $uriParts);
182  $publicUrl = $this->baseUri . $identifier;
183  }
184  return $publicUrl;
185  }
186 
192  public function getRootLevelFolder()
193  {
194  return '/';
195  }
196 
202  public function getDefaultFolder()
203  {
204  $identifier = '/user_upload/';
205  $createFolder = !$this->folderExists($identifier);
206  if ($createFolder === true) {
207  $identifier = $this->createFolder('user_upload');
208  }
209  return $identifier;
210  }
211 
221  public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false)
222  {
223  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
224  $newFolderName = trim($newFolderName, '/');
225  if ($recursive === false) {
226  $newFolderName = $this->sanitizeFileName($newFolderName);
227  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier . $newFolderName . '/');
228  GeneralUtility::mkdir($this->getAbsolutePath($newIdentifier));
229  } else {
230  $parts = GeneralUtility::trimExplode('/', $newFolderName);
231  $parts = array_map([$this, 'sanitizeFileName'], $parts);
232  $newFolderName = implode('/', $parts);
233  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier(
234  $parentFolderIdentifier . $newFolderName . '/'
235  );
236  GeneralUtility::mkdir_deep($this->getAbsolutePath($newIdentifier));
237  }
238  return $newIdentifier;
239  }
240 
249  public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = [])
250  {
251  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
252  // don't use $this->fileExists() because we need the absolute path to the file anyways, so we can directly
253  // use PHP's filesystem method.
254  if (!file_exists($absoluteFilePath) || !is_file($absoluteFilePath)) {
255  throw new \InvalidArgumentException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
256  }
257 
258  $dirPath = PathUtility::dirname($fileIdentifier);
259  $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
260  return $this->extractFileInformation($absoluteFilePath, $dirPath, $propertiesToExtract);
261  }
262 
270  public function getFolderInfoByIdentifier($folderIdentifier)
271  {
272  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
273 
274  if (!$this->folderExists($folderIdentifier)) {
275  throw new Exception\FolderDoesNotExistException(
276  'Folder "' . $folderIdentifier . '" does not exist.',
277  1314516810
278  );
279  }
280  $absolutePath = $this->getAbsolutePath($folderIdentifier);
281  return [
282  'identifier' => $folderIdentifier,
283  'name' => PathUtility::basename($folderIdentifier),
284  'mtime' => filemtime($absolutePath),
285  'ctime' => filectime($absolutePath),
286  'storage' => $this->storageUid
287  ];
288  }
289 
302  public function sanitizeFileName($fileName, $charset = 'utf-8')
303  {
304  // Handle UTF-8 characters
305  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
306  // Allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
307  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . ']/u', '_', trim($fileName));
308  } else {
309  $fileName = $this->getCharsetConversion()->specCharsToASCII($charset, $fileName);
310  // Replace unwanted characters by underscores
311  $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . '\\xC0-\\xFF]/', '_', trim($fileName));
312  }
313  // Strip trailing dots and return
314  $cleanFileName = rtrim($cleanFileName, '.');
315  if ($cleanFileName === '') {
316  throw new Exception\InvalidFileNameException(
317  'File name ' . $fileName . ' is invalid.',
318  1320288991
319  );
320  }
321  return $cleanFileName;
322  }
323 
343  protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = true, $includeDirs = true, $recursive = false, $sort = '', $sortRev = false)
344  {
345  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
346  $realPath = $this->getAbsolutePath($folderIdentifier);
347  if (!is_dir($realPath)) {
348  throw new \InvalidArgumentException(
349  'Cannot list items in directory ' . $folderIdentifier . ' - does not exist or is no directory',
350  1314349666
351  );
352  }
353 
354  $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs, $sort, $sortRev);
355  $iterator = new \ArrayIterator($items);
356  if ($iterator->count() === 0) {
357  return [];
358  }
359 
360  // $c is the counter for how many items we still have to fetch (-1 is unlimited)
361  $c = $numberOfItems > 0 ? $numberOfItems : - 1;
362  $items = [];
363  while ($iterator->valid() && ($numberOfItems === 0 || $c > 0)) {
364  // $iteratorItem is the file or folder name
365  $iteratorItem = $iterator->current();
366  // go on to the next iterator item now as we might skip this one early
367  $iterator->next();
368 
369  try {
370  if (
372  $filterMethods,
373  $iteratorItem['name'],
374  $iteratorItem['identifier'],
375  $this->getParentFolderIdentifierOfIdentifier($iteratorItem['identifier'])
376  )
377  ) {
378  continue;
379  }
380  if ($start > 0) {
381  $start--;
382  } else {
383  $items[$iteratorItem['identifier']] = $iteratorItem['identifier'];
384  // Decrement item counter to make sure we only return $numberOfItems
385  // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
386  // item here
387  --$c;
388  }
389  } catch (Exception\InvalidPathException $e) {
390  }
391  }
392  return $items;
393  }
394 
406  protected function applyFilterMethodsToDirectoryItem(array $filterMethods, $itemName, $itemIdentifier, $parentIdentifier)
407  {
408  foreach ($filterMethods as $filter) {
409  if (is_callable($filter)) {
410  $result = call_user_func($filter, $itemName, $itemIdentifier, $parentIdentifier, [], $this);
411  // We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
412  // If calling the method succeeded and thus we can't use that as a return value.
413  if ($result === -1) {
414  return false;
415  }
416  if ($result === false) {
417  throw new \RuntimeException(
418  'Could not apply file/folder name filter ' . $filter[0] . '::' . $filter[1],
419  1476046425
420  );
421  }
422  }
423  }
424  return true;
425  }
426 
434  public function getFileInFolder($fileName, $folderIdentifier)
435  {
436  return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
437  }
438 
455  public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $filenameFilterCallbacks = [], $sort = '', $sortRev = false)
456  {
457  return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks, true, false, $recursive, $sort, $sortRev);
458  }
459 
468  public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = [])
469  {
470  return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
471  }
472 
489  public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $folderNameFilterCallbacks = [], $sort = '', $sortRev = false)
490  {
491  return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks, false, true, $recursive, $sort, $sortRev);
492  }
493 
502  public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = [])
503  {
504  return count($this->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $folderNameFilterCallbacks));
505  }
506 
522  protected function retrieveFileAndFoldersInPath($path, $recursive = false, $includeFiles = true, $includeDirs = true, $sort = '', $sortRev = false)
523  {
524  $pathLength = strlen($this->getAbsoluteBasePath());
525  $iteratorMode = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::FOLLOW_SYMLINKS;
526  if ($recursive) {
527  $iterator = new \RecursiveIteratorIterator(
528  new \RecursiveDirectoryIterator($path, $iteratorMode),
529  \RecursiveIteratorIterator::SELF_FIRST,
530  \RecursiveIteratorIterator::CATCH_GET_CHILD
531  );
532  } else {
533  $iterator = new \RecursiveDirectoryIterator($path, $iteratorMode);
534  }
535 
536  $directoryEntries = [];
537  while ($iterator->valid()) {
539  $entry = $iterator->current();
540  $isFile = $entry->isFile();
541  $isDirectory = $isFile ? false : $entry->isDir();
542  if (
543  (!$isFile && !$isDirectory) // skip non-files/non-folders
544  || ($isFile && !$includeFiles) // skip files if they are excluded
545  || ($isDirectory && !$includeDirs) // skip directories if they are excluded
546  || $entry->getFilename() === '' // skip empty entries
547  ) {
548  $iterator->next();
549  continue;
550  }
551  $entryIdentifier = '/' . substr($entry->getPathname(), $pathLength);
552  $entryName = PathUtility::basename($entryIdentifier);
553  if ($isDirectory) {
554  $entryIdentifier .= '/';
555  }
556  $entryArray = [
557  'identifier' => $entryIdentifier,
558  'name' => $entryName,
559  'type' => $isDirectory ? 'dir' : 'file'
560  ];
561  $directoryEntries[$entryIdentifier] = $entryArray;
562  $iterator->next();
563  }
564  return $this->sortDirectoryEntries($directoryEntries, $sort, $sortRev);
565  }
566 
580  protected function sortDirectoryEntries($directoryEntries, $sort = '', $sortRev = false)
581  {
582  $entriesToSort = [];
583  foreach ($directoryEntries as $entryArray) {
584  $dir = pathinfo($entryArray['name'], PATHINFO_DIRNAME) . '/';
585  $fullPath = $this->getAbsoluteBasePath() . $entryArray['identifier'];
586  switch ($sort) {
587  case 'size':
588  $sortingKey = '0';
589  if ($entryArray['type'] === 'file') {
590  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'size');
591  }
592  // Add a character for a natural order sorting
593  $sortingKey .= 's';
594  break;
595  case 'rw':
596  $perms = $this->getPermissions($entryArray['identifier']);
597  $sortingKey = ($perms['r'] ? 'R' : '')
598  . ($perms['w'] ? 'W' : '');
599  break;
600  case 'fileext':
601  $sortingKey = pathinfo($entryArray['name'], PATHINFO_EXTENSION);
602  break;
603  case 'tstamp':
604  $sortingKey = '0';
605  if ($entryArray['type'] === 'file') {
606  $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'mtime');
607  }
608  // Add a character for a natural order sorting
609  $sortingKey .= 't';
610  break;
611  case 'name':
612  case 'file':
613  default:
614  $sortingKey = $entryArray['name'];
615  }
616  $i = 0;
617  while (isset($entriesToSort[$sortingKey . $i])) {
618  $i++;
619  }
620  $entriesToSort[$sortingKey . $i] = $entryArray;
621  }
622  uksort($entriesToSort, 'strnatcasecmp');
623 
624  if ($sortRev) {
625  $entriesToSort = array_reverse($entriesToSort);
626  }
627 
628  return $entriesToSort;
629  }
630 
639  protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = [])
640  {
641  if (empty($propertiesToExtract)) {
642  $propertiesToExtract = [
643  'size', 'atime', 'mtime', 'ctime', 'mimetype', 'name', 'extension',
644  'identifier', 'identifier_hash', 'storage', 'folder_hash'
645  ];
646  }
647  $fileInformation = [];
648  foreach ($propertiesToExtract as $property) {
649  $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
650  }
651  return $fileInformation;
652  }
653 
664  public function getSpecificFileInformation($fileIdentifier, $containerPath, $property)
665  {
666  $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
667 
669  $fileInfo = GeneralUtility::makeInstance(FileInfo::class, $fileIdentifier);
670  switch ($property) {
671  case 'size':
672  return $fileInfo->getSize();
673  case 'atime':
674  return $fileInfo->getATime();
675  case 'mtime':
676  return $fileInfo->getMTime();
677  case 'ctime':
678  return $fileInfo->getCTime();
679  case 'name':
680  return PathUtility::basename($fileIdentifier);
681  case 'extension':
682  return PathUtility::pathinfo($fileIdentifier, PATHINFO_EXTENSION);
683  case 'mimetype':
684  return (string)$fileInfo->getMimeType();
685  case 'identifier':
686  return $identifier;
687  case 'storage':
688  return $this->storageUid;
689  case 'identifier_hash':
690  return $this->hashIdentifier($identifier);
691  case 'folder_hash':
692  return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
693  default:
694  throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property), 1476047422);
695  }
696  }
697 
703  protected function getAbsoluteBasePath()
704  {
706  }
707 
715  protected function getAbsolutePath($fileIdentifier)
716  {
717  $relativeFilePath = ltrim($this->canonicalizeAndCheckFileIdentifier($fileIdentifier), '/');
718  $path = $this->absoluteBasePath . $relativeFilePath;
719  return $path;
720  }
721 
731  public function hash($fileIdentifier, $hashAlgorithm)
732  {
733  if (!in_array($hashAlgorithm, $this->supportedHashAlgorithms)) {
734  throw new \InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
735  }
736  switch ($hashAlgorithm) {
737  case 'sha1':
738  $hash = sha1_file($this->getAbsolutePath($fileIdentifier));
739  break;
740  case 'md5':
741  $hash = md5_file($this->getAbsolutePath($fileIdentifier));
742  break;
743  default:
744  throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
745  }
746  return $hash;
747  }
748 
762  public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true)
763  {
764  $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
765  // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
766  // thus, it is not checked here
767  // @todo is check in storage
768  if (GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storageUid > 0) {
769  throw new \InvalidArgumentException('Cannot add a file that is already part of this storage.', 1314778269);
770  }
771  $newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath));
772  $newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName;
773  $targetPath = $this->getAbsolutePath($newFileIdentifier);
774 
775  if ($removeOriginal) {
776  if (is_uploaded_file($localFilePath)) {
777  $result = move_uploaded_file($localFilePath, $targetPath);
778  } else {
779  $result = rename($localFilePath, $targetPath);
780  }
781  } else {
782  $result = copy($localFilePath, $targetPath);
783  }
784  if ($result === false || !file_exists($targetPath)) {
785  throw new \RuntimeException(
786  'Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.',
787  1476046453
788  );
789  }
790  clearstatcache();
791  // Change the permissions of the file
792  GeneralUtility::fixPermissions($targetPath);
793  return $newFileIdentifier;
794  }
795 
803  public function fileExists($fileIdentifier)
804  {
805  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
806  return is_file($absoluteFilePath);
807  }
808 
816  public function fileExistsInFolder($fileName, $folderIdentifier)
817  {
818  $identifier = $folderIdentifier . '/' . $fileName;
819  $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
820  return $this->fileExists($identifier);
821  }
822 
830  public function folderExists($folderIdentifier)
831  {
832  $absoluteFilePath = $this->getAbsolutePath($folderIdentifier);
833  return is_dir($absoluteFilePath);
834  }
835 
843  public function folderExistsInFolder($folderName, $folderIdentifier)
844  {
845  $identifier = $folderIdentifier . '/' . $folderName;
846  $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
847  return $this->folderExists($identifier);
848  }
849 
857  public function getFolderInFolder($folderName, $folderIdentifier)
858  {
859  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
860  return $folderIdentifier;
861  }
862 
871  public function replaceFile($fileIdentifier, $localFilePath)
872  {
873  $filePath = $this->getAbsolutePath($fileIdentifier);
874  if (is_uploaded_file($localFilePath)) {
875  $result = move_uploaded_file($localFilePath, $filePath);
876  } else {
877  $result = rename($localFilePath, $filePath);
878  }
880  if ($result === false) {
881  throw new \RuntimeException('Replacing file ' . $fileIdentifier . ' with ' . $localFilePath . ' failed.', 1315314711);
882  }
883  return $result;
884  }
885 
896  public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
897  {
898  $sourcePath = $this->getAbsolutePath($fileIdentifier);
899  $newIdentifier = $targetFolderIdentifier . '/' . $fileName;
900  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
901 
902  $absoluteFilePath = $this->getAbsolutePath($newIdentifier);
903  copy($sourcePath, $absoluteFilePath);
904  GeneralUtility::fixPermissions($absoluteFilePath);
905  return $newIdentifier;
906  }
907 
919  public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
920  {
921  $sourcePath = $this->getAbsolutePath($fileIdentifier);
922  $targetIdentifier = $targetFolderIdentifier . '/' . $newFileName;
923  $targetIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetIdentifier);
924  $result = rename($sourcePath, $this->getAbsolutePath($targetIdentifier));
925  if ($result === false) {
926  throw new \RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
927  }
928  return $targetIdentifier;
929  }
930 
938  protected function copyFileToTemporaryPath($fileIdentifier)
939  {
940  $sourcePath = $this->getAbsolutePath($fileIdentifier);
941  $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
942  $result = copy($sourcePath, $temporaryPath);
943  touch($temporaryPath, filemtime($sourcePath));
944  if ($result === false) {
945  throw new \RuntimeException(
946  'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.',
947  1320577649
948  );
949  }
950  return $temporaryPath;
951  }
952 
961  protected function recycleFileOrFolder($filePath, $recycleDirectory)
962  {
963  $destinationFile = $recycleDirectory . '/' . PathUtility::basename($filePath);
964  if (file_exists($destinationFile)) {
965  $timeStamp = \DateTimeImmutable::createFromFormat('U.u', microtime(true))->format('YmdHisu');
966  $destinationFile = $recycleDirectory . '/' . $timeStamp . '_' . PathUtility::basename($filePath);
967  }
968  $result = rename($filePath, $destinationFile);
969  // Update the mtime for the file, so the recycler garbage collection task knows which files to delete
970  // Using ctime() is not possible there since this is not supported on Windows
971  if ($result) {
972  touch($destinationFile);
973  }
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  $copySourcePath = $sourceFolderPath . '/' . $itemSubPath;
1077  $copyTargetPath = $targetFolderPath . '/' . $itemSubPath;
1078  $result = copy($copySourcePath, $copyTargetPath);
1079  if ($result === false) {
1080  // rollback
1081  GeneralUtility::rmdir($targetFolderIdentifier, true);
1082  throw new Exception\FileOperationErrorException(
1083  'Copying resource "' . $copySourcePath . '" to "' . $copyTargetPath . '" failed.',
1084  1330119452
1085  );
1086  }
1087  }
1088  $iterator->next();
1089  }
1090  GeneralUtility::fixPermissions($targetFolderPath, true);
1091  return true;
1092  }
1093 
1103  public function renameFile($fileIdentifier, $newName)
1104  {
1105  // Makes sure the Path given as parameter is valid
1106  $newName = $this->sanitizeFileName($newName);
1107  $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(PathUtility::dirname($fileIdentifier)), '/') . '/' . $newName;
1108  $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
1109  // The target should not exist already
1110  if ($this->fileExists($newIdentifier)) {
1111  throw new Exception\ExistingTargetFileNameException(
1112  'The target file "' . $newIdentifier . '" already exists.',
1113  1320291063
1114  );
1115  }
1116  $sourcePath = $this->getAbsolutePath($fileIdentifier);
1117  $targetPath = $this->getAbsolutePath($newIdentifier);
1118  $result = rename($sourcePath, $targetPath);
1119  if ($result === false) {
1120  throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
1121  }
1122  return $newIdentifier;
1123  }
1124 
1133  public function renameFolder($folderIdentifier, $newName)
1134  {
1135  $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
1136  $newName = $this->sanitizeFileName($newName);
1137 
1138  $newIdentifier = PathUtility::dirname($folderIdentifier) . '/' . $newName;
1139  $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier($newIdentifier);
1140 
1141  $sourcePath = $this->getAbsolutePath($folderIdentifier);
1142  $targetPath = $this->getAbsolutePath($newIdentifier);
1143  // get all files and folders we are going to move, to have a map for updating later.
1144  $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, true);
1145  $result = rename($sourcePath, $targetPath);
1146  if ($result === false) {
1147  throw new \RuntimeException(sprintf('Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
1148  }
1149  try {
1150  // Create a mapping from old to new identifiers
1151  $identifierMap = $this->createIdentifierMap($filesAndFolders, $folderIdentifier, $newIdentifier);
1152  } catch (\Exception $e) {
1153  rename($targetPath, $sourcePath);
1154  throw new \RuntimeException(
1155  sprintf(
1156  'Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"',
1157  $sourcePath,
1158  $targetPath,
1159  $e->getMessage()
1160  ),
1161  1334160746
1162  );
1163  }
1164  return $identifierMap;
1165  }
1166 
1176  public function deleteFile($fileIdentifier)
1177  {
1178  $filePath = $this->getAbsolutePath($fileIdentifier);
1179  $result = unlink($filePath);
1180  if ($result === false) {
1181  throw new \RuntimeException('Deletion of file ' . $fileIdentifier . ' failed.', 1320855304);
1182  }
1183  return $result;
1184  }
1185 
1195  public function deleteFolder($folderIdentifier, $deleteRecursively = false)
1196  {
1197  $folderPath = $this->getAbsolutePath($folderIdentifier);
1198  $recycleDirectory = $this->getRecycleDirectory($folderPath);
1199  if (!empty($recycleDirectory) && $folderPath !== $recycleDirectory) {
1200  $result = $this->recycleFileOrFolder($folderPath, $recycleDirectory);
1201  } else {
1202  $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
1203  }
1204  if ($result === false) {
1205  throw new Exception\FileOperationErrorException(
1206  'Deleting folder "' . $folderIdentifier . '" failed.',
1207  1330119451
1208  );
1209  }
1210  return $result;
1211  }
1212 
1219  public function isFolderEmpty($folderIdentifier)
1220  {
1221  $path = $this->getAbsolutePath($folderIdentifier);
1222  $dirHandle = opendir($path);
1223  while ($entry = readdir($dirHandle)) {
1224  if ($entry !== '.' && $entry !== '..') {
1225  closedir($dirHandle);
1226  return false;
1227  }
1228  }
1229  closedir($dirHandle);
1230  return true;
1231  }
1232 
1243  public function getFileForLocalProcessing($fileIdentifier, $writable = true)
1244  {
1245  if ($writable === false) {
1246  return $this->getAbsolutePath($fileIdentifier);
1247  }
1248  return $this->copyFileToTemporaryPath($fileIdentifier);
1249  }
1250 
1258  public function getPermissions($identifier)
1259  {
1260  $path = $this->getAbsolutePath($identifier);
1261  $permissionBits = fileperms($path);
1262  if ($permissionBits === false) {
1263  throw new Exception\ResourcePermissionsUnavailableException('Error while fetching permissions for ' . $path, 1319455097);
1264  }
1265  return [
1266  'r' => (bool)is_readable($path),
1267  'w' => (bool)is_writable($path)
1268  ];
1269  }
1270 
1280  public function isWithin($folderIdentifier, $identifier)
1281  {
1282  $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
1283  $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
1284  if ($folderIdentifier === $entryIdentifier) {
1285  return true;
1286  }
1287  // File identifier canonicalization will not modify a single slash so
1288  // we must not append another slash in that case.
1289  if ($folderIdentifier !== '/') {
1290  $folderIdentifier .= '/';
1291  }
1292  return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
1293  }
1294 
1303  public function createFile($fileName, $parentFolderIdentifier)
1304  {
1305  $fileName = $this->sanitizeFileName(ltrim($fileName, '/'));
1306  $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
1307  $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier(
1308  $parentFolderIdentifier . $fileName
1309  );
1310  $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
1311  $result = touch($absoluteFilePath);
1312  GeneralUtility::fixPermissions($absoluteFilePath);
1313  clearstatcache();
1314  if ($result !== true) {
1315  throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854);
1316  }
1317  return $fileIdentifier;
1318  }
1319 
1329  public function getFileContents($fileIdentifier)
1330  {
1331  $filePath = $this->getAbsolutePath($fileIdentifier);
1332  return file_get_contents($filePath);
1333  }
1334 
1343  public function setFileContents($fileIdentifier, $contents)
1344  {
1345  $filePath = $this->getAbsolutePath($fileIdentifier);
1346  $result = file_put_contents($filePath, $contents);
1347 
1348  // Make sure later calls to filesize() etc. return correct values.
1349  clearstatcache(true, $filePath);
1350 
1351  if ($result === false) {
1352  throw new \RuntimeException('Setting contents of file "' . $fileIdentifier . '" failed.', 1325419305);
1353  }
1354  return $result;
1355  }
1356 
1363  public function getRole($folderIdentifier)
1364  {
1365  $name = PathUtility::basename($folderIdentifier);
1366  $role = $this->mappingFolderNameToRole[$name];
1367  if (empty($role)) {
1369  }
1370  return $role;
1371  }
1372 
1380  public function dumpFileContents($identifier)
1381  {
1382  readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
1383  }
1384 
1392  protected function getRecycleDirectory($path)
1393  {
1394  $recyclerSubdirectory = array_search(FolderInterface::ROLE_RECYCLER, $this->mappingFolderNameToRole, true);
1395  if ($recyclerSubdirectory === false) {
1396  return '';
1397  }
1398  $rootDirectory = rtrim($this->getAbsolutePath($this->getRootLevelFolder()), '/');
1399  $searchDirectory = PathUtility::dirname($path);
1400  // Check if file or folder to be deleted is inside a recycler directory
1401  if ($this->getRole($searchDirectory) === FolderInterface::ROLE_RECYCLER) {
1402  $searchDirectory = PathUtility::dirname($searchDirectory);
1403  // Check if file or folder to be deleted is inside the root recycler
1404  if ($searchDirectory == $rootDirectory) {
1405  return '';
1406  }
1407  $searchDirectory = PathUtility::dirname($searchDirectory);
1408  }
1409  // Search for the closest recycler directory
1410  while ($searchDirectory) {
1411  $recycleDirectory = $searchDirectory . '/' . $recyclerSubdirectory;
1412  if (is_dir($recycleDirectory)) {
1413  return $recycleDirectory;
1414  }
1415  if ($searchDirectory === $rootDirectory) {
1416  return '';
1417  }
1418  $searchDirectory = PathUtility::dirname($searchDirectory);
1419  }
1420 
1421  return '';
1422  }
1423 }
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:66
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=[])
static makeInstance($className,... $constructorArguments)
recycleFileOrFolder($filePath, $recycleDirectory)
static pathinfo($path, $options=null)
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)
sanitizeFileName($fileName, $charset='utf-8')
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)
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)