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']; } }