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