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