Skip to content

Commit 7f686a9

Browse files
committed
Merge branch 'ACP2E-1627' of https://github.com/magento-l3/magento2ce into PR-L3-03132023
2 parents 5bf4909 + 0fd7a36 commit 7f686a9

File tree

5 files changed

+738
-138
lines changed

5 files changed

+738
-138
lines changed

dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public function testGenerateFileFromString()
3434
$contentType = 'application/pdf';
3535
$fileContent = ['type' => 'string', 'value' => ''];
3636
$response = $fileFactory->create($filename, $fileContent, DirectoryList::VAR_DIR, $contentType);
37+
ob_start();
38+
$response->sendResponse();
39+
ob_end_clean();
3740
/** @var ContentType $contentTypeHeader */
3841
$contentTypeHeader = $response->getHeader('Content-type');
3942

@@ -48,7 +51,10 @@ public function testGenerateFileFromString()
4851

4952
/* Check the file is removed after generation if the corresponding option is set */
5053
$fileContent = ['type' => 'string', 'value' => '', 'rm' => true];
51-
$fileFactory->create($filename, $fileContent, DirectoryList::VAR_DIR, $contentType);
54+
$response = $fileFactory->create($filename, $fileContent, DirectoryList::VAR_DIR, $contentType);
55+
ob_start();
56+
$response->sendResponse();
57+
ob_end_clean();
5258

