Completed
Branch prettify (768db4)
by samayo
01:48
created

Image::offsetGet()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
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     4.0.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
      
142
      // return false if $image['key'] isn't found
143
      if (!isset($this->_files[$offset])) {
144
          $this->error = sprintf('No file input found with name: (%s)', $offset); 
145
          return false;
146
      }
147
148
      $this->_files = $this->_files[$offset];
149
150
      // check for common upload errors
151
      if(isset($this->_files['error'])){
152
          $this->error = $this->commonUploadErrors[$this->_files['error']];
153
      }
154
155
      return true;
156
    }
157
158
    /**
159
     * Sets max image height and width limit
160
     *
161
     * @param $maxWidth int max width value
162
     * @param $maxHeight int max height value
163
     * 
164
     * @return $this
165
     */
166
    public function setDimension($maxWidth, $maxHeight)
167
    {
168
      $this->dimensions = array($maxWidth, $maxHeight);
169
      return $this;
170
    }
171
172
    /**
173
     * Returns the full path of the image ex 'location/image.mime'
174
     *
175
     * @return string
176
     */
177
    public function getFullPath()
178
    {
179
      return $this->fullPath = $this->getLocation() . '/' . $this->getName() . '.' . $this->getMime();
180
    }
181
182
    /**
183
     * Returns the image size in bytes
184
     *
185
     * @return int
186
     */
187
    public function getSize()
188
    {
189
      return (int) $this->_files['size'];
190
    }
191
192
    /**
193
     * Define a min and max image size for uploading
194
     *
195
     * @param $min int minimum value in bytes
196
     * @param $max int maximum value in bytes
197
     *
198
     * @return $this
199
     */
200
    public function setSize($min, $max)
201
    {
202
      $this->size = array($min, $max);
203
      return $this;
204
    }
205
206
    /**
207
     * Returns a JSON format of the image width, height, name, mime ...
208
     *
209
     * @return string
210
     */
211
    public function getJson()
212
    {
213
      return json_encode (
214
        array(
215
          'name'      => $this->name,
216
          'mime'      => $this->mime,
217
          'height'    => $this->height,
218
          'width'     => $this->width,
219
          'size'      => $this->_files['size'],
220
          'location'  => $this->location,
221
          'fullpath'  => $this->fullPath
222
        )
223
      );
224
    }
225
226
    /**
227
     * Returns the image mime type
228
     *
229
     * @return null|string
230
     */
231
    public function getMime()
232
    {
233
      if(!$this->mime){
234
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
235
      }
236
      
237
      return $this->mime;
238
    }
239
240
    /**
241
     * Define a mime type for uploading
242
     *
243
     * @param array $fileTypes
244
     * 
245
     * @return $this
246
     */
247
    public function setMime(array $fileTypes)
248
    {
249
      $this->mimeTypes = $fileTypes;
250
      return $this;
251
    }
252
253
    /**
254
     * Gets the real image mime type
255
     *
256
     * @param $tmp_name string The upload tmp directory
257
     * 
258
     * @return null|string
259
     */
260
    protected function getImageMime($tmp_name)
261
    {
262
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
263
      if (!$this->mime) {
264
        return null;
265
      }
266
267
      return $this->mime;
268
    }
269
270
    /**
271
     * Returns error string or false if no errors occurred
272
     *
273
     * @return string|false
274
     */
275
    public function getError()
276
    {
277
      return $this->error;
278
    }
279
280
    /**
281
     * Returns the image name
282
     *
283
     * @return string
284
     */
285
    public function getName()
286
    {
287
      if (!$this->name) {
288
        $this->name = uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
289
      }
290
291
      return $this->name;
292
    }
293
294
    /**
295
     * Provide image name if not provided
296
     *
297
     * @param null $isNameProvided
298
     * @return $this
299
     */
300
    public function setName($isNameProvided = null)
301
    {
302
      if ($isNameProvided) {
303
        $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
304
      }
305
306
      return $this;
307
    }
308
309
    /**
310
     * Returns the image width
311
     *
312
     * @return int
313
     */
314
    public function getWidth()
315
    {
316
      if ($this->width != null) {
317
        return $this->width;
318
      }
319
320
      list($width) = getImageSize($this->_files['tmp_name']);
321
      return $width;
322
    }
