[BUGFIX] Unify the filename sanitation for upload, create, rename
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / File / ExtendedFileUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility\File;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
21 use TYPO3\CMS\Core\Localization\LanguageService;
22 use TYPO3\CMS\Core\Messaging\FlashMessage;
23 use TYPO3\CMS\Core\Messaging\FlashMessageService;
24 use TYPO3\CMS\Core\Resource\DuplicationBehavior;
25 use TYPO3\CMS\Core\Resource\Exception;
26 use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
27 use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException;
28 use TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException;
29 use TYPO3\CMS\Core\Resource\Exception\InsufficientFileWritePermissionsException;
30 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
31 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException;
32 use TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException;
33 use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException;
34 use TYPO3\CMS\Core\Resource\Exception\NotInMountPointException;
35 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
36 use TYPO3\CMS\Core\Resource\Exception\UploadException;
37 use TYPO3\CMS\Core\Resource\Exception\UploadSizeException;
38 use TYPO3\CMS\Core\Resource\File;
39 use TYPO3\CMS\Core\Resource\Folder;
40 use TYPO3\CMS\Core\Resource\ResourceFactory;
41 use TYPO3\CMS\Core\Resource\ResourceStorage;
42 use TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException;
43 use TYPO3\CMS\Core\Utility\GeneralUtility;
44
45 /**
46 * Contains functions for performing file operations like copying, pasting, uploading, moving,
47 * deleting etc. through the TCE
48 *
49 * See document "TYPO3 Core API" for syntax
50 *
51 * This class contains functions primarily used by tce_file.php (TYPO3 Core Engine for file manipulation)
52 * Functions include copying, moving, deleting, uploading and so on...
53 *
54 * Important internal variables:
55 *
56 * $fileExtensionPermissions (see basicFileFunctions)
57 *
58 * All fileoperations must be within the filemount paths of the user. Further the fileextension
59 * MUST validate TRUE with the fileExtensionPermissions array
60 */
61 class ExtendedFileUtility extends BasicFileUtility
62 {
63 /**
64 * Defines behaviour when uploading files with names that already exist; possible values are
65 * the values of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
66 *
67 * @var \TYPO3\CMS\Core\Resource\DuplicationBehavior
68 */
69 protected $existingFilesConflictMode;
70
71 /**
72 * This array is self-explaining (look in the class below).
73 * It grants access to the functions. This could be set from outside in order to enabled functions to users.
74 * See also the function setActionPermissions() which takes input directly from the user-record
75 *
76 * @var array
77 */
78 public $actionPerms = [
79 // File permissions
80 'addFile' => false,
81 'readFile' => false,
82 'writeFile' => false,
83 'copyFile' => false,
84 'moveFile' => false,
85 'renameFile' => false,
86 'deleteFile' => false,
87 // Folder permissions
88 'addFolder' => false,
89 'readFolder' => false,
90 'writeFolder' => false,
91 'copyFolder' => false,
92 'moveFolder' => false,
93 'renameFolder' => false,
94 'deleteFolder' => false,
95 'recursivedeleteFolder' => false
96 ];
97
98 /**
99 * Will contain map between upload ID and the final filename
100 *
101 * @var array
102 */
103 public $internalUploadMap = [];
104
105 /**
106 * All error messages from the file operations of this script instance
107 *
108 * @var array
109 */
110 protected $errorMessages = [];
111
112 /**
113 * Container for FlashMessages so they can be localized
114 *
115 * @var array
116 */
117 protected $flashMessages = [];
118
119 /**
120 * @var array
121 */
122 protected $fileCmdMap;
123
124 /**
125 * The File Factory
126 *
127 * @var \TYPO3\CMS\Core\Resource\ResourceFactory
128 */
129 protected $fileFactory;
130
131 /**
132 * Get existingFilesConflictMode
133 *
134 * @return string
135 */
136 public function getExistingFilesConflictMode()
137 {
138 return (string)$this->existingFilesConflictMode;
139 }
140
141 /**
142 * Set existingFilesConflictMode
143 *
144 * @param \TYPO3\CMS\Core\Resource\DuplicationBehavior|string $existingFilesConflictMode Instance or constant of \TYPO3\CMS\Core\Resource\DuplicationBehavior
145 * @throws Exception
146 */
147 public function setExistingFilesConflictMode($existingFilesConflictMode)
148 {
149 try {
150 $this->existingFilesConflictMode = DuplicationBehavior::cast($existingFilesConflictMode);
151 } catch (InvalidEnumerationValueException $e) {
152 throw new Exception(
153 sprintf(
154 'Invalid argument, received: "%s", expected a value from enumeration \TYPO3\CMS\Core\Resource\DuplicationBehavior (%s)',
155 $existingFilesConflictMode,
156 implode(', ', DuplicationBehavior::getConstants())
157 ),
158 1476046229
159 );
160 }
161 }
162
163 /**
164 * Initialization of the class
165 *
166 * @param array $fileCmds Array with the commands to execute. See "TYPO3 Core API" document
167 */
168 public function start($fileCmds)
169 {
170 // Initialize Object Factory
171 $this->fileFactory = ResourceFactory::getInstance();
172 // Initializing file processing commands:
173 $this->fileCmdMap = $fileCmds;
174 }
175
176 /**
177 * Sets the file action permissions.
178 * If no argument is given, permissions of the currently logged in backend user are taken into account.
179 *
180 * @param array $permissions File Permissions.
181 */
182 public function setActionPermissions(array $permissions = [])
183 {
184 if (empty($permissions)) {
185 $permissions = $this->getBackendUser()->getFilePermissions();
186 }
187 $this->actionPerms = $permissions;
188 }
189
190 /**
191 * Processing the command array in $this->fileCmdMap
192 *
193 * @return mixed FALSE, if the file functions were not initialized
194 * @throws \UnexpectedValueException
195 */
196 public function processData()
197 {
198 $result = [];
199 if (is_array($this->fileCmdMap)) {
200 // Check if there were uploads expected, but no one made
201 if ($this->fileCmdMap['upload']) {
202 $uploads = $this->fileCmdMap['upload'];
203 foreach ($uploads as $upload) {
204 if (empty($_FILES['upload_' . $upload['data']]['name'])
205 || (
206 is_array($_FILES['upload_' . $upload['data']]['name'])
207 && empty($_FILES['upload_' . $upload['data']]['name'][0])
208 )
209 ) {
210 unset($this->fileCmdMap['upload'][$upload['data']]);
211 }
212 }
213 if (empty($this->fileCmdMap['upload'])) {
214 $this->writeLog(1, 1, 108, 'No file was uploaded!', []);
215 $this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploaded');
216 }
217 }
218
219 // Check if there were new folder names expected, but non given
220 if ($this->fileCmdMap['newfolder']) {
221 foreach ($this->fileCmdMap['newfolder'] as $key => $cmdArr) {
222 if (empty($cmdArr['data'])) {
223 unset($this->fileCmdMap['newfolder'][$key]);
224 }
225 }
226 if (empty($this->fileCmdMap['newfolder'])) {
227 $this->writeLog(6, 1, 108, 'No name for new folder given!', []);
228 $this->addMessageToFlashMessageQueue('FileUtility.NoNameForNewFolderGiven');
229 }
230 }
231
232 // Traverse each set of actions
233 foreach ($this->fileCmdMap as $action => $actionData) {
234 // Traverse all action data. More than one file might be affected at the same time.
235 if (is_array($actionData)) {
236 $result[$action] = [];
237 foreach ($actionData as $cmdArr) {
238 // Clear file stats
239 clearstatcache();
240 // Branch out based on command:
241 switch ($action) {
242 case 'delete':
243 $result[$action][] = $this->func_delete($cmdArr);
244 break;
245 case 'copy':
246 $result[$action][] = $this->func_copy($cmdArr);
247 break;
248 case 'move':
249 $result[$action][] = $this->func_move($cmdArr);
250 break;
251 case 'rename':
252 $result[$action][] = $this->func_rename($cmdArr);
253 break;
254 case 'newfolder':
255 $result[$action][] = $this->func_newfolder($cmdArr);
256 break;
257 case 'newfile':
258 $result[$action][] = $this->func_newfile($cmdArr);
259 break;
260 case 'editfile':
261 $result[$action][] = $this->func_edit($cmdArr);
262 break;
263 case 'upload':
264 $result[$action][] = $this->func_upload($cmdArr);
265 break;
266 case 'replace':
267 $result[$action][] = $this->replaceFile($cmdArr);
268 break;
269 }
270 // Hook for post-processing the action
271 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_extfilefunc.php']['processData'] ?? [] as $className) {
272 $hookObject = GeneralUtility::makeInstance($className);
273 if (!$hookObject instanceof ExtendedFileUtilityProcessDataHookInterface) {
274 throw new \UnexpectedValueException($className . ' must implement interface ' . ExtendedFileUtilityProcessDataHookInterface::class, 1279719168);
275 }
276 $hookObject->processData_postProcessAction($action, $cmdArr, $result[$action], $this);
277 }
278 }
279 }
280 }
281 }
282 return $result;
283 }
284
285 /**
286 * Return all error messages from the file operations of this script instance
287 *
288 * @return array all errorMessages as a numerical array
289 */
290 public function getErrorMessages()
291 {
292 return $this->errorMessages;
293 }
294
295 /**
296 * @param int $action The action number. See the functions in the class for a hint. Eg. edit is '9', upload is '1' ...
297 * @param int $error The severity: 0 = message, 1 = error, 2 = System Error, 3 = security notice (admin)
298 * @param int $details_nr This number is unique for every combination of $type and $action. This is the error-message number, which can later be used to translate error messages.
299 * @param string $details This is the default, raw error message in english
300 * @param array $data Array with special information that may go into $details by "%s" marks / sprintf() when the log is shown
301 */
302 public function writeLog($action, $error, $details_nr, $details, $data)
303 {
304 // Type value for tce_file.php
305 $type = 2;
306 if (is_object($this->getBackendUser())) {
307 $this->getBackendUser()->writelog($type, $action, $error, $details_nr, $details, $data);
308 }
309 if ($error > 0) {
310 $this->errorMessages[] = vsprintf($details, $data);
311 }
312 }
313
314 /**
315 * Adds a localized FlashMessage to the message queue
316 *
317 * @param string $localizationKey
318 * @param array $replaceMarkers
319 * @param int $severity
320 * @throws \InvalidArgumentException
321 */
322 protected function addMessageToFlashMessageQueue($localizationKey, array $replaceMarkers = [], $severity = FlashMessage::ERROR)
323 {
324 if (TYPO3_MODE !== 'BE') {
325 return;
326 }
327 $label = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/fileMessages.xlf:' . $localizationKey);
328 $message = vsprintf($label, $replaceMarkers);
329 $flashMessage = GeneralUtility::makeInstance(
330 FlashMessage::class,
331 $message,
332 '',
333 $severity,
334 true
335 );
336 $this->addFlashMessage($flashMessage);
337 }
338
339 /*************************************
340 *
341 * File operation functions
342 *
343 **************************************/
344 /**
345 * Deleting files and folders (action=4)
346 *
347 * @param array $cmds $cmds['data'] is the file/folder to delete
348 * @return bool Returns TRUE upon success
349 */
350 public function func_delete(array $cmds)
351 {
352 $result = false;
353 // Example identifier for $cmds['data'] => "4:mypath/tomyfolder/myfile.jpg"
354 // for backwards compatibility: the combined file identifier was the path+filename
355 try {
356 $fileObject = $this->getFileObject($cmds['data']);
357 } catch (ResourceDoesNotExistException $e) {
358 $flashMessage = GeneralUtility::makeInstance(
359 FlashMessage::class,
360 sprintf(
361 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.fileNotFound'),
362 $cmds['data']
363 ),
364 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.fileNotFound'),
365 FlashMessage::ERROR,
366 true
367 );
368 $this->addFlashMessage($flashMessage);
369
370 return false;
371 }
372 // checks to delete the file
373 if ($fileObject instanceof File) {
374 // check if the file still has references
375 // Exclude sys_file_metadata records as these are no use references
376 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
377 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
378 $refIndexRecords = $queryBuilder
379 ->select('tablename', 'recuid', 'ref_uid')
380 ->from('sys_refindex')
381 ->where(
382 $queryBuilder->expr()->eq(
383 'ref_table',
384 $queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
385 ),
386 $queryBuilder->expr()->eq(
387 'ref_uid',
388 $queryBuilder->createNamedParameter($fileObject->getUid(), \PDO::PARAM_INT)
389 ),
390 $queryBuilder->expr()->neq(
391 'tablename',
392 $queryBuilder->createNamedParameter('sys_file_metadata', \PDO::PARAM_STR)
393 ),
394 $queryBuilder->expr()->eq(
395 'deleted',
396 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
397 )
398 )
399 ->execute()
400 ->fetchAll();
401 $deleteFile = true;
402 if (!empty($refIndexRecords)) {
403 $shortcutContent = [];
404 $brokenReferences = [];
405
406 foreach ($refIndexRecords as $fileReferenceRow) {
407 if ($fileReferenceRow['tablename'] === 'sys_file_reference') {
408 $row = $this->transformFileReferenceToRecordReference($fileReferenceRow);
409 $shortcutRecord = BackendUtility::getRecord($row['tablename'], $row['recuid']);
410
411 if ($shortcutRecord) {
412 $shortcutContent[] = '[record:' . $row['tablename'] . ':' . $row['recuid'] . ']';
413 } else {
414 $brokenReferences[] = $fileReferenceRow['ref_uid'];
415 }
416 }
417 }
418 if (!empty($brokenReferences)) {
419 // render a message that the file has broken references
420 $flashMessage = GeneralUtility::makeInstance(
421 FlashMessage::class,
422 sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.fileHasBrokenReferences'), count($brokenReferences)),
423 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.fileHasBrokenReferences'),
424 FlashMessage::INFO,
425 true
426 );
427 $this->addFlashMessage($flashMessage);
428 }
429 if (!empty($shortcutContent)) {
430 // render a message that the file could not be deleted
431 $flashMessage = GeneralUtility::makeInstance(
432 FlashMessage::class,
433 sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.fileNotDeletedHasReferences'), $fileObject->getName()) . ' ' . implode(', ', $shortcutContent),
434 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.fileNotDeletedHasReferences'),
435 FlashMessage::WARNING,
436 true
437 );
438 $this->addFlashMessage($flashMessage);
439 $deleteFile = false;
440 }
441 }
442
443 if ($deleteFile) {
444 try {
445 $result = $fileObject->delete();
446
447 // show the user that the file was deleted
448 $flashMessage = GeneralUtility::makeInstance(
449 FlashMessage::class,
450 sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.fileDeleted'), $fileObject->getName()),
451 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.fileDeleted'),
452 FlashMessage::OK,
453 true
454 );
455 $this->addFlashMessage($flashMessage);
456 // Log success
457 $this->writeLog(4, 0, 1, 'File "%s" deleted', [$fileObject->getIdentifier()]);
458 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
459 $this->writeLog(4, 1, 112, 'You are not allowed to access the file', [$fileObject->getIdentifier()]);
460 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToAccessTheFile', [$fileObject->getIdentifier()]);
461 } catch (NotInMountPointException $e) {
462 $this->writeLog(4, 1, 111, 'Target was not within your mountpoints! T="%s"', [$fileObject->getIdentifier()]);
463 $this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotWithinYourMountpoints', [$fileObject->getIdentifier()]);
464 } catch (\RuntimeException $e) {
465 $this->writeLog(4, 1, 110, 'Could not delete file "%s". Write-permission problem?', [$fileObject->getIdentifier()]);
466 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteFile', [$fileObject->getIdentifier()]);
467 }
468 }
469 } else {
470 /** @var Folder $fileObject */
471 if (!$this->folderHasFilesInUse($fileObject)) {
472 try {
473 $result = $fileObject->delete(true);
474 if ($result) {
475 // notify the user that the folder was deleted
476 /** @var FlashMessage $flashMessage */
477 $flashMessage = GeneralUtility::makeInstance(
478 FlashMessage::class,
479 sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.folderDeleted'), $fileObject->getName()),
480 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.folderDeleted'),
481 FlashMessage::OK,
482 true
483 );
484 $this->addFlashMessage($flashMessage);
485 // Log success
486 $this->writeLog(4, 0, 3, 'Directory "%s" deleted', [$fileObject->getIdentifier()]);
487 }
488 } catch (InsufficientUserPermissionsException $e) {
489 $this->writeLog(4, 1, 120, 'Could not delete directory! Is directory "%s" empty? (You are not allowed to delete directories recursively).', [$fileObject->getIdentifier()]);
490 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteDirectory', [$fileObject->getIdentifier()]);
491 } catch (InsufficientFolderAccessPermissionsException $e) {
492 $this->writeLog(4, 1, 123, 'You are not allowed to access the directory', [$fileObject->getIdentifier()]);
493 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToAccessTheDirectory', [$fileObject->getIdentifier()]);
494 } catch (NotInMountPointException $e) {
495 $this->writeLog(4, 1, 121, 'Target was not within your mountpoints! T="%s"', [$fileObject->getIdentifier()]);
496 $this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotWithinYourMountpoints', [$fileObject->getIdentifier()]);
497 } catch (\TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException $e) {
498 $this->writeLog(4, 1, 120, 'Could not delete directory "%s"! Write-permission problem?', [$fileObject->getIdentifier()]);
499 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteDirectory', [$fileObject->getIdentifier()]);
500 }
501 }
502 }
503
504 return $result;
505 }
506
507 /**
508 * Checks files in given folder recursively for for existing references.
509 *
510 * Creates a flash message if there are references.
511 *
512 * @param Folder $folder
513 * @return bool TRUE if folder has files in use, FALSE otherwise
514 */
515 public function folderHasFilesInUse(Folder $folder)
516 {
517 $files = $folder->getFiles(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, true);
518 if (empty($files)) {
519 return false;
520 }
521
522 /** @var int[] $fileUids */
523 $fileUids = [];
524 foreach ($files as $file) {
525 $fileUids[] = $file->getUid();
526 }
527
528 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
529 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
530 $numberOfReferences = $queryBuilder
531 ->count('hash')
532 ->from('sys_refindex')
533 ->where(
534 $queryBuilder->expr()->eq(
535 'ref_table',
536 $queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
537 ),
538 $queryBuilder->expr()->in(
539 'ref_uid',
540 $queryBuilder->createNamedParameter($fileUids, Connection::PARAM_INT_ARRAY)
541 ),
542 $queryBuilder->expr()->neq(
543 'tablename',
544 $queryBuilder->createNamedParameter('sys_file_metadata', \PDO::PARAM_STR)
545 ),
546 $queryBuilder->expr()->eq(
547 'deleted',
548 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
549 )
550 )->execute()->fetchColumn(0);
551
552 $hasReferences = $numberOfReferences > 0;
553 if ($hasReferences) {
554 /** @var FlashMessage $flashMessage */
555 $flashMessage = GeneralUtility::makeInstance(
556 FlashMessage::class,
557 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.description.folderNotDeletedHasFilesWithReferences'),
558 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:message.header.folderNotDeletedHasFilesWithReferences'),
559 FlashMessage::WARNING,
560 true
561 );
562 $this->addFlashMessage($flashMessage);
563 }
564
565 return $hasReferences;
566 }
567
568 /**
569 * Maps results from the fal file reference table on the
570 * structure of the normal reference index table.
571 *
572 * @param array $referenceRecord
573 * @return array
574 */
575 protected function transformFileReferenceToRecordReference(array $referenceRecord)
576 {
577 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
578 $queryBuilder->getRestrictions()->removeAll();
579 $fileReference = $queryBuilder
580 ->select('uid_foreign', 'tablenames', 'fieldname', 'sorting_foreign')
581 ->from('sys_file_reference')
582 ->where(
583 $queryBuilder->expr()->eq(
584 'uid',
585 $queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
586 )
587 )
588 ->execute()
589 ->fetch();
590
591 return [
592 'recuid' => $fileReference['uid_foreign'],
593 'tablename' => $fileReference['tablenames'],
594 'field' => $fileReference['fieldname'],
595 'flexpointer' => '',
596 'softref_key' => '',
597 'sorting' => $fileReference['sorting_foreign']
598 ];
599 }
600
601 /**
602 * Gets a File or a Folder object from an identifier [storage]:[fileId]
603 *
604 * @param string $identifier
605 * @return File|Folder
606 * @throws Exception\InsufficientFileAccessPermissionsException
607 * @throws Exception\InvalidFileException
608 */
609 protected function getFileObject($identifier)
610 {
611 $object = $this->fileFactory->retrieveFileOrFolderObject($identifier);
612 if (!is_object($object)) {
613 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidFileException('The item ' . $identifier . ' was not a file or directory!!', 1320122453);
614 }
615 if ($object->getStorage()->getUid() === 0) {
616 throw new \TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889830);
617 }
618 return $object;
619 }
620
621 /**
622 * Copying files and folders (action=2)
623 *
624 * $cmds['data'] (string): The file/folder to copy
625 * + example "4:mypath/tomyfolder/myfile.jpg")
626 * + for backwards compatibility: the identifier was the path+filename
627 * $cmds['target'] (string): The path where to copy to.
628 * + example "2:targetpath/targetfolder/"
629 * $cmds['altName'] (string): Use an alternative name if the target already exists
630 *
631 * @param array $cmds Command details as described above
632 * @return \TYPO3\CMS\Core\Resource\File|false
633 */
634 protected function func_copy($cmds)
635 {
636 $sourceFileObject = $this->getFileObject($cmds['data']);
637 /** @var $targetFolderObject \TYPO3\CMS\Core\Resource\Folder */
638 $targetFolderObject = $this->getFileObject($cmds['target']);
639 // Basic check
640 if (!$targetFolderObject instanceof Folder) {
641 $this->writeLog(2, 2, 100, 'Destination "%s" was not a directory', [$cmds['target']]);
642 $this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
643 return false;
644 }
645 // If this is TRUE, we append _XX to the file name if
646 $appendSuffixOnConflict = (string)$cmds['altName'];
647 $resultObject = null;
648 $conflictMode = $appendSuffixOnConflict !== '' ? DuplicationBehavior::RENAME : DuplicationBehavior::CANCEL;
649 // Copying the file
650 if ($sourceFileObject instanceof File) {
651 try {
652 $resultObject = $sourceFileObject->copyTo($targetFolderObject, null, $conflictMode);
653 } catch (InsufficientUserPermissionsException $e) {
654 $this->writeLog(2, 1, 114, 'You are not allowed to copy files', []);
655 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCopyFiles');
656 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
657 $this->writeLog(2, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
658 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
659 } catch (IllegalFileExtensionException $e) {
660 $this->writeLog(2, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
661 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
662 } catch (ExistingTargetFileNameException $e) {
663 $this->writeLog(2, 1, 112, 'File "%s" already exists in folder "%s"!', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
664 $this->addMessageToFlashMessageQueue('FileUtility.FileAlreadyExistsInFolder', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
665 } catch (\BadMethodCallException $e) {
666 $this->writeLog(3, 1, 128, 'The function to copy a file between storages is not yet implemented', []);
667 $this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToCopyAFileBetweenStoragesIsNotYetImplemented');
668 } catch (\RuntimeException $e) {
669 $this->writeLog(2, 2, 109, 'File "%s" WAS NOT copied to "%s"! Write-permission problem?', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
670 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCopiedTo', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
671 }
672 if ($resultObject) {
673 $this->writeLog(2, 0, 1, 'File "%s" copied to "%s"', [$sourceFileObject->getIdentifier(), $resultObject->getIdentifier()]);
674 $this->addMessageToFlashMessageQueue('FileUtility.FileCopiedTo', [$sourceFileObject->getIdentifier(), $resultObject->getIdentifier()], FlashMessage::OK);
675 }
676 } else {
677 // Else means this is a Folder
678 $sourceFolderObject = $sourceFileObject;
679 try {
680 $resultObject = $sourceFolderObject->copyTo($targetFolderObject, null, $conflictMode);
681 } catch (InsufficientUserPermissionsException $e) {
682 $this->writeLog(2, 1, 125, 'You are not allowed to copy directories', []);
683 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCopyDirectories');
684 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
685 $this->writeLog(2, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
686 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
687 } catch (InsufficientFolderAccessPermissionsException $e) {
688 $this->writeLog(2, 1, 121, 'You don\'t have full access to the destination directory "%s"!', [$targetFolderObject->getIdentifier()]);
689 $this->addMessageToFlashMessageQueue('FileUtility.YouDontHaveFullAccessToTheDestinationDirectory', [$targetFolderObject->getIdentifier()]);
690 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
691 $this->writeLog(2, 1, 122, 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!', [$sourceFolderObject->getName(), $targetFolderObject->getName()]);
692 $this->addMessageToFlashMessageQueue('FileUtility.CannotCopyFolderIntoTargetFolderBecauseTheTargetFolderIsAlreadyWithinTheFolderToBeCopied', [$sourceFolderObject->getName(), $targetFolderObject->getName()]);
693 } catch (ExistingTargetFolderException $e) {
694 $this->writeLog(2, 1, 123, 'Target "%s" already exists!', [$targetFolderObject->getIdentifier()]);
695 $this->addMessageToFlashMessageQueue('FileUtility.TargetAlreadyExists', [$targetFolderObject->getIdentifier()]);
696 } catch (\BadMethodCallException $e) {
697 $this->writeLog(3, 1, 129, 'The function to copy a folder between storages is not yet implemented', []);
698 $this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToCopyAFolderBetweenStoragesIsNotYetImplemented');
699 } catch (\RuntimeException $e) {
700 $this->writeLog(2, 2, 119, 'Directory "%s" WAS NOT copied to "%s"! Write-permission problem?', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
701 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotCopiedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
702 }
703 if ($resultObject) {
704 $this->writeLog(2, 0, 2, 'Directory "%s" copied to "%s"', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
705 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryCopiedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()], FlashMessage::OK);
706 }
707 }
708 return $resultObject;
709 }
710
711 /**
712 * Moving files and folders (action=3)
713 *
714 * $cmds['data'] (string): The file/folder to move
715 * + example "4:mypath/tomyfolder/myfile.jpg")
716 * + for backwards compatibility: the identifier was the path+filename
717 * $cmds['target'] (string): The path where to move to.
718 * + example "2:targetpath/targetfolder/"
719 * $cmds['altName'] (string): Use an alternative name if the target already exists
720 *
721 * @param array $cmds Command details as described above
722 * @return \TYPO3\CMS\Core\Resource\File|false
723 */
724 protected function func_move($cmds)
725 {
726 $sourceFileObject = $this->getFileObject($cmds['data']);
727 $targetFolderObject = $this->getFileObject($cmds['target']);
728 // Basic check
729 if (!$targetFolderObject instanceof Folder) {
730 $this->writeLog(3, 2, 100, 'Destination "%s" was not a directory', [$cmds['target']]);
731 $this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
732 return false;
733 }
734 $alternativeName = (string)$cmds['altName'];
735 $resultObject = null;
736 // Moving the file
737 if ($sourceFileObject instanceof File) {
738 try {
739 if ($alternativeName !== '') {
740 // Don't allow overwriting existing files, but find a new name
741 $resultObject = $sourceFileObject->moveTo($targetFolderObject, $alternativeName, DuplicationBehavior::RENAME);
742 } else {
743 // Don't allow overwriting existing files
744 $resultObject = $sourceFileObject->moveTo($targetFolderObject, null, DuplicationBehavior::CANCEL);
745 }
746 $this->writeLog(3, 0, 1, 'File "%s" moved to "%s"', [$sourceFileObject->getIdentifier(), $resultObject->getIdentifier()]);
747 $this->addMessageToFlashMessageQueue('FileUtility.FileMovedTo', [$sourceFileObject->getIdentifier(), $resultObject->getIdentifier()], FlashMessage::OK);
748 } catch (InsufficientUserPermissionsException $e) {
749 $this->writeLog(3, 1, 114, 'You are not allowed to move files', []);
750 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToMoveFiles');
751 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
752 $this->writeLog(3, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
753 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
754 } catch (IllegalFileExtensionException $e) {
755 $this->writeLog(3, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
756 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
757 } catch (ExistingTargetFileNameException $e) {
758 $this->writeLog(3, 1, 112, 'File "%s" already exists in folder "%s"!', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
759 $this->addMessageToFlashMessageQueue('FileUtility.FileAlreadyExistsInFolder', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
760 } catch (\BadMethodCallException $e) {
761 $this->writeLog(3, 1, 126, 'The function to move a file between storages is not yet implemented', []);
762 $this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToMoveAFileBetweenStoragesIsNotYetImplemented');
763 } catch (\RuntimeException $e) {
764 $this->writeLog(3, 2, 109, 'File "%s" WAS NOT copied to "%s"! Write-permission problem?', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
765 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCopiedTo', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
766 }
767 } else {
768 // Else means this is a Folder
769 $sourceFolderObject = $sourceFileObject;
770 try {
771 if ($alternativeName !== '') {
772 // Don't allow overwriting existing files, but find a new name
773 $resultObject = $sourceFolderObject->moveTo($targetFolderObject, $alternativeName, DuplicationBehavior::RENAME);
774 } else {
775 // Don't allow overwriting existing files
776 $resultObject = $sourceFolderObject->moveTo($targetFolderObject, null, DuplicationBehavior::RENAME);
777 }
778 $this->writeLog(3, 0, 2, 'Directory "%s" moved to "%s"', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
779 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryMovedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()], FlashMessage::OK);
780 } catch (InsufficientUserPermissionsException $e) {
781 $this->writeLog(3, 1, 125, 'You are not allowed to move directories', []);
782 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToMoveDirectories');
783 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
784 $this->writeLog(3, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
785 $this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
786 } catch (InsufficientFolderAccessPermissionsException $e) {
787 $this->writeLog(3, 1, 121, 'You don\'t have full access to the destination directory "%s"!', [$targetFolderObject->getIdentifier()]);
788 $this->addMessageToFlashMessageQueue('FileUtility.YouDontHaveFullAccessToTheDestinationDirectory', [$targetFolderObject->getIdentifier()]);
789 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
790 $this->writeLog(3, 1, 122, 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!', [$sourceFolderObject->getName(), $targetFolderObject->getName()]);
791 $this->addMessageToFlashMessageQueue('FileUtility.CannotMoveFolderIntoTargetFolderBecauseTheTargetFolderIsAlreadyWithinTheFolderToBeMoved', [$sourceFolderObject->getName(), $targetFolderObject->getName()]);
792 } catch (ExistingTargetFolderException $e) {
793 $this->writeLog(3, 1, 123, 'Target "%s" already exists!', [$targetFolderObject->getIdentifier()]);
794 $this->addMessageToFlashMessageQueue('FileUtility.TargetAlreadyExists', [$targetFolderObject->getIdentifier()]);
795 } catch (\BadMethodCallException $e) {
796 $this->writeLog(3, 1, 127, 'The function to move a folder between storages is not yet implemented', []);
797 $this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToMoveAFolderBetweenStoragesIsNotYetImplemented', []);
798 } catch (\RuntimeException $e) {
799 $this->writeLog(3, 2, 119, 'Directory "%s" WAS NOT moved to "%s"! Write-permission problem?', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
800 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotMovedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
801 }
802 }
803 return $resultObject;
804 }
805
806 /**
807 * Renaming files or foldes (action=5)
808 *
809 * $cmds['data'] (string): The file/folder to copy
810 * + example "4:mypath/tomyfolder/myfile.jpg")
811 * + for backwards compatibility: the identifier was the path+filename
812 * $cmds['target'] (string): New name of the file/folder
813 *
814 * @param array $cmds Command details as described above
815 * @return \TYPO3\CMS\Core\Resource\File Returns the new file upon success
816 */
817 public function func_rename($cmds)
818 {
819 $sourceFileObject = $this->getFileObject($cmds['data']);
820 $sourceFile = $sourceFileObject->getName();
821 $targetFile = $cmds['target'];
822 $resultObject = null;
823 if ($sourceFileObject instanceof File) {
824 try {
825 // Try to rename the File
826 $resultObject = $sourceFileObject->rename($targetFile, $this->existingFilesConflictMode);
827 if ($resultObject->getName() !== $targetFile) {
828 $this->writeLog(5, 1, 1, 'File renamed from "%s" to "%s". Filename had to be sanitized!', [$sourceFile, $targetFile]);
829 $this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$targetFile, $resultObject->getName()], FlashMessage::WARNING);
830 } else {
831 $this->writeLog(5, 0, 1, 'File renamed from "%s" to "%s"', [$sourceFile, $targetFile]);
832 }
833 if ($sourceFile === $resultObject->getName()) {
834 $this->addMessageToFlashMessageQueue('FileUtility.FileRenamedSameName', [$sourceFile], FlashMessage::INFO);
835 } else {
836 $this->addMessageToFlashMessageQueue('FileUtility.FileRenamedFromTo', [$sourceFile, $resultObject->getName()], FlashMessage::OK);
837 }
838 } catch (InsufficientUserPermissionsException $e) {
839 $this->writeLog(5, 1, 102, 'You are not allowed to rename files!', []);
840 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToRenameFiles');
841 } catch (IllegalFileExtensionException $e) {
842 $this->writeLog(5, 1, 101, 'Extension of file name "%s" or "%s" was not allowed!', [$sourceFileObject->getName(), $targetFile]);
843 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameOrWasNotAllowed', [$sourceFileObject->getName(), $targetFile]);
844 } catch (ExistingTargetFileNameException $e) {
845 $this->writeLog(5, 1, 120, 'Destination "%s" existed already!', [$targetFile]);
846 $this->addMessageToFlashMessageQueue('FileUtility.DestinationExistedAlready', [$targetFile]);
847 } catch (NotInMountPointException $e) {
848 $this->writeLog(5, 1, 121, 'Destination path "%s" was not within your mountpoints!', [$targetFile]);
849 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFile]);
850 } catch (\RuntimeException $e) {
851 $this->writeLog(5, 1, 100, 'File "%s" was not renamed! Write-permission problem in "%s"?', [$sourceFileObject->getName(), $targetFile]);
852 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotRenamed', [$sourceFileObject->getName(), $targetFile]);
853 }
854 } else {
855 // Else means this is a Folder
856 try {
857 // Try to rename the Folder
858 $resultObject = $sourceFileObject->rename($targetFile);
859 $this->writeLog(5, 0, 2, 'Directory renamed from "%s" to "%s"', [$sourceFile, $targetFile]);
860 if ($sourceFile === $targetFile) {
861 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryRenamedSameName', [$sourceFile], FlashMessage::INFO);
862 } else {
863 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryRenamedFromTo', [$sourceFile, $targetFile], FlashMessage::OK);
864 }
865 } catch (InsufficientUserPermissionsException $e) {
866 $this->writeLog(5, 1, 111, 'You are not allowed to rename directories!', []);
867 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToRenameDirectories');
868 } catch (ExistingTargetFileNameException $e) {
869 $this->writeLog(5, 1, 120, 'Destination "%s" existed already!', [$targetFile]);
870 $this->addMessageToFlashMessageQueue('FileUtility.DestinationExistedAlready', [$targetFile]);
871 } catch (NotInMountPointException $e) {
872 $this->writeLog(5, 1, 121, 'Destination path "%s" was not within your mountpoints!', [$targetFile]);
873 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFile]);
874 } catch (\RuntimeException $e) {
875 $this->writeLog(5, 1, 110, 'Directory "%s" was not renamed! Write-permission problem in "%s"?', [$sourceFileObject->getName(), $targetFile]);
876 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotRenamed', [$sourceFileObject->getName(), $targetFile]);
877 }
878 }
879 return $resultObject;
880 }
881
882 /**
883 * This creates a new folder. (action=6)
884 *
885 * $cmds['data'] (string): The new folder name
886 * $cmds['target'] (string): The path where to copy to.
887 * + example "2:targetpath/targetfolder/"
888 *
889 * @param array $cmds Command details as described above
890 * @return \TYPO3\CMS\Core\Resource\Folder|false Returns the new foldername upon success
891 */
892 public function func_newfolder($cmds)
893 {
894 $targetFolderObject = $this->getFileObject($cmds['target']);
895 if (!$targetFolderObject instanceof Folder) {
896 $this->writeLog(6, 2, 104, 'Destination "%s" was not a directory', [$cmds['target']]);
897 $this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
898 return false;
899 }
900 $resultObject = null;
901 $folderName = $cmds['data'];
902 try {
903 $resultObject = $targetFolderObject->createFolder($folderName);
904 $this->writeLog(6, 0, 1, 'Directory "%s" created in "%s"', [$folderName, $targetFolderObject->getIdentifier()]);
905 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryCreatedIn', [$folderName, $targetFolderObject->getIdentifier()], FlashMessage::OK);
906 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException $e) {
907 $this->writeLog(6, 1, 104, 'Invalid folder name "%s"!', [$folderName]);
908 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCreateDirectories', [$folderName]);
909 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException $e) {
910 $this->writeLog(6, 1, 103, 'You are not allowed to create directories!', []);
911 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCreateDirectories');
912 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
913 $this->writeLog(6, 1, 102, 'Destination path "%s" was not within your mountpoints!', [$targetFolderObject->getIdentifier()]);
914 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
915 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
916 $this->writeLog(6, 1, 101, 'File or directory "%s" existed already!', [$folderName]);
917 $this->addMessageToFlashMessageQueue('FileUtility.FileOrDirectoryExistedAlready', [$folderName]);
918 } catch (\RuntimeException $e) {
919 $this->writeLog(6, 1, 100, 'Directory "%s" not created. Write-permission problem in "%s"?', [$folderName, $targetFolderObject->getIdentifier()]);
920 $this->addMessageToFlashMessageQueue('FileUtility.DirectoryNotCreated', [$folderName, $targetFolderObject->getIdentifier()]);
921 }
922 return $resultObject;
923 }
924
925 /**
926 * This creates a new file. (action=8)
927 * $cmds['data'] (string): The new file name
928 * $cmds['target'] (string): The path where to create it.
929 * + example "2:targetpath/targetfolder/"
930 *
931 * @param array $cmds Command details as described above
932 * @return string Returns the new filename upon success
933 */
934 public function func_newfile($cmds)
935 {
936 $targetFolderObject = $this->getFileObject($cmds['target']);
937 if (!$targetFolderObject instanceof Folder) {
938 $this->writeLog(8, 2, 104, 'Destination "%s" was not a directory', [$cmds['target']]);
939 $this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
940 return false;
941 }
942 $resultObject = null;
943 $fileName = $cmds['data'];
944 try {
945 $resultObject = $targetFolderObject->createFile($fileName);
946 $this->writeLog(8, 0, 1, 'File created: "%s"', [$fileName]);
947 if ($resultObject->getName() !== $fileName) {
948 $this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$fileName, $resultObject->getName()], FlashMessage::WARNING);
949 }
950 $this->addMessageToFlashMessageQueue('FileUtility.FileCreated', [$resultObject->getName()], FlashMessage::OK);
951 } catch (IllegalFileExtensionException $e) {
952 $this->writeLog(8, 1, 106, 'Extension of file "%s" was not allowed!', [$fileName]);
953 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileWasNotAllowed', [$fileName]);
954 } catch (InsufficientFolderWritePermissionsException $e) {
955 $this->writeLog(8, 1, 103, 'You are not allowed to create files!', []);
956 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCreateFiles');
957 } catch (NotInMountPointException $e) {
958 $this->writeLog(8, 1, 102, 'Destination path "%s" was not within your mountpoints!', [$targetFolderObject->getIdentifier()]);
959 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
960 } catch (ExistingTargetFileNameException $e) {
961 $this->writeLog(8, 1, 101, 'File existed already in "%s"!', [$targetFolderObject->getIdentifier()]);
962 $this->addMessageToFlashMessageQueue('FileUtility.FileExistedAlreadyIn', [$targetFolderObject->getIdentifier()]);
963 } catch (InvalidFileNameException $e) {
964 $this->writeLog(8, 1, 106, 'File name "%s" was not allowed!', [$fileName]);
965 $this->addMessageToFlashMessageQueue('FileUtility.FileNameWasNotAllowed', [$fileName]);
966 } catch (\RuntimeException $e) {
967 $this->writeLog(8, 1, 100, 'File "%s" was not created! Write-permission problem in "%s"?', [$fileName, $targetFolderObject->getIdentifier()]);
968 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCreated', [$fileName, $targetFolderObject->getIdentifier()]);
969 }
970 return $resultObject;
971 }
972
973 /**
974 * Editing textfiles or folders (action=9)
975 *
976 * @param array $cmds $cmds['data'] is the new content. $cmds['target'] is the target (file or dir)
977 * @return bool Returns TRUE on success
978 */
979 public function func_edit($cmds)
980 {
981 // Example identifier for $cmds['target'] => "4:mypath/tomyfolder/myfile.jpg"
982 // for backwards compatibility: the combined file identifier was the path+filename
983 $fileIdentifier = $cmds['target'];
984 $fileObject = $this->getFileObject($fileIdentifier);
985 // Example identifier for $cmds['target'] => "2:targetpath/targetfolder/"
986 $content = $cmds['data'];
987 if (!$fileObject instanceof File) {
988 $this->writeLog(9, 2, 123, 'Target "%s" was not a file!', [$fileIdentifier]);
989 $this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotAFile', [$fileIdentifier]);
990 return false;
991 }
992 $extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
993 if (!GeneralUtility::inList($extList, $fileObject->getExtension())) {
994 $this->writeLog(9, 1, 102, 'File extension "%s" is not a textfile format! (%s)', [$fileObject->getExtension(), $extList]);
995 $this->addMessageToFlashMessageQueue('FileUtility.FileExtensionIsNotATextfileFormat', [$fileObject->getExtension(), $extList]);
996 return false;
997 }
998 try {
999 $fileObject->setContents($content);
1000 clearstatcache();
1001 $this->writeLog(9, 0, 1, 'File saved to "%s", bytes: %s, MD5: %s ', [$fileObject->getIdentifier(), $fileObject->getSize(), md5($content)]);
1002 $this->addMessageToFlashMessageQueue('FileUtility.FileSavedToBytesMd5', [$fileObject->getIdentifier(), $fileObject->getSize(), md5($content)], FlashMessage::OK);
1003 return true;
1004 } catch (InsufficientUserPermissionsException $e) {
1005 $this->writeLog(9, 1, 104, 'You are not allowed to edit files!', []);
1006 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToEditFiles');
1007 return false;
1008 } catch (InsufficientFileWritePermissionsException $e) {
1009 $this->writeLog(9, 1, 100, 'File "%s" was not saved! Write-permission problem?', [$fileObject->getIdentifier()]);
1010 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotSaved', [$fileObject->getIdentifier()]);
1011 return false;
1012 } catch (IllegalFileExtensionException $e) {
1013 $this->writeLog(9, 1, 100, 'File "%s" was not saved! File extension rejected!', [$fileObject->getIdentifier()]);
1014 $this->addMessageToFlashMessageQueue('FileUtility.FileWasNotSaved', [$fileObject->getIdentifier()]);
1015 return false;
1016 }
1017 }
1018
1019 /**
1020 * Upload of files (action=1)
1021 * when having multiple uploads (HTML5-style), the array $_FILES looks like this:
1022 * Array(
1023 * [upload_1] => Array(
1024 * [name] => Array(
1025 * [0] => GData - Content-Elements and Media-Gallery.pdf
1026 * [1] => CMS Expo 2011.txt
1027 * )
1028 * [type] => Array(
1029 * [0] => application/pdf
1030 * [1] => text/plain
1031 * )
1032 * [tmp_name] => Array(
1033 * [0] => /Applications/MAMP/tmp/php/phpNrOB43
1034 * [1] => /Applications/MAMP/tmp/php/phpD2HQAK
1035 * )
1036 * [size] => Array(
1037 * [0] => 373079
1038 * [1] => 1291
1039 * )
1040 * )
1041 * )
1042 * in HTML you'd need sth like this: <input type="file" name="upload_1[]" multiple="true" />
1043 *
1044 * @param array $cmds $cmds['data'] is the ID-number (points to the global var that holds the filename-ref
1045 * ($_FILES['upload_' . $id]['name']) . $cmds['target'] is the target directory, $cmds['charset']
1046 * is the the character set of the file name (utf-8 is needed for JS-interaction)
1047 * @return File[] | FALSE Returns an array of new file objects upon success. False otherwise
1048 */
1049 public function func_upload($cmds)
1050 {
1051 $uploadPosition = $cmds['data'];
1052 $uploadedFileData = $_FILES['upload_' . $uploadPosition];
1053 if (empty($uploadedFileData['name']) || is_array($uploadedFileData['name']) && empty($uploadedFileData['name'][0])) {
1054 $this->writeLog(1, 2, 108, 'No file was uploaded!', []);
1055 $this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploaded');
1056 return false;
1057 }
1058 // Example identifier for $cmds['target'] => "2:targetpath/targetfolder/"
1059 $targetFolderObject = $this->getFileObject($cmds['target']);
1060 // Uploading with non HTML-5-style, thus, make an array out of it, so we can loop over it
1061 if (!is_array($uploadedFileData['name'])) {
1062 $uploadedFileData = [
1063 'name' => [$uploadedFileData['name']],
1064 'type' => [$uploadedFileData['type']],
1065 'tmp_name' => [$uploadedFileData['tmp_name']],
1066 'size' => [$uploadedFileData['size']]
1067 ];
1068 }
1069 $resultObjects = [];
1070 $numberOfUploadedFilesForPosition = count($uploadedFileData['name']);
1071 // Loop through all uploaded files
1072 for ($i = 0; $i < $numberOfUploadedFilesForPosition; $i++) {
1073 $fileInfo = [
1074 'name' => $uploadedFileData['name'][$i],
1075 'type' => $uploadedFileData['type'][$i],
1076 'tmp_name' => $uploadedFileData['tmp_name'][$i],
1077 'size' => $uploadedFileData['size'][$i]
1078 ];
1079 try {
1080 /** @var $fileObject File */
1081 $fileObject = $targetFolderObject->addUploadedFile($fileInfo, (string)$this->existingFilesConflictMode);
1082 $fileObject = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($targetFolderObject->getStorage()->getUid(), $fileObject->getIdentifier());
1083 if ($this->existingFilesConflictMode->equals(DuplicationBehavior::REPLACE)) {
1084 $this->getIndexer($fileObject->getStorage())->updateIndexEntry($fileObject);
1085 }
1086 $resultObjects[] = $fileObject;
1087 $this->internalUploadMap[$uploadPosition] = $fileObject->getCombinedIdentifier();
1088 if ($fileObject->getName() !== $fileInfo['name']) {
1089 $this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$fileInfo['name'], $fileObject->getName()], FlashMessage::WARNING);
1090 }
1091 $this->writeLog(1, 0, 1, 'Uploading file "%s" to "%s"', [$fileInfo['name'], $targetFolderObject->getIdentifier()]);
1092 $this->addMessageToFlashMessageQueue('FileUtility.UploadingFileTo', [$fileInfo['name'], $targetFolderObject->getIdentifier()], FlashMessage::OK);
1093 } catch (InsufficientFileWritePermissionsException $e) {
1094 $this->writeLog(1, 1, 107, 'You are not allowed to override "%s"!', [$fileInfo['name']]);
1095 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToOverride', [$fileInfo['name']]);
1096 } catch (UploadException $e) {
1097 $this->writeLog(1, 2, 106, 'The upload has failed, no uploaded file found!', []);
1098 $this->addMessageToFlashMessageQueue('FileUtility.TheUploadHasFailedNoUploadedFileFound');
1099 } catch (InsufficientUserPermissionsException $e) {
1100 $this->writeLog(1, 1, 105, 'You are not allowed to upload files!', []);
1101 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToUploadFiles');
1102 } catch (UploadSizeException $e) {
1103 $this->writeLog(1, 1, 104, 'The uploaded file "%s" exceeds the size-limit', [$fileInfo['name']]);
1104 $this->addMessageToFlashMessageQueue('FileUtility.TheUploadedFileExceedsTheSize-limit', [$fileInfo['name']]);
1105 } catch (InsufficientFolderWritePermissionsException $e) {
1106 $this->writeLog(1, 1, 103, 'Destination path "%s" was not within your mountpoints!', [$targetFolderObject->getIdentifier()]);
1107 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
1108 } catch (IllegalFileExtensionException $e) {
1109 $this->writeLog(1, 1, 102, 'Extension of file name "%s" is not allowed in "%s"!', [$fileInfo['name'], $targetFolderObject->getIdentifier()]);
1110 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$fileInfo['name'], $targetFolderObject->getIdentifier()]);
1111 } catch (ExistingTargetFileNameException $e) {
1112 $this->writeLog(1, 1, 101, 'No unique filename available in "%s"!', [$targetFolderObject->getIdentifier()]);
1113 $this->addMessageToFlashMessageQueue('FileUtility.NoUniqueFilenameAvailableIn', [$targetFolderObject->getIdentifier()]);
1114 } catch (\RuntimeException $e) {
1115 $this->writeLog(1, 1, 100, 'Uploaded file could not be moved! Write-permission problem in "%s"?', [$targetFolderObject->getIdentifier()]);
1116 $this->addMessageToFlashMessageQueue('FileUtility.UploadedFileCouldNotBeMoved', [$targetFolderObject->getIdentifier()]);
1117 }
1118 }
1119
1120 return $resultObjects;
1121 }
1122
1123 /**
1124 * Replaces a file on the filesystem and changes the identifier of the persisted file object in sys_file if
1125 * keepFilename is not checked. If keepFilename is checked, only the file content will be replaced.
1126 *
1127 * @param array $cmdArr
1128 * @return array|bool
1129 * @throws Exception\InsufficientFileAccessPermissionsException
1130 * @throws Exception\InvalidFileException
1131 * @throws \RuntimeException
1132 */
1133 protected function replaceFile(array $cmdArr)
1134 {
1135 $uploadPosition = $cmdArr['data'];
1136 $fileInfo = $_FILES['replace_' . $uploadPosition];
1137 if (empty($fileInfo['name'])) {
1138 $this->writeLog(1, 2, 108, 'No file was uploaded for replacing!', []);
1139 $this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploadedForReplacing');
1140 return false;
1141 }
1142
1143 $keepFileName = ($cmdArr['keepFilename'] == 1) ? true : false;
1144 $resultObjects = [];
1145
1146 try {
1147 $fileObjectToReplace = $this->getFileObject($cmdArr['uid']);
1148 $folder = $fileObjectToReplace->getParentFolder();
1149 $resourceStorage = $fileObjectToReplace->getStorage();
1150
1151 $fileObject = $resourceStorage->addUploadedFile($fileInfo, $folder, $fileObjectToReplace->getName(), DuplicationBehavior::REPLACE);
1152
1153 // Check if there is a file that is going to be uploaded that has a different name as the replacing one
1154 // but exists in that folder as well.
1155 // rename to another name, but check if the name is already given
1156 if ($keepFileName === false) {
1157 // if a file with the same name already exists, we need to change it to _01 etc.
1158 // if the file does not exist, we can do a simple rename
1159 $resourceStorage->moveFile($fileObject, $folder, $fileInfo['name'], DuplicationBehavior::RENAME);
1160 }
1161
1162 $resultObjects[] = $fileObject;
1163 $this->internalUploadMap[$uploadPosition] = $fileObject->getCombinedIdentifier();
1164
1165 $this->writeLog(1, 0, 1, 'Replacing file "%s" to "%s"', [$fileInfo['name'], $fileObjectToReplace->getIdentifier()]);
1166 $this->addMessageToFlashMessageQueue('FileUtility.ReplacingFileTo', [$fileInfo['name'], $fileObjectToReplace->getIdentifier()], FlashMessage::OK);
1167 } catch (InsufficientFileWritePermissionsException $e) {
1168 $this->writeLog(1, 1, 107, 'You are not allowed to override "%s"!', [$fileInfo['name']]);
1169 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToOverride', [$fileInfo['name']]);
1170 } catch (UploadException $e) {
1171 $this->writeLog(1, 2, 106, 'The upload has failed, no uploaded file found!', []);
1172 $this->addMessageToFlashMessageQueue('FileUtility.TheUploadHasFailedNoUploadedFileFound');
1173 } catch (InsufficientUserPermissionsException $e) {
1174 $this->writeLog(1, 1, 105, 'You are not allowed to upload files!', []);
1175 $this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToUploadFiles');
1176 } catch (UploadSizeException $e) {
1177 $this->writeLog(1, 1, 104, 'The uploaded file "%s" exceeds the size-limit', [$fileInfo['name']]);
1178 $this->addMessageToFlashMessageQueue('FileUtility.TheUploadedFileExceedsTheSize-limit', [$fileInfo['name']]);
1179 } catch (InsufficientFolderWritePermissionsException $e) {
1180 $this->writeLog(1, 1, 103, 'Destination path "%s" was not within your mountpoints!', [$fileObjectToReplace->getIdentifier()]);
1181 $this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$fileObjectToReplace->getIdentifier()]);
1182 } catch (IllegalFileExtensionException $e) {
1183 $this->writeLog(1, 1, 102, 'Extension of file name "%s" is not allowed in "%s"!', [$fileInfo['name'], $fileObjectToReplace->getIdentifier()]);
1184 $this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$fileInfo['name'], $fileObjectToReplace->getIdentifier()]);
1185 } catch (ExistingTargetFileNameException $e) {
1186 $this->writeLog(1, 1, 101, 'No unique filename available in "%s"!', [$fileObjectToReplace->getIdentifier()]);
1187 $this->addMessageToFlashMessageQueue('FileUtility.NoUniqueFilenameAvailableIn', [$fileObjectToReplace->getIdentifier()]);
1188 } catch (\RuntimeException $e) {
1189 throw $e;
1190 }
1191 return $resultObjects;
1192 }
1193
1194 /**
1195 * Add flash message to message queue
1196 *
1197 * @param FlashMessage $flashMessage
1198 */
1199 protected function addFlashMessage(FlashMessage $flashMessage)
1200 {
1201 /** @var $flashMessageService FlashMessageService */
1202 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1203
1204 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
1205 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1206 $defaultFlashMessageQueue->enqueue($flashMessage);
1207 }
1208
1209 /**
1210 * Gets Indexer
1211 *
1212 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
1213 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
1214 */
1215 protected function getIndexer(ResourceStorage $storage)
1216 {
1217 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $storage);
1218 }
1219
1220 /**
1221 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1222 */
1223 protected function getBackendUser()
1224 {
1225 return $GLOBALS['BE_USER'];
1226 }
1227
1228 /**
1229 * Returns LanguageService
1230 *
1231 * @return LanguageService
1232 */
1233 protected function getLanguageService()
1234 {
1235 return $GLOBALS['LANG'];
1236 }
1237 }