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