[TASK] Create own response instance in controller actions
[Packages/TYPO3.CMS.git] / typo3 / sysext / impexp / Classes / Controller / ImportExportController.php
1 <?php
2 namespace TYPO3\CMS\Impexp\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Module\BaseScriptClass;
20 use TYPO3\CMS\Backend\Template\DocumentTemplate;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate;
22 use TYPO3\CMS\Backend\Tree\View\PageTreeView;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
24 use TYPO3\CMS\Core\Database\ConnectionPool;
25 use TYPO3\CMS\Core\Database\Query\QueryHelper;
26 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
27 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
28 use TYPO3\CMS\Core\Http\HtmlResponse;
29 use TYPO3\CMS\Core\Imaging\Icon;
30 use TYPO3\CMS\Core\Imaging\IconFactory;
31 use TYPO3\CMS\Core\Localization\LanguageService;
32 use TYPO3\CMS\Core\Messaging\FlashMessage;
33 use TYPO3\CMS\Core\Messaging\FlashMessageService;
34 use TYPO3\CMS\Core\Resource\DuplicationBehavior;
35 use TYPO3\CMS\Core\Resource\Exception;
36 use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
37 use TYPO3\CMS\Core\Resource\ResourceFactory;
38 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
39 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
40 use TYPO3\CMS\Core\Utility\GeneralUtility;
41 use TYPO3\CMS\Core\Utility\MathUtility;
42 use TYPO3\CMS\Fluid\View\StandaloneView;
43 use TYPO3\CMS\Impexp\Domain\Repository\PresetRepository;
44 use TYPO3\CMS\Impexp\Export;
45 use TYPO3\CMS\Impexp\Import;
46 use TYPO3\CMS\Impexp\View\ExportPageTreeView;
47
48 /**
49 * Main script class for the Import / Export facility
50 */
51 class ImportExportController extends BaseScriptClass
52 {
53 /**
54 * @var array|\TYPO3\CMS\Core\Resource\File[]
55 */
56 protected $uploadedFiles = [];
57
58 /**
59 * Array containing the current page.
60 *
61 * @var array
62 */
63 public $pageinfo;
64
65 /**
66 * @var Export
67 */
68 protected $export;
69
70 /**
71 * @var Import
72 */
73 protected $import;
74
75 /**
76 * @var ExtendedFileUtility
77 */
78 protected $fileProcessor;
79
80 /**
81 * @var LanguageService
82 */
83 protected $lang = null;
84
85 /**
86 * @var string
87 */
88 protected $treeHTML = '';
89
90 /**
91 * @var IconFactory
92 */
93 protected $iconFactory;
94
95 /**
96 * The name of the module
97 *
98 * @var string
99 */
100 protected $moduleName = 'xMOD_tximpexp';
101
102 /**
103 * ModuleTemplate Container
104 *
105 * @var ModuleTemplate
106 */
107 protected $moduleTemplate;
108
109 /**
110 * The name of the shortcut for this page
111 *
112 * @var string
113 */
114 protected $shortcutName;
115
116 /**
117 * preset repository
118 *
119 * @var PresetRepository
120 */
121 protected $presetRepository;
122
123 /**
124 * @var StandaloneView
125 */
126 protected $standaloneView = null;
127
128 /**
129 * @var bool
130 */
131 protected $excludeDisabledRecords = false;
132
133 /**
134 * Return URL
135 *
136 * @var string
137 */
138 protected $returnUrl;
139
140 /**
141 * Constructor
142 */
143 public function __construct()
144 {
145 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
146 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
147 $this->presetRepository = GeneralUtility::makeInstance(PresetRepository::class);
148
149 $templatePath = ExtensionManagementUtility::extPath('impexp') . 'Resources/Private/';
150
151 /* @var $view StandaloneView */
152 $this->standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
153 $this->standaloneView->setTemplateRootPaths([$templatePath . 'Templates/ImportExport/']);
154 $this->standaloneView->setLayoutRootPaths([$templatePath . 'Layouts/']);
155 $this->standaloneView->setPartialRootPaths([$templatePath . 'Partials/']);
156 $this->standaloneView->getRequest()->setControllerExtensionName('impexp');
157 }
158
159 /**
160 * Initializes the module and defining necessary variables for this module to run.
161 */
162 public function init()
163 {
164 $this->MCONF['name'] = $this->moduleName;
165 parent::init();
166 $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
167 $this->lang = $this->getLanguageService();
168 }
169
170 /**
171 * Main module function
172 *
173 * @throws \BadFunctionCallException
174 * @throws \InvalidArgumentException
175 * @throws \RuntimeException
176 */
177 public function main()
178 {
179 $this->lang->includeLLFile('EXT:impexp/Resources/Private/Language/locallang.xlf');
180
181 // Start document template object:
182 // We keep this here, in case somebody relies on the old doc being here
183 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
184 $this->doc->bodyTagId = 'imp-exp-mod';
185 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
186 if (is_array($this->pageinfo)) {
187 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
188 }
189 // Setting up the context sensitive menu:
190 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
191 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Impexp/ImportExport');
192 $this->moduleTemplate->addJavaScriptCode(
193 'ImpexpInLineJS',
194 'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';'
195 );
196
197 // Input data grabbed:
198 $inData = GeneralUtility::_GP('tx_impexp');
199 if ($inData === null) {
200 // This happens if the post request was larger than allowed on the server
201 // We set the import action as default and output a user information
202 $inData = [
203 'action' => 'import',
204 ];
205 $flashMessage = GeneralUtility::makeInstance(
206 FlashMessage::class,
207 $this->getLanguageService()->getLL('importdata_upload_nodata'),
208 $this->getLanguageService()->getLL('importdata_upload_error'),
209 FlashMessage::ERROR
210 );
211 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
212 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
213 $defaultFlashMessageQueue->enqueue($flashMessage);
214 }
215 if (!array_key_exists('excludeDisabled', $inData)) {
216 // flag doesn't exist initially; state is on by default
217 $inData['excludeDisabled'] = 1;
218 }
219 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
220 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
221 $this->standaloneView->assign('moduleUrl', (string)$uriBuilder->buildUriFromRoute('xMOD_tximpexp'));
222 $this->standaloneView->assign('id', $this->id);
223 $this->standaloneView->assign('inData', $inData);
224
225 switch ((string)$inData['action']) {
226 case 'export':
227 $this->shortcutName = $this->lang->getLL('title_export');
228 // Call export interface
229 $this->exportData($inData);
230 $this->standaloneView->setTemplate('Export.html');
231 break;
232 case 'import':
233 $backendUser = $this->getBackendUser();
234 $isEnabledForNonAdmin = $backendUser->getTSConfig('options.impexp.enableImportForNonAdminUser');
235 if (!$backendUser->isAdmin() && empty($isEnabledForNonAdmin['value'])) {
236 throw new \RuntimeException(
237 'Import module is disabled for non admin users and '
238 . 'userTsConfig options.impexp.enableImportForNonAdminUser is not enabled.',
239 1464435459
240 );
241 }
242 $this->shortcutName = $this->lang->getLL('title_import');
243 if (GeneralUtility::_POST('_upload')) {
244 $this->checkUpload();
245 }
246 // Finally: If upload went well, set the new file as the import file:
247 if (!empty($this->uploadedFiles[0])) {
248 // Only allowed extensions....
249 $extension = $this->uploadedFiles[0]->getExtension();
250 if ($extension === 't3d' || $extension === 'xml') {
251 $inData['file'] = $this->uploadedFiles[0]->getCombinedIdentifier();
252 }
253 }
254 // Call import interface:
255 $this->importData($inData);
256 $this->standaloneView->setTemplate('Import.html');
257 break;
258 }
259
260 // Setting up the buttons and markers for docheader
261 $this->getButtons();
262 }
263
264 /**
265 * Injects the request object for the current request and gathers all data
266 *
267 * IMPORTING DATA:
268 *
269 * Incoming array has syntax:
270 * GETvar 'id' = import page id (must be readable)
271 *
272 * file = (pointing to filename relative to PATH_site)
273 *
274 * [all relation fields are clear, but not files]
275 * - page-tree is written first
276 * - then remaining pages (to the root of import)
277 * - then all other records are written either to related included pages or if not found to import-root (should be a sysFolder in most cases)
278 * - then all internal relations are set and non-existing relations removed, relations to static tables preserved.
279 *
280 * EXPORTING DATA:
281 *
282 * Incoming array has syntax:
283 *
284 * file[] = file
285 * dir[] = dir
286 * list[] = table:pid
287 * record[] = table:uid
288 *
289 * pagetree[id] = (single id)
290 * pagetree[levels]=1,2,3, -1 = currently unpacked tree, -2 = only tables on page
291 * pagetree[tables][]=table/_ALL
292 *
293 * external_ref[tables][]=table/_ALL
294 *
295 * @param ServerRequestInterface $request the current request
296 * @return ResponseInterface the response with the content
297 */
298 public function mainAction(ServerRequestInterface $request): ResponseInterface
299 {
300 $GLOBALS['SOBE'] = $this;
301 $this->init();
302 $this->main();
303 $this->moduleTemplate->setContent($this->standaloneView->render());
304 return new HtmlResponse($this->moduleTemplate->renderContent());
305 }
306
307 /**
308 * Create the panel of buttons for submitting the form or otherwise perform operations.
309 *
310 * @return array all available buttons as an associated array
311 */
312 protected function getButtons()
313 {
314 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
315 if ($this->getBackendUser()->mayMakeShortcut()) {
316 $shortcutButton = $buttonBar->makeShortcutButton()
317 ->setGetVariables(['tx_impexp'])
318 ->setDisplayName($this->shortcutName)
319 ->setModuleName($this->moduleName);
320 $buttonBar->addButton($shortcutButton);
321 }
322 // back button
323 if ($this->returnUrl) {
324 $backButton = $buttonBar->makeLinkButton()
325 ->setHref($this->returnUrl)
326 ->setTitle($this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
327 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
328 $buttonBar->addButton($backButton);
329 }
330 // Input data grabbed:
331 $inData = GeneralUtility::_GP('tx_impexp');
332 if ((string)$inData['action'] === 'import') {
333 if ($this->id && is_array($this->pageinfo) || $this->getBackendUser()->isAdmin() && !$this->id) {
334 if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
335 // View
336 $onClick = BackendUtility::viewOnClick(
337 $this->pageinfo['uid'],
338 '',
339 BackendUtility::BEgetRootLine($this->pageinfo['uid'])
340 );
341 $viewButton = $buttonBar->makeLinkButton()
342 ->setTitle($this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
343 ->setHref('#')
344 ->setIcon($this->iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL))
345 ->setOnClick($onClick);
346 $buttonBar->addButton($viewButton);
347 }
348 }
349 }
350 }
351
352 /**************************
353 * EXPORT FUNCTIONS
354 **************************/
355
356 /**
357 * Export part of module
358 * Setting content in $this->content
359 *
360 * @param array $inData Content of POST VAR tx_impexp[]..
361 * @throws \InvalidArgumentException
362 * @throws \RuntimeException
363 * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException
364 */
365 public function exportData($inData)
366 {
367 // BUILDING EXPORT DATA:
368 // Processing of InData array values:
369 $inData['filename'] = trim(preg_replace('/[^[:alnum:]._-]*/', '', preg_replace('/\\.(t3d|xml)$/', '', $inData['filename'])));
370 if (strlen($inData['filename'])) {
371 $inData['filename'] .= $inData['filetype'] === 'xml' ? '.xml' : '.t3d';
372 }
373 // Set exclude fields in export object:
374 if (!is_array($inData['exclude'])) {
375 $inData['exclude'] = [];
376 }
377 // Saving/Loading/Deleting presets:
378 $this->presetRepository->processPresets($inData);
379 // Create export object and configure it:
380 $this->export = GeneralUtility::makeInstance(Export::class);
381 $this->export->init(0);
382 $this->export->excludeMap = (array)$inData['exclude'];
383 $this->export->softrefCfg = (array)$inData['softrefCfg'];
384 $this->export->extensionDependencies = ($inData['extension_dep'] === '') ? [] : (array)$inData['extension_dep'];
385 $this->export->showStaticRelations = $inData['showStaticRelations'];
386 $this->export->includeExtFileResources = !$inData['excludeHTMLfileResources'];
387 $this->excludeDisabledRecords = (bool)$inData['excludeDisabled'];
388 $this->export->setExcludeDisabledRecords($this->excludeDisabledRecords);
389
390 // Static tables:
391 if (is_array($inData['external_static']['tables'])) {
392 $this->export->relStaticTables = $inData['external_static']['tables'];
393 }
394 // Configure which tables external relations are included for:
395 if (is_array($inData['external_ref']['tables'])) {
396 $this->export->relOnlyTables = $inData['external_ref']['tables'];
397 }
398 $saveFilesOutsideExportFile = false;
399 if (isset($inData['save_export']) && isset($inData['saveFilesOutsideExportFile']) && $inData['saveFilesOutsideExportFile'] === '1') {
400 $this->export->setSaveFilesOutsideExportFile(true);
401 $saveFilesOutsideExportFile = true;
402 }
403 $this->export->setHeaderBasics();
404 // Meta data setting:
405
406 $beUser = $this->getBackendUser();
407 $this->export->setMetaData(
408 $inData['meta']['title'],
409 $inData['meta']['description'],
410 $inData['meta']['notes'],
411 $beUser->user['username'],
412 $beUser->user['realName'],
413 $beUser->user['email']
414 );
415 // Configure which records to export
416 if (is_array($inData['record'])) {
417 foreach ($inData['record'] as $ref) {
418 $rParts = explode(':', $ref);
419 $this->export->export_addRecord($rParts[0], BackendUtility::getRecord($rParts[0], $rParts[1]));
420 }
421 }
422 // Configure which tables to export
423 if (is_array($inData['list'])) {
424 foreach ($inData['list'] as $ref) {
425 $rParts = explode(':', $ref);
426 if ($beUser->check('tables_select', $rParts[0])) {
427 $statement = $this->exec_listQueryPid($rParts[0], $rParts[1]);
428 while ($subTrow = $statement->fetch()) {
429 $this->export->export_addRecord($rParts[0], $subTrow);
430 }
431 }
432 }
433 }
434 // Pagetree
435 if (isset($inData['pagetree']['id'])) {
436 // Based on click-expandable tree
437 $idH = null;
438 if ($inData['pagetree']['levels'] == -1) {
439 $pagetree = GeneralUtility::makeInstance(ExportPageTreeView::class);
440 if ($this->excludeDisabledRecords) {
441 $pagetree->init(BackendUtility::BEenableFields('pages'));
442 }
443 $tree = $pagetree->ext_tree($inData['pagetree']['id'], $this->filterPageIds($this->export->excludeMap));
444 $this->treeHTML = $pagetree->printTree($tree);
445 $idH = $pagetree->buffer_idH;
446 } elseif ($inData['pagetree']['levels'] == -2) {
447 $this->addRecordsForPid($inData['pagetree']['id'], $inData['pagetree']['tables']);
448 } else {
449 // Based on depth
450 // Drawing tree:
451 // If the ID is zero, export root
452 if (!$inData['pagetree']['id'] && $beUser->isAdmin()) {
453 $sPage = [
454 'uid' => 0,
455 'title' => 'ROOT'
456 ];
457 } else {
458 $sPage = BackendUtility::getRecordWSOL('pages', $inData['pagetree']['id'], '*', ' AND ' . $this->perms_clause);
459 }
460 if (is_array($sPage)) {
461 $pid = $inData['pagetree']['id'];
462 $tree = GeneralUtility::makeInstance(PageTreeView::class);
463 $initClause = 'AND ' . $this->perms_clause . $this->filterPageIds($this->export->excludeMap);
464 if ($this->excludeDisabledRecords) {
465 $initClause .= BackendUtility::BEenableFields('pages');
466 }
467 $tree->init($initClause);
468 $HTML = $this->iconFactory->getIconForRecord('pages', $sPage, Icon::SIZE_SMALL)->render();
469 $tree->tree[] = ['row' => $sPage, 'HTML' => $HTML];
470 $tree->buffer_idH = [];
471 if ($inData['pagetree']['levels'] > 0) {
472 $tree->getTree($pid, $inData['pagetree']['levels'], '');
473 }
474 $idH = [];
475 $idH[$pid]['uid'] = $pid;
476 if (!empty($tree->buffer_idH)) {
477 $idH[$pid]['subrow'] = $tree->buffer_idH;
478 }
479 $pagetree = GeneralUtility::makeInstance(ExportPageTreeView::class);
480 $this->treeHTML = $pagetree->printTree($tree->tree);
481 $this->shortcutName .= ' (' . $sPage['title'] . ')';
482 }
483 }
484 // In any case we should have a multi-level array, $idH, with the page structure
485 // here (and the HTML-code loaded into memory for nice display...)
486 if (is_array($idH)) {
487 // Sets the pagetree and gets a 1-dim array in return with the pages (in correct submission order BTW...)
488 $flatList = $this->export->setPageTree($idH);
489 foreach ($flatList as $k => $value) {
490 $this->export->export_addRecord('pages', BackendUtility::getRecord('pages', $k));
491 $this->addRecordsForPid($k, $inData['pagetree']['tables']);
492 }
493 }
494 }
495 // After adding ALL records we set relations:
496 for ($a = 0; $a < 10; $a++) {
497 $addR = $this->export->export_addDBRelations($a);
498 if (empty($addR)) {
499 break;
500 }
501 }
502 // Finally files are added:
503 // MUST be after the DBrelations are set so that files from ALL added records are included!
504 $this->export->export_addFilesFromRelations();
505
506 $this->export->export_addFilesFromSysFilesRecords();
507
508 // If the download button is clicked, return file
509 if ($inData['download_export'] || $inData['save_export']) {
510 switch ((string)$inData['filetype']) {
511 case 'xml':
512 $out = $this->export->compileMemoryToFileContent('xml');
513 $fExt = '.xml';
514 break;
515 case 't3d':
516 $this->export->dontCompress = 1;
517 // intentional fall-through
518 // no break
519 default:
520 $out = $this->export->compileMemoryToFileContent();
521 $fExt = ($this->export->doOutputCompress() ? '-z' : '') . '.t3d';
522 }
523 // Filename:
524 $dlFile = $inData['filename'];
525 if (!$dlFile) {
526 $exportName = substr(preg_replace('/[^[:alnum:]_]/', '-', $inData['download_export_name']), 0, 20);
527 $dlFile = 'T3D_' . $exportName . '_' . date('Y-m-d_H-i') . $fExt;
528 }
529
530 // Export for download:
531 if ($inData['download_export']) {
532 $mimeType = 'application/octet-stream';
533 header('Content-Type: ' . $mimeType);
534 header('Content-Length: ' . strlen($out));
535 header('Content-Disposition: attachment; filename=' . basename($dlFile));
536 echo $out;
537 die;
538 }
539 // Export by saving:
540 if ($inData['save_export']) {
541 $saveFolder = $this->getDefaultImportExportFolder();
542 $lang = $this->getLanguageService();
543 if ($saveFolder !== false && $saveFolder->checkActionPermission('write')) {
544 $temporaryFileName = GeneralUtility::tempnam('export');
545 file_put_contents($temporaryFileName, $out);
546 $file = $saveFolder->addFile($temporaryFileName, $dlFile, 'replace');
547 if ($saveFilesOutsideExportFile) {
548 $filesFolderName = $dlFile . '.files';
549 $filesFolder = $saveFolder->createFolder($filesFolderName);
550 $temporaryFolderForExport = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->export->getTemporaryFilesPathForExport());
551 $temporaryFilesForExport = $temporaryFolderForExport->getFiles();
552 foreach ($temporaryFilesForExport as $temporaryFileForExport) {
553 $filesFolder->getStorage()->moveFile($temporaryFileForExport, $filesFolder);
554 }
555 $temporaryFolderForExport->delete();
556 }
557
558 /** @var FlashMessage $flashMessage */
559 $flashMessage = GeneralUtility::makeInstance(
560 FlashMessage::class,
561 sprintf($lang->getLL('exportdata_savedInSBytes'), $file->getPublicUrl(), GeneralUtility::formatSize(strlen($out))),
562 $lang->getLL('exportdata_savedFile'),
563 FlashMessage::OK
564 );
565 } else {
566 /** @var FlashMessage $flashMessage */
567 $flashMessage = GeneralUtility::makeInstance(
568 FlashMessage::class,
569 sprintf($lang->getLL('exportdata_badPathS'), $saveFolder->getPublicUrl()),
570 $lang->getLL('exportdata_problemsSavingFile'),
571 FlashMessage::ERROR
572 );
573 }
574 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
575 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
576 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
577 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
578 $defaultFlashMessageQueue->enqueue($flashMessage);
579 }
580 }
581
582 $this->makeConfigurationForm($inData);
583
584 $this->makeSaveForm($inData);
585
586 $this->makeAdvancedOptionsForm($inData);
587
588 $this->standaloneView->assign('errors', $this->export->errorLog);
589
590 // Generate overview:
591 $this->standaloneView->assign(
592 'contentOverview',
593 $this->export->displayContentOverview()
594 );
595 }
596
597 /**
598 * Adds records to the export object for a specific page id.
599 *
600 * @param int $k Page id for which to select records to add
601 * @param array $tables Array of table names to select from
602 * @param int $maxNumber @deprecated since TYPO3 v9, will be removed in TYPO3 v10
603 */
604 public function addRecordsForPid($k, $tables, $maxNumber = null)
605 {
606 if (!is_array($tables)) {
607 return;
608 }
609 foreach ($GLOBALS['TCA'] as $table => $value) {
610 if ($table !== 'pages' && (in_array($table, $tables) || in_array('_ALL', $tables))) {
611 if ($this->getBackendUser()->check('tables_select', $table) && !$GLOBALS['TCA'][$table]['ctrl']['is_static']) {
612 if ($maxNumber !== null) {
613 // @deprecated since TYPO3 v9, will be removed in TYPO3 v10. Remove this if in v10
614 // and the 3rd method argument. trigger_error() is called by method exec_listQueryPid() below
615 $statement = $this->exec_listQueryPid($table, $k, MathUtility::forceIntegerInRange($maxNumber, 1));
616 } else {
617 $statement = $this->exec_listQueryPid($table, $k);
618 }
619 while ($subTrow = $statement->fetch()) {
620 $this->export->export_addRecord($table, $subTrow);
621 }
622 }
623 }
624 }
625 }
626
627 /**
628 * Selects records from table / pid
629 *
630 * @param string $table Table to select from
631 * @param int $pid Page ID to select from
632 * @param int $limit @deprecated since TYPO3 v9, will be removed in TYPO3 v10
633 * @return \Doctrine\DBAL\Driver\Statement Query statement
634 */
635 public function exec_listQueryPid($table, $pid, $limit = null)
636 {
637 // @deprecated In v10, remove this if and the method argument
638 if ($limit !== null) {
639 trigger_error(
640 'The third argument of addRecordsForPid() and exec_listQueryPid() has been'
641 . ' deprecated, do not limit exports anymore. The parameter will be removed in TYPO3 v10.',
642 E_USER_DEPRECATED
643 );
644 }
645
646 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
647
648 $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
649 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
650
651 if ($this->excludeDisabledRecords === false) {
652 $queryBuilder->getRestrictions()
653 ->removeAll()
654 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
655 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
656 }
657
658 $queryBuilder->select('*')
659 ->from($table)
660 ->where(
661 $queryBuilder->expr()->eq(
662 'pid',
663 $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
664 )
665 );
666
667 // @deprecated In v10, remove this if
668 if ($limit !== null) {
669 $queryBuilder->setMaxResults($limit);
670 }
671
672 foreach (QueryHelper::parseOrderBy((string)$orderBy) as $orderPair) {
673 list($fieldName, $order) = $orderPair;
674 $queryBuilder->addOrderBy($fieldName, $order);
675 }
676
677 $statement = $queryBuilder->execute();
678
679 // @deprecated In v10, remove this if, and the two getLL locallang target keys
680 if ($limit !== null && $statement->rowCount() == $limit) {
681 $limitWarning = sprintf($this->lang->getLL('makeconfig_anSqlQueryReturned'), $limit);
682 /** @var FlashMessage $flashMessage */
683 $flashMessage = GeneralUtility::makeInstance(
684 FlashMessage::class,
685 $this->lang->getLL('execlistqu_maxNumberLimit'),
686 $limitWarning,
687 FlashMessage::WARNING
688 );
689 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
690 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
691 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
692 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
693 $defaultFlashMessageQueue->enqueue($flashMessage);
694 }
695
696 return $statement;
697 }
698
699 /**
700 * Create configuration form
701 *
702 * @param array $inData Form configuration data
703 */
704 public function makeConfigurationForm($inData)
705 {
706 $nameSuggestion = '';
707 // Page tree export options:
708 if (isset($inData['pagetree']['id'])) {
709 $this->standaloneView->assign('treeHTML', $this->treeHTML);
710
711 $opt = [
712 '-2' => $this->lang->getLL('makeconfig_tablesOnThisPage'),
713 '-1' => $this->lang->getLL('makeconfig_expandedTree'),
714 '0' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
715 '1' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
716 '2' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
717 '3' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
718 '4' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
719 '999' => $this->lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
720 ];
721 $this->standaloneView->assign('levelSelectOptions', $opt);
722 $this->standaloneView->assign('tableSelectOptions', $this->getTableSelectOptions('pages'));
723 $nameSuggestion .= 'tree_PID' . $inData['pagetree']['id'] . '_L' . $inData['pagetree']['levels'];
724 }
725 // Single record export:
726 if (is_array($inData['record'])) {
727 $records = [];
728 foreach ($inData['record'] as $ref) {
729 $rParts = explode(':', $ref);
730 $tName = $rParts[0];
731 $rUid = $rParts[1];
732 $nameSuggestion .= $tName . '_' . $rUid;
733 $rec = BackendUtility::getRecordWSOL($tName, $rUid);
734 if (!empty($rec)) {
735 $records[] = [
736 'icon' => $this->iconFactory->getIconForRecord($tName, $rec, Icon::SIZE_SMALL)->render(),
737 'title' => BackendUtility::getRecordTitle($tName, $rec, true),
738 'tableName' => $tName,
739 'recordUid' => $rUid
740 ];
741 }
742 }
743 $this->standaloneView->assign('records', $records);
744 }
745 // Single tables/pids:
746 if (is_array($inData['list'])) {
747
748 // Display information about pages from which the export takes place
749 $tableList = [];
750 foreach ($inData['list'] as $reference) {
751 $referenceParts = explode(':', $reference);
752 $tableName = $referenceParts[0];
753 if ($this->getBackendUser()->check('tables_select', $tableName)) {
754 // If the page is actually the root, handle it differently
755 // NOTE: we don't compare integers, because the number actually comes from the split string above
756 if ($referenceParts[1] === '0') {
757 $iconAndTitle = $this->iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render() . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
758 } else {
759 $record = BackendUtility::getRecordWSOL('pages', $referenceParts[1]);
760 $iconAndTitle = $this->iconFactory->getIconForRecord('pages', $record, Icon::SIZE_SMALL)->render()
761 . BackendUtility::getRecordTitle('pages', $record, true);
762 }
763
764 $tableList[] = [
765 'iconAndTitle' => sprintf($this->lang->getLL('makeconfig_tableListEntry'), $tableName, $iconAndTitle),
766 'reference' => $reference
767 ];
768 }
769 }
770 $this->standaloneView->assign('tableList', $tableList);
771 }
772
773 $this->standaloneView->assign('externalReferenceTableSelectOptions', $this->getTableSelectOptions());
774 $this->standaloneView->assign('externalStaticTableSelectOptions', $this->getTableSelectOptions());
775 $this->standaloneView->assign('nameSuggestion', $nameSuggestion);
776 }
777
778 /**
779 * Create advanced options form
780 * Sets content in $this->content
781 *
782 * @param array $inData Form configurat data
783 */
784 public function makeAdvancedOptionsForm($inData)
785 {
786 $loadedExtensions = ExtensionManagementUtility::getLoadedExtensionListArray();
787 $loadedExtensions = array_combine($loadedExtensions, $loadedExtensions);
788 $this->standaloneView->assign('extensions', $loadedExtensions);
789 $this->standaloneView->assign('inData', $inData);
790 }
791
792 /**
793 * Create configuration form
794 *
795 * @param array $inData Form configuration data
796 */
797 public function makeSaveForm($inData)
798 {
799 $opt = $this->presetRepository->getPresets((int)$inData['pagetree']['id']);
800
801 $this->standaloneView->assign('presetSelectOptions', $opt);
802
803 $saveFolder = $this->getDefaultImportExportFolder();
804 if ($saveFolder) {
805 $this->standaloneView->assign('saveFolder', $saveFolder->getCombinedIdentifier());
806 }
807
808 // Add file options:
809 $opt = [];
810 $opt['xml'] = $this->lang->getLL('makesavefo_xml');
811 if ($this->export->compress) {
812 $opt['t3d_compressed'] = $this->lang->getLL('makesavefo_t3dFileCompressed');
813 }
814 $opt['t3d'] = $this->lang->getLL('makesavefo_t3dFile');
815
816 $this->standaloneView->assign('filetypeSelectOptions', $opt);
817
818 $fileName = '';
819 if ($saveFolder) {
820 $this->standaloneView->assign('saveFolder', $saveFolder->getPublicUrl());
821 $this->standaloneView->assign('hasSaveFolder', true);
822 }
823 $this->standaloneView->assign('fileName', $fileName);
824 }
825
826 /**************************
827 * IMPORT FUNCTIONS
828 **************************/
829
830 /**
831 * Import part of module
832 *
833 * @param array $inData Content of POST VAR tx_impexp[]..
834 * @throws \BadFunctionCallException
835 * @throws \InvalidArgumentException
836 * @throws \RuntimeException
837 */
838 public function importData($inData)
839 {
840 $access = is_array($this->pageinfo);
841 $beUser = $this->getBackendUser();
842 if ($this->id && $access || $beUser->isAdmin() && !$this->id) {
843 if ($beUser->isAdmin() && !$this->id) {
844 $this->pageinfo = ['title' => '[root-level]', 'uid' => 0, 'pid' => 0];
845 }
846 if ($inData['new_import']) {
847 unset($inData['import_mode']);
848 }
849 /** @var $import Import */
850 $import = GeneralUtility::makeInstance(Import::class);
851 $import->init();
852 $import->update = $inData['do_update'];
853 $import->import_mode = $inData['import_mode'];
854 $import->enableLogging = $inData['enableLogging'];
855 $import->global_ignore_pid = $inData['global_ignore_pid'];
856 $import->force_all_UIDS = $inData['force_all_UIDS'];
857 $import->showDiff = !$inData['notShowDiff'];
858 $import->allowPHPScripts = $inData['allowPHPScripts'];
859 $import->softrefInputValues = $inData['softrefInputValues'];
860
861 // OUTPUT creation:
862
863 // Make input selector:
864 // must have trailing slash.
865 $path = $this->getDefaultImportExportFolder();
866 $exportFiles = $this->getExportFiles();
867
868 $this->shortcutName .= ' (' . $this->pageinfo['title'] . ')';
869
870 // Configuration
871 $selectOptions = [''];
872 foreach ($exportFiles as $file) {
873 $selectOptions[$file->getCombinedIdentifier()] = $file->getPublicUrl();
874 }
875
876 $this->standaloneView->assign('import', $import);
877 $this->standaloneView->assign('inData', $inData);
878 $this->standaloneView->assign('fileSelectOptions', $selectOptions);
879
880 if ($path) {
881 $this->standaloneView->assign('importPath', sprintf($this->lang->getLL('importdata_fromPathS'), $path->getCombinedIdentifier()));
882 } else {
883 $this->standaloneView->assign('importPath', $this->lang->getLL('importdata_no_default_upload_folder'));
884 }
885 $this->standaloneView->assign('isAdmin', $beUser->isAdmin());
886
887 // Upload file:
888 $tempFolder = $this->getDefaultImportExportFolder();
889 if ($tempFolder) {
890 $this->standaloneView->assign('tempFolder', $tempFolder->getCombinedIdentifier());
891 $this->standaloneView->assign('hasTempUploadFolder', true);
892 if (GeneralUtility::_POST('_upload')) {
893 $this->standaloneView->assign('submitted', GeneralUtility::_POST('_upload'));
894 $this->standaloneView->assign('noFileUploaded', $this->fileProcessor->internalUploadMap[1]);
895 if ($this->uploadedFiles[0]) {
896 $this->standaloneView->assign('uploadedFile', $this->uploadedFiles[0]->getName());
897 }
898 }
899 }
900
901 // Perform import or preview depending:
902 $inFile = $this->getFile($inData['file']);
903 if ($inFile !== null && $inFile->exists()) {
904 $this->standaloneView->assign('metaDataInFileExists', true);
905 $importInhibitedMessages = [];
906 if ($import->loadFile($inFile->getForLocalProcessing(false), 1)) {
907 $importInhibitedMessages = $import->checkImportPrerequisites();
908 if ($inData['import_file']) {
909 if (empty($importInhibitedMessages)) {
910 $import->importData($this->id);
911 BackendUtility::setUpdateSignal('updatePageTree');
912 }
913 }
914 $import->display_import_pid_record = $this->pageinfo;
915 $this->standaloneView->assign('contentOverview', $import->displayContentOverview());
916 }
917 // Compile messages which are inhibiting a proper import and add them to output.
918 if (!empty($importInhibitedMessages)) {
919 $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier('impexp.errors');
920 foreach ($importInhibitedMessages as $message) {
921 $flashMessageQueue->addMessage(GeneralUtility::makeInstance(
922 FlashMessage::class,
923 $message,
924 '',
925 FlashMessage::ERROR
926 ));
927 }
928 }
929 }
930
931 $this->standaloneView->assign('errors', $import->errorLog);
932 }
933 }
934
935 /****************************
936 * Helper functions
937 ****************************/
938
939 /**
940 * Returns a \TYPO3\CMS\Core\Resource\Folder object for saving export files
941 * to the server and is also used for uploading import files.
942 *
943 * @throws \InvalidArgumentException
944 * @return \TYPO3\CMS\Core\Resource\Folder|null
945 */
946 protected function getDefaultImportExportFolder()
947 {
948 $defaultImportExportFolder = null;
949
950 $defaultTemporaryFolder = $this->getBackendUser()->getDefaultUploadTemporaryFolder();
951 if ($defaultTemporaryFolder !== null) {
952 $importExportFolderName = 'importexport';
953 $createFolder = !$defaultTemporaryFolder->hasFolder($importExportFolderName);
954 if ($createFolder === true) {
955 try {
956 $defaultImportExportFolder = $defaultTemporaryFolder->createFolder($importExportFolderName);
957 } catch (Exception $folderAccessException) {
958 }
959 } else {
960 $defaultImportExportFolder = $defaultTemporaryFolder->getSubfolder($importExportFolderName);
961 }
962 }
963
964 return $defaultImportExportFolder;
965 }
966
967 /**
968 * Check if a file has been uploaded
969 *
970 * @throws \InvalidArgumentException
971 * @throws \UnexpectedValueException
972 */
973 public function checkUpload()
974 {
975 $file = GeneralUtility::_GP('file');
976 // Initializing:
977 $this->fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class);
978 $this->fileProcessor->setActionPermissions();
979 $conflictMode = empty(GeneralUtility::_GP('overwriteExistingFiles')) ? DuplicationBehavior::__default : DuplicationBehavior::REPLACE;
980 $this->fileProcessor->setExistingFilesConflictMode(DuplicationBehavior::cast($conflictMode));
981 $this->fileProcessor->start($file);
982 $result = $this->fileProcessor->processData();
983 if (!empty($result['upload'])) {
984 foreach ($result['upload'] as $uploadedFiles) {
985 $this->uploadedFiles += $uploadedFiles;
986 }
987 }
988 }
989
990 /**
991 * Returns option array to be used in Fluid
992 *
993 * @param string $excludeList Table names (and the string "_ALL") to exclude. Comma list
994 * @return array
995 */
996 public function getTableSelectOptions($excludeList = '')
997 {
998 $optValues = [];
999 if (!GeneralUtility::inList($excludeList, '_ALL')) {
1000 $optValues['_ALL'] = '[' . $this->lang->getLL('ALL_tables') . ']';
1001 }
1002 foreach ($GLOBALS['TCA'] as $table => $_) {
1003 if ($this->getBackendUser()->check('tables_select', $table) && !GeneralUtility::inList($excludeList, $table)) {
1004 $optValues[$table] = $table;
1005 }
1006 }
1007 return $optValues;
1008 }
1009
1010 /**
1011 * Filter page IDs by traversing exclude array, finding all
1012 * excluded pages (if any) and making an AND NOT IN statement for the select clause.
1013 *
1014 * @param array $exclude Exclude array from import/export object.
1015 * @return string AND where clause part to filter out page uids.
1016 */
1017 public function filterPageIds($exclude)
1018 {
1019 // Get keys:
1020 $exclude = array_keys($exclude);
1021 // Traverse
1022 $pageIds = [];
1023 foreach ($exclude as $element) {
1024 list($table, $uid) = explode(':', $element);
1025 if ($table === 'pages') {
1026 $pageIds[] = (int)$uid;
1027 }
1028 }
1029 // Add to clause:
1030 if (!empty($pageIds)) {
1031 return ' AND uid NOT IN (' . implode(',', $pageIds) . ')';
1032 }
1033 return '';
1034 }
1035
1036 /**
1037 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1038 */
1039 protected function getBackendUser()
1040 {
1041 return $GLOBALS['BE_USER'];
1042 }
1043
1044 /**
1045 * @return LanguageService
1046 */
1047 protected function getLanguageService()
1048 {
1049 return $GLOBALS['LANG'];
1050 }
1051
1052 /**
1053 * Gets all export files.
1054 *
1055 * @throws \InvalidArgumentException
1056 * @return array|\TYPO3\CMS\Core\Resource\File[]
1057 */
1058 protected function getExportFiles()
1059 {
1060 $exportFiles = [];
1061
1062 $folder = $this->getDefaultImportExportFolder();
1063 if ($folder !== null) {
1064
1065 /** @var $filter FileExtensionFilter */
1066 $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
1067 $filter->setAllowedFileExtensions(['t3d', 'xml']);
1068 $folder->getStorage()->addFileAndFolderNameFilter([$filter, 'filterFileList']);
1069
1070 $exportFiles = $folder->getFiles();
1071 }
1072
1073 return $exportFiles;
1074 }
1075
1076 /**
1077 * Gets a file by combined identifier.
1078 *
1079 * @param string $combinedIdentifier
1080 * @return \TYPO3\CMS\Core\Resource\File|null
1081 */
1082 protected function getFile($combinedIdentifier)
1083 {
1084 try {
1085 $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($combinedIdentifier);
1086 } catch (\Exception $exception) {
1087 $file = null;
1088 }
1089
1090 return $file;
1091 }
1092 }