e6a39552ec919e9191193bbfbc7b65ef8b67114c
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Driver / LocalDriver.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Driver;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011-2013 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * (c) 2013 Stefan Neufeind <info (at) speedpartner.de>
9 * (c) 2013 Steffen Ritter <steffen.ritter@typo3.org>
10 * All rights reserved
11 *
12 * This script is part of the TYPO3 project. The TYPO3 project is
13 * free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * The GNU General Public License can be found at
19 * http://www.gnu.org/copyleft/gpl.html.
20 * A copy is found in the text file GPL.txt and important notices to the license
21 * from the author is found in LICENSE.txt distributed with these scripts.
22 *
23 *
24 * This script is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31
32 use TYPO3\CMS\Core\Resource\FolderInterface;
33 use TYPO3\CMS\Core\Utility\GeneralUtility;
34 use TYPO3\CMS\Core\Utility\PathUtility;
35
36 /**
37 * Driver for the local file system
38 *
39 */
40 class LocalDriver extends AbstractHierarchicalFilesystemDriver {
41
42 /**
43 * @var string
44 */
45 const UNSAFE_FILENAME_CHARACTER_EXPRESSION = '\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF';
46
47 /**
48 * The absolute base path. It always contains a trailing slash.
49 *
50 * @var string
51 */
52 protected $absoluteBasePath;
53
54 /**
55 * A list of all supported hash algorithms, written all lower case.
56 *
57 * @var array
58 */
59 protected $supportedHashAlgorithms = array('sha1', 'md5');
60
61 /**
62 * The base URL that points to this driver's storage. As long is this
63 * is not set, it is assumed that this folder is not publicly available
64 *
65 * @var string
66 */
67 protected $baseUri = NULL;
68
69 /**
70 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
71 */
72 protected $charsetConversion;
73
74 /** @var array */
75 protected $mappingFolderNameToRole = array(
76 '_recycler_' => FolderInterface::ROLE_RECYCLER,
77 '_temp_' => FolderInterface::ROLE_TEMPORARY,
78 'user_upload' => FolderInterface::ROLE_USERUPLOAD,
79 );
80
81 /**
82 * @param array $configuration
83 */
84 public function __construct(array $configuration = array()) {
85 parent::__construct($configuration);
86 // The capabilities default of this driver. See CAPABILITY_* constants for possible values
87 $this->capabilities =
88 \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_BROWSABLE
89 | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC
90 | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_WRITABLE;
91 }
92
93 /**
94 * Merges the capabilites merged by the user at the storage
95 * configuration into the actual capabilities of the driver
96 * and returns the result.
97 *
98 * @param integer $capabilities
99 *
100 * @return integer
101 */
102 public function mergeConfigurationCapabilities($capabilities) {
103 $this->capabilities &= $capabilities;
104 return $this->capabilities;
105 }
106
107
108 /**
109 * Processes the configuration for this driver.
110 *
111 * @return void
112 */
113 public function processConfiguration() {
114 $this->absoluteBasePath = $this->calculateBasePath($this->configuration);
115 $this->determineBaseUrl();
116 if ($this->baseUri === NULL) {
117 // remove public flag
118 $this->capabilities &= ~\TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC;
119 }
120 }
121
122 /**
123 * Initializes this object. This is called by the storage after the driver
124 * has been attached.
125 *
126 * @return void
127 */
128 public function initialize() {
129 }
130
131 /**
132 * Determines the base URL for this driver, from the configuration or
133 * the TypoScript frontend object
134 *
135 * @return void
136 */
137 protected function determineBaseUrl() {
138 // only calculate baseURI if the storage does not enforce jumpUrl Script
139 if ($this->hasCapability(\TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC)) {
140 if (GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
141 // use site-relative URLs
142 $temporaryBaseUri = rtrim(\TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($this->absoluteBasePath), '/');
143 if ($temporaryBaseUri !== '') {
144 $uriParts = explode('/', $temporaryBaseUri);
145 array_map('rawurlencode', $uriParts);
146 $temporaryBaseUri = implode('/', $uriParts) . '/';
147 }
148 $this->baseUri = $temporaryBaseUri;
149 } elseif (isset($this->configuration['baseUri']) && GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
150 $this->baseUri = rtrim($this->configuration['baseUri'], '/') . '/';
151 }
152 }
153 }
154
155 /**
156 * Calculates the absolute path to this drivers storage location.
157 *
158 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException
159 * @param array $configuration
160 * @return string
161 */
162 protected function calculateBasePath(array $configuration) {
163 if (!array_key_exists('basePath', $configuration) || empty($configuration['basePath'])) {
164 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException(
165 'Configuration must contain base path.',
166 1346510477
167 );
168 }
169
170 if ($configuration['pathType'] === 'relative') {
171 $relativeBasePath = $configuration['basePath'];
172 $absoluteBasePath = PATH_site . $relativeBasePath;
173 } else {
174 $absoluteBasePath = $configuration['basePath'];
175 }
176 $this->canonicalizeAndCheckFilePath($absoluteBasePath);
177 $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
178 if (!is_dir($absoluteBasePath)) {
179 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException(
180 'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
181 1299233097
182 );
183 }
184 return $absoluteBasePath;
185 }
186
187 /**
188 * Returns the public URL to a file.
189 * For the local driver, this will always return a path relative to PATH_site.
190 *
191 * @param string $identifier
192 * @return string
193 * @throws \TYPO3\CMS\Core\Resource\Exception
194 */
195 public function getPublicUrl($identifier) {
196 $publicUrl = NULL;
197 if ($this->baseUri !== NULL) {
198 $uriParts = explode('/', ltrim($identifier, '/'));
199 $uriParts = array_map('rawurlencode', $uriParts);
200 $identifier = implode('/', $uriParts);
201 $publicUrl = $this->baseUri . $identifier;
202 }
203 return $publicUrl;
204 }
205
206 /**
207 * Returns the Identifier of the root level folder of the storage.
208 *
209 * @return string
210 */
211 public function getRootLevelFolder() {
212 return '/';
213 }
214
215 /**
216 * Returns identifier of the default folder new files should be put into.
217 *
218 * @return string
219 */
220 public function getDefaultFolder() {
221 $identifier = '/user_upload/';
222 $createFolder = !$this->folderExists($identifier);
223 if ($createFolder === TRUE) {
224 $identifier = $this->createFolder('user_upload');
225 }
226 return $identifier;
227 }
228
229 /**
230 * Creates a folder, within a parent folder.
231 * If no parent folder is given, a rootlevel folder will be created
232 *
233 * @param string $newFolderName
234 * @param string $parentFolderIdentifier
235 * @param boolean $recursive
236 * @return string the Identifier of the new folder
237 */
238 public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = FALSE) {
239 $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
240 $newFolderName = trim($newFolderName, '/');
241 if ($recursive == FALSE) {
242 $newFolderName = $this->sanitizeFileName($newFolderName);
243 $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
244 GeneralUtility::mkdir($this->getAbsoluteBasePath() . $newIdentifier);
245 } else {
246 $parts = GeneralUtility::trimExplode('/', $newFolderName);
247 array_map(array($this, 'sanitizeFileName'), $parts);
248 $newFolderName = implode('/', $parts);
249 $newIdentifier = $parentFolderIdentifier . $newFolderName . '/';
250 GeneralUtility::mkdir_deep($this->getAbsoluteBasePath() . $parentFolderIdentifier, $newFolderName);
251 }
252 return $newIdentifier;
253 }
254
255 /**
256 * Returns information about a file.
257 *
258 * @param string $fileIdentifier In the case of the LocalDriver, this is the (relative) path to the file.
259 * @param array $propertiesToExtract Array of properties which should be extracted, if empty all will be extracted
260 * @return array
261 * @throws \InvalidArgumentException
262 */
263 public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = array()) {
264 $dirPath = PathUtility::dirname($fileIdentifier);
265 $dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath);
266
267 $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
268 // don't use $this->fileExists() because we need the absolute path to the file anyways, so we can directly
269 // use PHP's filesystem method.
270 if (!file_exists($absoluteFilePath)) {
271 throw new \InvalidArgumentException('File ' . $fileIdentifier . ' does not exist.', 1314516809);
272 }
273 return $this->extractFileInformation($absoluteFilePath, $dirPath, $propertiesToExtract);
274 }
275
276 /**
277 * Returns information about a folder.
278 *
279 * @param string $folderIdentifier In the case of the LocalDriver, this is the (relative) path to the file.
280 * @return array
281 * @throws \TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
282 */
283 public function getFolderInfoByIdentifier($folderIdentifier) {
284 $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
285
286 if (!$this->folderExists($folderIdentifier)) {
287 throw new \TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException(
288 'File ' . $folderIdentifier . ' does not exist.',
289 1314516810
290 );
291 }
292 return array(
293 'identifier' => $folderIdentifier,
294 'name' => PathUtility::basename($folderIdentifier),
295 'storage' => $this->storageUid
296 );
297 }
298
299 /**
300 * Returns a string where any character not matching [.a-zA-Z0-9_-] is
301 * substituted by '_'
302 * Trailing dots are removed
303 *
304 * Previously in \TYPO3\CMS\Core\Utility\File\BasicFileUtility::cleanFileName()
305 *
306 * @param string $fileName Input string, typically the body of a fileName
307 * @param string $charset Charset of the a fileName (defaults to current charset; depending on context)
308 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_' and trailing dots removed
309 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException
310 */
311 public function sanitizeFileName($fileName, $charset = '') {
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 = preg_replace('/\\.*$/', '', $cleanFileName);
335 if (!$cleanFileName) {
336 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException(
337 'File name ' . $cleanFileName . ' is invalid.',
338 1320288991
339 );
340 }
341 return $cleanFileName;
342 }
343
344 /**
345 * Generic wrapper for extracting a list of items from a path.
346 *
347 * @param string $folderIdentifier
348 * @param integer $start The position to start the listing; if not set, start from the beginning
349 * @param integer $numberOfItems The number of items to list; if set to zero, all items are returned
350 * @param array $filterMethods The filter methods used to filter the directory items
351 * @param boolean $includeFiles
352 * @param boolean $includeDirs
353 * @param boolean $recursive
354 *
355 * @return array
356 * @throws \InvalidArgumentException
357 */
358 protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = TRUE, $includeDirs = TRUE, $recursive = FALSE) {
359 $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
360 $realPath = $this->getAbsolutePath($folderIdentifier);
361 if (!is_dir($realPath)) {
362 throw new \InvalidArgumentException(
363 'Cannot list items in directory ' . $folderIdentifier . ' - does not exist or is no directory',
364 1314349666
365 );
366 }
367
368 if ($start > 0) {
369 $start--;
370 }
371
372 // Fetch the files and folders and sort them by name; we have to do
373 // this here because the directory iterator does return them in
374 // an arbitrary order
375 $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs);
376 uksort(
377 $items,
378 array('\\TYPO3\\CMS\\Core\\Utility\\ResourceUtility', 'recursiveFileListSortingHelper')
379 );
380
381 $iterator = new \ArrayIterator($items);
382 if ($iterator->count() === 0) {
383 return array();
384 }
385 $iterator->seek($start);
386
387 // $c is the counter for how many items we still have to fetch (-1 is unlimited)
388 $c = $numberOfItems > 0 ? $numberOfItems : - 1;
389 $items = array();
390 while ($iterator->valid() && ($numberOfItems === 0 || $c > 0)) {
391 // $iteratorItem is the file or folder name
392 $iteratorItem = $iterator->current();
393 // go on to the next iterator item now as we might skip this one early
394 $iterator->next();
395
396 if (
397 !$this->applyFilterMethodsToDirectoryItem(
398 $filterMethods,
399 $iteratorItem['name'],
400 $iteratorItem['identifier'],
401 $this->getParentFolderIdentifierOfIdentifier($iteratorItem['identifier'])
402 )
403 ) {
404 continue;
405 }
406
407
408 $items[$iteratorItem['identifier']] = $iteratorItem['identifier'];
409 // Decrement item counter to make sure we only return $numberOfItems
410 // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
411 // item here
412 --$c;
413 }
414 return $items;
415 }
416
417 /**
418 * Applies a set of filter methods to a file name to find out if it should be used or not. This is e.g. used by
419 * directory listings.
420 *
421 * @param array $filterMethods The filter methods to use
422 * @param string $itemName
423 * @param string $itemIdentifier
424 * @param string $parentIdentifier
425 * @throws \RuntimeException
426 * @return boolean
427 */
428 protected function applyFilterMethodsToDirectoryItem(array $filterMethods, $itemName, $itemIdentifier, $parentIdentifier) {
429 foreach ($filterMethods as $filter) {
430 if (is_array($filter)) {
431 $result = call_user_func($filter, $itemName, $itemIdentifier, $parentIdentifier, array(), $this);
432 // We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
433 // If calling the method succeeded and thus we can't use that as a return value.
434 if ($result === -1) {
435 return FALSE;
436 } elseif ($result === FALSE) {
437 throw new \RuntimeException('Could not apply file/folder name filter ' . $filter[0] . '::' . $filter[1]);
438 }
439 }
440 }
441 return TRUE;
442 }
443
444 /**
445 * Returns a list of files inside the specified path
446 *
447 * @param string $folderIdentifier
448 * @param integer $start
449 * @param integer $numberOfItems
450 * @param boolean $recursive
451 * @param array $filenameFilterCallbacks The method callbacks to use for filtering the items
452 *
453 * @return array of FileIdentifiers
454 */
455 public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $filenameFilterCallbacks = array()) {
456 return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks, TRUE, FALSE, $recursive);
457 }
458
459 /**
460 * Returns a list of folders inside the specified path
461 *
462 * @param string $folderIdentifier
463 * @param integer $start
464 * @param integer $numberOfItems
465 * @param boolean $recursive
466 * @param array $folderNameFilterCallbacks The method callbacks to use for filtering the items
467 *
468 * @return array of Folder Identifier
469 */
470 public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $folderNameFilterCallbacks = array()) {
471 return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks, FALSE, TRUE, $recursive);
472 }
473
474 /**
475 * Returns a list with the names of all files and folders in a path, optionally recursive.
476 *
477 * @param string $path The absolute path
478 * @param boolean $recursive If TRUE, recursively fetches files and folders
479 * @param boolean $includeFiles
480 * @param boolean $includeDirs
481 * @return array
482 */
483 protected function retrieveFileAndFoldersInPath($path, $recursive = FALSE, $includeFiles = TRUE, $includeDirs = TRUE) {
484 $pathLength = strlen($this->getAbsoluteBasePath());
485 $iteratorMode = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO;
486 if ($recursive) {
487 $iterator = new \RecursiveIteratorIterator(
488 new \RecursiveDirectoryIterator($path, $iteratorMode),
489 \RecursiveIteratorIterator::SELF_FIRST
490 );
491 } else {
492 $iterator = new \RecursiveDirectoryIterator($path, $iteratorMode);
493 }
494
495 $directoryEntries = array();
496 while ($iterator->valid()) {
497 /** @var $entry \SplFileInfo */
498 $entry = $iterator->current();
499 // skip non-files/non-folders, and empty entries
500 if ((!$entry->isFile() && !$entry->isDir()) || $entry->getFilename() == '' ||
501 ($entry->isFile() && !$includeFiles) || ($entry->isDir() && !$includeDirs)) {
502 $iterator->next();
503 continue;
504 }
505 $entryIdentifier = '/' . substr($entry->getPathname(), $pathLength);
506 $entryName = PathUtility::basename($entryIdentifier);
507 if ($entry->isDir()) {
508 $entryIdentifier .= '/';
509 }
510 $entryArray = array(
511 'identifier' => $entryIdentifier,
512 'name' => $entryName,
513 'type' => $entry->isDir() ? 'dir' : 'file'
514 );
515 $directoryEntries[$entryIdentifier] = $entryArray;
516 $iterator->next();
517 }
518 return $directoryEntries;
519 }
520
521 /**
522 * Extracts information about a file from the filesystem.
523 *
524 * @param string $filePath The absolute path to the file
525 * @param string $containerPath The relative path to the file's container
526 * @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted
527 * @return array
528 */
529 protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array()) {
530 if (count($propertiesToExtract) === 0) {
531 $propertiesToExtract = array(
532 'size', 'atime', 'atime', 'mtime', 'ctime', 'mimetype', 'name',
533 'identifier', 'identifier_hash', 'storage', 'folder_hash'
534 );
535 }
536 $fileInformation = array();
537 foreach ($propertiesToExtract as $property) {
538 $fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property);
539 }
540 return $fileInformation;
541 }
542
543
544 /**
545 * Extracts a specific FileInformation from the FileSystems.
546 *
547 * @param string $fileIdentifier
548 * @param string $containerPath
549 * @param string $property
550 *
551 * @return bool|int|string
552 * @throws \InvalidArgumentException
553 */
554 public function getSpecificFileInformation($fileIdentifier, $containerPath, $property) {
555 $identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier));
556
557 switch ($property) {
558 case 'size':
559 return filesize($fileIdentifier);
560 case 'atime':
561 return fileatime($fileIdentifier);
562 case 'mtime':
563 return filemtime($fileIdentifier);
564 case 'ctime':
565 return filectime($fileIdentifier);
566 case 'name':
567 return PathUtility::basename($fileIdentifier);
568 case 'mimetype':
569 return $this->getMimeTypeOfFile($fileIdentifier);
570 case 'identifier':
571 return $identifier;
572 case 'storage':
573 return $this->storageUid;
574 case 'identifier_hash':
575 return $this->hashIdentifier($identifier);
576 case 'folder_hash':
577 return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier));
578 default:
579 throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
580 }
581 return NULL;
582 }
583
584 /**
585 * Returns the absolute path of the folder this driver operates on.
586 *
587 * @return string
588 */
589 protected function getAbsoluteBasePath() {
590 return $this->absoluteBasePath;
591 }
592
593 /**
594 * Returns the absolute path of a file or folder.
595 *
596 * @param string $fileIdentifier
597 * @return string
598 */
599 protected function getAbsolutePath($fileIdentifier) {
600 $relativeFilePath = ltrim($this->canonicalizeAndCheckFileIdentifier($fileIdentifier), '/');
601 $path = $this->absoluteBasePath . $relativeFilePath;
602 return $path;
603 }
604
605 /**
606 * Get MIME type of file.
607 *
608 * @param string $absoluteFilePath Absolute path to file
609 * @return string|boolean MIME type. eg, text/html, FALSE on error
610 */
611 protected function getMimeTypeOfFile($absoluteFilePath) {
612 if (function_exists('finfo_file')) {
613 $fileInfo = new \finfo();
614 return $fileInfo->file($absoluteFilePath, FILEINFO_MIME_TYPE);
615 } elseif (function_exists('mime_content_type')) {
616 return mime_content_type($absoluteFilePath);
617 }
618 return FALSE;
619 }
620
621 /**
622 * Creates a (cryptographic) hash for a file.
623 *
624 * @param string $fileIdentifier
625 * @param string $hashAlgorithm The hash algorithm to use
626 * @return string
627 * @throws \RuntimeException
628 * @throws \InvalidArgumentException
629 */
630 public function hash($fileIdentifier, $hashAlgorithm) {
631 if (!in_array($hashAlgorithm, $this->supportedHashAlgorithms)) {
632 throw new \InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
633 }
634 switch ($hashAlgorithm) {
635 case 'sha1':
636 $hash = sha1_file($this->getAbsolutePath($fileIdentifier));
637 break;
638 case 'md5':
639 $hash = md5_file($this->getAbsolutePath($fileIdentifier));
640 break;
641 default:
642 throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451);
643 }
644 return $hash;
645 }
646
647 /**
648 * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
649 * This assumes that the local file exists, so no further check is done here!
650 * After a successful the original file must not exist anymore.
651 *
652 * @param string $localFilePath (within PATH_site)
653 * @param string $targetFolderIdentifier
654 * @param string $newFileName optional, if not given original name is used
655 * @param boolean $removeOriginal if set the original file will be removed after successful operation
656 * @return string the identifier of the new file
657 * @throws \RuntimeException
658 * @throws \InvalidArgumentException
659 */
660 public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = TRUE) {
661 $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
662 // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
663 // thus, it is not checked here
664 // @ todo is check in storage
665 if (GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storageUid > 0) {
666 throw new \InvalidArgumentException('Cannot add a file that is already part of this storage.', 1314778269);
667 }
668 $newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath));
669 $newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName;
670 $targetPath = $this->absoluteBasePath . $newFileIdentifier;
671
672 if ($removeOriginal) {
673 if (is_uploaded_file($localFilePath)) {
674 $result = move_uploaded_file($localFilePath, $targetPath);
675 } else {
676 $result = rename($localFilePath, $targetPath);
677 }
678 } else {
679 $result = copy($localFilePath, $targetPath);
680 }
681 if ($result === FALSE || !file_exists($targetPath)) {
682 throw new \RuntimeException('Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.');
683 }
684 clearstatcache();
685 // Change the permissions of the file
686 GeneralUtility::fixPermissions($targetPath);
687 return $newFileIdentifier;
688 }
689
690 /**
691 * Checks if a file exists.
692 *
693 * @param string $fileIdentifier
694 *
695 * @return boolean
696 */
697 public function fileExists($fileIdentifier) {
698 $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
699 return is_file($absoluteFilePath);
700 }
701
702 /**
703 * Checks if a file inside a folder exists
704 *
705 * @param string $fileName
706 * @param string $folderIdentifier
707 * @return boolean
708 */
709 public function fileExistsInFolder($fileName, $folderIdentifier) {
710 $identifier = $folderIdentifier . '/' . $fileName;
711 $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
712 return $this->fileExists($identifier);
713 }
714
715 /**
716 * Checks if a folder exists.
717 *
718 * @param string $folderIdentifier
719 *
720 * @return boolean
721 */
722 public function folderExists($folderIdentifier) {
723 $absoluteFilePath = $this->getAbsolutePath($folderIdentifier);
724 return is_dir($absoluteFilePath);
725 }
726
727 /**
728 * Checks if a folder inside a folder exists.
729 *
730 * @param string $folderName
731 * @param string $folderIdentifier
732 * @return boolean
733 */
734 public function folderExistsInFolder($folderName, $folderIdentifier) {
735 $identifier = $folderIdentifier . '/' . $folderName;
736 $identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier);
737 return $this->folderExists($identifier);
738 }
739
740 /**
741 * Returns the Identifier for a folder within a given folder.
742 *
743 * @param string $folderName The name of the target folder
744 * @param string $folderIdentifier
745 *
746 * @return string
747 */
748 public function getFolderInFolder($folderName, $folderIdentifier) {
749 $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
750 return $folderIdentifier;
751 }
752
753 /**
754 * Replaces the contents (and file-specific metadata) of a file object with a local file.
755 *
756 * @param string $fileIdentifier
757 * @param string $localFilePath
758 * @return boolean TRUE if the operation succeeded
759 * @throws \RuntimeException
760 */
761 public function replaceFile($fileIdentifier, $localFilePath) {
762 $filePath = $this->getAbsolutePath($fileIdentifier);
763 $result = rename($localFilePath, $filePath);
764 if ($result === FALSE) {
765 throw new \RuntimeException('Replacing file ' . $fileIdentifier . ' with ' . $localFilePath . ' failed.', 1315314711);
766 }
767 return $result;
768 }
769
770 /**
771 * Copies a file *within* the current storage.
772 * Note that this is only about an intra-storage copy action, where a file is just
773 * copied to another folder in the same storage.
774 *
775 * @param string $fileIdentifier
776 * @param string $targetFolderIdentifier
777 * @param string $fileName
778 * @return string the Identifier of the new file
779 */
780 public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName) {
781 $sourcePath = $this->getAbsolutePath($fileIdentifier);
782 $newIdentifier = $targetFolderIdentifier . '/' . $fileName;
783 $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
784
785 copy($sourcePath, $this->absoluteBasePath . $newIdentifier);
786 GeneralUtility::fixPermissions($this->absoluteBasePath . $newIdentifier);
787 return $newIdentifier;
788 }
789
790 /**
791 * Moves a file *within* the current storage.
792 * Note that this is only about an inner-storage move action, where a file is just
793 * moved to another folder in the same storage.
794 *
795 * @param string $fileIdentifier
796 * @param string $targetFolderIdentifier
797 * @param string $newFileName
798 *
799 * @return string
800 * @throws \RuntimeException
801 */
802 public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName) {
803 $sourcePath = $this->getAbsolutePath($fileIdentifier);
804 $targetIdentifier = $targetFolderIdentifier . '/' . $newFileName;
805 $targetIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetIdentifier);
806 $result = rename($sourcePath, $this->getAbsolutePath($targetIdentifier));
807 if ($result === FALSE) {
808 throw new \RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
809 }
810 return $targetIdentifier;
811 }
812
813 /**
814 * Copies a file to a temporary path and returns that path.
815 *
816 * @param string $fileIdentifier
817 * @return string The temporary path
818 * @throws \RuntimeException
819 */
820 protected function copyFileToTemporaryPath($fileIdentifier) {
821 $sourcePath = $this->getAbsolutePath($fileIdentifier);
822 $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
823 $result = copy($sourcePath, $temporaryPath);
824 touch($temporaryPath, filemtime($sourcePath));
825 if ($result === FALSE) {
826 throw new \RuntimeException('Copying file ' . $fileIdentifier . ' to temporary path failed.', 1320577649);
827 }
828 return $temporaryPath;
829 }
830
831 /**
832 * Creates a map of old and new file/folder identifiers after renaming or
833 * moving a folder. The old identifier is used as the key, the new one as the value.
834 *
835 * @param array $filesAndFolders
836 * @param string $sourceFolderIdentifier
837 * @param string $targetFolderIdentifier
838 *
839 * @return array
840 * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
841 */
842 protected function createIdentifierMap(array $filesAndFolders, $sourceFolderIdentifier, $targetFolderIdentifier) {
843 $identifierMap = array();
844 $identifierMap[$sourceFolderIdentifier] = $targetFolderIdentifier;
845 foreach ($filesAndFolders as $oldItem) {
846 if ($oldItem['type'] == 'dir') {
847 $oldIdentifier = $oldItem['identifier'];
848 $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier(
849 str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
850 );
851 } else {
852 $oldIdentifier = $oldItem['identifier'];
853 $newIdentifier = $this->canonicalizeAndCheckFileIdentifier(
854 str_replace($sourceFolderIdentifier, $targetFolderIdentifier, $oldItem['identifier'])
855 );
856 }
857 if (!file_exists($this->getAbsolutePath($newIdentifier))) {
858 throw new \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException(
859 sprintf('File "%1$s" was not found (should have been copied/moved from "%2$s").', $newIdentifier, $oldIdentifier),
860 1330119453
861 );
862 }
863 $identifierMap[$oldIdentifier] = $newIdentifier;
864 }
865 return $identifierMap;
866 }
867
868 /**
869 * Folder equivalent to moveFileWithinStorage().
870 *
871 * @param string $sourceFolderIdentifier
872 * @param string $targetFolderIdentifier
873 * @param string $newFolderName
874 *
875 * @return array A map of old to new file identifiers
876 * @throws \RuntimeException
877 */
878 public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) {
879 $sourcePath = $this->getAbsolutePath($sourceFolderIdentifier);
880 $relativeTargetPath = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
881 $targetPath = $this->getAbsolutePath($relativeTargetPath);
882 // get all files and folders we are going to move, to have a map for updating later.
883 $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, TRUE);
884 $result = rename($sourcePath, $targetPath);
885 if ($result === FALSE) {
886 throw new \RuntimeException('Moving folder ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320711817);
887 }
888 // Create a mapping from old to new identifiers
889 $identifierMap = $this->createIdentifierMap($filesAndFolders, $sourceFolderIdentifier, $relativeTargetPath);
890 return $identifierMap;
891 }
892
893 /**
894 * Folder equivalent to copyFileWithinStorage().
895 *
896 * @param string $sourceFolderIdentifier
897 * @param string $targetFolderIdentifier
898 * @param string $newFolderName
899 *
900 * @return boolean
901 * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
902 */
903 public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) {
904 // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
905 // We can thus rely on this folder being present and just create the subfolder we want to copy to.
906 $newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName);
907 $sourceFolderPath = $this->getAbsolutePath($sourceFolderIdentifier);
908 $targetFolderPath = $this->getAbsolutePath($newFolderIdentifier);
909
910 mkdir($targetFolderPath);
911 /** @var $iterator \RecursiveDirectoryIterator */
912 $iterator = new \RecursiveIteratorIterator(
913 new \RecursiveDirectoryIterator($sourceFolderPath),
914 \RecursiveIteratorIterator::SELF_FIRST
915 );
916 // Rewind the iterator as this is important for some systems e.g. Windows
917 $iterator->rewind();
918 while ($iterator->valid()) {
919 /** @var $current \RecursiveDirectoryIterator */
920 $current = $iterator->current();
921 $fileName = $current->getFilename();
922 $itemSubPath = GeneralUtility::fixWindowsFilePath($iterator->getSubPathname());
923 if ($current->isDir() && !($fileName === '..' || $fileName === '.')) {
924 GeneralUtility::mkdir($targetFolderPath . '/' . $itemSubPath);
925 } elseif ($current->isFile()) {
926 $result = copy($sourceFolderPath . '/' . $itemSubPath, $targetFolderPath . '/' . $itemSubPath);
927 if ($result === FALSE) {
928 // rollback
929 GeneralUtility::rmdir($targetFolderIdentifier, TRUE);
930 throw new \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException(
931 'Copying file "' . $sourceFolderPath . $itemSubPath . '" to "' . $targetFolderPath . $itemSubPath . '" failed.',
932 1330119452
933 );
934
935 }
936 }
937 $iterator->next();
938 }
939 GeneralUtility::fixPermissions($targetFolderPath, TRUE);
940 return TRUE;
941 }
942
943 /**
944 * Renames a file in this storage.
945 *
946 * @param string $fileIdentifier
947 * @param string $newName The target path (including the file name!)
948 * @return string The identifier of the file after renaming
949 * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException
950 * @throws \RuntimeException
951 */
952 public function renameFile($fileIdentifier, $newName) {
953 // Makes sure the Path given as parameter is valid
954 $newName = $this->sanitizeFileName($newName);
955 $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(PathUtility::dirname($fileIdentifier)), '/') . '/' . $newName;
956 $newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newIdentifier);
957 // The target should not exist already
958 if ($this->fileExists($newIdentifier)) {
959 throw new \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException('The target file already exists.', 1320291063);
960 }
961 $sourcePath = $this->getAbsolutePath($fileIdentifier);
962 $targetPath = $this->getAbsolutePath($newIdentifier);
963 $result = rename($sourcePath, $targetPath);
964 if ($result === FALSE) {
965 throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115);
966 }
967 return $newIdentifier;
968 }
969
970
971 /**
972 * Renames a folder in this storage.
973 *
974 * @param string $folderIdentifier
975 * @param string $newName
976 * @return array A map of old to new file identifiers of all affected files and folders
977 * @throws \RuntimeException if renaming the folder failed
978 */
979 public function renameFolder($folderIdentifier, $newName) {
980 $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
981 $newName = $this->sanitizeFileName($newName);
982
983 $newIdentifier = PathUtility::dirname($folderIdentifier) . '/' . $newName;
984 $newIdentifier = $this->canonicalizeAndCheckFolderIdentifier($newIdentifier);
985
986 $sourcePath = $this->getAbsolutePath($folderIdentifier);
987 $targetPath = $this->getAbsolutePath($newIdentifier);
988 // get all files and folders we are going to move, to have a map for updating later.
989 $filesAndFolders = $this->retrieveFileAndFoldersInPath($sourcePath, TRUE);
990 $result = rename($sourcePath, $targetPath);
991 if ($result === FALSE) {
992 throw new \RuntimeException(sprintf('Renaming folder "%1$s" to "%2$s" failed."', $sourcePath, $targetPath), 1320375116);
993 }
994 try {
995 // Create a mapping from old to new identifiers
996 $identifierMap = $this->createIdentifierMap($filesAndFolders, $folderIdentifier, $newIdentifier);
997 } catch (\Exception $e) {
998 rename($targetPath, $sourcePath);
999 throw new \RuntimeException(
1000 sprintf(
1001 'Creating filename mapping after renaming "%1$s" to "%2$s" failed. Reverted rename operation.\\n\\nOriginal error: %3$s"',
1002 $sourcePath, $targetPath, $e->getMessage()
1003 ),
1004 1334160746
1005 );
1006 }
1007 return $identifierMap;
1008 }
1009
1010 /**
1011 * Removes a file from the filesystem. This does not check if the file is
1012 * still used or if it is a bad idea to delete it for some other reason
1013 * this has to be taken care of in the upper layers (e.g. the Storage)!
1014 *
1015 * @param string $fileIdentifier
1016 * @return boolean TRUE if deleting the file succeeded
1017 * @throws \RuntimeException
1018 */
1019 public function deleteFile($fileIdentifier) {
1020 $filePath = $this->getAbsolutePath($fileIdentifier);
1021 $result = unlink($filePath);
1022 if ($result === FALSE) {
1023 throw new \RuntimeException('Deletion of file ' . $fileIdentifier . ' failed.', 1320855304);
1024 }
1025 return $result;
1026 }
1027
1028 /**
1029 * Removes a folder from this storage.
1030 *
1031 * @param string $folderIdentifier
1032 * @param boolean $deleteRecursively
1033 * @return boolean
1034 * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
1035 */
1036 public function deleteFolder($folderIdentifier, $deleteRecursively = FALSE) {
1037 $folderPath = $this->getAbsolutePath($folderIdentifier);
1038 $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
1039 if ($result === FALSE) {
1040 throw new \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException(
1041 'Deleting folder "' . $folderIdentifier . '" failed.',
1042 1330119451
1043 );
1044 }
1045 return $result;
1046 }
1047
1048 /**
1049 * Checks if a folder contains files and (if supported) other folders.
1050 *
1051 * @param string $folderIdentifier
1052 * @return boolean TRUE if there are no files and folders within $folder
1053 */
1054 public function isFolderEmpty($folderIdentifier) {
1055 $path = $this->getAbsolutePath($folderIdentifier);
1056 $dirHandle = opendir($path);
1057 while ($entry = readdir($dirHandle)) {
1058 if ($entry !== '.' && $entry !== '..') {
1059 closedir($dirHandle);
1060 return FALSE;
1061 }
1062 }
1063 closedir($dirHandle);
1064 return TRUE;
1065 }
1066
1067 /**
1068 * Returns (a local copy of) a file for processing it. This makes a copy
1069 * first when in writable mode, so if you change the file, you have to update it yourself afterwards.
1070 *
1071 * @param string $fileIdentifier
1072 * @param boolean $writable Set this to FALSE if you only need the file for read operations.
1073 * This might speed up things, e.g. by using a cached local version.
1074 * Never modify the file if you have set this flag!
1075 * @return string The path to the file on the local disk
1076 */
1077 public function getFileForLocalProcessing($fileIdentifier, $writable = TRUE) {
1078 if ($writable === FALSE) {
1079 return $this->getAbsolutePath($fileIdentifier);
1080 } else {
1081 return $this->copyFileToTemporaryPath($fileIdentifier);
1082 }
1083 }
1084
1085
1086 /**
1087 * Returns the permissions of a file/folder as an array (keys r, w) of boolean flags
1088 *
1089 * @param string $identifier
1090 * @return array
1091 * @throws \RuntimeException
1092 */
1093 public function getPermissions($identifier) {
1094 $path = $this->getAbsolutePath($identifier);
1095 $permissionBits = fileperms($path);
1096 if ($permissionBits === FALSE) {
1097 throw new \RuntimeException('Error while fetching permissions for ' . $path, 1319455097);
1098 }
1099 return array(
1100 'r' => (bool)is_readable($path),
1101 'w' => (bool)is_writable($path)
1102 );
1103 }
1104
1105 /**
1106 * Checks if a given identifier is within a container, e.g. if
1107 * a file or folder is within another folder. It will also return
1108 * TRUE if both canonicalized identifiers are equal.
1109 *
1110 * @param string $folderIdentifier
1111 * @param string $identifier identifier to be checked against $folderIdentifier
1112 * @return boolean TRUE if $content is within or matches $folderIdentifier
1113 */
1114 public function isWithin($folderIdentifier, $identifier) {
1115 $folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier);
1116 $entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
1117 if ($folderIdentifier === $entryIdentifier) {
1118 return TRUE;
1119 }
1120 // File identifier canonicalization will not modify a single slash so
1121 // we must not append another slash in that case.
1122 if ($folderIdentifier !== '/') {
1123 $folderIdentifier .= '/';
1124 }
1125 return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier);
1126 }
1127
1128 /**
1129 * Creates a new (empty) file and returns the identifier.
1130 *
1131 * @param string $fileName
1132 * @param string $parentFolderIdentifier
1133 * @return string
1134 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException
1135 * @throws \RuntimeException
1136 */
1137 public function createFile($fileName, $parentFolderIdentifier) {
1138 if (!$this->isValidFilename($fileName)) {
1139 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException(
1140 'Invalid characters in fileName "' . $fileName . '"',
1141 1320572272
1142 );
1143 }
1144 $parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier);
1145 $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier(
1146 $parentFolderIdentifier . $this->sanitizeFileName(ltrim($fileName, '/'))
1147 );
1148 $absoluteFilePath = $this->getAbsolutePath($fileIdentifier);
1149 $result = touch($absoluteFilePath);
1150 GeneralUtility::fixPermissions($absoluteFilePath);
1151 clearstatcache();
1152 if ($result !== TRUE) {
1153 throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854);
1154 }
1155 return $fileIdentifier;
1156 }
1157
1158 /**
1159 * Returns the contents of a file. Beware that this requires to load the
1160 * complete file into memory and also may require fetching the file from an
1161 * external location. So this might be an expensive operation (both in terms of
1162 * processing resources and money) for large files.
1163 *
1164 * @param string $fileIdentifier
1165 * @return string The file contents
1166 */
1167 public function getFileContents($fileIdentifier) {
1168 $filePath = $this->getAbsolutePath($fileIdentifier);
1169 return file_get_contents($filePath);
1170 }
1171
1172 /**
1173 * Sets the contents of a file to the specified value.
1174 *
1175 * @param string $fileIdentifier
1176 * @param string $contents
1177 * @return integer The number of bytes written to the file
1178 * @throws \RuntimeException if the operation failed
1179 */
1180 public function setFileContents($fileIdentifier, $contents) {
1181 $filePath = $this->getAbsolutePath($fileIdentifier);
1182 $result = file_put_contents($filePath, $contents);
1183
1184 // Make sure later calls to filesize() etc. return correct values.
1185 clearstatcache(TRUE, $filePath);
1186
1187 if ($result === FALSE) {
1188 throw new \RuntimeException('Setting contents of file "' . $fileIdentifier . '" failed.', 1325419305);
1189 }
1190 return $result;
1191 }
1192
1193 /**
1194 * Gets the charset conversion object.
1195 *
1196 * @return \TYPO3\CMS\Core\Charset\CharsetConverter
1197 */
1198 protected function getCharsetConversion() {
1199 if (!isset($this->charsetConversion)) {
1200 if (TYPO3_MODE === 'FE') {
1201 $this->charsetConversion = $GLOBALS['TSFE']->csConvObj;
1202 } elseif (is_object($GLOBALS['LANG'])) {
1203 // BE assumed:
1204 $this->charsetConversion = $GLOBALS['LANG']->csConvObj;
1205 } else {
1206 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
1207 $this->charsetConversion = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
1208 }
1209 }
1210 return $this->charsetConversion;
1211 }
1212
1213 /**
1214 * Returns the role of an item (currently only folders; can later be extended for files as well)
1215 *
1216 * @param string $folderIdentifier
1217 * @return string
1218 */
1219 public function getRole($folderIdentifier) {
1220 $name = PathUtility::basename($folderIdentifier);
1221 $role = $this->mappingFolderNameToRole[$name];
1222 if (empty($role)) {
1223 $role = FolderInterface::ROLE_DEFAULT;
1224 }
1225 return $role;
1226 }
1227
1228 /**
1229 * Directly output the contents of the file to the output
1230 * buffer. Should not take care of header files or flushing
1231 * buffer before. Will be taken care of by the Storage.
1232 *
1233 * @param string $identifier
1234 *
1235 * @return void
1236 */
1237 public function dumpFileContents($identifier) {
1238 readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
1239 }
1240
1241
1242 }