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