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