Completed
Branch prettify (84326c)
by samayo
02:11
created

Image::getMime()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
/**
3
 * BULLETPROOF
4
 *
5
 * A single-class PHP library to upload images securely.
6
 *
7
 * PHP support 5.3+
8
 *
9
 * @package     bulletproof
10
 * @version     3.4.0
11
 * @author      https://twitter.com/_samayo
12
 * @link        https://github.com/samayo/bulletproof
13
 * @license     MIT
14
 */
15
namespace Bulletproof;
16
17
18
class Image implements \ArrayAccess
19
{
20
    /**
21
     * @var string The new image name, to be provided or will be generated.
22
     */
23
    protected $name;
24
25
    /**
26
     * @var int The image width in pixels
27
     */
28
    protected $width;
29
30
    /**
31
     * @var int The image height in pixels
32
     */
33
    protected $height;
34
35
    /**
36
     * @var string The image mime type (extension)
37
     */
38
    protected $mime;
39
40
    /**
41
     * @var string The full image path (dir + image + mime)
42
     */
43
    protected $fullPath;
44
45
    /**
46
     * @var string The folder or image storage location
47
     */
48
    protected $location;
49
50
    /**
51
     * @var array The min and max image size allowed for upload (in bytes)
52
     */
53
    protected $size = array(100, 500000);
54
55
    /**
56
     * @var array The max height and width image allowed
57
     */
58
    protected $dimensions = array(5000, 5000);
59
60
    /**
61
     * @var array The mime types allowed for upload
62
     */
63
    protected $mimeTypes = array('jpeg', 'png', 'gif', 'jpg');
64
65
    /**
66
     * @var array list of known image types
67
     */
68
    protected $acceptedMimes = array(
69
      1 => 'gif', 'jpeg', 'png', 'swf', 'psd',
70
            'bmp', 'tiff', 'tiff', 'jpc', 'jp2', 'jpx',
71
            'jb2', 'swc', 'iff', 'wbmp', 'xbm', 'ico'
72
    );
73
74
    /**
75
     * @var array error messages strings
76
     */
77
    protected $commonUploadErrors = array(
78
      UPLOAD_ERR_OK         => '',
79
      UPLOAD_ERR_INI_SIZE   => 'Image is larger than the specified amount set by the server',
80
      UPLOAD_ERR_FORM_SIZE  => 'Image is larger than the specified amount specified by browser',
81
      UPLOAD_ERR_PARTIAL    => 'Image could not be fully uploaded. Please try again later',
82
      UPLOAD_ERR_NO_FILE    => 'Image is not found',
83
      UPLOAD_ERR_NO_TMP_DIR => 'Can\'t write to disk, due to server configuration ( No tmp dir found )',
84
      UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk. Please check you file permissions',
85
      UPLOAD_ERR_EXTENSION  => 'A PHP extension has halted this file upload process'
86
    );
87
    
88
    /**
89
     * @var array storage for the $_FILES global array
90
     */
91
    private $_files = array();
92
93
    /**
94
     * @var string storage for any errors
95
     */
96
    private $error = '';
97
98
    /**
99
     * @param array $_files represents the $_FILES array passed as dependency
100
     */
101
    public function __construct(array $_files = array())
102
    {
103
      /* check if php_exif is enabled */
104
      if (!function_exists('exif_imagetype')) {
105
          $this->error = 'Function \'exif_imagetype\' Not found. Please enable \'php_exif\' in your php.ini';
106
      }
107
108
      $this->_files = $_files;
109
    }
110
111
    /**
112
     * @param mixed $offset
113
     * @param mixed $value
114
     * 
115
     * @return null
116
     */
117
    public function offsetSet($offset, $value){}
118
119
    /**
120
     * @param mixed $offset
121
     * 
122
     * @return null
123
     */
124
    public function offsetExists($offset){}
125
126
    /**
127
     * @param mixed $offset
128
     * 
129
     * @return null
130
     */
131
    public function offsetUnset($offset){}
132
133
    /**
134
     * Gets array value \ArrayAccess
135
     * 
136
     * @param mixed $offset
137
     * @return string|boolean
138
     */
139
    public function offsetGet($offset)
140
    {   
141
      // return error if requested
142
      if ($offset == 'error') {
143
          return $this->error;
144
      }
145
      
146
      // return false if $image['key'] isn't found
147
      if (!isset($this->_files[$offset])) {
148
          $this->error = sprintf('No file input found with name: (%s)', $offset); 
149
          return false;
150
      }
151
152
      $this->_files = $this->_files[$offset];
153
154
      // check for common upload errors
155
      if(isset($this->_files['error'])){
156
          $this->error = $this->commonUploadErrors[$this->_files['error']];
157
      }
158
159
      return true;
160
    }
161
162
    /**
163
     * Sets max image height and width limit
164
     *
165
     * @param $maxWidth int max width value
166
     * @param $maxHeight int max height value
167
     * 
168
     * @return $this
169
     */
170
    public function setDimension($maxWidth, $maxHeight)
171
    {
172
      $this->dimensions = array($maxWidth, $maxHeight);
173
      return $this;
174
    }
175
176
    /**
177
     * Returns the full path of the image ex 'location/image.mime'
178
     *
179
     * @return string
180
     */
181
    public function getFullPath()
182
    {
183
      return $this->fullPath = $this->getLocation() . '/' . $this->getName() . '.' . $this->getMime();
184
    }
185
186
    /**
187
     * Returns the image size in bytes
188
     *
189
     * @return int
190
     */
191
    public function getSize()
192
    {
193
      return (int) $this->_files['size'];
194
    }
195
196
    /**
197
     * Define a min and max image size for uploading
198
     *
199
     * @param $min int minimum value in bytes
200
     * @param $max int maximum value in bytes
201
     *
202
     * @return $this
203
     */
204
    public function setSize($min, $max)
205
    {
206
      $this->size = array($min, $max);
207
      return $this;
208
    }
209
210
    /**
211
     * Returns a JSON format of the image width, height, name, mime ...
212
     *
213
     * @return string
214
     */
215
    public function getJson()
216
    {
217
      return json_encode (
218
        array(
219
          'name'      => $this->name,
220
          'mime'      => $this->mime,
221
          'height'    => $this->height,
222
          'width'     => $this->width,
223
          'size'      => $this->_files['size'],
224
          'location'  => $this->location,
225
          'fullpath'  => $this->fullPath
226
        )
227
      );
228
    }
229
230
    /**
231
     * Returns the image mime type
232
     *
233
     * @return null|string
234
     */
235
    public function getMime()
236
    {
237
      if(!$this->mime){
238
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
239
      }
240
      
241
      return $this->mime;
242
    }
243
244
    /**
245
     * Define a mime type for uploading
246
     *
247
     * @param array $fileTypes
248
     * 
249
     * @return $this
250
     */
251
    public function setMime(array $fileTypes)
252
    {
253
      $this->mimeTypes = $fileTypes;
254
      return $this;
255
    }
256
257
    /**
258
     * Gets the real image mime type
259
     *
260
     * @param $tmp_name string The upload tmp directory
261
     * 
262
     * @return null|string
263
     */
264
    protected function getImageMime($tmp_name)
265
    {
266
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
267
      if (!$this->mime) {
268
        return null;
269
      }
270
271
      return $this->mime;
272
    }
273
274
    /**
275
     * Returns error string or false if no errors occurred
276
     *
277
     * @return string|false
278
     */
279
    public function getError()
280
    {
281
      return $this->error;
282
    }
283
284
    /**
285
     * Returns the image name
286
     *
287
     * @return string
288
     */
289
    public function getName()
290
    {
291
      if (!$this->name) {
292
        $this->name = uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
293
      }
294
295
      return $this->name;
296
    }
297
298
    /**
299
     * Provide image name if not provided
300
     *
301
     * @param null $isNameProvided
302
     * @return $this
303
     */
304
    public function setName($isNameProvided = null)
305
    {
306
      if ($isNameProvided) {
307
        $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
308
      }
309
310
      return $this;
311
    }
312
313
    /**
314
     * Returns the image width
315
     *
316
     * @return int
317
     */
318
    public function getWidth()
319
    {
320
      if ($this->width != null) {
321
        return $this->width;
322
      }
323
324
      list($width) = getImageSize($this->_files['tmp_name']);
325
      return $width;
326
    }
327
328
    /**
329
     * Returns the image height in pixels
330
     *
331
     * @return int
332
     */
333
    public function getHeight()
334
    {
335
      if ($this->height != null) {
336
        return $this->height;
337
      }
338
339
      list(, $height) = getImageSize($this->_files['tmp_name']);
340
      return $height;
341
    }
342
343
    /**
344
     * Returns the storage / folder name
345
     *
346
     * @return string
347
     */
348
    public function getLocation()
349
    {
350
      if (!$this->location) {
351
        $this->setLocation();
352
      }
353
354
      return $this->location;
355
    }
356
357
    /**
358
     * Validate directory/permission before creating a folder
359
     * 
360
     * @param $dir string the folder name to check
361
     * @return bool
362
     */
363
    private function isDirectoryValid($dir) 
364
    {
365
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir); 
366
    }
