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