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