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