323
324
    /**
325
     * Returns the image height in pixels
326
     *
327
     * @return int
328
     */
329
    public function getHeight()
330
    {
331
      if ($this->height != null) {
332
        return $this->height;
333
      }
334
335
      list(, $height) = getImageSize($this->_files['tmp_name']);
336
      return $height;
337
    }
338
339
    /**
340
     * Returns the storage / folder name
341
     *
342
     * @return string
343
     */
344
    public function getLocation()
345
    {
346
      if (!$this->location) {
347
        $this->setLocation();
348
      }
349
350
      return $this->location;
351
    }
352
353
    /**
354
     * Validate directory/permission before creating a folder
355
     * 
356
     * @param $dir string the folder name to check
357
     * @return bool
358
     */
359
    private function isDirectoryValid($dir) 
360
    {
361
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir); 
362
    }
363
364
    /**
365
     * Creates a location for upload storage
366
     *
367
     * @param $dir string the folder name to create
368
     * @param int $permission chmod permission
369
     * @return $this
370
     */
371
    public function setLocation($dir = 'bulletproof', $permission = 0666)
372
    {
373
      $isDirectoryValid = $this->isDirectoryValid($dir); 
374
375
      if(!$isDirectoryValid){
376
        $this->error = 'Can not create a directory  \'' . $dir . '\', please check your dir permission';
377
        return false;
378
      }
379
    
380
      $create = !is_dir($dir) ? @mkdir('' . $dir, (int) $permission, true) : true; 
381
382
      if (!$create) {
383
        $this->error = 'Error! directory \'' . $dir . '\' could not be created';
384
        return false;
385
      }
386
387
      $this->location = $dir;
388
      return $this;
389
    }
390
391
392
    /**
393
     * Validate image and upload
394
     * 
395
     * @return false|null|Image
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|Image.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
396
     */
397
    public function upload()
398
    {
399
      $image = $this;
400
      $files = $this->_files;
401
402
      if ($this->error || !isset($files['tmp_name'])) {
403
        return false;
404
      }
405
406
      /* check image for valid mime types and return mime */
407
      $image->getImageMime($files['tmp_name']);
408
      /* validate image mime type */
409
      if (!in_array($image->mime, $image->mimeTypes)) {
410
        $ext = implode(', ', $image->mimeTypes);
411
        $image->error = sprintf('Invalid File! Only (%s) image types are allowed', $ext);
412
        return false;
413
      }
414
415
      /* initialize image properties */
416
      $image->name = $image->getName();
417
      $image->width = $image->getWidth();
418
      $image->height = $image->getHeight();
419
      $image->getFullPath();
420
      
421
422
      /* get image sizes */
423
      list($minSize, $maxSize) = $image->size;
424
425
      /* check image size based on the settings */
426
      if ($files['size'] < $minSize || $files['size'] > $maxSize) {
427
        $min = intval($minSize / 1000) ?: 1;
428
        $max = intval($maxSize / 1000) ?: 1;
429
        $image->error = 'Image size should be at least ' . $min . ' KB, and no more than ' . $max . ' KB';
430
        return false;
431
      }
432
433
      /* check image dimension */
434
      list($allowedWidth, $allowedHeight) = $image->dimensions;
435
436
      if (
437
          $image->height > $allowedHeight || 
438
          $image->width > $allowedWidth ||
439
          $image->height < 2 ||
440
          $image->width < 2
441
        ) {
442
        $image->error = 'Image height/width should be less than ' . $allowedHeight . '/' . $allowedWidth . ' pixels';
443
        return false;
444
      }
445
446
      $isSaved = $image->isSaved($files['tmp_name'], $image->fullPath);
447
448
      return $isSaved ? $image : false;
449
    }
450
451
452
    /**
453
     * Final upload method to be called, isolated for testing purposes
454
     *
455
     * @param $tmp_name int the temporary location of the image file
456
     * @param $destination int upload destination
457
     *
458
     * @return bool
459
     */
460
    protected function isSaved($tmp_name, $destination)
461
    {
462
      return move_uploaded_file($tmp_name, $destination);
463
    }
464
}