1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace ICanBoogie\HTTP;
13
14 use ICanBoogie\ToArray;
15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 class File implements ToArray
33 {
34 use \ICanBoogie\PrototypeTrait;
35
36 static protected $types = [
37
38 '.doc' => 'application/msword',
39 '.docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
40 '.gif' => 'image/gif',
41 '.jpg' => 'image/jpeg',
42 '.jpeg' => 'image/jpeg',
43 '.js' => 'application/javascript',
44 '.mp3' => 'audio/mpeg',
45 '.odt' => 'application/vnd.oasis.opendocument.text',
46 '.pdf' => 'application/pdf',
47 '.php' => 'application/x-php',
48 '.png' => 'image/png',
49 '.psd' => 'application/psd',
50 '.rar' => 'application/rar',
51 '.txt' => 'text/plain',
52 '.zip' => 'application/zip',
53 '.xls' => 'application/vnd.ms-excel',
54 '.xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
55
56 ];
57
58 static protected $types_alias = [
59
60 'text/x-php' => 'application/x-php'
61
62 ];
63
64 static public function from($properties_or_name)
65 {
66 $properties = [];
67
68 if (is_string($properties_or_name))
69 {
70 $properties = isset($_FILES[$properties_or_name])
71 ? $_FILES[$properties_or_name]
72 : [ 'name' => $properties_or_name];
73 }
74 else if (is_array($properties_or_name))
75 {
76 $properties = $properties_or_name;
77 }
78
79 return new static($properties);
80 }
81
82 83 84 85 86 87 88 89 90 91
92 static public function resolve_type($pathname, &$extension=null)
93 {
94 $extension = '.' . strtolower(pathinfo($pathname, PATHINFO_EXTENSION));
95
96 if (file_exists($pathname) && extension_loaded('fileinfo'))
97 {
98 $fi = new \finfo(FILEINFO_MIME_TYPE);
99 $type = $fi->file($pathname);
100
101 if ($type)
102 {
103 return isset(self::$types_alias[$type]) ? self::$types_alias[$type] : $type;
104 }
105 }
106
107 if (isset(self::$types[$extension]))
108 {
109 return self::$types[$extension];
110 }
111
112 return 'application/octet-stream';
113 }
114
115 116 117 118 119
120 static private function format($format, array $args=[], array $options=[])
121 {
122 if (class_exists('ICanBoogie\I18n\FormattedString', true))
123 {
124 return new \ICanBoogie\I18n\FormattedString($format, $args, $options);
125 }
126
127 if (class_exists('ICanBoogie\FormattedString', true))
128 {
129 return new \ICanBoogie\FormattedString($format, $args, $options);
130 }
131
132 return \ICanBoogie\format($format, $args, $options);
133 }
134
135 136 137
138
139 protected $name;
140
141 142 143 144 145
146 protected function get_name()
147 {
148 return $this->name;
149 }
150
151 152 153 154 155
156 protected function get_unsuffixed_name()
157 {
158 return $this->name ? basename($this->name, $this->extension) : null;
159 }
160
161 protected $type;
162
163 164 165 166 167 168 169 170
171 protected function get_type()
172 {
173 if (!empty($this->type))
174 {
175 return $this->type;
176 }
177
178 if (!$this->pathname && !$this->tmp_name)
179 {
180 return;
181 }
182
183 return self::resolve_type($this->pathname ?: $this->tmp_name);
184 }
185
186 protected $size;
187
188 189 190 191 192 193 194 195 196
197 protected function get_size()
198 {
199 if (!empty($this->size))
200 {
201 return $this->size;
202 }
203
204 if ($this->pathname)
205 {
206 return filesize($this->pathname);
207 }
208 }
209
210 protected $tmp_name;
211
212 protected $error;
213
214 215 216 217 218 219 220 221
222 protected function get_is_valid()
223 {
224 return !$this->error
225 && $this->size
226 && ($this->tmp_name || ($this->pathname && file_exists($this->pathname)));
227 }
228
229 protected $pathname;
230
231 232 233 234 235 236 237 238
239 protected function get_pathname()
240 {
241 return $this->pathname ?: $this->tmp_name;
242 }
243
244 protected function __construct(array $properties)
245 {
246 static $initial_properties = [ 'name', 'type', 'size', 'tmp_name', 'error', 'pathname' ];
247
248 foreach ($properties as $property => $value)
249 {
250 if (!in_array($property, $initial_properties))
251 {
252 continue;
253 }
254
255 $this->$property = $value;
256 }
257
258 if (!$this->name && $this->pathname)
259 {
260 $this->name = basename($this->pathname);
261 }
262
263 if (empty($this->type))
264 {
265 unset($this->type);
266 }
267
268 if (empty($this->size))
269 {
270 unset($this->size);
271 }
272 }
273
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
290 public function to_array()
291 {
292 $error_message = $this->error_message;
293
294 if ($error_message !== null)
295 {
296 $error_message = (string) $error_message;
297 }
298
299 return [
300
301 'name' => $this->name,
302 'unsuffixed_name' => $this->unsuffixed_name,
303 'extension' => $this->extension,
304 'type' => $this->type,
305 'size' => $this->size,
306 'pathname' => $this->pathname,
307 'error' => $this->error,
308 'error_message' => $error_message
309
310 ];
311 }
312
313 314 315 316 317
318 protected function get_error()
319 {
320 return $this->error;
321 }
322
323 324 325 326 327
328 protected function get_error_message()
329 {
330 switch ($this->error)
331 {
332 case UPLOAD_ERR_OK:
333
334 return;
335
336 case UPLOAD_ERR_INI_SIZE:
337
338 return $this->format("Maximum file size is :size Mb", [ ':size' => (int) ini_get('upload_max_filesize') ]);
339
340 case UPLOAD_ERR_FORM_SIZE:
341
342 return $this->format("Maximum file size is :size Mb", [ ':size' => 'MAX_FILE_SIZE' ]);
343
344 case UPLOAD_ERR_PARTIAL:
345
346 return $this->format("The uploaded file was only partially uploaded.");
347
348 case UPLOAD_ERR_NO_FILE:
349
350 return $this->format("No file was uploaded.");
351
352 case UPLOAD_ERR_NO_TMP_DIR:
353
354 return $this->format("Missing a temporary folder.");
355
356 case UPLOAD_ERR_CANT_WRITE:
357
358 return $this->format("Failed to write file to disk.");
359
360 case UPLOAD_ERR_EXTENSION:
361
362 return $this->format("A PHP extension stopped the file upload.");
363
364 default:
365
366 return $this->format("An error has occured.");
367 }
368 }
369
370 371 372 373 374 375 376
377 protected function get_extension()
378 {
379 $extension = pathinfo($this->name, PATHINFO_EXTENSION);
380
381 if (!$extension)
382 {
383 return;
384 }
385
386 return '.' . strtolower($extension);
387 }
388
389 390 391 392 393
394 protected function get_is_uploaded()
395 {
396 return $this->tmp_name && is_uploaded_file($this->tmp_name);
397 }
398
399 400 401 402 403 404 405 406 407 408
409 public function match($type)
410 {
411 if (!$type)
412 {
413 return true;
414 }
415
416 if (is_array($type))
417 {
418 $type_list = $type;
419
420 foreach ($type_list as $type)
421 {
422 if ($this->match($type))
423 {
424 return true;
425 }
426 }
427
428 return false;
429 }
430
431 if ($type{0} === '.')
432 {
433 return $type === $this->extension;
434 }
435
436 if (strpos($type, '/') === false)
437 {
438 return (bool) preg_match('#^' . preg_quote($type) . '/#', $this->type);
439 }
440
441 return $type === $this->type;
442 }
443
444 445 446 447 448 449 450 451
452 public function move($destination, $overwrite=false)
453 {
454 if (file_exists($destination))
455 {
456 if (!$overwrite)
457 {
458 throw new \Exception("The destination file already exists: $destination.");
459 }
460
461 unlink($destination);
462 }
463
464 if ($this->pathname)
465 {
466 if (!rename($this->pathname, $destination))
467 {
468 throw new \Exception("Unable to move file to destination: $destination.");
469 }
470 }
471 else
472 {
473 if (!move_uploaded_file($this->tmp_name, $destination))
474 {
475 throw new \Exception("Unable to move file to destination: $destination.");
476 }
477 }
478
479 $this->pathname = $destination;
480 }
481 }