1183 lines
76 KiB
PHP
1183 lines
76 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Core\Utility\File;
|
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Http\Message\UploadedFileInterface;
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
|
use TYPO3\CMS\Core\Database\Connection;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Http\ApplicationType;
|
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
|
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
|
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
|
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
|
|
use TYPO3\CMS\Core\Resource\Event\AfterFileCommandProcessedEvent;
|
|
use TYPO3\CMS\Core\Resource\Exception;
|
|
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
|
|
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException;
|
|
use TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException;
|
|
use TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileWritePermissionsException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InvalidFileException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
|
|
use TYPO3\CMS\Core\Resource\Exception\NotInMountPointException;
|
|
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
|
|
use TYPO3\CMS\Core\Resource\Exception\UploadException;
|
|
use TYPO3\CMS\Core\Resource\Exception\UploadSizeException;
|
|
use TYPO3\CMS\Core\Resource\File;
|
|
use TYPO3\CMS\Core\Resource\Folder;
|
|
use TYPO3\CMS\Core\Resource\Index\Indexer;
|
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
|
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
|
use TYPO3\CMS\Core\SysLog\Action\File as SystemLogFileAction;
|
|
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
|
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
|
|
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
|
use TYPO3\CMS\Core\Utility\Exception\NotImplementedMethodException;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Validation\ResultException;
|
|
|
|
/**
|
|
* Contains functions for performing file operations like copying, pasting, uploading, moving,
|
|
* deleting etc. through the TCE
|
|
*
|
|
* See document "TYPO3 Core API" for syntax
|
|
*
|
|
* This class contains functions primarily used by tce_file.php (TYPO3 Core Engine for file manipulation)
|
|
* Functions include copying, moving, deleting, uploading and so on...
|
|
*
|
|
* All fileoperations must be within the file mount paths of the user.
|
|
*
|
|
* @internal Since TYPO3 v10, this class should not be used anymore outside of TYPO3 Core, and is considered internal,
|
|
* as the FAL API should be used instead.
|
|
*/
|
|
class ExtendedFileUtility extends BasicFileUtility
|
|
{
|
|
/**
|
|
* Defines behaviour when uploading files with names that already exist;
|
|
*/
|
|
protected DuplicationBehavior $existingFilesConflictMode = DuplicationBehavior::CANCEL;
|
|
|
|
/**
|
|
* This array is self-explaining (look in the class below).
|
|
* It grants access to the functions. This could be set from outside in order to enabled functions to users.
|
|
* See also the function setActionPermissions() which takes input directly from the user-record
|
|
*/
|
|
public array $actionPerms = [
|
|
// File permissions
|
|
'addFile' => false,
|
|
'readFile' => false,
|
|
'writeFile' => false,
|
|
'copyFile' => false,
|
|
'moveFile' => false,
|
|
'renameFile' => false,
|
|
'deleteFile' => false,
|
|
// Folder permissions
|
|
'addFolder' => false,
|
|
'readFolder' => false,
|
|
'writeFolder' => false,
|
|
'copyFolder' => false,
|
|
'moveFolder' => false,
|
|
'renameFolder' => false,
|
|
'deleteFolder' => false,
|
|
'recursivedeleteFolder' => false,
|
|
];
|
|
|
|
/**
|
|
* Will contain map between upload ID and the final filename
|
|
*/
|
|
public array $internalUploadMap = [];
|
|
|
|
/**
|
|
* Container for FlashMessages so they can be localized
|
|
*
|
|
* @var FlashMessage[]
|
|
*/
|
|
protected array $flashMessages = [];
|
|
|
|
protected array $fileCmdMap = [];
|
|
|
|
/**
|
|
* @var array<string, UploadedFileInterface|list<UploadedFileInterface>>
|
|
*/
|
|
protected array $uploadedFiles = [];
|
|
|
|
protected ResourceFactory $fileFactory;
|
|
|
|
/**
|
|
* Get existingFilesConflictMode
|
|
*/
|
|
public function getExistingFilesConflictMode(): string
|
|
{
|
|
return $this->existingFilesConflictMode->value;
|
|
}
|
|
|
|
/**
|
|
* Set existingFilesConflictMode
|
|
*/
|
|
public function setExistingFilesConflictMode(DuplicationBehavior $existingFilesConflictMode): void
|
|
{
|
|
$this->existingFilesConflictMode = $existingFilesConflictMode;
|
|
}
|
|
|
|
/**
|
|
* Initialization of the class
|
|
*
|
|
* @param array $fileCmds Array with the commands to execute. See "TYPO3 Core API" document
|
|
*/
|
|
public function start(array $fileCmds, array $uploadedFiles): void
|
|
{
|
|
// Initialize Object Factory
|
|
$this->fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
|
|
// Initializing file processing commands:
|
|
$this->fileCmdMap = $fileCmds;
|
|
$this->uploadedFiles = $uploadedFiles;
|
|
}
|
|
|
|
/**
|
|
* Sets the file action permissions.
|
|
* If no argument is given, permissions of the currently logged in backend user are taken into account.
|
|
*
|
|
* @param array $permissions File Permissions.
|
|
*/
|
|
public function setActionPermissions(array $permissions = [])
|
|
{
|
|
if (empty($permissions)) {
|
|
$permissions = $this->getBackendUser()->getFilePermissions();
|
|
}
|
|
$this->actionPerms = $permissions;
|
|
}
|
|
|
|
/**
|
|
* Processing the command array in $this->fileCmdMap
|
|
*
|
|
* @return mixed FALSE, if the file functions were not initialized
|
|
* @throws \UnexpectedValueException
|
|
*/
|
|
public function processData()
|
|
{
|
|
$result = [];
|
|
if ($this->fileCmdMap !== []) {
|
|
// Check if there were uploads expected, but no one made
|
|
if ($this->fileCmdMap['upload'] ?? false) {
|
|
$uploads = $this->fileCmdMap['upload'];
|
|
foreach ($uploads as $upload) {
|
|
$uploadedFileIndex = 'upload_' . $upload['data'];
|
|
if (!$this->uploadedFileHasClientName($this->uploadedFiles[$uploadedFileIndex] ?? null)) {
|
|
unset($this->fileCmdMap['upload'][$upload['data']]);
|
|
}
|
|
}
|
|
if (empty($this->fileCmdMap['upload'])) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'No file was uploaded');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploaded');
|
|
}
|
|
}
|
|
|
|
// Check if there were new folder names expected, but non given
|
|
if ($this->fileCmdMap['newfolder'] ?? false) {
|
|
foreach ($this->fileCmdMap['newfolder'] as $key => $cmdArr) {
|
|
if ((string)($cmdArr['data'] ?? '') === '') {
|
|
unset($this->fileCmdMap['newfolder'][$key]);
|
|
}
|
|
}
|
|
if (empty($this->fileCmdMap['newfolder'])) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'No name was provided for the new folder');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoNameForNewFolderGiven');
|
|
}
|
|
}
|
|
|
|
// Traverse each set of actions
|
|
foreach ($this->fileCmdMap as $action => $actionData) {
|
|
// Traverse all action data. More than one file might be affected at the same time.
|
|
if (is_array($actionData)) {
|
|
$result[$action] = [];
|
|
// We reset the array keys of $actionData to keep track of the corresponding
|
|
// result, while not changing the previous behaviour of $result[$action][].
|
|
foreach (array_values($actionData) as $key => $cmdArr) {
|
|
// Clear file stats
|
|
clearstatcache();
|
|
// Branch out based on command:
|
|
switch ($action) {
|
|
case 'delete':
|
|
$result[$action][$key] = $this->func_delete($cmdArr);
|
|
break;
|
|
case 'copy':
|
|
$result[$action][$key] = $this->func_copy($cmdArr);
|
|
break;
|
|
case 'move':
|
|
$result[$action][$key] = $this->func_move($cmdArr);
|
|
break;
|
|
case 'rename':
|
|
$result[$action][$key] = $this->func_rename($cmdArr);
|
|
break;
|
|
case 'newfolder':
|
|
$result[$action][$key] = $this->func_newfolder($cmdArr);
|
|
break;
|
|
case 'newfile':
|
|
$result[$action][$key] = $this->func_newfile($cmdArr);
|
|
break;
|
|
case 'editfile':
|
|
$result[$action][$key] = $this->func_edit($cmdArr);
|
|
break;
|
|
case 'upload':
|
|
$result[$action][$key] = $this->func_upload($cmdArr);
|
|
break;
|
|
case 'replace':
|
|
$result[$action][$key] = $this->replaceFile($cmdArr);
|
|
break;
|
|
}
|
|
|
|
GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
|
|
new AfterFileCommandProcessedEvent([$action => $cmdArr], $result[$action][$key], $this->existingFilesConflictMode->value)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param int $action The action number. See the functions in the class for a hint. Eg. edit is '9', upload is '1' ...
|
|
* @param int $severity The severity: 0 = message, 1 = error, 2 = System Error, 3 = security notice (admin)
|
|
* @param string $message This is the default, raw error message in english
|
|
* @param array $context Additional information when the log is shown
|
|
*/
|
|
protected function writeLog(int $action, int $severity, string $message, array $context = []): void
|
|
{
|
|
$this->getBackendUser()->writelog(SystemLogType::FILE, $action, $severity, null, $message, $context);
|
|
}
|
|
|
|
/**
|
|
* Adds a localized FlashMessage to the message queue
|
|
*
|
|
* @param string $localizationKey
|
|
* @param ContextualFeedbackSeverity $severity
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function addMessageToFlashMessageQueue($localizationKey, array $replaceMarkers = [], ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::ERROR)
|
|
{
|
|
if ($this->isBackendScope()) {
|
|
$label = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/fileMessages.xlf:' . $localizationKey);
|
|
$message = vsprintf($label, $replaceMarkers);
|
|
$flashMessage = new FlashMessage(
|
|
$message,
|
|
'',
|
|
$severity,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
}
|
|
}
|
|
|
|
protected function addEvaluationResultHintsToFlashMessageQueue(
|
|
ResultException $exception,
|
|
ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::ERROR,
|
|
): void {
|
|
if (!$this->isBackendScope()) {
|
|
return;
|
|
}
|
|
foreach ($exception->messages as $messageItem) {
|
|
$message = $messageItem->labelBag?->compile($this->getLanguageService()) ?? $messageItem->message;
|
|
$flashMessage = new FlashMessage(
|
|
$message,
|
|
'',
|
|
$severity,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
}
|
|
}
|
|
|
|
/*************************************
|
|
*
|
|
* File operation functions
|
|
*
|
|
**************************************/
|
|
/**
|
|
* Deleting files and folders (action=4)
|
|
*
|
|
* @param array $cmds $cmds['data'] is the file/folder to delete
|
|
* @return bool Returns TRUE upon success
|
|
*/
|
|
public function func_delete(array $cmds)
|
|
{
|
|
$result = false;
|
|
// Example identifier for $cmds['data'] => "4:mypath/tomyfolder/myfile.jpg"
|
|
// for backwards compatibility: the combined file identifier was the path+filename
|
|
try {
|
|
$fileObject = $this->getFileObject($cmds['data']);
|
|
} catch (ResourceDoesNotExistException $e) {
|
|
$flashMessage = new FlashMessage(
|
|
sprintf(
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.fileNotFound'),
|
|
$cmds['data']
|
|
),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.fileNotFound'),
|
|
ContextualFeedbackSeverity::ERROR,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
|
|
return false;
|
|
}
|
|
// checks to delete the file
|
|
if ($fileObject instanceof File) {
|
|
// check if the file still has references
|
|
// Exclude sys_file_metadata records as these are no use references
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
|
|
$refIndexRecords = $queryBuilder
|
|
->select('tablename', 'recuid', 'ref_uid')
|
|
->from('sys_refindex')
|
|
->where(
|
|
$queryBuilder->expr()->eq(
|
|
'ref_table',
|
|
$queryBuilder->createNamedParameter('sys_file')
|
|
),
|
|
$queryBuilder->expr()->eq(
|
|
'ref_uid',
|
|
$queryBuilder->createNamedParameter($fileObject->getUid(), Connection::PARAM_INT)
|
|
),
|
|
$queryBuilder->expr()->neq(
|
|
'tablename',
|
|
$queryBuilder->createNamedParameter('sys_file_metadata')
|
|
)
|
|
)
|
|
->executeQuery()
|
|
->fetchAllAssociative();
|
|
$deleteFile = true;
|
|
if (!empty($refIndexRecords)) {
|
|
$shortcutContent = [];
|
|
$brokenReferences = [];
|
|
|
|
foreach ($refIndexRecords as $fileReferenceRow) {
|
|
if ($fileReferenceRow['tablename'] === 'sys_file_reference') {
|
|
$row = $this->transformFileReferenceToRecordReference($fileReferenceRow);
|
|
if ($row === null) {
|
|
$brokenReferences[] = $fileReferenceRow['ref_uid'];
|
|
continue;
|
|
}
|
|
$shortcutRecord = BackendUtility::getRecord($row['tablename'], $row['recuid']);
|
|
|
|
if ($shortcutRecord) {
|
|
$shortcutContent[] = '[record:' . $row['tablename'] . ':' . $row['recuid'] . ']';
|
|
} else {
|
|
$brokenReferences[] = $fileReferenceRow['ref_uid'];
|
|
}
|
|
} else {
|
|
$shortcutContent[] = '[record:' . $fileReferenceRow['tablename'] . ':' . $fileReferenceRow['recuid'] . ']';
|
|
}
|
|
}
|
|
if (!empty($brokenReferences)) {
|
|
// render a message that the file has broken references
|
|
$flashMessage = new FlashMessage(
|
|
sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.fileHasBrokenReferences'), count($brokenReferences)),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.fileHasBrokenReferences'),
|
|
ContextualFeedbackSeverity::INFO,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
}
|
|
if (!empty($shortcutContent)) {
|
|
// render a message that the file could not be deleted
|
|
$flashMessage = new FlashMessage(
|
|
sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.fileNotDeletedHasReferences'), $fileObject->getName()) . ' ' . implode(', ', $shortcutContent),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.fileNotDeletedHasReferences'),
|
|
ContextualFeedbackSeverity::WARNING,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
$deleteFile = false;
|
|
}
|
|
}
|
|
|
|
if ($deleteFile) {
|
|
try {
|
|
$result = $fileObject->delete();
|
|
|
|
// show the user that the file was deleted
|
|
$flashMessage = new FlashMessage(
|
|
sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.fileDeleted'), $fileObject->getName()),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.fileDeleted'),
|
|
ContextualFeedbackSeverity::OK,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
// Log success
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" deleted', ['identifier' => $fileObject->getIdentifier()]);
|
|
} catch (InsufficientFileAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'Access denied for file "{identifier}" due to insufficient permissions', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToAccessTheFile', [$fileObject->getIdentifier()]);
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'The file or folder {destination} was not accessible within permitted mountpoints', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotWithinYourMountpoints', [$fileObject->getIdentifier()]);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'Delete failed for file "{identifier}": insufficient write permissions', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteFile', [$fileObject->getIdentifier()]);
|
|
}
|
|
}
|
|
} else {
|
|
if ($fileObject instanceof Folder && !$this->folderHasFilesInUse($fileObject)) {
|
|
try {
|
|
$result = $fileObject->delete(true);
|
|
if ($result) {
|
|
// notify the user that the folder was deleted
|
|
$flashMessage = new FlashMessage(
|
|
sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.folderDeleted'), $fileObject->getName()),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.folderDeleted'),
|
|
ContextualFeedbackSeverity::OK,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
// Log success
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::MESSAGE, 'Directory "{identifier}" deleted', ['identifier' => $fileObject->getIdentifier()]);
|
|
}
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'Delete failed for directory "{identifier}": recursive deletion is not supported', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteDirectory', [$fileObject->getIdentifier()]);
|
|
} catch (InsufficientFolderAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'Access denied for directory "{identifier}" due to insufficient permissions', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToAccessTheDirectory', [$fileObject->getIdentifier()]);
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'The file or folder {destination} was not accessible within permitted mountpoints', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotWithinYourMountpoints', [$fileObject->getIdentifier()]);
|
|
} catch (FileOperationErrorException $e) {
|
|
$this->writeLog(SystemLogFileAction::DELETE, SystemLogErrorClassification::USER_ERROR, 'Delete failed for directory "{identifier}": insufficient write permissions', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotDeleteDirectory', [$fileObject->getIdentifier()]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Checks files in given folder recursively for for existing references.
|
|
*
|
|
* Creates a flash message if there are references.
|
|
*
|
|
* @param Folder $folder
|
|
* @return bool TRUE if folder has files in use, FALSE otherwise
|
|
*/
|
|
public function folderHasFilesInUse(Folder $folder)
|
|
{
|
|
$files = $folder->getFiles(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, true);
|
|
if (empty($files)) {
|
|
return false;
|
|
}
|
|
|
|
/** @var int[] $fileUids */
|
|
$fileUids = [];
|
|
foreach ($files as $file) {
|
|
$fileUids[] = $file->getUid();
|
|
}
|
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
|
|
$numberOfReferences = $queryBuilder
|
|
->count('hash')
|
|
->from('sys_refindex')
|
|
->where(
|
|
$queryBuilder->expr()->eq(
|
|
'ref_table',
|
|
$queryBuilder->createNamedParameter('sys_file')
|
|
),
|
|
$queryBuilder->expr()->in(
|
|
'ref_uid',
|
|
$queryBuilder->createNamedParameter($fileUids, Connection::PARAM_INT_ARRAY)
|
|
),
|
|
$queryBuilder->expr()->neq(
|
|
'tablename',
|
|
$queryBuilder->createNamedParameter('sys_file_metadata')
|
|
)
|
|
)->executeQuery()->fetchOne();
|
|
|
|
$hasReferences = $numberOfReferences > 0;
|
|
if ($hasReferences) {
|
|
$flashMessage = new FlashMessage(
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.description.folderNotDeletedHasFilesWithReferences'),
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:message.header.folderNotDeletedHasFilesWithReferences'),
|
|
ContextualFeedbackSeverity::WARNING,
|
|
true
|
|
);
|
|
$this->addFlashMessage($flashMessage);
|
|
}
|
|
|
|
return $hasReferences;
|
|
}
|
|
|
|
/**
|
|
* Maps results from the fal file reference table on the
|
|
* structure of the normal reference index table.
|
|
*/
|
|
protected function transformFileReferenceToRecordReference(array $referenceRecord): ?array
|
|
{
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
|
|
$queryBuilder->getRestrictions()->removeAll();
|
|
$fileReference = $queryBuilder
|
|
->select('uid_foreign', 'tablenames', 'fieldname', 'sorting_foreign')
|
|
->from('sys_file_reference')
|
|
->where(
|
|
$queryBuilder->expr()->eq(
|
|
'uid',
|
|
$queryBuilder->createNamedParameter($referenceRecord['recuid'], Connection::PARAM_INT)
|
|
)
|
|
)
|
|
->executeQuery()
|
|
->fetchAssociative();
|
|
|
|
if ($fileReference === false) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'recuid' => $fileReference['uid_foreign'],
|
|
'tablename' => $fileReference['tablenames'],
|
|
'field' => $fileReference['fieldname'],
|
|
'flexpointer' => '',
|
|
'softref_key' => '',
|
|
'sorting' => $fileReference['sorting_foreign'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Gets a File or a Folder object from an identifier [storage]:[fileId]
|
|
*/
|
|
protected function getFileObject(string $identifier)
|
|
{
|
|
$object = $this->fileFactory->retrieveFileOrFolderObject($identifier);
|
|
if ($object === null) {
|
|
throw new InvalidFileException('The item ' . $identifier . ' was not a file or directory', 1320122453);
|
|
}
|
|
if ($object->getStorage()->isFallbackStorage()) {
|
|
throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889830);
|
|
}
|
|
return $object;
|
|
}
|
|
|
|
/**
|
|
* Copying files and folders (action=2)
|
|
*
|
|
* $cmds['data'] (string): The file/folder to copy
|
|
* + example "4:mypath/tomyfolder/myfile.jpg")
|
|
* + for backwards compatibility: the identifier was the path+filename
|
|
* $cmds['target'] (string): The path where to copy to.
|
|
* + example "2:targetpath/targetfolder/"
|
|
* $cmds['altName'] (string): Use an alternative name if the target already exists
|
|
*
|
|
* @param array $cmds Command details as described above
|
|
* @return \TYPO3\CMS\Core\Resource\File|false
|
|
*/
|
|
protected function func_copy($cmds)
|
|
{
|
|
$sourceFileObject = $this->getFileObject($cmds['data']);
|
|
$targetFolderObject = $this->getFileObject($cmds['target']);
|
|
// Basic check
|
|
if (!$targetFolderObject instanceof Folder) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::SYSTEM_ERROR, 'Destination "{identifier}" was not a directory', ['identifier' => $cmds['target']]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
|
|
return false;
|
|
}
|
|
// If this is TRUE, we append _XX to the file name if
|
|
$appendSuffixOnConflict = (string)($cmds['altName'] ?? '');
|
|
$resultObject = null;
|
|
$conflictMode = $appendSuffixOnConflict !== '' ? DuplicationBehavior::RENAME : DuplicationBehavior::CANCEL;
|
|
// Copying the file
|
|
if ($sourceFileObject instanceof File) {
|
|
try {
|
|
$resultObject = $sourceFileObject->copyTo($targetFolderObject, null, $conflictMode);
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'File copy denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCopyFiles');
|
|
} catch (InsufficientFileAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Resource access failed: source "{identifier}" or destination {"destination}" file is outside the configured mountpoints', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Extension of file name "{identifier}" is not allowed in "{destination}"', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'File "{identifier}" already exists in directory "{destination}"', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileAlreadyExistsInFolder', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (NotImplementedMethodException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'The function to copy a file between storages is not yet implemented');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToCopyAFileBetweenStoragesIsNotYetImplemented');
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::SYSTEM_ERROR, 'Copy failed for file "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCopiedTo', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
}
|
|
if ($resultObject) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" copied to "{destination}"', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $resultObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileCopiedTo', [$sourceFileObject->getIdentifier(), $resultObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
}
|
|
} else {
|
|
// Else means this is a Folder
|
|
$sourceFolderObject = $sourceFileObject;
|
|
try {
|
|
$resultObject = $sourceFolderObject->copyTo($targetFolderObject, null, $conflictMode);
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Directory copy denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCopyDirectories');
|
|
} catch (InsufficientFileAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Resource access failed: source "{identifier}" or destination {"destination}" file is outside the configured mountpoints', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (InsufficientFolderAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Access denied: insufficient permissions for destination directory "{destination}"', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouDontHaveFullAccessToTheDestinationDirectory', [$targetFolderObject->getIdentifier()]);
|
|
} catch (InvalidTargetFolderException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Copy failed: destination "{destination}" already contains a file or directory with the same name "{name}"', ['name' => $sourceFolderObject->getName(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CannotCopyFolderIntoTargetFolderBecauseTheTargetFolderIsAlreadyWithinTheFolderToBeCopied', [$sourceFolderObject->getName(), $targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFolderException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::USER_ERROR, 'Target "{destination}" already exists', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TargetAlreadyExists', [$targetFolderObject->getIdentifier()]);
|
|
} catch (NotImplementedMethodException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'The function to copy a folder between storages is not yet implemented');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToCopyAFolderBetweenStoragesIsNotYetImplemented');
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::SYSTEM_ERROR, 'Copy failed for directory "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotCopiedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
}
|
|
if ($resultObject) {
|
|
$this->writeLog(SystemLogFileAction::COPY, SystemLogErrorClassification::MESSAGE, 'Directory "{identifier}" copied to "{destination}"', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryCopiedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
}
|
|
}
|
|
return $resultObject;
|
|
}
|
|
|
|
/**
|
|
* Moving files and folders (action=3)
|
|
*
|
|
* $cmds['data'] (string): The file/folder to move
|
|
* + example "4:mypath/tomyfolder/myfile.jpg")
|
|
* + for backwards compatibility: the identifier was the path+filename
|
|
* $cmds['target'] (string): The path where to move to.
|
|
* + example "2:targetpath/targetfolder/"
|
|
* $cmds['altName'] (string): Use an alternative name if the target already exists
|
|
*
|
|
* @param array $cmds Command details as described above
|
|
* @return \TYPO3\CMS\Core\Resource\File|false
|
|
*/
|
|
protected function func_move($cmds)
|
|
{
|
|
$sourceFileObject = $this->getFileObject($cmds['data']);
|
|
$targetFolderObject = $this->getFileObject($cmds['target']);
|
|
// Basic check
|
|
if (!$targetFolderObject instanceof Folder) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::SYSTEM_ERROR, 'Destination "{destination}" was not a directory', ['destination' => $cmds['target']]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
|
|
return false;
|
|
}
|
|
$alternativeName = (string)($cmds['altName'] ?? '');
|
|
$resultObject = null;
|
|
// Moving the file
|
|
if ($sourceFileObject instanceof File) {
|
|
try {
|
|
$sourcePath = $sourceFileObject->getIdentifier();
|
|
if ($alternativeName !== '') {
|
|
// Don't allow overwriting existing files, but find a new name
|
|
$resultObject = $sourceFileObject->moveTo($targetFolderObject, $alternativeName, DuplicationBehavior::RENAME);
|
|
} else {
|
|
// Don't allow overwriting existing files
|
|
$resultObject = $sourceFileObject->moveTo($targetFolderObject, null, DuplicationBehavior::CANCEL);
|
|
}
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" moved to "{destination}"', ['identifier' => $sourcePath, 'destination' => $resultObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileMovedTo', [$sourcePath, $resultObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'File moving denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToMoveFiles');
|
|
} catch (InsufficientFileAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Resource access failed: source "{identifier}" or destination {"destination}" file is outside the configured mountpoints', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Extension of file name "{identifier}" is not allowed in "{destination}"', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'File "{identifier}" already exists in directory "{destination}"', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileAlreadyExistsInFolder', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (NotImplementedMethodException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'The function to move a file between storages is not yet implemented');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToMoveAFileBetweenStoragesIsNotYetImplemented');
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::SYSTEM_ERROR, 'Copy failed for file "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFileObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCopiedTo', [$sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
}
|
|
} else {
|
|
// Else means this is a Folder
|
|
$sourceFolderObject = $sourceFileObject;
|
|
try {
|
|
if ($alternativeName !== '') {
|
|
// Don't allow overwriting existing files, but find a new name
|
|
$resultObject = $sourceFolderObject->moveTo($targetFolderObject, $alternativeName, DuplicationBehavior::RENAME);
|
|
} else {
|
|
// Don't allow overwriting existing files
|
|
$resultObject = $sourceFolderObject->moveTo($targetFolderObject, null, DuplicationBehavior::RENAME);
|
|
}
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::MESSAGE, 'Directory "{identifier}" moved to "{destination}"', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryMovedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Directory moving denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToMoveDirectories');
|
|
} catch (InsufficientFileAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Resource access failed: source folder "{identifier}" or destination {"destination}" directory is outside the configured mountpoints', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CouldNotAccessAllNecessaryResources', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
} catch (InsufficientFolderAccessPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'You don\'t have full access to the destination directory "{destination}"', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouDontHaveFullAccessToTheDestinationDirectory', [$targetFolderObject->getIdentifier()]);
|
|
} catch (InvalidTargetFolderException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Move failed: destination "{destination}" is located within the source directory "{identifier}"', ['identifier' => $sourceFolderObject->getName(), 'destination' => $targetFolderObject->getName()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.CannotMoveFolderIntoTargetFolderBecauseTheTargetFolderIsAlreadyWithinTheFolderToBeMoved', [$sourceFolderObject->getName(), $targetFolderObject->getName()]);
|
|
} catch (ExistingTargetFolderException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'Target "{destination}" already exists', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TargetAlreadyExists', [$targetFolderObject->getIdentifier()]);
|
|
} catch (NotImplementedMethodException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::USER_ERROR, 'The function to move a folder between storages is not yet implemented');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheFunctionToMoveAFolderBetweenStoragesIsNotYetImplemented');
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::MOVE, SystemLogErrorClassification::SYSTEM_ERROR, 'Move failed for directory "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFolderObject->getIdentifier(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotMovedTo', [$sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()]);
|
|
}
|
|
}
|
|
return $resultObject;
|
|
}
|
|
|
|
/**
|
|
* Renaming files or folders (action=5)
|
|
*
|
|
* $cmds['data'] (string): The file/folder to copy
|
|
* + example "4:mypath/tomyfolder/myfile.jpg")
|
|
* + for backwards compatibility: the identifier was the path+filename
|
|
* $cmds['target'] (string): New name of the file/folder
|
|
*
|
|
* @param array $cmds Command details as described above
|
|
* @return \TYPO3\CMS\Core\Resource\File Returns the new file upon success
|
|
*/
|
|
public function func_rename($cmds)
|
|
{
|
|
$sourceFileObject = $this->getFileObject($cmds['data']);
|
|
$sourceFile = $sourceFileObject->getName();
|
|
$targetFile = $cmds['target'];
|
|
$resultObject = null;
|
|
if ($sourceFileObject instanceof File) {
|
|
try {
|
|
// Try to rename the File
|
|
$resultObject = $sourceFileObject->rename($targetFile, $this->existingFilesConflictMode);
|
|
if ($resultObject->getName() !== $targetFile) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'File renamed from "{identifier}" to "{destination}": unsupported characters were replaced', ['identifier' => $sourceFile, 'destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$targetFile, $resultObject->getName()], ContextualFeedbackSeverity::WARNING);
|
|
} else {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::MESSAGE, 'File renamed from "{identifier}" to "{destination}"', ['identifier' => $sourceFile, 'destination' => $targetFile]);
|
|
}
|
|
if ($sourceFile === $resultObject->getName()) {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileRenamedSameName', [$sourceFile], ContextualFeedbackSeverity::INFO);
|
|
} else {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileRenamedFromTo', [$sourceFile, $resultObject->getName()], ContextualFeedbackSeverity::OK);
|
|
}
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'File renaming denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToRenameFiles');
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Operation failed due to illegal file extension on "{identifier}" or "{destination}"', ['identifier' => $sourceFileObject->getName(), 'destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameOrWasNotAllowed', [$sourceFileObject->getName(), $targetFile]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Rename failed because the destination file "{destination}" already exists', ['destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationExistedAlready', [$targetFile]);
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFile]);
|
|
} catch (ResultException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'File {identifier} was not renamed to {destination}', ['identifier' => $sourceFileObject->getName(), 'destination' => $targetFile]);
|
|
$this->addEvaluationResultHintsToFlashMessageQueue($e);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Rename failed for file "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFileObject->getName(), 'destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotRenamed', [$sourceFileObject->getName(), $targetFile]);
|
|
}
|
|
} else {
|
|
// Else means this is a Folder
|
|
try {
|
|
// Try to rename the Folder
|
|
$resultObject = $sourceFileObject->rename($targetFile);
|
|
$newFolderName = $resultObject->getName();
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::MESSAGE, 'Directory renamed from "{identifier}" to "{destination}"', ['identifier' => $sourceFile, 'destination' => $targetFile]);
|
|
if ($sourceFile === $newFolderName) {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryRenamedSameName', [$sourceFile], ContextualFeedbackSeverity::INFO);
|
|
} else {
|
|
if ($newFolderName === $targetFile) {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryRenamedFromTo', [$sourceFile, $newFolderName], ContextualFeedbackSeverity::OK);
|
|
} else {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryRenamedFromToCharReplaced', [$sourceFile, $newFolderName], ContextualFeedbackSeverity::WARNING);
|
|
}
|
|
}
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Directory renaming denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToRenameDirectories');
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Rename failed because the destination folder "{destination}" already exists', ['destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationExistedAlready', [$targetFile]);
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFile]);
|
|
} catch (ResultException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'File {identifier} was not renamed to {destination}', ['identifier' => $sourceFileObject->getName(), 'destination' => $targetFile]);
|
|
$this->addEvaluationResultHintsToFlashMessageQueue($e);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::RENAME, SystemLogErrorClassification::USER_ERROR, 'Rename failed for directory "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $sourceFileObject->getName(), 'destination' => $targetFile]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryWasNotRenamed', [$sourceFileObject->getName(), $targetFile]);
|
|
}
|
|
}
|
|
return $resultObject;
|
|
}
|
|
|
|
/**
|
|
* This creates a new folder. (action=6)
|
|
*
|
|
* $cmds['data'] (string): The new folder name
|
|
* $cmds['target'] (string): The path where to copy to.
|
|
* + example "2:targetpath/targetfolder/"
|
|
*
|
|
* @param array $cmds Command details as described above
|
|
* @return Folder|false Returns the new foldername upon success
|
|
*/
|
|
public function func_newfolder($cmds)
|
|
{
|
|
$resultObject = false;
|
|
$targetFolderObject = $this->getFileObject($cmds['target']);
|
|
if (!$targetFolderObject instanceof Folder) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::SYSTEM_ERROR, 'Destination "{destination}" was not a directory', ['destination' => $cmds['target']]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
|
|
return false;
|
|
}
|
|
$folderName = $cmds['data'];
|
|
try {
|
|
$resultObject = $targetFolderObject->createFolder($folderName);
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::MESSAGE, 'Directory "{identifier}" created in "{destination}"', ['identifier' => $folderName, 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryCreatedIn', [$folderName, $targetFolderObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
} catch (InvalidFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'Invalid folder name "{identifier}"', ['identifier' => $folderName]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.InvalidFolderName', [$folderName]);
|
|
} catch (InsufficientFolderWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'Directory creation denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCreateDirectories');
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFolderException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'File or directory "{identifier}" already exists', ['identifier' => $folderName]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileOrDirectoryExistedAlready', [$folderName]);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FOLDER, SystemLogErrorClassification::USER_ERROR, 'Creation failed for directory "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $folderName, 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DirectoryNotCreated', [$folderName, $targetFolderObject->getIdentifier()]);
|
|
}
|
|
return $resultObject;
|
|
}
|
|
|
|
/**
|
|
* This creates a new file. (action=8)
|
|
* $cmds['data'] (string): The new file name
|
|
* $cmds['target'] (string): The path where to create it.
|
|
* + example "2:targetpath/targetfolder/"
|
|
*
|
|
* @param array $cmds Command details as described above
|
|
*/
|
|
public function func_newfile($cmds): File|false|null
|
|
{
|
|
$targetFolderObject = $this->getFileObject($cmds['target']);
|
|
if (!$targetFolderObject instanceof Folder) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::SYSTEM_ERROR, 'Destination "{destination}" was not a directory', ['destination' => $cmds['target']]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
|
|
return false;
|
|
}
|
|
$resultObject = null;
|
|
$fileName = $cmds['data'];
|
|
try {
|
|
$resultObject = $targetFolderObject->createFile($fileName);
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" created', ['identifier' => $fileName]);
|
|
if ($resultObject->getName() !== $fileName) {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$fileName, $resultObject->getName()], ContextualFeedbackSeverity::WARNING);
|
|
}
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileCreated', [$resultObject->getName()], ContextualFeedbackSeverity::OK);
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'Extension of file "{identifier}" was not allowed', ['identifier' => $fileName]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileWasNotAllowed', [$fileName]);
|
|
} catch (InsufficientFolderWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'File creation denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToCreateFiles');
|
|
} catch (NotInMountPointException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'File existed already in "{destination}"', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileExistedAlreadyIn', [$targetFolderObject->getIdentifier()]);
|
|
} catch (InvalidFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'File name "{identifier}" was not allowed', ['identifier' => $fileName]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileNameWasNotAllowed', [$fileName]);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::NEW_FILE, SystemLogErrorClassification::USER_ERROR, 'Creation failed for file "{identifier}" in "{destination}": insufficient write permissions', ['identifier' => $fileName, 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotCreated', [$fileName, $targetFolderObject->getIdentifier()]);
|
|
}
|
|
return $resultObject;
|
|
}
|
|
|
|
/**
|
|
* Editing textfiles or folders (action=9)
|
|
*
|
|
* @param array $cmds $cmds['data'] is the new content. $cmds['target'] is the target (file or dir)
|
|
* @return bool Returns TRUE on success
|
|
*/
|
|
public function func_edit($cmds)
|
|
{
|
|
// Example identifier for $cmds['target'] => "4:mypath/tomyfolder/myfile.jpg"
|
|
// for backwards compatibility: the combined file identifier was the path+filename
|
|
$fileIdentifier = $cmds['target'];
|
|
$fileObject = $this->getFileObject($fileIdentifier);
|
|
if (!$fileObject instanceof File) {
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::SYSTEM_ERROR, 'Target "{destination}" was not a file', ['destination' => $fileIdentifier]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TargetWasNotAFile', [$fileIdentifier]);
|
|
return false;
|
|
}
|
|
if (!$fileObject->isTextFile()) {
|
|
$extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::USER_ERROR, 'Unsupported text file extension "{extension}" (allowed: {allowedExtensions})', ['extension' => $fileObject->getExtension(), 'allowedExtensions' => $extList]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileExtensionIsNotATextfileFormat', [$fileObject->getExtension(), $extList]);
|
|
return false;
|
|
}
|
|
try {
|
|
// Example identifier for $cmds['target'] => "2:targetpath/targetfolder/"
|
|
$content = $cmds['data'];
|
|
$fileObject->setContents($content);
|
|
clearstatcache();
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::MESSAGE, 'File saved to "{identifier}", bytes: {size}', ['identifier' => $fileObject->getIdentifier(), 'size' => $fileObject->getSize()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileSavedTo', [$fileObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
return true;
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::USER_ERROR, 'File editing denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToEditFiles');
|
|
return false;
|
|
} catch (InsufficientFileWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::USER_ERROR, 'Save failed for file "{identifier}": insufficient write permissions', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotSaved', [$fileObject->getIdentifier()]);
|
|
return false;
|
|
} catch (IllegalFileExtensionException|\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::EDIT, SystemLogErrorClassification::USER_ERROR, 'Save failed for file "{identifier}": file extension rejected', ['identifier' => $fileObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileWasNotSaved', [$fileObject->getIdentifier()]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload of files (action=1)
|
|
* in HTML you'd need sth like this: <input type="file" name="upload_1[]" multiple="true" />
|
|
*
|
|
* @param array $cmds $cmds['data'] is the ID-number (points to the global var that holds the filename-ref
|
|
* ($this->uploadedFiles['upload_' . $id]['name']) . $cmds['target'] is the target directory, $cmds['charset']
|
|
* is the the character set of the file name (utf-8 is needed for JS-interaction)
|
|
* @return File[]|bool Returns an array of new file objects upon success. False otherwise
|
|
*/
|
|
public function func_upload($cmds)
|
|
{
|
|
$uploadPosition = $cmds['data'];
|
|
$uploadedFileData = $this->uploadedFiles['upload_' . $uploadPosition] ?? null;
|
|
if (!$this->uploadedFileHasClientName($uploadedFileData)) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::SYSTEM_ERROR, 'No file was uploaded');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploaded');
|
|
return false;
|
|
}
|
|
// Example identifier for $cmds['target'] => "2:targetpath/targetfolder/"
|
|
$targetFolderObject = $this->getFileObject($cmds['target']);
|
|
// Uploading with non HTML-5-style, thus, make an array out of it, so we can loop over it
|
|
if (!is_array($uploadedFileData)) {
|
|
$uploadedFileData = [$uploadedFileData];
|
|
}
|
|
$resultObjects = [];
|
|
// Loop through all uploaded files
|
|
foreach ($uploadedFileData as $uploadedFile) {
|
|
try {
|
|
$fileObject = $targetFolderObject->addUploadedFile($uploadedFile, $this->existingFilesConflictMode);
|
|
if ($this->existingFilesConflictMode === DuplicationBehavior::REPLACE) {
|
|
$this->getIndexer($fileObject->getStorage())->updateIndexEntry($fileObject);
|
|
}
|
|
$resultObjects[] = $fileObject;
|
|
$this->internalUploadMap[$uploadPosition] = $fileObject->getCombinedIdentifier();
|
|
if ($fileObject->getName() !== $uploadedFile->getClientFilename()) {
|
|
$this->addMessageToFlashMessageQueue('FileUtility.FileNameSanitized', [$uploadedFile->getClientFilename(), $fileObject->getName()], ContextualFeedbackSeverity::WARNING);
|
|
}
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" uploaded to "{destination}"', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.UploadingFileTo', [$uploadedFile->getClientFilename(), $targetFolderObject->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
} catch (InsufficientFileWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Overwrite denied for "{identifier}" due to insufficient permissions', ['identifier' => $uploadedFile->getClientFilename()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToOverride', [$uploadedFile->getClientFilename()]);
|
|
} catch (UploadException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::SYSTEM_ERROR, 'Upload failed because no file was provided');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheUploadHasFailedNoUploadedFileFound');
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'File uploading denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToUploadFiles');
|
|
} catch (UploadSizeException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Upload failed: file "{identifier}" exceeds the configured size limit', ['identifier' => $uploadedFile->getClientFilename()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheUploadedFileExceedsTheSize-limit', [$uploadedFile->getClientFilename()]);
|
|
} catch (InsufficientFolderWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$targetFolderObject->getIdentifier()]);
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Extension of file name "{identifier}" is not allowed in "{destination}"', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$uploadedFile->getClientFilename(), $targetFolderObject->getIdentifier()]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'No unique filename available in "{destination}"', ['destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoUniqueFilenameAvailableIn', [$targetFolderObject->getIdentifier()]);
|
|
} catch (ResultException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Uploading file "{identifier}" to "{destination}" failed', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $targetFolderObject->getIdentifier()]);
|
|
$this->addEvaluationResultHintsToFlashMessageQueue($e);
|
|
} catch (\RuntimeException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Move failed for the uploaded file in "{destination}": insufficient write permissions. Error: {error}', ['destination' => $targetFolderObject->getIdentifier(), 'error' => $e->getMessage()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.UploadedFileCouldNotBeMoved', [$targetFolderObject->getIdentifier()]);
|
|
}
|
|
}
|
|
|
|
return $resultObjects;
|
|
}
|
|
|
|
/**
|
|
* Replaces a file on the filesystem and changes the identifier of the persisted file object in sys_file if
|
|
* keepFilename is not checked. If keepFilename is checked, only the file content will be replaced.
|
|
*
|
|
* @return array|bool
|
|
* @throws Exception\InsufficientFileAccessPermissionsException
|
|
* @throws Exception\InvalidFileException
|
|
* @throws \RuntimeException
|
|
*/
|
|
protected function replaceFile(array $cmdArr)
|
|
{
|
|
$fileObjectToReplace = null;
|
|
$uploadPosition = $cmdArr['data'];
|
|
$uploadedFile = $this->uploadedFiles['replace_' . $uploadPosition];
|
|
if (empty($uploadedFile->getClientFilename())) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::SYSTEM_ERROR, 'No file was uploaded for replacement');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoFileWasUploadedForReplacing');
|
|
return false;
|
|
}
|
|
|
|
$keepFileName = (bool)($cmdArr['keepFilename'] ?? false);
|
|
$resultObjects = [];
|
|
|
|
try {
|
|
$fileObjectToReplace = $this->getFileObject($cmdArr['uid']);
|
|
$folder = $fileObjectToReplace->getParentFolder();
|
|
$resourceStorage = $fileObjectToReplace->getStorage();
|
|
$uploadedFileExtension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
|
|
|
|
if (!$keepFileName) {
|
|
$fileObject = $resourceStorage->replaceAndRenameUploadedFile($uploadedFile, $fileObjectToReplace);
|
|
} elseif ($uploadedFileExtension !== $fileObjectToReplace->getExtension()) {
|
|
// `keepFileName` would cause a failing consistency check, for instance, when adding `image/png` contents to an existing `file.pdf`.
|
|
// This step ensures the file is renamed from `file.pdf` to `file.png`.
|
|
$targetFileName = pathinfo($fileObjectToReplace->getName(), PATHINFO_FILENAME) . '.' . $uploadedFileExtension;
|
|
$fileObject = $resourceStorage->replaceAndRenameUploadedFile($uploadedFile, $fileObjectToReplace, $targetFileName);
|
|
} else {
|
|
$fileObject = $resourceStorage->addUploadedFile($uploadedFile, $folder, $fileObjectToReplace->getName(), DuplicationBehavior::REPLACE);
|
|
}
|
|
|
|
$resultObjects[] = $fileObject;
|
|
$this->internalUploadMap[$uploadPosition] = $fileObject->getCombinedIdentifier();
|
|
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::MESSAGE, 'File "{identifier}" replaced with "{destination}"', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $fileObjectToReplace->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ReplacingFileTo', [$uploadedFile->getClientFilename(), $fileObjectToReplace->getIdentifier()], ContextualFeedbackSeverity::OK);
|
|
} catch (InsufficientFileWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Overwrite denied for "{destination}" due to insufficient permissions', ['destination' => $uploadedFile->getClientFilename()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToOverride', [$uploadedFile->getClientFilename()]);
|
|
} catch (UploadException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::SYSTEM_ERROR, 'Upload failed because no file was provided');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheUploadHasFailedNoUploadedFileFound');
|
|
} catch (InsufficientUserPermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'File uploading denied due to insufficient permissions');
|
|
$this->addMessageToFlashMessageQueue('FileUtility.YouAreNotAllowedToUploadFiles');
|
|
} catch (UploadSizeException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Upload failed: file "{identifier}" exceeds the configured size limit', ['identifier' => $uploadedFile->getClientFilename()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.TheUploadedFileExceedsTheSize-limit', [$uploadedFile->getClientFilename()]);
|
|
} catch (InsufficientFolderWritePermissionsException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Destination path "{destination}" is outside the configured mountpoints', ['destination' => $fileObjectToReplace->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.DestinationPathWasNotWithinYourMountpoints', [$fileObjectToReplace->getIdentifier()]);
|
|
} catch (IllegalFileExtensionException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Extension of file name "{identifier}" is not allowed in "{destination}"', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $fileObjectToReplace->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.ExtensionOfFileNameIsNotAllowedIn', [$uploadedFile->getClientFilename(), $fileObjectToReplace->getIdentifier()]);
|
|
} catch (ExistingTargetFileNameException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'No unique filename available in "{destination}"', ['destination' => $fileObjectToReplace->getIdentifier()]);
|
|
$this->addMessageToFlashMessageQueue('FileUtility.NoUniqueFilenameAvailableIn', [$fileObjectToReplace->getIdentifier()]);
|
|
} catch (ResultException $e) {
|
|
$this->writeLog(SystemLogFileAction::UPLOAD, SystemLogErrorClassification::USER_ERROR, 'Replacing file "{identifier}" to "{destination}" failed', ['identifier' => $uploadedFile->getClientFilename(), 'destination' => $fileObjectToReplace->getIdentifier()]);
|
|
$this->addEvaluationResultHintsToFlashMessageQueue($e);
|
|
} catch (\RuntimeException $e) {
|
|
throw $e;
|
|
}
|
|
return $resultObjects;
|
|
}
|
|
|
|
/**
|
|
* Add flash message to message queue
|
|
*/
|
|
protected function addFlashMessage(FlashMessage $flashMessage)
|
|
{
|
|
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
|
|
|
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
|
$defaultFlashMessageQueue->enqueue($flashMessage);
|
|
}
|
|
|
|
protected function isBackendScope(): bool
|
|
{
|
|
return ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
|
|
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
|
|
}
|
|
|
|
protected function uploadedFileHasClientName(array|UploadedFileInterface|null $file): bool
|
|
{
|
|
if ($file instanceof UploadedFileInterface) {
|
|
return !empty($file->getClientFilename());
|
|
}
|
|
if (isset($file[0]) && $file[0] instanceof UploadedFileInterface) {
|
|
return !empty($file[0]->getClientFilename());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets Indexer
|
|
*
|
|
* @return \TYPO3\CMS\Core\Resource\Index\Indexer
|
|
*/
|
|
protected function getIndexer(ResourceStorage $storage)
|
|
{
|
|
return GeneralUtility::makeInstance(Indexer::class, $storage);
|
|
}
|
|
|
|
protected function getBackendUser(): BackendUserAuthentication
|
|
{
|
|
return $GLOBALS['BE_USER'];
|
|
}
|
|
|
|
protected function getLanguageService(): LanguageService
|
|
{
|
|
return $GLOBALS['LANG'];
|
|
}
|
|
}
|