2 namespace TYPO3\CMS\Core\
Resource\Driver
;
4 /***************************************************************
7 * (c) 2011-2013 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * (c) 2013 Stefan Neufeind <info (at) speedpartner.de>
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
31 use TYPO3\CMS\Core\
Resource\FolderInterface
;
32 use TYPO3\CMS\Core\Utility\GeneralUtility
;
33 use TYPO3\CMS\Core\Utility\PathUtility
;
36 * Driver for the local file system
38 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
40 class LocalDriver
extends AbstractHierarchicalFilesystemDriver
{
43 * The absolute base path. It always contains a trailing slash.
47 protected $absoluteBasePath;
50 * A list of all supported hash algorithms, written all lower case.
54 protected $supportedHashAlgorithms = array('sha1', 'md5');
57 * The base URL that points to this driver's storage. As long is this
58 * is not set, it is assumed that this folder is not publicly available
65 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
67 protected $charsetConversion;
70 protected $mappingFolderNameToRole = array(
71 '_recycler_' => FolderInterface
::ROLE_RECYCLER
,
72 '_temp_' => FolderInterface
::ROLE_TEMPORARY
,
73 'user_upload' => FolderInterface
::ROLE_USERUPLOAD
,
77 * Checks if a configuration is valid for this storage.
79 * @param array $configuration The configuration
81 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException
83 static public function verifyConfiguration(array $configuration) {
84 self
::calculateBasePath($configuration);
88 * Processes the configuration for this driver.
92 public function processConfiguration() {
93 $this->absoluteBasePath
= $this->calculateBasePath($this->configuration
);
97 * Initializes this object. This is called by the storage after the driver
102 public function initialize() {
103 $this->determineBaseUrl();
104 // The capabilities of this driver. See CAPABILITY_* constants for possible values
105 $this->capabilities
= \TYPO3\CMS\Core\
Resource\ResourceStorage
::CAPABILITY_BROWSABLE | \TYPO3\CMS\Core\
Resource\ResourceStorage
::CAPABILITY_PUBLIC | \TYPO3\CMS\Core\
Resource\ResourceStorage
::CAPABILITY_WRITABLE
;
109 * Determines the base URL for this driver, from the configuration or
110 * the TypoScript frontend object
114 protected function determineBaseUrl() {
115 if (\TYPO3\CMS\Core\Utility\GeneralUtility
::isFirstPartOfStr($this->absoluteBasePath
, PATH_site
)) {
116 // use site-relative URLs
117 // TODO add unit test
118 $this->baseUri
= substr($this->absoluteBasePath
, strlen(PATH_site
));
119 } elseif (isset($this->configuration
['baseUri']) && \TYPO3\CMS\Core\Utility\GeneralUtility
::isValidUrl($this->configuration
['baseUri'])) {
120 $this->baseUri
= rtrim($this->configuration
['baseUri'], '/') . '/';
127 * Calculates the absolute path to this drivers storage location.
129 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException
130 * @param array $configuration
133 protected function calculateBasePath(array $configuration) {
134 if (!array_key_exists('basePath', $configuration) ||
empty($configuration['basePath'])) {
135 throw new \TYPO3\CMS\Core\
Resource\Exception\
InvalidConfigurationException('Configuration must contain base path.', 1346510477);
138 if ($configuration['pathType'] === 'relative') {
139 $relativeBasePath = $configuration['basePath'];
140 $absoluteBasePath = PATH_site
. $relativeBasePath;
142 $absoluteBasePath = $configuration['basePath'];
144 $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
145 if (!is_dir($absoluteBasePath)) {
146 throw new \TYPO3\CMS\Core\
Resource\Exception\
InvalidConfigurationException('Base path "' . $absoluteBasePath . '" does not exist or is no directory.', 1299233097);
148 return $absoluteBasePath;
152 * Returns the public URL to a file. For the local driver, this will always
153 * return a path relative to PATH_site.
155 * @param \TYPO3\CMS\Core\Resource\ResourceInterface $fileOrFolder
156 * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
159 public function getPublicUrl(\TYPO3\CMS\Core\
Resource\ResourceInterface
$fileOrFolder, $relativeToCurrentScript = FALSE) {
160 if ($this->configuration
['pathType'] === 'relative' && rtrim($this->configuration
['basePath'], '/') !== '') {
161 $publicUrl = rtrim($this->configuration
['basePath'], '/') . '/' . ltrim($fileOrFolder->getIdentifier(), '/');
162 } elseif (isset($this->baseUri
)) {
163 $publicUrl = $this->baseUri
. ltrim($fileOrFolder->getIdentifier(), '/');
165 throw new \TYPO3\CMS\Core\
Resource\
Exception('Public URL of file cannot be determined', 1329765518);
167 // If requested, make the path relative to the current script in order to make it possible
168 // to use the relative file
169 if ($relativeToCurrentScript) {
170 $publicUrl = PathUtility
::getRelativePathTo(PathUtility
::dirname((PATH_site
. $publicUrl))) . PathUtility
::basename($publicUrl);
176 * Returns the root level folder of the storage.
178 * @return \TYPO3\CMS\Core\Resource\Folder
180 public function getRootLevelFolder() {
181 if (!$this->rootLevelFolder
) {
182 $this->rootLevelFolder
= \TYPO3\CMS\Core\
Resource\ResourceFactory
::getInstance()->createFolderObject($this->storage
, '/', '');
184 return $this->rootLevelFolder
;
188 * Returns the default folder new files should be put into.
190 * @return \TYPO3\CMS\Core\Resource\Folder
192 public function getDefaultFolder() {
193 if (!$this->defaultLevelFolder
) {
194 if (!file_exists(($this->absoluteBasePath
. 'user_upload/'))) {
195 mkdir($this->absoluteBasePath
. 'user_upload/');
197 $this->defaultLevelFolder
= \TYPO3\CMS\Core\
Resource\ResourceFactory
::getInstance()->createFolderObject($this->storage
, '/user_upload/', '');
199 return $this->defaultLevelFolder
;
205 * @param string $newFolderName
206 * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
207 * @return \TYPO3\CMS\Core\Resource\Folder The new (created) folder object
209 public function createFolder($newFolderName, \TYPO3\CMS\Core\
Resource\Folder
$parentFolder) {
210 $newFolderName = trim($this->sanitizeFileName($newFolderName), '/');
211 $newFolderPath = $this->getAbsolutePath($parentFolder) . $newFolderName;
212 \TYPO3\CMS\Core\Utility\GeneralUtility
::mkdir($newFolderPath);
213 return \TYPO3\CMS\Core\
Resource\ResourceFactory
::getInstance()->createFolderObject($this->storage
, $parentFolder->getIdentifier() . $newFolderName, $newFolderName);
217 * Returns information about a file.
219 * @param string $fileIdentifier In the case of the LocalDriver, this is the (relative) path to the file.
222 public function getFileInfoByIdentifier($fileIdentifier) {
223 // Makes sure the Path given as parameter is valid
224 $fileIdentifier = $this->canonicalizeAndCheckFilePath($fileIdentifier);
225 $dirPath = PathUtility
::dirname($fileIdentifier);
226 if ($dirPath === '' ||
$dirPath === '.') {
228 } elseif ($dirPath !== '/') {
229 $dirPath = '/' . trim($dirPath, '/') . '/';
231 $absoluteFilePath = $this->absoluteBasePath
. ltrim($fileIdentifier, '/');
232 // don't use $this->fileExists() because we need the absolute path to the file anyways, so we can directly
233 // use PHP's filesystem method.
234 if (!file_exists($absoluteFilePath)) {
235 throw new \
InvalidArgumentException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
237 return $this->extractFileInformation($absoluteFilePath, $dirPath);
242 * Returns a string where any character not matching [.a-zA-Z0-9_-] is
244 * Trailing dots are removed
246 * Previously in \TYPO3\CMS\Core\Utility\File\BasicFileUtility::cleanFileName()
248 * @param string $fileName Input string, typically the body of a fileName
249 * @param string $charset Charset of the a fileName (defaults to current charset; depending on context)
250 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_' and trailing dots removed
251 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException
253 public function sanitizeFileName($fileName, $charset = '') {
254 // Handle UTF-8 characters
255 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
256 // Allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
257 $cleanFileName = preg_replace('/[\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF]/u', '_', trim($fileName));
259 // Define character set
261 if (TYPO3_MODE
=== 'FE') {
262 $charset = $GLOBALS['TSFE']->renderCharset
;
264 // default for Backend
268 // If a charset was found, convert fileName
270 $fileName = $this->getCharsetConversion()->specCharsToASCII($charset, $fileName);
272 // Replace unwanted characters by underscores
273 $cleanFileName = preg_replace('/[^.[:alnum:]_-]/', '_', trim($fileName));
275 // Strip trailing dots and return
276 $cleanFileName = preg_replace('/\\.*$/', '', $cleanFileName);
277 if (!$cleanFileName) {
278 throw new \TYPO3\CMS\Core\
Resource\Exception\
InvalidFileNameException('File name ' . $cleanFileName . ' is invalid.', 1320288991);
280 return $cleanFileName;
284 * Generic wrapper for extracting a list of items from a path. The
285 * extraction itself is done by the given handler method
287 * @param string $basePath
288 * @param integer $start The position to start the listing; if not set, start from the beginning
289 * @param integer $numberOfItems The number of items to list; if set to zero, all items are returned
290 * @param array $filterMethods The filter methods used to filter the directory items
291 * @param string $itemHandlerMethod The method (in this class) that handles the single iterator elements.
292 * @param array $itemRows
293 * @param boolean $recursive
296 // TODO add unit tests
297 protected function getDirectoryItemList($basePath, $start, $numberOfItems, array $filterMethods, $itemHandlerMethod, $itemRows = array(), $recursive = FALSE) {
298 $realPath = rtrim(($this->absoluteBasePath
. trim($basePath, '/')), '/') . '/';
299 if (!is_dir($realPath)) {
300 throw new \
InvalidArgumentException('Cannot list items in directory ' . $basePath . ' - does not exist or is no directory', 1314349666);
307 // Fetch the files and folders and sort them by name; we have to do
308 // this here because the directory iterator does return them in
309 // an arbitrary order
310 $items = $this->getFileAndFoldernamesInPath($realPath, $recursive);
313 array('\\TYPO3\\CMS\\Core\\Utility\\ResourceUtility', 'recursiveFileListSortingHelper')
316 $iterator = new \
ArrayIterator($items);
317 if ($iterator->count() == 0) {
320 $iterator->seek($start);
322 if ($basePath !== '' && $basePath !== '/') {
323 $basePath = '/' . trim($basePath, '/') . '/';
326 // $c is the counter for how many items we still have to fetch (-1 is unlimited)
327 $c = $numberOfItems > 0 ?
$numberOfItems : -1;
329 while ($iterator->valid() && ($numberOfItems == 0 ||
$c > 0)) {
330 // $iteratorItem is the file or folder name
331 $iteratorItem = $iterator->current();
333 // go on to the next iterator item now as we might skip this one early
335 $identifier = $basePath . $iteratorItem['path'];
337 if ($this->applyFilterMethodsToDirectoryItem($filterMethods, $iteratorItem['name'], $identifier, dirname($identifier) . '/', isset($itemRows[$identifier]) ?
array('indexData' => $itemRows[$identifier]) : array()) === FALSE) {
341 // dirname returns "/" when called with "/" as the argument, so strip trailing slashes here to be sure
342 $path = rtrim(GeneralUtility
::fixWindowsFilePath(dirname($identifier)), '/') . '/';
343 if (isset($itemRows[$identifier])) {
344 list($key, $item) = $this->{$itemHandlerMethod}($iteratorItem['name'], $path, $itemRows[$identifier]);
346 list($key, $item) = $this->{$itemHandlerMethod}($iteratorItem['name'], $path);
353 $key = $iteratorItem['path'];
356 $items[$key] = $item;
357 // Decrement item counter to make sure we only return $numberOfItems
358 // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
366 * Handler for items in a file list.
368 * @param string $fileName
369 * @param string $path
370 * @param array $fileRow The pre-loaded file row
373 protected function getFileList_itemCallback($fileName, $path, array $fileRow = array()) {
374 $filePath = $this->getAbsolutePath($path . $fileName);
375 if (!is_file($filePath)) {
376 return array('', array());
379 // TODO add unit test for existing file row case
380 if (!empty($fileRow) && filemtime($filePath) <= $fileRow['modification_date']) {
381 return array($fileName, $fileRow);
383 return array($fileName, $this->extractFileInformation($filePath, $path));
388 * Handler for items in a directory listing.
390 * @param string $folderName The folder's name
391 * @param string $parentPath The path to the folder's parent folder
392 * @param array $folderRow [optional]
395 protected function getFolderList_itemCallback($folderName, $parentPath, array $folderRow = array()) {
396 $folderPath = $this->getAbsolutePath($parentPath . $folderName);
398 if (!is_dir($folderPath)) {
399 return array('', array());
402 // also don't show hidden files
403 if ($folderName === '..' ||
$folderName === '.' ||
$folderName === '') {
404 return array('', array());
407 // remove the trailing slash from the folder name (the trailing slash comes from the DirectoryIterator)
408 $folderName = substr($folderName, 0, -1);
410 return array($folderName, $this->extractFolderInformation($folderPath, $parentPath));
414 * Returns a list with the names of all files and folders in a path, optionally recursive.
415 * Folder names have a trailing slash.
417 * @param string $path The absolute path
418 * @param bool $recursive If TRUE, recursively fetches files and folders
421 protected function getFileAndFoldernamesInPath($path, $recursive = FALSE) {
423 $iterator = new \
RecursiveIteratorIterator(new \
RecursiveDirectoryIterator($path, \FilesystemIterator
::CURRENT_AS_FILEINFO
), \RecursiveIteratorIterator
::SELF_FIRST
);
425 $iterator = new \
RecursiveDirectoryIterator($path, \FilesystemIterator
::CURRENT_AS_FILEINFO
);
428 $directoryEntries = array();
429 while ($iterator->valid()) {
430 /** @var $entry \SplFileInfo */
431 $entry = $iterator->current();
432 // skip non-files/non-folders, and empty entries
433 if (!$entry->isFile() && !$entry->isDir() ||
$entry->getFilename() == '') {
437 // skip the pseudo-directories "." and ".."
438 if ($entry->getFilename() == '..' ||
$entry->getFilename() == '.') {
442 $entryPath = substr($entry->getPathname(), strlen($path));
443 $entryPath = GeneralUtility
::fixWindowsFilePath($entryPath);
444 $entryName = PathUtility
::basename(basename($entryPath));
445 if ($entry->isDir()) {
450 'path' => $entryPath,
451 'name' => $entryName,
452 'type' => $entry->isDir() ?
'dir' : 'file'
454 $directoryEntries[$entryPath] = $entry;
457 return $directoryEntries;
461 * Extracts information about a file from the filesystem.
463 * @param string $filePath The absolute path to the file
464 * @param string $containerPath The relative path to the file's container
467 protected function extractFileInformation($filePath, $containerPath) {
468 $fileName = PathUtility
::basename($filePath);
469 $fileInformation = array(
470 'size' => filesize($filePath),
471 'atime' => fileatime($filePath),
472 'mtime' => filemtime($filePath),
473 'ctime' => filectime($filePath),
474 'mimetype' => $this->getMimeTypeOfFile($filePath),
476 'identifier' => $containerPath . $fileName,
477 'storage' => $this->storage
->getUid()
479 return $fileInformation;
483 * Extracts information about a folder from the filesystem.
485 * @param string $folderPath The absolute path to the folder
486 * @param string $containerPath The relative path to the folder's container inside the storage (must end with a trailing slash)
489 protected function extractFolderInformation($folderPath, $containerPath) {
490 $folderName = PathUtility
::basename($folderPath);
491 $folderInformation = array(
492 'ctime' => filectime($folderPath),
493 'mtime' => filemtime($folderPath),
494 'name' => $folderName,
495 'identifier' => $containerPath . $folderName . '/',
496 'storage' => $this->storage
->getUid()
498 return $folderInformation;
502 * Returns the absolute path of the folder this driver operates on.
506 public function getAbsoluteBasePath() {
507 return $this->absoluteBasePath
;
511 * Returns the absolute path of a file or folder.
513 * @param \TYPO3\CMS\Core\Resource\FileInterface|\TYPO3\CMS\Core\Resource\Folder|string $file
516 public function getAbsolutePath($file) {
517 if ($file instanceof \TYPO3\CMS\Core\
Resource\FileInterface
) {
518 $path = $this->absoluteBasePath
. ltrim($file->getIdentifier(), '/');
519 } elseif ($file instanceof \TYPO3\CMS\Core\
Resource\Folder
) {
520 // We can assume a trailing slash here because it is added by the folder object on construction.
521 $path = $this->absoluteBasePath
. ltrim($file->getIdentifier(), '/');
522 } elseif (is_string($file)) {
523 $path = $this->absoluteBasePath
. ltrim($file, '/');
525 throw new \
RuntimeException('Type "' . gettype($file) . '" is not supported.', 1325191178);
531 * Returns metadata of a file (size, times, mimetype)
533 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
536 public function getLowLevelFileInfo(\TYPO3\CMS\Core\
Resource\FileInterface
$file) {
537 // TODO define which data should be returned
538 // TODO write unit test
539 // TODO cache this info. Registry?
540 // TODO merge with extractFolderInformation() above?!
541 $filePath = $this->getAbsolutePath($file);
542 $fileStat = stat($filePath);
543 $mimeType = $this->getMimeTypeOfFile($filePath);
545 'size' => filesize($filePath),
546 'atime' => $fileStat['atime'],
547 'mtime' => $fileStat['mtime'],
548 'ctime' => $fileStat['ctime'],
549 'nlink' => $fileStat['nlink'],
551 'mimetype' => $mimeType
557 * Get MIME type of file.
559 * @param string $absoluteFilePath Absolute path to file
560 * @return string|boolean MIME type. eg, text/html, FALSE on error
562 protected function getMimeTypeOfFile($absoluteFilePath) {
563 if (function_exists('finfo_file')) {
564 $fileInfo = new \finfo
();
565 return $fileInfo->file($absoluteFilePath, FILEINFO_MIME_TYPE
);
566 } elseif (function_exists('mime_content_type')) {
567 return mime_content_type($absoluteFilePath);
573 * Creates a (cryptographic) hash for a file.
575 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
576 * @param string $hashAlgorithm The hash algorithm to use
579 public function hash(\TYPO3\CMS\Core\
Resource\FileInterface
$file, $hashAlgorithm) {
580 if (!in_array($hashAlgorithm, $this->getSupportedHashAlgorithms())) {
581 throw new \
InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
583 switch ($hashAlgorithm) {
585 $hash = sha1_file($this->getAbsolutePath($file));
588 $hash = md5_file($this->getAbsolutePath($file));
591 throw new \
RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
597 * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
599 * This assumes that the local file exists, so no further check is done here!
601 * @param string $localFilePath
602 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
603 * @param string $fileName The name to add the file under
604 * @param \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject File object to update (instead of creating a new object). With this parameter, this function can be used to "populate" a dummy file object with a real file underneath.
605 * @todo \TYPO3\CMS\Core\Resource\File $updateFileObject should be \TYPO3\CMS\Core\Resource\FileInterface, but indexer logic is only in \TYPO3\CMS\Core\Resource\File
606 * @return \TYPO3\CMS\Core\Resource\FileInterface
608 public function addFile($localFilePath, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $fileName, \TYPO3\CMS\Core\
Resource\AbstractFile
$updateFileObject = NULL) {
609 // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
610 // thus, it is not checked here
611 if (\TYPO3\CMS\Core\Utility\GeneralUtility
::isFirstPartOfStr($localFilePath, $this->absoluteBasePath
) && $this->storage
->getUid() > 0) {
612 throw new \
InvalidArgumentException('Cannot add a file that is already part of this storage.', 1314778269);
614 $relativeTargetPath = ltrim($targetFolder->getIdentifier(), '/');
615 $relativeTargetPath .= $this->sanitizeFileName($fileName ?
$fileName : PathUtility
::basename($localFilePath));
616 $targetPath = $this->absoluteBasePath
. $relativeTargetPath;
617 if (is_uploaded_file($localFilePath)) {
618 $moveResult = move_uploaded_file($localFilePath, $targetPath);
620 $moveResult = rename($localFilePath, $targetPath);
622 if ($moveResult !== TRUE) {
623 throw new \
RuntimeException('Moving file ' . $localFilePath . ' to ' . $targetPath . ' failed.', 1314803096);
626 // Change the permissions of the file
627 \TYPO3\CMS\Core\Utility\GeneralUtility
::fixPermissions($targetPath);
628 $fileInfo = $this->getFileInfoByIdentifier($relativeTargetPath);
629 if ($updateFileObject) {
630 $updateFileObject->updateProperties($fileInfo);
631 return $updateFileObject;
633 $fileObject = $this->getFileObject($fileInfo);
639 * Checks if a resource exists - does not care for the type (file or folder).
644 public function resourceExists($identifier) {
645 $absoluteResourcePath = $this->absoluteBasePath
. ltrim($identifier, '/');
646 return file_exists($absoluteResourcePath);
650 * Checks if a file exists.
652 * @param string $identifier
655 public function fileExists($identifier) {
656 $absoluteFilePath = $this->absoluteBasePath
. ltrim($identifier, '/');
657 return is_file($absoluteFilePath);
661 * Checks if a file inside a storage folder exists
663 * @param string $fileName
664 * @param \TYPO3\CMS\Core\Resource\Folder $folder
667 public function fileExistsInFolder($fileName, \TYPO3\CMS\Core\
Resource\Folder
$folder) {
668 $identifier = ltrim($folder->getIdentifier(), '/') . $fileName;
669 return $this->fileExists($identifier);
673 * Checks if a folder exists.
675 * @param string $identifier
678 public function folderExists($identifier) {
679 $absoluteFilePath = $this->absoluteBasePath
. ltrim($identifier, '/');
680 return is_dir($absoluteFilePath);
684 * Checks if a file inside a storage folder exists.
686 * @param string $folderName
687 * @param \TYPO3\CMS\Core\Resource\Folder $folder
690 public function folderExistsInFolder($folderName, \TYPO3\CMS\Core\
Resource\Folder
$folder) {
691 $identifier = $folder->getIdentifier() . $folderName;
692 return $this->folderExists($identifier);
696 * Returns a folder within the given folder.
698 * @param string $name The name of the folder to get
699 * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
700 * @return \TYPO3\CMS\Core\Resource\Folder
702 public function getFolderInFolder($name, \TYPO3\CMS\Core\
Resource\Folder
$parentFolder) {
703 $folderIdentifier = $parentFolder->getIdentifier() . $name . '/';
704 return $this->getFolder($folderIdentifier);
708 * Replaces the contents (and file-specific metadata) of a file object with a local file.
710 * @param \TYPO3\CMS\Core\Resource\AbstractFile $file
711 * @param string $localFilePath
712 * @return boolean TRUE if the operation succeeded
714 public function replaceFile(\TYPO3\CMS\Core\
Resource\AbstractFile
$file, $localFilePath) {
715 $filePath = $this->getAbsolutePath($file);
716 $result = rename($localFilePath, $filePath);
717 if ($result === FALSE) {
718 throw new \
RuntimeException('Replacing file ' . $filePath . ' with ' . $localFilePath . ' failed.', 1315314711);
720 $fileInfo = $this->getFileInfoByIdentifier($file->getIdentifier());
721 $file->updateProperties($fileInfo);
727 * Adds a file at the specified location. This should only be used internally.
729 * @param string $localFilePath
730 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
731 * @param string $targetFileName
732 * @return boolean TRUE if adding the file succeeded
734 public function addFileRaw($localFilePath, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $targetFileName) {
735 $fileIdentifier = $targetFolder->getIdentifier() . $targetFileName;
736 $absoluteFilePath = $this->absoluteBasePath
. $fileIdentifier;
737 $result = copy($localFilePath, $absoluteFilePath);
738 if ($result === FALSE ||
!file_exists($absoluteFilePath)) {
739 throw new \
RuntimeException('Adding file ' . $localFilePath . ' at ' . $fileIdentifier . ' failed.');
741 return $fileIdentifier;
745 * Deletes a file without access and usage checks. This should only be used internally.
747 * This accepts an identifier instead of an object because we might want to delete files that have no object
748 * associated with (or we don't want to create an object for) them - e.g. when moving a file to another storage.
750 * @param string $identifier
751 * @return boolean TRUE if removing the file succeeded
753 public function deleteFileRaw($identifier) {
754 $targetPath = $this->absoluteBasePath
. ltrim($identifier, '/');
755 $result = unlink($targetPath);
756 if ($result === FALSE ||
file_exists($targetPath)) {
757 throw new \
RuntimeException('Deleting file ' . $identifier . ' failed.', 1320381534);
763 * Copies a file *within* the current storage.
764 * Note that this is only about an intra-storage move action, where a file is just
765 * moved to another folder in the same storage.
767 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
768 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
769 * @param string $fileName
770 * @return \TYPO3\CMS\Core\Resource\FileInterface The new (copied) file object.
772 public function copyFileWithinStorage(\TYPO3\CMS\Core\
Resource\FileInterface
$file, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $fileName) {
773 // TODO add unit test
774 $sourcePath = $this->getAbsolutePath($file);
775 $targetPath = ltrim($targetFolder->getIdentifier(), '/') . $fileName;
776 copy($sourcePath, $this->absoluteBasePath
. $targetPath);
777 return $this->getFile($targetPath);
781 * Moves a file *within* the current storage.
782 * Note that this is only about an intra-storage move action, where a file is just
783 * moved to another folder in the same storage.
785 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
786 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
787 * @param string $fileName
790 public function moveFileWithinStorage(\TYPO3\CMS\Core\
Resource\FileInterface
$file, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $fileName) {
791 $sourcePath = $this->getAbsolutePath($file);
792 $targetIdentifier = $targetFolder->getIdentifier() . $fileName;
793 $result = rename($sourcePath, $this->absoluteBasePath
. $targetIdentifier);
794 if ($result === FALSE) {
795 throw new \
RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
797 return $targetIdentifier;
801 * Copies a file to a temporary path and returns that path.
803 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
804 * @return string The temporary path
806 public function copyFileToTemporaryPath(\TYPO3\CMS\Core\
Resource\FileInterface
$file) {
807 $sourcePath = $this->getAbsolutePath($file);
808 $temporaryPath = $this->getTemporaryPathForFile($file);
809 $result = copy($sourcePath, $temporaryPath);
810 if ($result === FALSE) {
811 throw new \
RuntimeException('Copying file ' . $file->getIdentifier() . ' to temporary path failed.', 1320577649);
813 return $temporaryPath;
817 * Creates a map of old and new file/folder identifiers after renaming or
818 * moving a folder. The old identifier is used as the key, the new one as the value.
820 * @param array $filesAndFolders
821 * @param string $relativeSourcePath
822 * @param string $relativeTargetPath
825 protected function createIdentifierMap(array $filesAndFolders, $relativeSourcePath, $relativeTargetPath) {
826 $identifierMap = array();
827 $identifierMap[$relativeSourcePath] = $relativeTargetPath;
828 foreach ($filesAndFolders as $oldItem) {
829 $oldIdentifier = $relativeSourcePath . $oldItem['path'];
830 $newIdentifier = $relativeTargetPath . $oldItem['path'];
831 if (!$this->resourceExists($newIdentifier)) {
832 throw new \TYPO3\CMS\Core\
Resource\Exception\
FileOperationErrorException(sprintf('File "%1$s" was not found (should have been copied/moved from "%2$s").', $newIdentifier, $oldIdentifier), 1330119453);
834 $identifierMap[$oldIdentifier] = $newIdentifier;
836 return $identifierMap;
840 * Folder equivalent to moveFileWithinStorage().
842 * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
843 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
844 * @param string $newFolderName
845 * @return array A map of old to new file identifiers
847 public function moveFolderWithinStorage(\TYPO3\CMS\Core\
Resource\Folder
$folderToMove, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $newFolderName) {
848 $relativeSourcePath = $folderToMove->getIdentifier();
849 $sourcePath = $this->getAbsolutePath($relativeSourcePath);
850 $relativeTargetPath = $targetFolder->getIdentifier() . $newFolderName . '/';
851 $targetPath = $this->getAbsolutePath($relativeTargetPath);
852 // get all files and folders we are going to move, to have a map for updating later.
853 $filesAndFolders = $this->getFileAndFoldernamesInPath($sourcePath, TRUE);
854 $result = rename($sourcePath, $targetPath);
855 if ($result === FALSE) {
856 throw new \
RuntimeException('Moving folder ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320711817);
858 // Create a mapping from old to new identifiers
859 $identifierMap = $this->createIdentifierMap($filesAndFolders, $relativeSourcePath, $relativeTargetPath);
860 return $identifierMap;
864 * Folder equivalent to copyFileWithinStorage().
866 * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
867 * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
868 * @param string $newFolderName
869 * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
872 public function copyFolderWithinStorage(\TYPO3\CMS\Core\
Resource\Folder
$folderToCopy, \TYPO3\CMS\Core\
Resource\Folder
$targetFolder, $newFolderName) {
873 // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
874 // We can thus rely on this folder being present and just create the subfolder we want to copy to.
875 $targetFolderPath = $this->getAbsolutePath($targetFolder) . $newFolderName . '/';
876 mkdir($targetFolderPath);
877 $sourceFolderPath = $this->getAbsolutePath($folderToCopy);
878 /** @var $iterator \RecursiveDirectoryIterator */
879 $iterator = new \
RecursiveIteratorIterator(new \
RecursiveDirectoryIterator($sourceFolderPath), \RecursiveIteratorIterator
::SELF_FIRST
);
880 // Rewind the iterator as this is important for some systems e.g. Windows
882 while ($iterator->valid()) {
883 /** @var $current \RecursiveDirectoryIterator */
884 $current = $iterator->current();
885 $fileName = $current->getFilename();
886 $itemSubPath = GeneralUtility
::fixWindowsFilePath($iterator->getSubPathname());
887 if ($current->isDir() && !($fileName === '..' ||
$fileName === '.')) {
888 mkdir($targetFolderPath . $itemSubPath);
889 } elseif ($current->isFile()) {
890 $result = copy($sourceFolderPath . $itemSubPath, $targetFolderPath . $itemSubPath);
891 if ($result === FALSE) {
892 throw new \TYPO3\CMS\Core\
Resource\Exception\
FileOperationErrorException(
893 'Copying file "' . $sourceFolderPath . $itemSubPath . '" to "' . $targetFolderPath . $itemSubPath . '" failed.',
904 * Move a folder from another storage.
906 * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
907 * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
908 * @param string $newFolderName
911 public function moveFolderBetweenStorages(\TYPO3\CMS\Core\
Resource\Folder
$folderToMove, \TYPO3\CMS\Core\
Resource\Folder
$targetParentFolder, $newFolderName) {
912 // TODO implement a clever shortcut here if both storages are of type local
913 return parent
::moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
917 * Copy a folder from another storage.
919 * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
920 * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
921 * @param string $newFolderName
924 public function copyFolderBetweenStorages(\TYPO3\CMS\Core\
Resource\Folder
$folderToCopy, \TYPO3\CMS\Core\
Resource\Folder
$targetParentFolder, $newFolderName) {
925 // TODO implement a clever shortcut here if both storages are of type local
926 return parent
::copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
930 * Renames a file in this storage.
932 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
933 * @param string $newName The target path (including the file name!)
934 * @return string The identifier of the file after renaming
936 public function renameFile(\TYPO3\CMS\Core\
Resource\FileInterface
$file, $newName) {
937 // Makes sure the Path given as parameter is valid
938 $newName = $this->sanitizeFileName($newName);
939 $newIdentifier = rtrim(GeneralUtility
::fixWindowsFilePath(PathUtility
::dirname($file->getIdentifier())), '/') . '/' . $newName;
940 // The target should not exist already
941 if ($this->fileExists($newIdentifier)) {
942 throw new \TYPO3\CMS\Core\
Resource\Exception\
ExistingTargetFileNameException('The target file already exists.', 1320291063);
944 $sourcePath = $this->getAbsolutePath($file);
945 $targetPath = $this->absoluteBasePath
. '/' . ltrim($newIdentifier, '/');
946 $result = rename($sourcePath, $targetPath);
947 if ($result === FALSE) {
948 throw new \
RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
950 return $newIdentifier;
955 * Renames a folder in this storage.
957 * @param \TYPO3\CMS\Core\Resource\Folder $folder
958 * @param string $newName The target path (including the file name!)
959 * @return array A map of old to new file identifiers
960 * @throws \RuntimeException if renaming the folder failed
962 public function renameFolder(\TYPO3\CMS\Core\
Resource\Folder
$folder, $newName) {
963 // Makes sure the path given as parameter is valid
964 $newName = $this->sanitizeFileName($newName);
965 $relativeSourcePath = $folder->getIdentifier();
966 $sourcePath = $this->getAbsolutePath($relativeSourcePath);
967 $relativeTargetPath = rtrim(GeneralUtility
::fixWindowsFilePath(PathUtility
::dirname($relativeSourcePath)), '/') . '/' . $newName . '/';
968 $targetPath = $this->getAbsolutePath($relativeTargetPath);
969 // get all files and folders we are going to move, to have a map for updating later.
970 $filesAndFolders = $this->getFileAndFoldernamesInPath($sourcePath, TRUE);
971 $result = rename($sourcePath, $targetPath);
972 if ($result === FALSE) {
973 throw new \
RuntimeException(sprintf('Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
976 // Create a mapping from old to new identifiers
977 $identifierMap = $this->createIdentifierMap($filesAndFolders, $relativeSourcePath, $relativeTargetPath);
978 } catch (\Exception
$e) {
979 rename($targetPath, $sourcePath);
980 throw new \
RuntimeException(sprintf('Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"', $sourcePath, $targetPath, $e->getMessage()), 1334160746);
982 return $identifierMap;
986 * Removes a file from this storage.
988 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
989 * @return boolean TRUE if deleting the file succeeded
990 * @throws \RuntimeException
992 public function deleteFile(\TYPO3\CMS\Core\
Resource\FileInterface
$file) {
993 $filePath = $this->getAbsolutePath($file);
994 $result = unlink($filePath);
995 if ($result === FALSE) {
996 throw new \
RuntimeException('Deletion of file ' . $file->getIdentifier() . ' failed.', 1320855304);
1002 * Removes a folder from this storage.
1004 * @param \TYPO3\CMS\Core\Resource\Folder $folder
1005 * @param bool $deleteRecursively
1008 public function deleteFolder(\TYPO3\CMS\Core\
Resource\Folder
$folder, $deleteRecursively = FALSE) {
1009 $folderPath = $this->getAbsolutePath($folder);
1010 $result = \TYPO3\CMS\Core\Utility\GeneralUtility
::rmdir($folderPath, $deleteRecursively);
1011 if ($result === FALSE) {
1012 throw new \TYPO3\CMS\Core\
Resource\Exception\
FileOperationErrorException('Deleting folder "' . $folder->getIdentifier() . '" failed.', 1330119451);
1018 * Checks if a folder contains files and (if supported) other folders.
1020 * @param \TYPO3\CMS\Core\Resource\Folder $folder
1021 * @return boolean TRUE if there are no files and folders within $folder
1023 public function isFolderEmpty(\TYPO3\CMS\Core\
Resource\Folder
$folder) {
1024 $path = $this->getAbsolutePath($folder);
1025 $dirHandle = opendir($path);
1026 while ($entry = readdir($dirHandle)) {
1027 if ($entry !== '.' && $entry !== '..') {
1028 closedir($dirHandle);
1036 * Returns a (local copy of) a file for processing it. This makes a copy
1037 * first when in writable mode, so if you change the file,
1038 * you have to update it yourself afterwards.
1040 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
1041 * @param boolean $writable Set this to FALSE if you only need the file for read operations. This might speed up things, e.g. by using a cached local version. Never modify the file if you have set this flag!
1042 * @return string The path to the file on the local disk
1044 public function getFileForLocalProcessing(\TYPO3\CMS\Core\
Resource\FileInterface
$file, $writable = TRUE) {
1045 if ($writable === FALSE) {
1046 // TODO check if this is ok or introduce additional measures against file changes
1047 return $this->getAbsolutePath($file);
1049 // TODO check if this might also serve as a dump basic implementation in the abstract driver.
1050 return $this->copyFileToTemporaryPath($file);
1055 * Returns the permissions of a file as an array (keys r, w) of boolean flags
1057 * @param \TYPO3\CMS\Core\Resource\FileInterface $file The file object to check
1059 * @throws \RuntimeException If fetching the permissions failed
1061 public function getFilePermissions(\TYPO3\CMS\Core\
Resource\FileInterface
$file) {
1062 $filePath = $this->getAbsolutePath($file);
1063 return $this->getPermissions($filePath);
1067 * Returns the permissions of a folder as an array (keys r, w) of boolean flags
1069 * @param \TYPO3\CMS\Core\Resource\Folder $folder
1071 * @throws \RuntimeException If fetching the permissions failed
1073 public function getFolderPermissions(\TYPO3\CMS\Core\
Resource\Folder
$folder) {
1074 $folderPath = $this->getAbsolutePath($folder);
1075 return $this->getPermissions($folderPath);
1079 * Helper function to unify access to permission information
1081 * @param string $path
1083 * @throws \RuntimeException If fetching the permissions failed
1085 protected function getPermissions($path) {
1086 $permissionBits = fileperms($path);
1087 if ($permissionBits === FALSE) {
1088 throw new \
RuntimeException('Error while fetching permissions for ' . $path, 1319455097);
1091 'r' => (bool) is_readable($path),
1092 'w' => (bool) is_writable($path)
1097 * Checks if a given object or identifier is within a container, e.g. if
1098 * a file or folder is within another folder.
1099 * This can e.g. be used to check for webmounts.
1101 * @param \TYPO3\CMS\Core\Resource\Folder $container
1102 * @param mixed $content An object or an identifier to check
1103 * @return boolean TRUE if $content is within $container, always FALSE if $container is not within this storage
1105 public function isWithin(\TYPO3\CMS\Core\
Resource\Folder
$container, $content) {
1106 if ($container->getStorage() != $this->storage
) {
1109 if ($content instanceof \TYPO3\CMS\Core\
Resource\FileInterface ||
$content instanceof \TYPO3\CMS\Core\
Resource\Folder
) {
1110 $content = $container->getIdentifier();
1112 $folderPath = $container->getIdentifier();
1113 $content = '/' . ltrim($content, '/');
1114 return \TYPO3\CMS\Core\Utility\GeneralUtility
::isFirstPartOfStr($content, $folderPath);
1118 * Creates a new file and returns the matching file object for it.
1120 * @param string $fileName
1121 * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
1122 * @return \TYPO3\CMS\Core\Resource\File
1124 public function createFile($fileName, \TYPO3\CMS\Core\
Resource\Folder
$parentFolder) {
1125 if (!$this->isValidFilename($fileName)) {
1126 throw new \TYPO3\CMS\Core\
Resource\Exception\
InvalidFileNameException('Invalid characters in fileName "' . $fileName . '"', 1320572272);
1128 $filePath = $parentFolder->getIdentifier() . $this->sanitizeFileName(ltrim($fileName, '/'));
1129 $result = touch($this->absoluteBasePath
. $filePath);
1130 GeneralUtility
::fixPermissions($this->absoluteBasePath
. ltrim($filePath, '/'));
1132 if ($result !== TRUE) {
1133 throw new \
RuntimeException('Creating file ' . $filePath . ' failed.', 1320569854);
1135 $fileInfo = $this->getFileInfoByIdentifier($filePath);
1136 return $this->getFileObject($fileInfo);
1140 * Returns the contents of a file. Beware that this requires to load the
1141 * complete file into memory and also may require fetching the file from an
1142 * external location. So this might be an expensive operation (both in terms of
1143 * processing resources and money) for large files.
1145 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
1146 * @return string The file contents
1148 public function getFileContents(\TYPO3\CMS\Core\
Resource\FileInterface
$file) {
1149 $filePath = $this->getAbsolutePath($file);
1150 return file_get_contents($filePath);
1154 * Sets the contents of a file to the specified value.
1156 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
1157 * @param string $contents
1158 * @return integer The number of bytes written to the file
1159 * @throws \RuntimeException if the operation failed
1161 public function setFileContents(\TYPO3\CMS\Core\
Resource\FileInterface
$file, $contents) {
1162 $filePath = $this->getAbsolutePath($file);
1163 $result = file_put_contents($filePath, $contents);
1164 if ($result === FALSE) {
1165 throw new \
RuntimeException('Setting contents of file "' . $file->getIdentifier() . '" failed.', 1325419305);
1171 * Gets the charset conversion object.
1173 * @return \TYPO3\CMS\Core\Charset\CharsetConverter
1175 protected function getCharsetConversion() {
1176 if (!isset($this->charsetConversion
)) {
1177 if (TYPO3_MODE
=== 'FE') {
1178 $this->charsetConversion
= $GLOBALS['TSFE']->csConvObj
;
1179 } elseif (is_object($GLOBALS['LANG'])) {
1181 $this->charsetConversion
= $GLOBALS['LANG']->csConvObj
;
1183 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
1184 $this->charsetConversion
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
1187 return $this->charsetConversion
;
1192 * Returns the role of an item (currently only folders; can later be extended for files as well)
1194 * @param \TYPO3\CMS\Core\Resource\ResourceInterface $item
1197 public function getRole(\TYPO3\CMS\Core\
Resource\ResourceInterface
$item) {
1198 $role = $this->mappingFolderNameToRole
[$item->getName()];
1200 $role = FolderInterface
::ROLE_DEFAULT
;