2be9119d25f7c6e3cb677d887bfa89aeed05c05a
[lhc/web/wiklou.git] / includes / libs / ParamValidator / Util / UploadedFile.php
1 <?php
2
3 namespace Wikimedia\ParamValidator\Util;
4
5 use Psr\Http\Message\UploadedFileInterface;
6 use RuntimeException;
7 use Wikimedia\AtEase\AtEase;
8
9 /**
10 * A simple implementation of UploadedFileInterface
11 *
12 * This exists so ParamValidator needn't depend on any specific PSR-7
13 * implementation for a class implementing UploadedFileInterface. It shouldn't
14 * be used directly by other code, other than perhaps when implementing
15 * Callbacks::getUploadedFile() when another PSR-7 library is not already in use.
16 *
17 * @since 1.34
18 */
19 class UploadedFile implements UploadedFileInterface {
20
21 /** @var array File data */
22 private $data;
23
24 /** @var bool */
25 private $fromUpload;
26
27 /** @var UploadedFileStream|null */
28 private $stream = null;
29
30 /** @var bool Whether moveTo() was called */
31 private $moved = false;
32
33 /**
34 * @param array $data Data from $_FILES
35 * @param bool $fromUpload Set false if using this task with data not from
36 * $_FILES. Intended for unit testing.
37 */
38 public function __construct( array $data, $fromUpload = true ) {
39 $this->data = $data;
40 $this->fromUpload = $fromUpload;
41 }
42
43 /**
44 * Throw if there was an error
45 * @throws RuntimeException
46 */
47 private function checkError() {
48 switch ( $this->data['error'] ) {
49 case UPLOAD_ERR_OK:
50 break;
51
52 case UPLOAD_ERR_INI_SIZE:
53 throw new RuntimeException( 'Upload exceeded maximum size' );
54
55 case UPLOAD_ERR_FORM_SIZE:
56 throw new RuntimeException( 'Upload exceeded form-specified maximum size' );
57
58 case UPLOAD_ERR_PARTIAL:
59 throw new RuntimeException( 'File was only partially uploaded' );
60
61 case UPLOAD_ERR_NO_FILE:
62 throw new RuntimeException( 'No file was uploaded' );
63
64 case UPLOAD_ERR_NO_TMP_DIR:
65 throw new RuntimeException( 'PHP has no temporary folder for storing uploaded files' );
66
67 case UPLOAD_ERR_CANT_WRITE:
68 throw new RuntimeException( 'PHP was unable to save the uploaded file' );
69
70 case UPLOAD_ERR_EXTENSION:
71 throw new RuntimeException( 'A PHP extension stopped the file upload' );
72
73 default:
74 throw new RuntimeException( 'Unknown upload error code ' . $this->data['error'] );
75 }
76
77 if ( $this->moved ) {
78 throw new RuntimeException( 'File has already been moved' );
79 }
80 if ( !isset( $this->data['tmp_name'] ) || !file_exists( $this->data['tmp_name'] ) ) {
81 throw new RuntimeException( 'Uploaded file is missing' );
82 }
83 }
84
85 public function getStream() {
86 if ( $this->stream ) {
87 return $this->stream;
88 }
89
90 $this->checkError();
91 $this->stream = new UploadedFileStream( $this->data['tmp_name'] );
92 return $this->stream;
93 }
94
95 public function moveTo( $targetPath ) {
96 $this->checkError();
97
98 if ( $this->fromUpload && !is_uploaded_file( $this->data['tmp_name'] ) ) {
99 throw new RuntimeException( 'Specified file is not an uploaded file' );
100 }
101
102 // TODO remove the function_exists check once we drop HHVM support
103 if ( function_exists( 'error_clear_last' ) ) {
104 error_clear_last();
105 }
106 $ret = AtEase::quietCall(
107 $this->fromUpload ? 'move_uploaded_file' : 'rename',
108 $this->data['tmp_name'],
109 $targetPath
110 );
111 if ( $ret === false ) {
112 $err = error_get_last();
113 throw new RuntimeException( "Move failed: " . ( $err['message'] ?? 'Unknown error' ) );
114 }
115
116 $this->moved = true;
117 if ( $this->stream ) {
118 $this->stream->close();
119 $this->stream = null;
120 }
121 }
122
123 public function getSize() {
124 return $this->data['size'] ?? null;
125 }
126
127 public function getError() {
128 return $this->data['error'] ?? UPLOAD_ERR_NO_FILE;
129 }
130
131 public function getClientFilename() {
132 $ret = $this->data['name'] ?? null;
133 return $ret === '' ? null : $ret;
134 }
135
136 public function getClientMediaType() {
137 $ret = $this->data['type'] ?? null;
138 return $ret === '' ? null : $ret;
139 }
140
141 }