5359
self::assertFalse($varDirectory->isFile($filename));
5460
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\App\Response;
9+
10+
use InvalidArgumentException;
11+
use Magento\Framework\App\Filesystem\DirectoryList;
12+
use Magento\Framework\App\Http\Context;
13+
use Magento\Framework\App\PageCache\NotCacheableInterface;
14+
use Magento\Framework\App\Request\Http as HttpRequest;
15+
use Magento\Framework\Exception\FileSystemException;
16+
use Magento\Framework\Filesystem;
17+
use Magento\Framework\Filesystem\Driver\File\Mime;
18+
use Magento\Framework\Session\Config\ConfigInterface;
19+
use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory;
20+
use Magento\Framework\Stdlib\CookieManagerInterface;
21+
use Magento\Framework\Stdlib\DateTime;
22+
23+
/**
24+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
25+
*/
26+
class File extends Http implements NotCacheableInterface
27+
{
28+
private const DEFAULT_RAW_CONTENT_TYPE = 'application/octet-stream';
29+
30+
/**
31+
* @var Http
32+
*/
33+
private Http $response;
34+
35+
/**
36+
* @var Filesystem
37+
*/
38+
private Filesystem $filesystem;
39+
40+
/**
41+
* @var Mime
42+
*/
43+
private Mime $mime;
44+
45+
/**
46+
* @var array
47+
*/
48+
private array $options = [
49+
'directoryCode' => DirectoryList::ROOT,
50+
'filePath' => null,
51+
// File name to send to the client
52+
'fileName' => null,
53+
'contentType' => null,
54+
'contentLength' => null,
55+
// Whether to remove the file after it is sent to the client
56+
'remove' => false,
57+
// Whether to send the file as attachment
58+
'attachment' => true
59+
];
60+
61+
/**
62+
* @param HttpRequest $request
63+
* @param CookieManagerInterface $cookieManager
64+
* @param CookieMetadataFactory $cookieMetadataFactory
65+
* @param Context $context
66+
* @param DateTime $dateTime
67+
* @param ConfigInterface $sessionConfig
68+
* @param Http $response
69+
* @param Filesystem $filesystem
70+
* @param Mime $mime
71+
* @param array $options
72+
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
73+
*/
74+
public function __construct(
75+
HttpRequest $request,
76+
CookieManagerInterface $cookieManager,
77+
CookieMetadataFactory $cookieMetadataFactory,
78+
Context $context,
79+
DateTime $dateTime,
80+
ConfigInterface $sessionConfig,
81+
Http $response,
82+
Filesystem $filesystem,
83+
Mime $mime,
84+
array $options = []
85+
) {
86+
parent::__construct($request, $cookieManager, $cookieMetadataFactory, $context, $dateTime, $sessionConfig);
87+
$this->response = $response;
88+
$this->filesystem = $filesystem;
89+
$this->mime = $mime;
90+
$this->options = array_merge($this->options, $options);
91+
if (!isset($this->options['filePath'])) {
92+
if (!isset($this->options['fileName'])) {
93+
throw new InvalidArgumentException("File name is required.");
94+
}
95+
$this->options['contentType'] ??= self::DEFAULT_RAW_CONTENT_TYPE;
96+
}
97+
}
98+
99+
/**
100+
* @inheritDoc
101+
*/
102+
public function sendResponse()
103+
{
104+
$dir = $this->filesystem->getDirectoryRead($this->options['directoryCode']);
105+
$forceHeaders = true;
106+
if (isset($this->options['filePath'])) {
107+
if (!$dir->isExist($this->options['filePath'])) {
108+
throw new InvalidArgumentException("File '{$this->options['filePath']}' does not exists.");
109+
}
110+
$filePath = $this->options['filePath'];
111+
$this->options['contentType'] ??= $dir->stat($filePath)['mimeType']
112+
?? $this->mime->getMimeType($dir->getAbsolutePath($filePath));
113+
$this->options['contentLength'] ??= $dir->stat($filePath)['size'];
114+
$this->options['fileName'] ??= basename($filePath);
115+
} else {
116+
$this->options['contentLength'] = mb_strlen((string) $this->response->getContent(), '8bit');
117+
$forceHeaders = false;
118+
}
119+
120+
$this->response->setHttpResponseCode(200);
121+
if ($this->options['attachment']) {
122+
$this->response->setHeader(
123+
'Content-Disposition',
124+
'attachment; filename="' . $this->options['fileName'] . '"',
125+
$forceHeaders
126+
);
127+
}
128+
$this->response->setHeader('Content-Type', $this->options['contentType'], $forceHeaders)
129+
->setHeader('Content-Length', $this->options['contentLength'], $forceHeaders)
130+
->setHeader('Pragma', 'public', $forceHeaders)
131+
->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', $forceHeaders)
132+
->setHeader('Last-Modified', date('r'), $forceHeaders);
133+
134+
if (isset($this->options['filePath'])) {
135+
$this->response->sendHeaders();
136+
if (!$this->request->isHead()) {
137+
$this->sendFileContent();
138+
$this->afterFileIsSent();
139+
}
140+
} else {
141+
$this->response->sendResponse();
142+
}
143+
return $this;
144+
}
145+
146+
/**
147+
* @inheritDoc
148+
*/
149+
public function setHeader($name, $value, $replace = false)
150+
{
151+
$this->response->setHeader($name, $value, $replace);
152+
return $this;
153+
}
154+
155+
/**
156+
* @inheritDoc
157+
*/
158+
public function getHeader($name)
159+
{
160+
return $this->response->getHeader($name);
161+
}
162+
163+
/**
164+
* @inheritDoc
165+
*/
166+
public function clearHeader($name)
167+
{
168+
$this->response->clearHeader($name);
169+
return $this;
170+
}
171+
172+
/**
173+
* @inheritDoc
174+
*/
175+
public function setBody($value)
176+
{
177+
$this->response->setBody($value);
178+
return $this;
179+
}
180+
181+
/**
182+
* @inheritDoc
183+
*/
184+
public function appendBody($value)
185+
{
186+
$this->response->appendBody($value);
187+
return $this;
188+
}
189+
190+
/**
191+
* @inheritDoc
192+
*/
193+
public function getContent()
194+
{
195+
return $this->response->getContent();
196+
}
197+
198+
/**
199+
* @inheritDoc
200+
*/
201+
public function setContent($value)
202+
{
203+
$this->response->setContent($value);
204+
return $this;
205+
}
206+
207+
/**
208+
* Sends file content to the client
209+
*
210+
* @return void
211+
* @throws FileSystemException
212+
*/
213+
private function sendFileContent(): void
214+
{
215+
$dir = $this->filesystem->getDirectoryRead($this->options['directoryCode']);
216+
$stream = $dir->openFile($this->options['filePath'], 'r');
217+
while (!$stream->eof()) {
218+
// phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput
219+
echo $stream->read(1024);
220+
}
221+
$stream->close();
222+
}
223+
224+
/**
225+
* Callback after file is sent to the client
226+
*
227+
* @return void
228+
* @throws FileSystemException
229+
*/
230+
private function afterFileIsSent(): void
231+
{
232+
$this->response->clearBody();
233+
if ($this->options['remove']) {
234+
$dir = $this->filesystem->getDirectoryWrite($this->options['directoryCode']);
235+
$dir->delete($this->options['filePath']);
236+
}
237+
}
238+
}

