TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
<?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\Impexp\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Impexp\Import;
|
||||
|
||||
/**
|
||||
* Import module controller
|
||||
*
|
||||
* @internal This class is not considered part of the public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class ImportController
|
||||
{
|
||||
protected const NO_UPLOAD = 0;
|
||||
protected const UPLOAD_DONE = 1;
|
||||
protected const UPLOAD_FAILED = 2;
|
||||
|
||||
/**
|
||||
* File extensions accepted by the import upload. Uploading any other file type is rejected
|
||||
* before the file is written to storage.
|
||||
*/
|
||||
protected const ALLOWED_UPLOAD_EXTENSIONS = ['t3d', 'xml'];
|
||||
protected const ALLOWED_UPLOAD_EXTENSION_LIST = '.t3d,.xml';
|
||||
|
||||
public function __construct(
|
||||
protected IconFactory $iconFactory,
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected ExtendedFileUtility $fileProcessor,
|
||||
protected ResourceFactory $resourceFactory,
|
||||
protected ComponentFactory $componentFactory,
|
||||
) {}
|
||||
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->getBackendUser()->isImportEnabled()) {
|
||||
throw new \RuntimeException(
|
||||
'Import module is disabled for non admin users and user TSconfig options.impexp.enableImportForNonAdminUser is not enabled.',
|
||||
1464435459
|
||||
);
|
||||
}
|
||||
|
||||
$backendUser = $this->getBackendUser();
|
||||
$languageService = $this->getLanguageService();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$parsedBody = $request->getParsedBody();
|
||||
|
||||
$id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
|
||||
$permsClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
$pageInfo = BackendUtility::readPageAccess($id, $permsClause) ?: [];
|
||||
if ($pageInfo === []) {
|
||||
throw new \RuntimeException("You don't have access to this page.", 1604308205);
|
||||
}
|
||||
|
||||
$inputData = $request->getParsedBody()['tx_impexp'] ?? $request->getQueryParams()['tx_impexp'] ?? [];
|
||||
if ($inputData['new_import'] ?? false) {
|
||||
unset($inputData['import_mode']);
|
||||
}
|
||||
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
|
||||
$import = GeneralUtility::makeInstance(Import::class);
|
||||
$import->setPid($id);
|
||||
// Resolve the upload destination server-side. The import upload target is pinned to this
|
||||
// folder and must never be taken from the (client-controlled) request body.
|
||||
$importFolder = $import->getOrCreateDefaultImportExportFolder();
|
||||
|
||||
$uploadStatus = self::NO_UPLOAD;
|
||||
$uploadedFileName = '';
|
||||
if ($request->getMethod() === 'POST' && empty($parsedBody)) {
|
||||
// This happens if the post request was larger than allowed on the server.
|
||||
$view->addFlashMessage(
|
||||
$languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:importdata_upload_nodata'),
|
||||
$languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:importdata_upload_error'),
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
);
|
||||
}
|
||||
if ($request->getMethod() === 'POST' && isset($parsedBody['_upload'])) {
|
||||
$uploadStatus = self::UPLOAD_FAILED;
|
||||
$file = $this->handleFileUpload($request, $importFolder, $view);
|
||||
if ($file !== null) {
|
||||
$inputData['file'] = $file->getCombinedIdentifier();
|
||||
$uploadStatus = self::UPLOAD_DONE;
|
||||
$uploadedFileName = $file->getName();
|
||||
}
|
||||
}
|
||||
|
||||
$this->configureImportFromFormDataAndImportIfRequested($view, $import, $inputData);
|
||||
|
||||
if (!$this->getBackendUser()->isAdmin()
|
||||
&& $import->getSiteConfigurations() !== []
|
||||
) {
|
||||
$view->addFlashMessage(
|
||||
$languageService->translate('importdata_siteConfigurationsAdminOnly', 'impexp.messages'),
|
||||
$languageService->translate('importdata_siteConfigurations', 'impexp.messages'),
|
||||
ContextualFeedbackSeverity::WARNING
|
||||
);
|
||||
}
|
||||
|
||||
$view->assignMultiple([
|
||||
'importFolder' => $importFolder?->getCombinedIdentifier() ?? '',
|
||||
'import' => $import,
|
||||
'errors' => $import->getErrorLog(),
|
||||
'preview' => $import->renderPreview(),
|
||||
'id' => $id,
|
||||
'fileSelectOptions' => $this->getSelectableFileList($import),
|
||||
'inData' => $inputData,
|
||||
'isAdmin' => $this->getBackendUser()->isAdmin(),
|
||||
'uploadedFile' => $uploadedFileName,
|
||||
'uploadStatus' => $uploadStatus,
|
||||
'allowedUploadExtensionList' => self::ALLOWED_UPLOAD_EXTENSION_LIST,
|
||||
]);
|
||||
$view->setModuleName('');
|
||||
$view->getDocHeaderComponent()->setPageBreadcrumb($pageInfo);
|
||||
if ((int)($pageInfo['uid'] ?? 0) > 0) {
|
||||
$view->addButtonToButtonBar($this->componentFactory->createViewButton(PreviewUriBuilder::create($pageInfo)
|
||||
->withRootLine(BackendUtility::BEgetRootLine($pageInfo['uid']))
|
||||
->buildDispatcherDataAttributes() ?? []));
|
||||
}
|
||||
return $view->renderResponse('Import');
|
||||
}
|
||||
|
||||
protected function handleFileUpload(ServerRequestInterface $request, ?Folder $importFolder, ModuleTemplate $view): ?File
|
||||
{
|
||||
if ($importFolder === null) {
|
||||
return null;
|
||||
}
|
||||
// Reject any file that is not an import file before it is written to storage. The import
|
||||
// upload must never be used to place arbitrary file types in a (potentially public) storage.
|
||||
$uploadedFile = $request->getUploadedFiles()['upload_1'] ?? null;
|
||||
if (!$uploadedFile instanceof UploadedFileInterface) {
|
||||
return null;
|
||||
}
|
||||
$uploadExtension = strtolower(pathinfo((string)$uploadedFile->getClientFilename(), PATHINFO_EXTENSION));
|
||||
if (!in_array($uploadExtension, self::ALLOWED_UPLOAD_EXTENSIONS, true)) {
|
||||
$view->addFlashMessage(
|
||||
$this->getLanguageService()->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:importdata_upload_invalidExtension'),
|
||||
$this->getLanguageService()->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:importdata_upload_error'),
|
||||
ContextualFeedbackSeverity::ERROR
|
||||
);
|
||||
return null;
|
||||
}
|
||||
$parsedBody = $request->getParsedBody() ?? [];
|
||||
$conflictMode = empty($parsedBody['overwriteExistingFiles']) ? DuplicationBehavior::CANCEL : DuplicationBehavior::REPLACE;
|
||||
// The upload target is pinned to the import/export folder resolved server-side and must
|
||||
// not be taken from the (client-controlled) request body, otherwise an uploaded file
|
||||
// could be redirected to an arbitrary, potentially publicly accessible, storage location.
|
||||
$fileCommands = [
|
||||
'upload' => [
|
||||
1 => [
|
||||
'target' => $importFolder->getCombinedIdentifier(),
|
||||
'data' => '1',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->fileProcessor->setActionPermissions();
|
||||
$this->fileProcessor->setExistingFilesConflictMode($conflictMode);
|
||||
$this->fileProcessor->start($fileCommands, $request->getUploadedFiles());
|
||||
$result = $this->fileProcessor->processData();
|
||||
// If upload went well, set the new file as the import file.
|
||||
return $result['upload'][0][0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \BadFunctionCallException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function configureImportFromFormDataAndImportIfRequested(ModuleTemplate $view, Import $import, array $inputData): void
|
||||
{
|
||||
$import->setUpdate((bool)($inputData['do_update'] ?? false));
|
||||
$import->setImportMode((array)($inputData['import_mode'] ?? null));
|
||||
$import->setEnableLogging((bool)($inputData['enableLogging'] ?? false));
|
||||
$import->setGlobalIgnorePid((bool)($inputData['global_ignore_pid'] ?? false));
|
||||
$import->setForceAllUids((bool)($inputData['force_all_UIDS'] ?? false));
|
||||
$import->setShowDiff(!(bool)($inputData['notShowDiff'] ?? false));
|
||||
$import->setSoftrefInputValues((array)($inputData['softrefInputValues'] ?? null));
|
||||
if (!empty($inputData['file'])) {
|
||||
if (PathUtility::isExtensionPath($inputData['file'])) {
|
||||
$filePath = $inputData['file'];
|
||||
} else {
|
||||
$filePath = $this->getFilePathWithinFileMountBoundaries((string)$inputData['file']);
|
||||
}
|
||||
try {
|
||||
$import->loadFile($filePath);
|
||||
$import->checkImportPrerequisites();
|
||||
if ($inputData['import_file'] ?? false) {
|
||||
$import->importData();
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$view->addFlashMessage($e->getMessage(), '', ContextualFeedbackSeverity::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFilePathWithinFileMountBoundaries(string $filePath): string
|
||||
{
|
||||
try {
|
||||
$file = $this->resourceFactory->getFileObjectFromCombinedIdentifier($filePath);
|
||||
return $file->getForLocalProcessing(false);
|
||||
} catch (\Exception $exception) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected function getSelectableFileList(Import $import): array
|
||||
{
|
||||
$exportFiles = [];
|
||||
|
||||
// Fileadmin
|
||||
$folder = $import->getOrCreateDefaultImportExportFolder();
|
||||
if ($folder !== null) {
|
||||
$filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
|
||||
$filter->setAllowedFileExtensions(['t3d', 'xml']);
|
||||
$folder->getStorage()->addFileAndFolderNameFilter([$filter, 'filterFileList']);
|
||||
$exportFiles = $folder->getFiles();
|
||||
}
|
||||
$selectableFiles = [''];
|
||||
foreach ($exportFiles as $file) {
|
||||
$selectableFiles[$file->getCombinedIdentifier()] = $file->getPublicUrl();
|
||||
}
|
||||
|
||||
// Extension Distribution
|
||||
if ($this->getBackendUser()->isAdmin()) {
|
||||
$possibleImportFiles = [
|
||||
'Initialisation/data.t3d',
|
||||
'Initialisation/data.xml',
|
||||
];
|
||||
$activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
|
||||
foreach ($activePackages as $package) {
|
||||
foreach ($possibleImportFiles as $possibleImportFile) {
|
||||
if (!file_exists($package->getPackagePath() . $possibleImportFile)) {
|
||||
continue;
|
||||
}
|
||||
$selectableFiles['EXT:' . $package->getPackageKey() . '/' . $possibleImportFile] = 'EXT:' . $package->getPackageKey() . '/' . $possibleImportFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $selectableFiles;
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user