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