‪TYPO3CMS  ‪main
ExportController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseFactoryInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
26 use TYPO3\CMS\Backend\Utility\BackendUtility;
30 use TYPO3\CMS\Core\Imaging\IconSize;
45 
51 #[Controller]
53 {
54  protected array ‪$defaultInputData = [
55  'excludeDisabled' => 1,
56  'preset' => [],
57  'external_static' => [
58  'tables' => [],
59  ],
60  'external_ref' => [
61  'tables' => [],
62  ],
63  'pagetree' => [
64  'tables' => [],
65  ],
66  'extension_dep' => [],
67  'meta' => [
68  'title' => '',
69  'description' => '',
70  'notes' => '',
71  ],
72  'record' => [],
73  'list' => [],
74  ];
75 
76  public function ‪__construct(
77  protected readonly ‪IconFactory $iconFactory,
78  protected readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
79  protected readonly ResponseFactoryInterface $responseFactory,
80  protected readonly ‪PresetRepository $presetRepository
81  ) {
82  }
83 
84  public function ‪handleRequest(ServerRequestInterface $request): ResponseInterface
85  {
86  if ($this->‪getBackendUser()->isExportEnabled() === false) {
87  throw new \RuntimeException(
88  'Export module is disabled for non admin users and '
89  . 'user TSconfig options.impexp.enableExportForNonAdminUser is not enabled.',
90  1636901978
91  );
92  }
93 
94  $backendUser = $this->‪getBackendUser();
95  $queryParams = $request->getQueryParams();
96  $parsedBody = $request->getParsedBody();
97 
98  $id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
99  $permsClause = $backendUser->getPagePermsClause(‪Permission::PAGE_SHOW);
100  $pageInfo = BackendUtility::readPageAccess($id, $permsClause) ?: [];
101  if ($pageInfo === []) {
102  throw new \RuntimeException("You don't have access to this page.", 1604308206);
103  }
104 
105  // @todo: Only small parts of tx_impexp can be hand over as GET, e.g. ['list'] and 'id', drop GET of everything else.
106  // Also, there's a clash with id: it can be ['list']'table:id', it can be 'id', it can be tx_impexp['id']. This
107  // should be de-messed somehow.
108  $inputDataFromGetPost = $parsedBody['tx_impexp'] ?? $queryParams['tx_impexp'] ?? [];
109  $inputData = ‪$this->defaultInputData;
110  ArrayUtility::mergeRecursiveWithOverrule($inputData, $inputDataFromGetPost);
111  if ($inputData['resetExclude'] ?? false) {
112  $inputData['exclude'] = [];
113  }
114  $inputData['preset']['public'] = (int)($inputData['preset']['public'] ?? 0);
115 
116  $view = $this->moduleTemplateFactory->create($request);
117 
118  $presetAction = $parsedBody['preset'] ?? [];
119  $inputData = $this->‪processPresets($view, $presetAction, $inputData);
120 
121  $export = $this->‪configureExportFromFormData($inputData);
122  $export->process();
123 
124  if ($inputData['download_export'] ?? false) {
125  return $this->‪getDownload($export);
126  }
127  $saveFolder = $export->getOrCreateDefaultImportExportFolder();
128  if (($inputData['save_export'] ?? false) && ($saveFolder instanceof ‪Folder)) {
129  $this->‪saveExportToFile($view, $export, $saveFolder);
130  }
131  $inputData['filename'] = $export->getExportFileName();
132 
133  $view->assignMultiple([
134  'id' => $id,
135  'errors' => $export->getErrorLog(),
136  'preview' => $export->renderPreview(),
137  'tableSelectOptions' => $this->getTableSelectOptions(['pages']),
138  'treeHTML' => $export->getTreeHTML(),
139  'levelSelectOptions' => $this->getPageLevelSelectOptions($inputData),
140  'records' => $this->getRecordSelectOptions($inputData),
141  'tableList' => $this->getSelectableTableList($inputData),
142  'externalReferenceTableSelectOptions' => $this->getTableSelectOptions(),
143  'externalStaticTableSelectOptions' => $this->getTableSelectOptions(),
144  'presetSelectOptions' => $this->presetRepository->getPresets($id),
145  'fileName' => '',
146  'filetypeSelectOptions' => $this->getFileSelectOptions($export),
147  'saveFolder' => ($saveFolder instanceof ‪Folder) ? $saveFolder->getPublicUrl() : '',
148  'hasSaveFolder' => true,
149  'extensions' => $this->getExtensionList(),
150  'inData' => $inputData,
151  ]);
152  $view->setModuleName('');
153  $view->getDocHeaderComponent()->setMetaInformation($pageInfo);
154  return $view->renderResponse('Export');
155  }
156 
157  protected function ‪processPresets(‪ModuleTemplate $view, array $presetAction, array $inputData): array
158  {
159  if (empty($presetAction)) {
160  return $inputData;
161  }
162  $presetUid = (int)$presetAction['select'];
163  try {
164  if (isset($presetAction['save'])) {
165  if ($presetUid > 0) {
166  // Update existing
167  $this->presetRepository->updatePreset($presetUid, $inputData);
168  $view->‪addFlashMessage('Preset #' . $presetUid . ' saved!', 'Presets', ContextualFeedbackSeverity::INFO);
169  } else {
170  // Insert new
171  $this->presetRepository->createPreset($inputData);
172  $view->‪addFlashMessage('New preset "' . $inputData['preset']['title'] . '" is created', 'Presets', ContextualFeedbackSeverity::INFO);
173  }
174  }
175  if (isset($presetAction['delete'])) {
176  if ($presetUid > 0) {
177  $this->presetRepository->deletePreset($presetUid);
178  $view->‪addFlashMessage('Preset #' . $presetUid . ' deleted!', 'Presets', ContextualFeedbackSeverity::INFO);
179  } else {
180  $view->‪addFlashMessage('ERROR: No preset selected for deletion.', 'Presets', ContextualFeedbackSeverity::ERROR);
181  }
182  }
183  if (isset($presetAction['load']) || isset($presetAction['merge'])) {
184  if ($presetUid > 0) {
185  $presetData = $this->presetRepository->loadPreset($presetUid);
186  if (isset($presetAction['merge'])) {
187  // Merge records
188  if (is_array($presetData['record'] ?? null)) {
189  $inputData['record'] = array_merge((array)$inputData['record'], $presetData['record']);
190  }
191  // Merge lists
192  if (is_array($presetData['list'] ?? null)) {
193  $inputData['list'] = array_merge((array)$inputData['list'], $presetData['list']);
194  }
195  $view->‪addFlashMessage('Preset #' . $presetUid . ' merged!', 'Presets', ContextualFeedbackSeverity::INFO);
196  } else {
197  $inputData = $presetData;
198  $view->‪addFlashMessage('Preset #' . $presetUid . ' loaded!', 'Presets', ContextualFeedbackSeverity::INFO);
199  }
200  } else {
201  $view->‪addFlashMessage('ERROR: No preset selected for loading.', 'Presets', ContextualFeedbackSeverity::ERROR);
202  }
203  }
205  $view->‪addFlashMessage($e->getMessage(), 'Presets', ContextualFeedbackSeverity::ERROR);
206  }
207  return $inputData;
208  }
209 
210  protected function ‪configureExportFromFormData(array $inputData): ‪Export
211  {
212  $export = GeneralUtility::makeInstance(Export::class);
213  $export->setExcludeMap((array)($inputData['exclude'] ?? []));
214  $export->setSoftrefCfg((array)($inputData['softrefCfg'] ?? []));
215  $export->setExtensionDependencies((($inputData['extension_dep'] ?? '') === '') ? [] : (array)$inputData['extension_dep']);
216  $export->setShowStaticRelations((bool)($inputData['showStaticRelations'] ?? false));
217  $export->setIncludeExtFileResources(!($inputData['excludeHTMLfileResources'] ?? false));
218  $export->setExcludeDisabledRecords((bool)($inputData['excludeDisabled'] ?? false));
219  if (!empty($inputData['filetype'])) {
220  $export->setExportFileType((string)$inputData['filetype']);
221  }
222  $export->setExportFileName((string)($inputData['filename'] ?? ''));
223  $export->setRelStaticTables((($inputData['external_static']['tables'] ?? '') === '') ? [] : (array)$inputData['external_static']['tables']);
224  $export->setRelOnlyTables((($inputData['external_ref']['tables'] ?? '') === '') ? [] : (array)$inputData['external_ref']['tables']);
225  if (isset($inputData['save_export'], $inputData['saveFilesOutsideExportFile']) && $inputData['saveFilesOutsideExportFile'] === '1') {
226  $export->setSaveFilesOutsideExportFile(true);
227  }
228  $export->setTitle((string)($inputData['meta']['title'] ?? ''));
229  $export->setDescription((string)($inputData['meta']['description'] ?? ''));
230  $export->setNotes((string)($inputData['meta']['notes'] ?? ''));
231  $export->setRecord((($inputData['record'] ?? '') === '') ? [] : (array)$inputData['record']);
232  $export->setList((($inputData['list'] ?? '') === '') ? [] : (array)$inputData['list']);
233  if (‪MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['id'] ?? null)) {
234  $export->setPid((int)$inputData['pagetree']['id']);
235  }
236  if (‪MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['levels'] ?? null)) {
237  $export->setLevels((int)$inputData['pagetree']['levels']);
238  }
239  $export->setTables((($inputData['pagetree']['tables'] ?? '') === '') ? [] : (array)$inputData['pagetree']['tables']);
240  return $export;
241  }
242 
243  protected function ‪getDownload(‪Export $export): ResponseInterface
244  {
246  $fileContent = $export->‪render();
247  $response = $this->responseFactory->createResponse()
248  ->withHeader('Content-Type', 'application/octet-stream')
249  ->withHeader('Content-Length', (string)strlen($fileContent))
250  ->withHeader('Content-Disposition', 'attachment; filename=' . ‪PathUtility::basename($fileName));
251  $response->getBody()->write($export->‪render());
252  return $response;
253  }
254 
255  protected function ‪saveExportToFile(‪ModuleTemplate $view, ‪Export $export, ‪Folder $saveFolder): void
256  {
257  $languageService = $this->‪getLanguageService();
258  try {
259  $saveFile = $export->‪saveToFile();
260  $saveFileSize = $saveFile->getProperty('size');
261  $view->‪addFlashMessage(
262  sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_savedInSBytes'), $saveFile->getPublicUrl(), GeneralUtility::formatSize($saveFileSize)),
263  $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_savedFile')
264  );
265  } catch (‪CoreException $e) {
266  $view->‪addFlashMessage(
267  sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_badPathS'), $saveFolder->‪getPublicUrl()),
268  $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:exportdata_problemsSavingFile'),
269  ContextualFeedbackSeverity::ERROR
270  );
271  }
272  }
273 
274  protected function ‪getPageLevelSelectOptions(array $inputData): array
275  {
276  $languageService = $this->‪getLanguageService();
277  $options = [];
278  if (‪MathUtility::canBeInterpretedAsInteger($inputData['pagetree']['id'] ?? '')) {
279  $options = [
280  ‪Export::LEVELS_RECORDS_ON_THIS_PAGE => $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makeconfig_tablesOnThisPage'),
281  ‪Export::LEVELS_EXPANDED_TREE => $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makeconfig_expandedTree'),
282  0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
283  1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
284  2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
285  3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
286  4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
287  ‪Export::LEVELS_INFINITE => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
288  ];
289  }
290  return $options;
291  }
292 
293  protected function ‪getRecordSelectOptions(array $inputData): array
294  {
295  $records = [];
296  foreach ($inputData['record'] ?? [] as $tableNameColonUid) {
297  [$tableName, $recordUid] = explode(':', $tableNameColonUid);
298  if (‪$record = BackendUtility::getRecordWSOL((string)$tableName, (int)$recordUid)) {
299  $records[] = [
300  'icon' => $this->iconFactory->getIconForRecord($tableName, ‪$record, IconSize::SMALL)->render(),
301  'title' => BackendUtility::getRecordTitle($tableName, ‪$record, true),
302  'tableName' => $tableName,
303  'recordUid' => $recordUid,
304  ];
305  }
306  }
307  return $records;
308  }
309 
310  protected function ‪getSelectableTableList(array $inputData): array
311  {
312  $backendUser = $this->‪getBackendUser();
313  $languageService = $this->‪getLanguageService();
314  $tableList = [];
315  foreach ($inputData['list'] ?? [] as $reference) {
316  $referenceParts = explode(':', $reference);
317  $tableName = $referenceParts[0];
318  if ($backendUser->check('tables_select', $tableName)) {
319  // If the page is actually the root, handle it differently.
320  // NOTE: we don't compare integers, because the number comes from the split string above
321  if ($referenceParts[1] === '0') {
322  $iconAndTitle = $this->iconFactory->getIcon('apps-pagetree-root', IconSize::SMALL)->render() . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
323  } else {
324  ‪$record = BackendUtility::getRecordWSOL('pages', (int)$referenceParts[1]);
325  $iconAndTitle = $this->iconFactory->getIconForRecord('pages', ‪$record, IconSize::SMALL)->render()
326  . BackendUtility::getRecordTitle('pages', ‪$record, true);
327  }
328  $tableList[] = [
329  'iconAndTitle' => sprintf($languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makeconfig_tableListEntry'), $tableName, $iconAndTitle),
330  'reference' => $reference,
331  ];
332  }
333  }
334  return $tableList;
335  }
336 
337  protected function ‪getExtensionList(): array
338  {
340  return array_combine($loadedExtensions, $loadedExtensions);
341  }
342 
343  protected function ‪getFileSelectOptions(‪Export $export): array
344  {
345  $languageService = $this->‪getLanguageService();
346  $fileTypeOptions = [];
347  foreach ($export->‪getSupportedFileTypes() as $supportedFileType) {
348  $fileTypeOptions[$supportedFileType] = $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:makesavefo_' . $supportedFileType);
349  }
350  return $fileTypeOptions;
351  }
352 
357  protected function ‪getTableSelectOptions(array $excludeList = []): array
358  {
359  $languageService = $this->‪getLanguageService();
360  $backendUser = $this->‪getBackendUser();
361  $options = [
362  '_ALL' => $languageService->sL('LLL:EXT:impexp/Resources/Private/Language/locallang.xlf:ALL_tables'),
363  ];
364  $availableTables = array_keys(‪$GLOBALS['TCA']);
365  foreach ($availableTables as $table) {
366  if (!in_array($table, $excludeList, true) && $backendUser->check('tables_select', $table)) {
367  $options[$table] = $table;
368  }
369  }
370  natsort($options);
371  return $options;
372  }
373 
375  {
376  return ‪$GLOBALS['BE_USER'];
377  }
378 
380  {
381  return ‪$GLOBALS['LANG'];
382  }
383 }
‪TYPO3\CMS\Impexp\Export\render
‪string render()
Definition: Export.php:1159
‪TYPO3\CMS\Impexp\Exception\MalformedPresetException
Definition: MalformedPresetException.php:28
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\getLoadedExtensionListArray
‪static getLoadedExtensionListArray()
Definition: ExtensionManagementUtility.php:1480
‪TYPO3\CMS\Impexp\Controller\ExportController\getTableSelectOptions
‪getTableSelectOptions(array $excludeList=[])
Definition: ExportController.php:357
‪TYPO3\CMS\Impexp\Controller\ExportController\saveExportToFile
‪saveExportToFile(ModuleTemplate $view, Export $export, Folder $saveFolder)
Definition: ExportController.php:255
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Impexp\Controller
Definition: ExportController.php:18
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Impexp\Domain\Repository\PresetRepository
Definition: PresetRepository.php:35
‪TYPO3\CMS\Core\Exception
‪TYPO3\CMS\Backend\Template\ModuleTemplate\addFlashMessage
‪addFlashMessage(string $messageBody, string $messageTitle='', ContextualFeedbackSeverity $severity=ContextualFeedbackSeverity::OK, bool $storeInSession=true)
Definition: ModuleTemplate.php:229
‪TYPO3\CMS\Backend\Attribute\Controller
Definition: Controller.php:25
‪TYPO3\CMS\Impexp\Controller\ExportController\getSelectableTableList
‪getSelectableTableList(array $inputData)
Definition: ExportController.php:310
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Impexp\Export\saveToFile
‪saveToFile()
Definition: Export.php:1294
‪TYPO3\CMS\Impexp\Exception\InsufficientUserPermissionsException
Definition: InsufficientUserPermissionsException.php:28
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Impexp\Export\LEVELS_INFINITE
‪const LEVELS_INFINITE
Definition: Export.php:54
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:46
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Impexp\Controller\ExportController\__construct
‪__construct(protected readonly IconFactory $iconFactory, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly ResponseFactoryInterface $responseFactory, protected readonly PresetRepository $presetRepository)
Definition: ExportController.php:76
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:40
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Impexp\Export\getOrGenerateExportFileNameWithFileExtension
‪getOrGenerateExportFileNameWithFileExtension()
Definition: Export.php:1339
‪TYPO3\CMS\Impexp\Controller\ExportController
Definition: ExportController.php:53
‪TYPO3\CMS\Impexp\Controller\ExportController\getLanguageService
‪getLanguageService()
Definition: ExportController.php:379
‪TYPO3\CMS\Impexp\Controller\ExportController\getPageLevelSelectOptions
‪getPageLevelSelectOptions(array $inputData)
Definition: ExportController.php:274
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:37
‪TYPO3\CMS\Impexp\Exception\PresetNotFoundException
Definition: PresetNotFoundException.php:28
‪TYPO3\CMS\Impexp\Controller\ExportController\$defaultInputData
‪array $defaultInputData
Definition: ExportController.php:54
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Impexp\Export\getSupportedFileTypes
‪getSupportedFileTypes()
Definition: Export.php:1393
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:64
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Impexp\Export\LEVELS_EXPANDED_TREE
‪const LEVELS_EXPANDED_TREE
Definition: Export.php:53
‪TYPO3\CMS\Impexp\Controller\ExportController\handleRequest
‪handleRequest(ServerRequestInterface $request)
Definition: ExportController.php:84
‪TYPO3\CMS\Impexp\Export\LEVELS_RECORDS_ON_THIS_PAGE
‪const LEVELS_RECORDS_ON_THIS_PAGE
Definition: Export.php:52
‪TYPO3\CMS\Impexp\Controller\ExportController\getDownload
‪getDownload(Export $export)
Definition: ExportController.php:243
‪TYPO3\CMS\Impexp\Controller\ExportController\getFileSelectOptions
‪getFileSelectOptions(Export $export)
Definition: ExportController.php:343
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Impexp\Controller\ExportController\configureExportFromFormData
‪configureExportFromFormData(array $inputData)
Definition: ExportController.php:210
‪TYPO3\CMS\Impexp\Controller\ExportController\processPresets
‪processPresets(ModuleTemplate $view, array $presetAction, array $inputData)
Definition: ExportController.php:157
‪TYPO3\CMS\Impexp\Export
Definition: Export.php:51
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Impexp\Controller\ExportController\getBackendUser
‪getBackendUser()
Definition: ExportController.php:374
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Resource\Folder\getPublicUrl
‪string null getPublicUrl()
Definition: Folder.php:178
‪TYPO3\CMS\Impexp\Controller\ExportController\getRecordSelectOptions
‪getRecordSelectOptions(array $inputData)
Definition: ExportController.php:293
‪TYPO3\CMS\Impexp\Controller\ExportController\getExtensionList
‪getExtensionList()
Definition: ExportController.php:337