1, 'preset' => [], 'external_static' => [ 'tables' => [], ], 'external_ref' => [ 'tables' => [], ], 'pagetree' => [ 'tables' => [], ], 'extension_dep' => [], 'meta' => [ 'title' => '', 'description' => '', 'notes' => '', ], 'record' => [], 'list' => [], 'includeSiteConfigurations' => 0, ]; public function __construct( protected readonly IconFactory $iconFactory, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly ResponseFactoryInterface $responseFactory, protected readonly PresetRepository $presetRepository, protected readonly TcaSchemaFactory $tcaSchemaFactory, ) {} public function handleRequest(ServerRequestInterface $request): ResponseInterface { if ($this->getBackendUser()->isExportEnabled() === false) { throw new \RuntimeException( 'Export module is disabled for non admin users and ' . 'user TSconfig options.impexp.enableExportForNonAdminUser is not enabled.', 1636901978 ); } $backendUser = $this->getBackendUser(); $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.", 1604308206); } // @todo: Only small parts of tx_impexp can be hand over as GET, e.g. ['list'] and 'id', drop GET of everything else. // Also, there's a clash with id: it can be ['list']'table:id', it can be 'id', it can be tx_impexp['id']. This // should be de-messed somehow. $inputDataFromGetPost = $parsedBody['tx_impexp'] ?? $queryParams['tx_impexp'] ?? []; $inputData = $this->defaultInputData; ArrayUtility::mergeRecursiveWithOverrule($inputData, $inputDataFromGetPost); if ($inputData['resetExclude'] ?? false) { $inputData['exclude'] = []; } $inputData['preset']['public'] = (int)($inputData['preset']['public'] ?? 0); $view = $this->moduleTemplateFactory->create($request); $presetAction = $parsedBody['preset'] ?? []; $inputData = $this->processPresets($view, $presetAction, $inputData); $export = $this->configureExportFromFormData($inputData); $export->process(); if ($inputData['download_export'] ?? false) { return $this->getDownload($export); } $saveFolder = $export->getOrCreateDefaultImportExportFolder(); if (($inputData['save_export'] ?? false) && $saveFolder !== null) { $this->saveExportToFile($view, $export, $saveFolder); } $inputData['filename'] = $export->getExportFileName(); $view->assignMultiple([ 'id' => $id, 'errors' => $export->getErrorLog(), 'preview' => $export->renderPreview(), 'tableSelectOptions' => $this->getTableSelectOptions(['pages']), 'treeHTML' => $export->getTreeHTML(), 'levelSelectOptions' => $this->getPageLevelSelectOptions($inputData), 'records' => $this->getRecordSelectOptions($inputData), 'tableList' => $this->getSelectableTableList($inputData), 'externalReferenceTableSelectOptions' => $this->getTableSelectOptions(), 'externalStaticTableSelectOptions' => $this->getTableSelectOptions(), 'presetSelectOptions' => $this->presetRepository->getPresets($id), 'fileName' => '', 'filetypeSelectOptions' => $this->getFileSelectOptions($export), 'saveFolder' => $saveFolder?->getPublicUrl() ?? '', 'hasSaveFolder' => true, 'extensions' => $this->getExtensionList(), 'inData' => $inputData, 'isAdmin' => $backendUser->isAdmin(), ]); $view->setModuleName(''); $view->getDocHeaderComponent()->setPageBreadcrumb($pageInfo); return $view->renderResponse('Export'); } protected function processPresets(ModuleTemplate $view, array $presetAction, array $inputData): array { if (empty($presetAction)) { return $inputData; } $presetUid = (int)$presetAction['select']; try { if (isset($presetAction['save'])) { if ($presetUid > 0) { // Update existing $this->presetRepository->updatePreset($presetUid, $inputData); $view->addFlashMessage('Preset #' . $presetUid . ' saved!', 'Presets', ContextualFeedbackSeverity::INFO); } else { // Insert new $this->presetRepository->createPreset($inputData); $view->addFlashMessage('New preset "' . $inputData['preset']['title'] . '" is created', 'Presets', ContextualFeedbackSeverity::INFO); } } if (isset($presetAction['delete'])) { if ($presetUid > 0) { $this->presetRepository->deletePreset($presetUid); $view->addFlashMessage('Preset #' . $presetUid . ' deleted!', 'Presets', ContextualFeedbackSeverity::INFO); } else { $view->addFlashMessage('ERROR: No preset selected for deletion.', 'Presets', ContextualFeedbackSeverity::ERROR); } } if (isset($presetAction['load']) || isset($presetAction['merge'])) { if ($presetUid > 0) { $presetData = $this->presetRepository->loadPreset($presetUid); if (isset($presetAction['merge'])) { // Merge records if (is_array($presetData['record'] ?? null)) { $inputData['record'] = array_merge((array)$inputData['record'], $presetData['record']); } // Merge lists if (is_array($presetData['list'] ?? null)) { $inputData['list'] = array_merge((array)$inputData['list'], $presetData['list']); } $view->addFlashMessage('Preset #' . $presetUid . ' merged!', 'Presets', ContextualFeedbackSeverity::INFO); } else { $inputData = $presetData; $view->addFlashMessage('Preset #' . $presetUid . ' loaded!', 'Presets', ContextualFeedbackSeverity::INFO); } } else { $view->addFlashMessage('ERROR: No preset selected for loading.', 'Presets', ContextualFeedbackSeverity::ERROR); } } } catch (PresetNotFoundException|InsufficientUserPermissionsException|MalformedPresetException $e) { $view->addFlashMessage($e->getMessage(), 'Presets', ContextualFeedbackSeverity::ERROR); } return $inputData; } protected function configureExportFromFormData(array $inputData): Export { $export = GeneralUtility::makeInstance(Export::class); $export->setExcludeMap((array)($inputData['exclude'] ?? [])); $export->setSoftrefCfg((array)($inputData['softrefCfg'] ?? [])); $export->setExtensionDependencies((($inputData['extension_dep'] ?? '') === '') ? [] : (array)$inputData['extension_dep']); $export->setShowStaticRelations((bool)($inputData['showStaticRelations'] ?? false)); $export->setExcludeDisabledRecords((bool)($inputData['excludeDisabled'] ?? false)); if (!empty($inputData['filetype'])) { $export->setExportFileType((string)$inputData['filetype']); } $export->setExportFileName((string)($inputData['filename'] ?? '')); $export->setRelStaticTables((($inputData['external_static']['tables'] ?? '') === '') ? [] : (array)$inputData['external_static']['tables']); $export->setRelOnlyTables((($inputData['external_ref']['tables'] ?? '') === '') ? [] : (array)$inputData['external_ref']['tables']); if (isset($inputData['save_export'], $inputData['saveFilesOutsideExportFile']) && $inputData['saveFilesOutsideExportFile'] === '1') { $export->setSaveFilesOutsideExportFile(true); } if ($this->getBackendUser()->isAdmin()) { $export->setIncludeSiteConfigurations((bool)($inputData['includeSiteConfigurations'] ?? false)); } $export->setTitle((string)($inputData['meta']['title'] ?? '')); $export->setDescription((string)($inputData['meta']['description'] ?? '')); $export->setNotes((string)($inputData['meta']['notes'] ?? '')); $export->setRecord((($inputData['record'] ?? '') === '') ? [] : (array)$inputData['record']); $export->setList((($inputData['list'] ?? '') === '') ? [] : (array)$inputData['list']); if (MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['id'] ?? null)) { $export->setPid((int)$inputData['pagetree']['id']); } if (MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['levels'] ?? null)) { $export->setLevels((int)$inputData['pagetree']['levels']); } $export->setTables((($inputData['pagetree']['tables'] ?? '') === '') ? [] : (array)$inputData['pagetree']['tables']); return $export; } protected function getDownload(Export $export): ResponseInterface { $fileName = $export->getOrGenerateExportFileNameWithFileExtension(); $fileContent = $export->render(); $response = $this->responseFactory->createResponse() ->withHeader('Content-Type', 'application/octet-stream') ->withHeader('Content-Length', (string)strlen($fileContent)) ->withHeader('Content-Disposition', 'attachment; filename=' . PathUtility::basename($fileName)); $response->getBody()->write($export->render()); return $response; } protected function saveExportToFile(ModuleTemplate $view, Export $export, Folder $saveFolder): void { $languageService = $this->getLanguageService(); try { $saveFile = $export->saveToFile(); $saveFileSize = $saveFile->getProperty('size'); $view->addFlashMessage( sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_savedInSBytes'), $saveFile->getPublicUrl(), GeneralUtility::formatSize($saveFileSize)), $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_savedFile') ); } catch (CoreException $e) { $view->addFlashMessage( sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_badPathS'), $saveFolder->getPublicUrl()), $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_problemsSavingFile'), ContextualFeedbackSeverity::ERROR ); } } protected function getPageLevelSelectOptions(array $inputData): array { $languageService = $this->getLanguageService(); $options = []; if (MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['id'] ?? '')) { $options = [ Export::LEVELS_RECORDS_ON_THIS_PAGE => $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makeconfig_tablesOnThisPage'), 0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'), 1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'), 2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'), 3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'), 4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'), Export::LEVELS_INFINITE => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'), ]; } return $options; } protected function getRecordSelectOptions(array $inputData): array { $records = []; foreach ($inputData['record'] ?? [] as $tableNameColonUid) { [$tableName, $recordUid] = explode(':', $tableNameColonUid); if ($record = BackendUtility::getRecordWSOL((string)$tableName, (int)$recordUid)) { $records[] = [ 'icon' => $this->iconFactory->getIconForRecord($tableName, $record, IconSize::SMALL)->render(), 'title' => BackendUtility::getRecordTitle($tableName, $record, true), 'tableName' => $tableName, 'recordUid' => $recordUid, ]; } } return $records; } protected function getSelectableTableList(array $inputData): array { $backendUser = $this->getBackendUser(); $languageService = $this->getLanguageService(); $tableList = []; foreach ($inputData['list'] ?? [] as $reference) { $referenceParts = explode(':', $reference); $tableName = $referenceParts[0]; if ($backendUser->check('tables_select', $tableName)) { // If the page is actually the root, handle it differently. // NOTE: we don't compare integers, because the number comes from the split string above if ($referenceParts[1] === '0') { $iconAndTitle = $this->iconFactory->getIcon('apps-pagetree-root', IconSize::SMALL)->render() . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']; } else { $record = BackendUtility::getRecordWSOL('pages', (int)$referenceParts[1]); $iconAndTitle = $this->iconFactory->getIconForRecord('pages', $record, IconSize::SMALL)->render() . BackendUtility::getRecordTitle('pages', $record, true); } $tableList[] = [ 'iconAndTitle' => sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makeconfig_tableListEntry'), $tableName, $iconAndTitle), 'reference' => $reference, ]; } } return $tableList; } protected function getExtensionList(): array { $loadedExtensions = ExtensionManagementUtility::getLoadedExtensionListArray(); return array_combine($loadedExtensions, $loadedExtensions); } protected function getFileSelectOptions(Export $export): array { $languageService = $this->getLanguageService(); $fileTypeOptions = []; foreach ($export->getSupportedFileTypes() as $supportedFileType) { $fileTypeOptions[$supportedFileType] = $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makesavefo_' . $supportedFileType); } return $fileTypeOptions; } /** * Get a list of all exportable tables - basically all TCA tables. Blacklist some if wanted. * Returned array keys are table names, values are "translations". */ protected function getTableSelectOptions(array $excludeList = []): array { $languageService = $this->getLanguageService(); $backendUser = $this->getBackendUser(); $options = [ '_ALL' => $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:ALL_tables'), ]; foreach ($this->tcaSchemaFactory->all()->getNames() as $table) { if (!in_array($table, $excludeList, true) && $backendUser->check('tables_select', $table)) { $options[$table] = $table; } } natsort($options); return $options; } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }