Chris@0
|
1 <?php
|
Chris@0
|
2 /**
|
Chris@12
|
3 * @see https://github.com/zendframework/zend-diactoros for the canonical source repository
|
Chris@12
|
4 * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
|
Chris@0
|
5 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
|
Chris@0
|
6 */
|
Chris@0
|
7
|
Chris@0
|
8 namespace Zend\Diactoros;
|
Chris@0
|
9
|
Chris@0
|
10 use InvalidArgumentException;
|
Chris@0
|
11 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
12 use Psr\Http\Message\UploadedFileInterface;
|
Chris@0
|
13 use RuntimeException;
|
Chris@0
|
14
|
Chris@16
|
15 use function dirname;
|
Chris@16
|
16 use function fclose;
|
Chris@16
|
17 use function fopen;
|
Chris@16
|
18 use function fwrite;
|
Chris@16
|
19 use function is_dir;
|
Chris@16
|
20 use function is_int;
|
Chris@16
|
21 use function is_resource;
|
Chris@16
|
22 use function is_string;
|
Chris@16
|
23 use function is_writable;
|
Chris@16
|
24 use function move_uploaded_file;
|
Chris@16
|
25 use function sprintf;
|
Chris@16
|
26 use function strpos;
|
Chris@16
|
27
|
Chris@16
|
28 use const PHP_SAPI;
|
Chris@16
|
29 use const UPLOAD_ERR_CANT_WRITE;
|
Chris@16
|
30 use const UPLOAD_ERR_EXTENSION;
|
Chris@16
|
31 use const UPLOAD_ERR_FORM_SIZE;
|
Chris@16
|
32 use const UPLOAD_ERR_INI_SIZE;
|
Chris@16
|
33 use const UPLOAD_ERR_NO_FILE;
|
Chris@16
|
34 use const UPLOAD_ERR_NO_TMP_DIR;
|
Chris@16
|
35 use const UPLOAD_ERR_OK;
|
Chris@16
|
36 use const UPLOAD_ERR_PARTIAL;
|
Chris@16
|
37
|
Chris@0
|
38 class UploadedFile implements UploadedFileInterface
|
Chris@0
|
39 {
|
Chris@12
|
40 const ERROR_MESSAGES = [
|
Chris@12
|
41 UPLOAD_ERR_OK => 'There is no error, the file uploaded with success',
|
Chris@12
|
42 UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
|
Chris@12
|
43 UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '
|
Chris@12
|
44 . 'specified in the HTML form',
|
Chris@12
|
45 UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded',
|
Chris@12
|
46 UPLOAD_ERR_NO_FILE => 'No file was uploaded',
|
Chris@12
|
47 UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
|
Chris@12
|
48 UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
|
Chris@12
|
49 UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.',
|
Chris@12
|
50 ];
|
Chris@12
|
51
|
Chris@0
|
52 /**
|
Chris@12
|
53 * @var string|null
|
Chris@0
|
54 */
|
Chris@0
|
55 private $clientFilename;
|
Chris@0
|
56
|
Chris@0
|
57 /**
|
Chris@12
|
58 * @var string|null
|
Chris@0
|
59 */
|
Chris@0
|
60 private $clientMediaType;
|
Chris@0
|
61
|
Chris@0
|
62 /**
|
Chris@0
|
63 * @var int
|
Chris@0
|
64 */
|
Chris@0
|
65 private $error;
|
Chris@0
|
66
|
Chris@0
|
67 /**
|
Chris@0
|
68 * @var null|string
|
Chris@0
|
69 */
|
Chris@0
|
70 private $file;
|
Chris@0
|
71
|
Chris@0
|
72 /**
|
Chris@0
|
73 * @var bool
|
Chris@0
|
74 */
|
Chris@0
|
75 private $moved = false;
|
Chris@0
|
76
|
Chris@0
|
77 /**
|
Chris@0
|
78 * @var int
|
Chris@0
|
79 */
|
Chris@0
|
80 private $size;
|
Chris@0
|
81
|
Chris@0
|
82 /**
|
Chris@0
|
83 * @var null|StreamInterface
|
Chris@0
|
84 */
|
Chris@0
|
85 private $stream;
|
Chris@0
|
86
|
Chris@0
|
87 /**
|
Chris@0
|
88 * @param string|resource $streamOrFile
|
Chris@0
|
89 * @param int $size
|
Chris@0
|
90 * @param int $errorStatus
|
Chris@0
|
91 * @param string|null $clientFilename
|
Chris@0
|
92 * @param string|null $clientMediaType
|
Chris@0
|
93 * @throws InvalidArgumentException
|
Chris@0
|
94 */
|
Chris@0
|
95 public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
|
Chris@0
|
96 {
|
Chris@0
|
97 if ($errorStatus === UPLOAD_ERR_OK) {
|
Chris@0
|
98 if (is_string($streamOrFile)) {
|
Chris@0
|
99 $this->file = $streamOrFile;
|
Chris@0
|
100 }
|
Chris@0
|
101 if (is_resource($streamOrFile)) {
|
Chris@0
|
102 $this->stream = new Stream($streamOrFile);
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 if (! $this->file && ! $this->stream) {
|
Chris@0
|
106 if (! $streamOrFile instanceof StreamInterface) {
|
Chris@0
|
107 throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile');
|
Chris@0
|
108 }
|
Chris@0
|
109 $this->stream = $streamOrFile;
|
Chris@0
|
110 }
|
Chris@0
|
111 }
|
Chris@0
|
112
|
Chris@0
|
113 if (! is_int($size)) {
|
Chris@0
|
114 throw new InvalidArgumentException('Invalid size provided for UploadedFile; must be an int');
|
Chris@0
|
115 }
|
Chris@0
|
116 $this->size = $size;
|
Chris@0
|
117
|
Chris@0
|
118 if (! is_int($errorStatus)
|
Chris@0
|
119 || 0 > $errorStatus
|
Chris@0
|
120 || 8 < $errorStatus
|
Chris@0
|
121 ) {
|
Chris@0
|
122 throw new InvalidArgumentException(
|
Chris@0
|
123 'Invalid error status for UploadedFile; must be an UPLOAD_ERR_* constant'
|
Chris@0
|
124 );
|
Chris@0
|
125 }
|
Chris@0
|
126 $this->error = $errorStatus;
|
Chris@0
|
127
|
Chris@0
|
128 if (null !== $clientFilename && ! is_string($clientFilename)) {
|
Chris@0
|
129 throw new InvalidArgumentException(
|
Chris@0
|
130 'Invalid client filename provided for UploadedFile; must be null or a string'
|
Chris@0
|
131 );
|
Chris@0
|
132 }
|
Chris@0
|
133 $this->clientFilename = $clientFilename;
|
Chris@0
|
134
|
Chris@0
|
135 if (null !== $clientMediaType && ! is_string($clientMediaType)) {
|
Chris@0
|
136 throw new InvalidArgumentException(
|
Chris@0
|
137 'Invalid client media type provided for UploadedFile; must be null or a string'
|
Chris@0
|
138 );
|
Chris@0
|
139 }
|
Chris@0
|
140 $this->clientMediaType = $clientMediaType;
|
Chris@0
|
141 }
|
Chris@0
|
142
|
Chris@0
|
143 /**
|
Chris@0
|
144 * {@inheritdoc}
|
Chris@0
|
145 * @throws \RuntimeException if the upload was not successful.
|
Chris@0
|
146 */
|
Chris@0
|
147 public function getStream()
|
Chris@0
|
148 {
|
Chris@0
|
149 if ($this->error !== UPLOAD_ERR_OK) {
|
Chris@12
|
150 throw new RuntimeException(sprintf(
|
Chris@12
|
151 'Cannot retrieve stream due to upload error: %s',
|
Chris@12
|
152 self::ERROR_MESSAGES[$this->error]
|
Chris@12
|
153 ));
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 if ($this->moved) {
|
Chris@0
|
157 throw new RuntimeException('Cannot retrieve stream after it has already been moved');
|
Chris@0
|
158 }
|
Chris@0
|
159
|
Chris@0
|
160 if ($this->stream instanceof StreamInterface) {
|
Chris@0
|
161 return $this->stream;
|
Chris@0
|
162 }
|
Chris@0
|
163
|
Chris@0
|
164 $this->stream = new Stream($this->file);
|
Chris@0
|
165 return $this->stream;
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 /**
|
Chris@0
|
169 * {@inheritdoc}
|
Chris@0
|
170 *
|
Chris@0
|
171 * @see http://php.net/is_uploaded_file
|
Chris@0
|
172 * @see http://php.net/move_uploaded_file
|
Chris@0
|
173 * @param string $targetPath Path to which to move the uploaded file.
|
Chris@0
|
174 * @throws \RuntimeException if the upload was not successful.
|
Chris@0
|
175 * @throws \InvalidArgumentException if the $path specified is invalid.
|
Chris@0
|
176 * @throws \RuntimeException on any error during the move operation, or on
|
Chris@0
|
177 * the second or subsequent call to the method.
|
Chris@0
|
178 */
|
Chris@0
|
179 public function moveTo($targetPath)
|
Chris@0
|
180 {
|
Chris@0
|
181 if ($this->moved) {
|
Chris@0
|
182 throw new RuntimeException('Cannot move file; already moved!');
|
Chris@0
|
183 }
|
Chris@0
|
184
|
Chris@0
|
185 if ($this->error !== UPLOAD_ERR_OK) {
|
Chris@12
|
186 throw new RuntimeException(sprintf(
|
Chris@12
|
187 'Cannot retrieve stream due to upload error: %s',
|
Chris@12
|
188 self::ERROR_MESSAGES[$this->error]
|
Chris@12
|
189 ));
|
Chris@0
|
190 }
|
Chris@0
|
191
|
Chris@0
|
192 if (! is_string($targetPath) || empty($targetPath)) {
|
Chris@0
|
193 throw new InvalidArgumentException(
|
Chris@0
|
194 'Invalid path provided for move operation; must be a non-empty string'
|
Chris@0
|
195 );
|
Chris@0
|
196 }
|
Chris@0
|
197
|
Chris@0
|
198 $targetDirectory = dirname($targetPath);
|
Chris@0
|
199 if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
|
Chris@0
|
200 throw new RuntimeException(sprintf(
|
Chris@0
|
201 'The target directory `%s` does not exists or is not writable',
|
Chris@0
|
202 $targetDirectory
|
Chris@0
|
203 ));
|
Chris@0
|
204 }
|
Chris@0
|
205
|
Chris@0
|
206 $sapi = PHP_SAPI;
|
Chris@0
|
207 switch (true) {
|
Chris@0
|
208 case (empty($sapi) || 0 === strpos($sapi, 'cli') || ! $this->file):
|
Chris@0
|
209 // Non-SAPI environment, or no filename present
|
Chris@0
|
210 $this->writeFile($targetPath);
|
Chris@0
|
211 break;
|
Chris@0
|
212 default:
|
Chris@0
|
213 // SAPI environment, with file present
|
Chris@0
|
214 if (false === move_uploaded_file($this->file, $targetPath)) {
|
Chris@0
|
215 throw new RuntimeException('Error occurred while moving uploaded file');
|
Chris@0
|
216 }
|
Chris@0
|
217 break;
|
Chris@0
|
218 }
|
Chris@0
|
219
|
Chris@0
|
220 $this->moved = true;
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 /**
|
Chris@0
|
224 * {@inheritdoc}
|
Chris@0
|
225 *
|
Chris@0
|
226 * @return int|null The file size in bytes or null if unknown.
|
Chris@0
|
227 */
|
Chris@0
|
228 public function getSize()
|
Chris@0
|
229 {
|
Chris@0
|
230 return $this->size;
|
Chris@0
|
231 }
|
Chris@0
|
232
|
Chris@0
|
233 /**
|
Chris@0
|
234 * {@inheritdoc}
|
Chris@0
|
235 *
|
Chris@0
|
236 * @see http://php.net/manual/en/features.file-upload.errors.php
|
Chris@0
|
237 * @return int One of PHP's UPLOAD_ERR_XXX constants.
|
Chris@0
|
238 */
|
Chris@0
|
239 public function getError()
|
Chris@0
|
240 {
|
Chris@0
|
241 return $this->error;
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 /**
|
Chris@0
|
245 * {@inheritdoc}
|
Chris@0
|
246 *
|
Chris@0
|
247 * @return string|null The filename sent by the client or null if none
|
Chris@0
|
248 * was provided.
|
Chris@0
|
249 */
|
Chris@0
|
250 public function getClientFilename()
|
Chris@0
|
251 {
|
Chris@0
|
252 return $this->clientFilename;
|
Chris@0
|
253 }
|
Chris@0
|
254
|
Chris@0
|
255 /**
|
Chris@0
|
256 * {@inheritdoc}
|
Chris@0
|
257 */
|
Chris@0
|
258 public function getClientMediaType()
|
Chris@0
|
259 {
|
Chris@0
|
260 return $this->clientMediaType;
|
Chris@0
|
261 }
|
Chris@0
|
262
|
Chris@0
|
263 /**
|
Chris@0
|
264 * Write internal stream to given path
|
Chris@0
|
265 *
|
Chris@0
|
266 * @param string $path
|
Chris@0
|
267 */
|
Chris@0
|
268 private function writeFile($path)
|
Chris@0
|
269 {
|
Chris@0
|
270 $handle = fopen($path, 'wb+');
|
Chris@0
|
271 if (false === $handle) {
|
Chris@0
|
272 throw new RuntimeException('Unable to write to designated path');
|
Chris@0
|
273 }
|
Chris@0
|
274
|
Chris@0
|
275 $stream = $this->getStream();
|
Chris@0
|
276 $stream->rewind();
|
Chris@0
|
277 while (! $stream->eof()) {
|
Chris@0
|
278 fwrite($handle, $stream->read(4096));
|
Chris@0
|
279 }
|
Chris@0
|
280
|
Chris@0
|
281 fclose($handle);
|
Chris@0
|
282 }
|
Chris@0
|
283 }
|