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