lib/internal/Magento/Framework/App/Response/Http/FileFactory.php

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
namespace Magento\Framework\App\Response\Http;
99

1010
use Magento\Framework\App\Filesystem\DirectoryList;
11+
use Magento\Framework\App\ObjectManager;
12+
use Magento\Framework\App\ResponseInterface;
13+
use Magento\Framework\Filesystem;
1114

1215
/**
1316
* Class FileFactory serves to declare file content in response for download.
@@ -17,6 +20,8 @@
1720
class FileFactory
1821
{
1922
/**
23+
* @deprecared
24+
* @see $fileResponseFactory
2025
* @var \Magento\Framework\App\ResponseInterface
2126
*/
2227
protected $_response;
@@ -27,15 +32,24 @@ class FileFactory
2732
protected $_filesystem;
2833

2934
/**
30-
* @param \Magento\Framework\App\ResponseInterface $response
31-
* @param \Magento\Framework\Filesystem $filesystem
35+
* @var \Magento\Framework\App\Response\FileFactory
36+
*/
37+
private $fileResponseFactory;
38+
39+
/**
40+
* @param ResponseInterface $response
41+
* @param Filesystem $filesystem
42+
* @param \Magento\Framework\App\Response\FileFactory|null $fileResponseFactory
3243
*/
3344
public function __construct(
3445
\Magento\Framework\App\ResponseInterface $response,
35-
\Magento\Framework\Filesystem $filesystem
46+
\Magento\Framework\Filesystem $filesystem,
47+
?\Magento\Framework\App\Response\FileFactory $fileResponseFactory = null
3648
) {
3749
$this->_response = $response;
3850
$this->_filesystem = $filesystem;
51+
$this->fileResponseFactory = $fileResponseFactory
52+
?? ObjectManager::getInstance()->get(\Magento\Framework\App\Response\FileFactory::class);
3953
}
4054

4155
/**
@@ -79,38 +93,23 @@ public function create(
7993
$contentLength = $dir->stat($file)['size'];
8094
}
8195
}
82-
$this->_response->setHttpResponseCode(200)
83-
->setHeader('Pragma', 'public', true)
84-
->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true)
85-
->setHeader('Content-type', $contentType, true)
86-
->setHeader('Content-Length', $contentLength === null ? strlen((string)$fileContent) : $contentLength, true)
87-
->setHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"', true)
88-
->setHeader('Last-Modified', date('r'), true);
8996

9097
if ($content !== null) {
91-
$this->_response->sendHeaders();
92-
if ($isFile) {
93-
$stream = $dir->openFile($file, 'r');
94-
while (!$stream->eof()) {
95-
// phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput
96-
echo $stream->read(1024);
97-
}
98-
} else {
98+
if (!$isFile) {
9999
$dir->writeFile($fileName, $fileContent);
100100
$file = $fileName;
101-
$stream = $dir->openFile($fileName, 'r');
102-
while (!$stream->eof()) {
103-
// phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput
104-
echo $stream->read(1024);
105-
}
106-
}
107-
$stream->close();
108-
flush();
109-
if (!empty($content['rm'])) {
110-
$dir->delete($file);
111101
}
112102
}
113-
return $this->_response;
103+
return $this->fileResponseFactory->create([
104+
'options' => [
105+
'filePath' => $file,
106+
'fileName' => $fileName,
107+
'contentType' => $contentType,
108+
'contentLength' => $contentLength,
109+
'directoryCode' => $baseDir,
110+
'remove' => is_array($content) && !empty($content['rm'])
111+
]
112+
]);
114113
}
115114

116115
/**

0 commit comments

Comments
 (0)