1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Impexp\Controller;
19
20use Psr\Http\Message\ResponseInterface;
21use Psr\Http\Message\ServerRequestInterface;
22use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
23use TYPO3\CMS\Backend\Routing\UriBuilder;
24use TYPO3\CMS\Backend\Utility\BackendUtility;
25use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
26use TYPO3\CMS\Core\Exception;
27use TYPO3\CMS\Core\Http\HtmlResponse;
28use TYPO3\CMS\Core\Imaging\Icon;
29use TYPO3\CMS\Core\Messaging\FlashMessage;
30use TYPO3\CMS\Core\Messaging\FlashMessageService;
31use TYPO3\CMS\Core\Resource\DuplicationBehavior;
32use TYPO3\CMS\Core\Resource\File;
33use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
34use TYPO3\CMS\Core\Resource\ResourceFactory;
35use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
36use TYPO3\CMS\Core\Utility\GeneralUtility;
37use TYPO3\CMS\Impexp\Import;
38
39/**
40 * Main script class for the Import facility
41 *
42 * @internal this is a TYPO3 Backend controller implementation and not part of TYPO3's Core API.
43 */
44class ImportController extends ImportExportController
45{
46    /**
47     * The name of the module
48     *
49     * @var string
50     */
51    protected $moduleName = 'tx_impexp_import';
52
53    /**
54     * @var array|File[]
55     */
56    protected $uploadedFiles = [];
57
58    /**
59     * @var Import
60     */
61    protected $import;
62
63    /**
64     * @var ExtendedFileUtility
65     */
66    protected $fileProcessor;
67
68    /**
69     * @param ServerRequestInterface $request
70     * @return ResponseInterface
71     * @throws Exception
72     * @throws RouteNotFoundException
73     * @throws \TYPO3\CMS\Core\Resource\Exception
74     */
75    public function mainAction(ServerRequestInterface $request): ResponseInterface
76    {
77        $this->lang->includeLLFile('EXT:impexp/Resources/Private/Language/locallang.xlf');
78
79        $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
80        if (is_array($this->pageinfo)) {
81            $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
82        }
83        // Setting up the context sensitive menu:
84        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
85        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Impexp/ImportExport');
86        $this->moduleTemplate->addJavaScriptCode(
87            'ImpexpInLineJS',
88            'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';'
89        );
90
91        // Input data grabbed:
92        $inData = $request->getParsedBody()['tx_impexp'] ?? $request->getQueryParams()['tx_impexp'] ?? [];
93        if ($request->getMethod() === 'POST' && empty($request->getParsedBody())) {
94            // This happens if the post request was larger than allowed on the server
95            // We set the import action as default and output a user information
96            $flashMessage = GeneralUtility::makeInstance(
97                FlashMessage::class,
98                $this->lang->getLL('importdata_upload_nodata'),
99                $this->lang->getLL('importdata_upload_error'),
100                FlashMessage::ERROR
101            );
102            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
103            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
104            $defaultFlashMessageQueue->enqueue($flashMessage);
105        }
106        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
107        $this->standaloneView->assign('moduleUrl', (string)$uriBuilder->buildUriFromRoute($this->moduleName));
108        $this->standaloneView->assign('id', $this->id);
109        $this->standaloneView->assign('inData', $inData);
110
111        $backendUser = $this->getBackendUser();
112        $isEnabledForNonAdmin = (bool)($backendUser->getTSConfig()['options.']['impexp.']['enableImportForNonAdminUser'] ?? false);
113        if (!$backendUser->isAdmin() && !$isEnabledForNonAdmin) {
114            throw new \RuntimeException(
115                'Import module is disabled for non admin users and '
116                . 'userTsConfig options.impexp.enableImportForNonAdminUser is not enabled.',
117                1464435459
118            );
119        }
120        $this->shortcutName = $this->lang->getLL('title_import');
121        if (GeneralUtility::_POST('_upload')) {
122            $this->checkUpload();
123        }
124        // Finally: If upload went well, set the new file as the import file:
125        if (!empty($this->uploadedFiles[0])) {
126            // Only allowed extensions....
127            $extension = $this->uploadedFiles[0]->getExtension();
128            if ($extension === 't3d' || $extension === 'xml') {
129                $inData['file'] = $this->uploadedFiles[0]->getCombinedIdentifier();
130            }
131        }
132        // Call import interface:
133        $this->importData($inData);
134        $this->standaloneView->setTemplate('Import.html');
135
136        // Setting up the buttons and markers for docheader
137        $this->getButtons();
138
139        $this->moduleTemplate->setContent($this->standaloneView->render());
140        return new HtmlResponse($this->moduleTemplate->renderContent());
141    }
142
143    /**
144     * Import part of module
145     *
146     * @param array $inData Content of POST VAR tx_impexp[]..
147     * @throws \BadFunctionCallException
148     * @throws \InvalidArgumentException
149     * @throws \RuntimeException
150     */
151    protected function importData(array $inData): void
152    {
153        $access = is_array($this->pageinfo);
154        $beUser = $this->getBackendUser();
155        if ($this->id && $access || $beUser->isAdmin() && !$this->id) {
156            if ($beUser->isAdmin() && !$this->id) {
157                $this->pageinfo = ['title' => '[root-level]', 'uid' => 0, 'pid' => 0];
158            }
159            if ($inData['new_import']) {
160                unset($inData['import_mode']);
161            }
162            /** @var Import $import */
163            $import = GeneralUtility::makeInstance(Import::class);
164            $import->init();
165            $import->update = $inData['do_update'];
166            $import->import_mode = $inData['import_mode'];
167            $import->enableLogging = $inData['enableLogging'];
168            $import->global_ignore_pid = $inData['global_ignore_pid'];
169            $import->force_all_UIDS = $inData['force_all_UIDS'];
170            $import->showDiff = !$inData['notShowDiff'];
171            $import->softrefInputValues = $inData['softrefInputValues'];
172
173            // OUTPUT creation:
174
175            // Make input selector:
176            // must have trailing slash.
177            $path = $this->getDefaultImportExportFolder();
178            $exportFiles = $this->getExportFiles();
179
180            $this->shortcutName .= ' (' . htmlspecialchars($this->pageinfo['title']) . ')';
181
182            // Configuration
183            $selectOptions = [''];
184            foreach ($exportFiles as $file) {
185                $selectOptions[$file->getCombinedIdentifier()] = $file->getPublicUrl();
186            }
187
188            $this->standaloneView->assign('import', $import);
189            $this->standaloneView->assign('inData', $inData);
190            $this->standaloneView->assign('fileSelectOptions', $selectOptions);
191
192            if ($path) {
193                $this->standaloneView->assign('importPath', sprintf($this->lang->getLL('importdata_fromPathS'), $path->getCombinedIdentifier()));
194            } else {
195                $this->standaloneView->assign('importPath', $this->lang->getLL('importdata_no_default_upload_folder'));
196            }
197            $this->standaloneView->assign('isAdmin', $beUser->isAdmin());
198
199            // Upload file:
200            $tempFolder = $this->getDefaultImportExportFolder();
201            if ($tempFolder) {
202                $this->standaloneView->assign('tempFolder', $tempFolder->getCombinedIdentifier());
203                $this->standaloneView->assign('hasTempUploadFolder', true);
204                if (GeneralUtility::_POST('_upload')) {
205                    $this->standaloneView->assign('submitted', GeneralUtility::_POST('_upload'));
206                    $this->standaloneView->assign('noFileUploaded', $this->fileProcessor->internalUploadMap[1]);
207                    if ($this->uploadedFiles[0]) {
208                        $this->standaloneView->assign('uploadedFile', $this->uploadedFiles[0]->getName());
209                    }
210                }
211            }
212
213            // Perform import or preview depending:
214            if (isset($inData['file'])) {
215                $inFile = $this->getFile($inData['file']);
216                if ($inFile !== null && $inFile->exists()) {
217                    $this->standaloneView->assign('metaDataInFileExists', true);
218                    $importInhibitedMessages = [];
219                    if ($import->loadFile($inFile->getForLocalProcessing(false), 1)) {
220                        $importInhibitedMessages = $import->checkImportPrerequisites();
221                        if ($inData['import_file']) {
222                            if (empty($importInhibitedMessages)) {
223                                $import->importData($this->id);
224                                BackendUtility::setUpdateSignal('updatePageTree');
225                            }
226                        }
227                        $import->display_import_pid_record = $this->pageinfo;
228                        $this->standaloneView->assign('contentOverview', $import->displayContentOverview());
229                    }
230                    // Compile messages which are inhibiting a proper import and add them to output.
231                    if (!empty($importInhibitedMessages)) {
232                        $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier('impexp.errors');
233                        foreach ($importInhibitedMessages as $message) {
234                            $flashMessageQueue->addMessage(GeneralUtility::makeInstance(
235                                FlashMessage::class,
236                                $message,
237                                '',
238                                FlashMessage::ERROR
239                            ));
240                        }
241                    }
242                }
243            }
244
245            $this->standaloneView->assign('errors', $import->errorLog);
246        }
247    }
248
249    protected function getButtons(): void
250    {
251        parent::getButtons();
252
253        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
254        if ($this->id && is_array($this->pageinfo) || $this->getBackendUser()->isAdmin() && !$this->id) {
255            if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
256                // View
257                $onClick = BackendUtility::viewOnClick(
258                    $this->pageinfo['uid'],
259                    '',
260                    BackendUtility::BEgetRootLine($this->pageinfo['uid'])
261                );
262                $viewButton = $buttonBar->makeLinkButton()
263                    ->setTitle($this->lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
264                    ->setHref('#')
265                    ->setIcon($this->iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL))
266                    ->setOnClick($onClick);
267                $buttonBar->addButton($viewButton);
268            }
269        }
270    }
271
272    /**
273     * Check if a file has been uploaded
274     *
275     * @throws \TYPO3\CMS\Core\Resource\Exception
276     */
277    protected function checkUpload(): void
278    {
279        $file = GeneralUtility::_GP('file');
280        // Initializing:
281        $this->fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class);
282        $this->fileProcessor->setActionPermissions();
283        $conflictMode = empty(GeneralUtility::_GP('overwriteExistingFiles')) ? DuplicationBehavior::__default : DuplicationBehavior::REPLACE;
284        $this->fileProcessor->setExistingFilesConflictMode(DuplicationBehavior::cast($conflictMode));
285        $this->fileProcessor->start($file);
286        $result = $this->fileProcessor->processData();
287        if (!empty($result['upload'])) {
288            foreach ($result['upload'] as $uploadedFiles) {
289                $this->uploadedFiles += $uploadedFiles;
290            }
291        }
292    }
293
294    /**
295     * Gets all export files.
296     *
297     * @return File[]
298     * @throws \InvalidArgumentException
299     */
300    protected function getExportFiles(): array
301    {
302        $exportFiles = [];
303
304        $folder = $this->getDefaultImportExportFolder();
305        if ($folder !== null) {
306
307            /** @var FileExtensionFilter $filter */
308            $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
309            $filter->setAllowedFileExtensions(['t3d', 'xml']);
310            $folder->getStorage()->addFileAndFolderNameFilter([$filter, 'filterFileList']);
311
312            $exportFiles = $folder->getFiles();
313        }
314
315        return $exportFiles;
316    }
317
318    /**
319     * Gets a file by combined identifier.
320     *
321     * @param string $combinedIdentifier
322     * @return File|null
323     */
324    protected function getFile(string $combinedIdentifier): ?File
325    {
326        try {
327            $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectFromCombinedIdentifier($combinedIdentifier);
328        } catch (\Exception $exception) {
329            $file = null;
330        }
331
332        return $file;
333    }
334
335    /**
336     * @return BackendUserAuthentication
337     */
338    protected function getBackendUser(): BackendUserAuthentication
339    {
340        return $GLOBALS['BE_USER'];
341    }
342}
343