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