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