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