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