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