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