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