diff --git a/typo3/sysext/impexp/Classes/Controller/ExportController.php b/typo3/sysext/impexp/Classes/Controller/ExportController.php index b2608e5e1c0a983bea61442fc975addb24f8c648..2700f3b992be85b89ec0e4c9d63acb0518f3ea56 100644 --- a/typo3/sysext/impexp/Classes/Controller/ExportController.php +++ b/typo3/sysext/impexp/Classes/Controller/ExportController.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Impexp\Controller; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -24,6 +25,7 @@ use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Exception as CoreException; use TYPO3\CMS\Core\Http\HtmlResponse; +use TYPO3\CMS\Core\Http\PropagateResponseException; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Messaging\FlashMessage; @@ -59,15 +61,19 @@ class ExportController extends ImportExportController */ protected $presetRepository; + protected ResponseFactoryInterface $responseFactory; + public function __construct( IconFactory $iconFactory, PageRenderer $pageRenderer, UriBuilder $uriBuilder, - ModuleTemplateFactory $moduleTemplateFactory + ModuleTemplateFactory $moduleTemplateFactory, + ResponseFactoryInterface $responseFactory ) { parent::__construct($iconFactory, $pageRenderer, $uriBuilder, $moduleTemplateFactory); $this->presetRepository = GeneralUtility::makeInstance(PresetRepository::class); + $this->responseFactory = $responseFactory; } /** @@ -274,15 +280,15 @@ class ExportController extends ImportExportController // Export by download: if ($inData['download_export'] ?? null) { - // @todo: create response and return! $fileName = $this->export->getOrGenerateExportFileNameWithFileExtension(); $fileContent = $this->export->render(); - $mimeType = 'application/octet-stream'; - header('Content-Type: ' . $mimeType); - header('Content-Length: ' . strlen($fileContent)); - header('Content-Disposition: attachment; filename=' . PathUtility::basename($fileName)); - echo $fileContent; - die; + $response = $this->responseFactory->createResponse() + ->withHeader('Content-Type', 'application/octet-stream') + ->withHeader('Content-Length', (string)strlen($fileContent)) + ->withHeader('Content-Disposition', 'attachment; filename=' . PathUtility::basename($fileName)); + $response->getBody()->write($fileContent); + // @todo: Refactor to *return* the response instead of throwing PropagateResponseException + throw new PropagateResponseException($response, 1629196918); } // Export by saving on server: diff --git a/typo3/sysext/impexp/Tests/Functional/Export/ExportControllerTest.php b/typo3/sysext/impexp/Tests/Functional/Export/ExportControllerTest.php index a8be6d8b6f83390e665906782a6c582c89f7dbab..85409e283366189d2d5e59eac578ecded3c75778 100644 --- a/typo3/sysext/impexp/Tests/Functional/Export/ExportControllerTest.php +++ b/typo3/sysext/impexp/Tests/Functional/Export/ExportControllerTest.php @@ -18,7 +18,14 @@ declare(strict_types=1); namespace TYPO3\CMS\Impexp\Tests\Functional\Export; use PHPUnit\Framework\MockObject\MockObject; -use TYPO3\CMS\Core\Localization\LanguageService; +use Psr\Http\Message\ResponseFactoryInterface; +use TYPO3\CMS\Backend\Routing\UriBuilder; +use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\Http\PropagateResponseException; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Impexp\Controller\ExportController; use TYPO3\CMS\Impexp\Tests\Functional\AbstractImportExportTestCase; use TYPO3\TestingFramework\Core\AccessibleObjectInterface; @@ -33,9 +40,18 @@ class ExportControllerTest extends AbstractImportExportTestCase protected function setUp(): void { parent::setUp(); - - $this->exportControllerMock = $this->getAccessibleMock(ExportController::class, ['dummy'], [], '', false); - $this->exportControllerMock->_set('lang', $this->createMock(LanguageService::class)); + $container = $this->getContainer(); + $this->exportControllerMock = $this->getAccessibleMock( + ExportController::class, + ['dummy'], + [ + $container->get(IconFactory::class), + $container->get(PageRenderer::class), + $container->get(UriBuilder::class), + $container->get(ModuleTemplateFactory::class), + $container->get(ResponseFactoryInterface::class) + ] + ); } /** @@ -46,4 +62,22 @@ class ExportControllerTest extends AbstractImportExportTestCase $tables = $this->exportControllerMock->_call('getTableSelectOptions'); self::assertArrayHasKey('tx_impexp_presets', $tables); } + + /** + * @test + */ + public function throwsPropagateResponseExceptionOnDownloadExportFile(): void + { + $request = (new ServerRequest()) + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE) + ->withParsedBody([ + 'tx_impexp' => [ + 'download_export' => 1 + ] + ]); + + $this->expectExceptionCode(1629196918); + $this->expectException(PropagateResponseException::class); + $this->exportControllerMock->mainAction($request); + } } diff --git a/typo3/sysext/impexp/Tests/Functional/Export/PresetsTest.php b/typo3/sysext/impexp/Tests/Functional/Export/PresetsTest.php index cf692d116b78d91b5ec2581a828097ec9b0d335b..384fecf9b8ccd305e5a54d3c19156b8f299a1898 100644 --- a/typo3/sysext/impexp/Tests/Functional/Export/PresetsTest.php +++ b/typo3/sysext/impexp/Tests/Functional/Export/PresetsTest.php @@ -20,6 +20,7 @@ namespace TYPO3\CMS\Impexp\Tests\Functional\Export; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\ModuleTemplate; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Core\Http\ResponseFactory; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Impexp\Controller\ExportController; @@ -101,12 +102,14 @@ class PresetsTest extends AbstractImportExportTestCase $moduleTemplateMock->expects(self::once())->method('addFlashMessage')->with(self::equalTo($expected)); $moduleTemplateFactoryMock = $this->getAccessibleMock(ModuleTemplateFactory::class, ['create'], [], '', false); $moduleTemplateFactoryMock->expects(self::any())->method('create')->willReturn($moduleTemplateMock); + $responseFactory = $this->getAccessibleMock(ResponseFactory::class, ['dummy'], [], '', false); $subject = $this->getAccessibleMock(ExportController::class, ['addFlashMessage'], [ $iconFactoryMock, $pageRendererMock, $uriBuilderMock, - $moduleTemplateFactoryMock + $moduleTemplateFactoryMock, + $responseFactory ]); $subject->_set('moduleTemplate', $moduleTemplateMock); $inData = $subject->preprocessInputData($inData);