367
368
    /**
369
     * Creates a location for upload storage
370
     *
371
     * @param $dir string the folder name to create
372
     * @param int $permission chmod permission
373
     * @return $this
374
     */
375
    public function setLocation($dir = 'bulletproof', $permission = 0666)
376
    {
377
      $isDirectoryValid = $this->isDirectoryValid($dir); 
378
379
      if(!$isDirectoryValid){
380
        $this->error = 'Can not create a directory  \'' . $dir . '\', please check your dir permission';
381
        return false;
382
      }
383
    
384
      $create = !is_dir($dir) ? @mkdir('' . $dir, (int) $permission, true) : true; 
385
386
      if (!$create) {
387
        $this->error = 'Error! directory \'' . $dir . '\' could not be created';
388
        return false;
389
      }
390
391
      $this->location = $dir;
392
      return $this;
393
    }
394
395
    /**
396
     * Validate image and upload
397
     * 
398
     * @return false|Image
399
     */
400
    public function upload()
401
    {
402
      $image = $this;
403
      $files = $this->_files;
404
405
      if ($this->error || !isset($files['tmp_name'])) {
406
        return false;
407
      }
408
409
      /* check image for valid mime types and return mime */
410
      $image->getImageMime($files['tmp_name']);
411
      /* validate image mime type */
412
      if (!in_array($image->mime, $image->mimeTypes)) {
413
        $ext = implode(', ', $image->mimeTypes);
414
        $image->error = sprintf('Invalid File! Only (%s) image types are allowed', $ext);
415
        return false;
416
      }
417
418
      /* initialize image properties */
419
      $image->name = $image->getName();
420
      $image->width = $image->getWidth();
421
      $image->height = $image->getHeight();
422
      $image->getFullPath();
423
      
424
425
      /* get image sizes */
426
      list($minSize, $maxSize) = $image->size;
427
428
      /* check image size based on the settings */
429
      if ($files['size'] < $minSize || $files['size'] > $maxSize) {
430
        $min = intval($minSize / 1000) ?: 1;
431
        $max = intval($maxSize / 1000) ?: 1;
432
        $image->error = 'Image size should be at least ' . $min . ' KB, and no more than ' . $max . ' KB';
433
        return false;
434
      }
435
436
      /* check image dimension */
437
      list($allowedWidth, $allowedHeight) = $image->dimensions;
438
439
      if (
440
          $image->height > $allowedHeight || 
441
          $image->width > $allowedWidth ||
442
          $image->height < 2 ||
443
          $image->width < 2
444
        ) {
445
        $image->error = 'Image height/width should be less than ' . $allowedHeight . '/' . $allowedWidth . ' pixels';
446
        return false;
447
      }
448
449
      $isSaved = $image->isSaved($files['tmp_name'], $image->fullPath);
450
451
      return $isSaved ? $image : false;
452
    }
453
454
455
    /**
456
     * Final upload method to be called, isolated for testing purposes
457
     *
458
     * @param $tmp_name int the temporary location of the image file
459
     * @param $destination int upload destination
460
     *
461
     * @return bool
462
     */
463
    protected function isSaved($tmp_name, $destination)
464
    {
465
      return move_uploaded_file($tmp_name, $destination);
466
    }
467
}