resourceFactory = $resourceFactory; $this->responseFactory = $responseFactory; $this->streamFactory = $streamFactory; $this->context = $context; } public function handleRequest(ServerRequestInterface $request): ResponseInterface { $items = (array)($request->getParsedBody()['items'] ?? []); if ($items === []) { // Return in case no items are given return $this->responseFactory->createResponse(400); } $fileExtensionFilter = null; $fileDownloadConfiguration = (array)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['fileDownload.'] ?? []); if ($fileDownloadConfiguration !== []) { if (!($fileDownloadConfiguration['enabled'] ?? true)) { // Return if file download is disabled return $this->responseFactory->createResponse(403); } // Initialize file extension filter, if configured $fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class); $fileExtensionFilter->setAllowedFileExtensions( GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['allowedFileExtensions'] ?? ''), true) ); $fileExtensionFilter->setDisallowedFileExtensions( GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['disallowedFileExtensions'] ?? ''), true) ); } $zipStream = tmpfile(); if (!is_resource($zipStream)) { throw new \RuntimeException('Could not open temporary resource for creating archive', 1630346631); } $zipFileName = stream_get_meta_data($zipStream)['uri']; $zipFile = new \ZipArchive(); $zipFile->open($zipFileName, \ZipArchive::OVERWRITE); $filesAdded = 0; foreach ($this->collectFiles($items) as $fileName => $fileObject) { // Add files with read permission and allowed file extension if (!$fileObject->getStorage()->checkFileActionPermission('read', $fileObject) || ($fileExtensionFilter !== null && !$fileExtensionFilter->isAllowed($fileObject->getExtension())) ) { continue; } $filesAdded++; $zipFile->addFile($fileObject->getForLocalProcessing(false), $fileName); } $zipFile->close(); $response = $this->createResponse($zipFileName, $filesAdded); if ($filesAdded > 0) { unlink($zipFileName); } return $response; } protected function createResponse(string $temporaryFileName, int $filesAdded): ResponseInterface { if ($filesAdded === 0) { return $this->responseFactory->createResponse() ->withHeader('Content-Type', 'application/json; charset=utf-8') ->withBody($this->streamFactory->createStream(json_encode(['success' => false, 'status' => 'noFiles']))); } $downloadFileName = 'typo3_download_' . $this->context->getAspect('date')->getDateTime()->format('Y-m-d-His') . '.zip'; return $this->responseFactory->createResponse() ->withHeader('Content-Type', 'application/zip') ->withHeader('Content-Disposition', 'attachment; filename=' . $downloadFileName) ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Pragma', 'no-cache') ->withHeader('Cache-Control', 'no-cache, no-store') ->withBody($this->streamFactory->createStreamFromFile($temporaryFileName)); } /** * @return FileInterface[] */ protected function collectFiles(array $items): array { $files = []; foreach ($items as $itemIdentifier) { $fileOrFolderObject = $this->resourceFactory->retrieveFileOrFolderObject($itemIdentifier); if ($fileOrFolderObject === null) { continue; } // Files from fallback storage must be skipped in general if ($fileOrFolderObject->getStorage()->isFallbackStorage()) { continue; } $baseIdentifier = dirname($fileOrFolderObject->getIdentifier()); if ($fileOrFolderObject instanceof Folder) { // handle file / folder structure foreach ($this->getFilesAndFoldersRecursive($fileOrFolderObject) as $fileObject) { $commonPrefix = (string)PathUtility::getCommonPrefix([$baseIdentifier, $fileObject->getIdentifier()]); $files[substr($fileObject->getIdentifier(), strlen($commonPrefix))] = $fileObject; } } else { $files[$fileOrFolderObject->getName()] = $fileOrFolderObject; } } return $files; } protected function getFilesAndFoldersRecursive(Folder $folder): iterable { foreach ($folder->getSubfolders() as $subFolder) { yield from $this->getFilesAndFoldersRecursive($subFolder); } foreach ($folder->getFiles() as $file) { yield $file; } } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } }