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