Completed
Branch prettify (595da1)
by samayo
01:23
created

Image::isDirectoryValid()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 3
eloc 2
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
      if((int) $maxWidth && (int) $maxHeight){
169
        $this->dimensions = array($maxWidth, $maxHeight);
170
      }else{
171
        $this->error = 'Invalid dimention! Values must be integers'; 
172
      }
173
174
      return $this;
175
    }
176
177
    /**
178
     * Returns the full path of the image ex 'location/image.mime'
179
     *
180
     * @return string
181
     */
182
    public function getFullPath()
183
    {
184
      return $this->fullPath = $this->getLocation() . '/' . $this->getName() . '.' . $this->getMime();
185
    }
186
187
    /**
188
     * Returns the image size in bytes
189
     *
190
     * @return int
191
     */
192
    public function getSize()
193
    {
194
      return (int) $this->_files['size'];
195
    }
196
197
    /**
198
     * Define a min and max image size for uploading
199
     *
200
     * @param $min int minimum value in bytes
201
     * @param $max int maximum value in bytes
202
     *
203
     * @return $this
204
     */
205
    public function setSize($min, $max)
206
    {
207
      $this->size = array($min, $max);
208
      return $this;
209
    }
210
211
    /**
212
     * Returns a JSON format of the image width, height, name, mime ...
213
     *
214
     * @return string
215
     */
216
    public function getJson()
217
    {
218
      return json_encode (
219
        array(
220
          'name'      => $this->name,
221
          'mime'      => $this->mime,
222
          'height'    => $this->height,
223
          'width'     => $this->width,
224
          'size'      => $this->_files['size'],
225
          'location'  => $this->location,
226
          'fullpath'  => $this->fullPath
227
        )
228
      );
229
    }
230
231
    /**
232
     * Returns the image mime type
233
     *
234
     * @return null|string
235
     */
236
    public function getMime()
237
    {
238
      if(!$this->mime){
239
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
240
      }
241
      
242
      return $this->mime;
243
    }
244
245
    /**
246
     * Define a mime type for uploading
247
     *
248
     * @param array $fileTypes
249
     * 
250
     * @return $this
251
     */
252
    public function setMime(array $fileTypes)
253
    {
254
      $this->mimeTypes = $fileTypes;
255
      return $this;
256
    }
257
258
    /**
259
     * Gets the real image mime type
260
     *
261
     * @param $tmp_name string The upload tmp directory
262
     * 
263
     * @return null|string
264
     */
265
    protected function getImageMime($tmp_name)
266
    {
267
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
268
      if (!$this->mime) {
269
        return null;
270
      }
271
272
      return $this->mime;
273
    }
274
275
    /**
276
     * Returns error string or false if no errors occurred
277
     *
278
     * @return string|false
279
     */
280
    public function getError()
281
    {
282
      return $this->error;
283
    }
284
285
    /**
286
     * Returns the image name
287
     *
288
     * @return string
289
     */
290
    public function getName()
291
    {
292
      if (!$this->name) {
293
        $this->name = uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
294
      }
295
296
      return $this->name;
297
    }
298
299
    /**
300
     * Provide image name if not provided
301
     *
302
     * @param null $isNameProvided
303
     * @return $this
304
     */
305
    public function setName($isNameProvided = null)
306
    {
307
      if ($isNameProvided) {
308
        $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
309
      }
310
311
      return $this;
312
    }
313
314
    /**
315
     * Returns the image width
316
     *
317
     * @return int
318
     */
319
    public function getWidth()
320
    {
321
      if ($this->width != null) {
322
        return $this->width;
323
      }
324
325
      list($width) = getImageSize($this->_files['tmp_name']);
326
      return $width;
327
    }
328
329
    /**
330
     * Returns the image height in pixels
331
     *
332
     * @return int
333
     */
334
    public function getHeight()
335
    {
336
      if ($this->height != null) {
337
        return $this->height;
338
      }
339
340
      list(, $height) = getImageSize($this->_files['tmp_name']);
341
      return $height;
342
    }
343
344
    /**
345
     * Returns the storage / folder name
346
     *
347
     * @return string
348
     */
349
    public function getLocation()
350
    {
351
      if (!$this->location) {
352
        $this->setLocation();
353
      }
354
355
      return $this->location;
356
    }
357
358
    /**
359
     * Validate directory/permission before creating a folder
360
     * 
361
     * @param $dir string the folder name to check
362
     * @return bool
363
     */
364
    private function isDirectoryValid($dir) 
365
    {
366
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir); 
367
    }
368
369
    /**
370
     * Creates a location for upload storage
371
     *
372
     * @param $dir string the folder name to create
373
     * @param int $permission chmod permission
374
     * @return $this
375
     */
376
    public function setLocation($dir = 'bulletproof', $permission = 0666)
377
    {
378
      $isDirectoryValid = $this->isDirectoryValid($dir); 
379
380
      if(!$isDirectoryValid){
381
        $this->error = 'Can not create a directory  \'' . $dir . '\', please check your dir permission';
382
        return false;
383
      }
384
    
385
      $create = !is_dir($dir) ? @mkdir('' . $dir, (int) $permission, true) : true; 
386
387
      if (!$create) {
388
        $this->error = 'Error! directory \'' . $dir . '\' could not be created';
389
        return false;
390
      }
391
392
      $this->location = $dir;
393
      return $this;
394
    }
395
396
397
    /**
398
     * Validate image and upload
399
     * 
400
     * @return false|Image
401
     */
402
    public function upload()
403
    {
404
      $image = $this;
405
      $files = $this->_files;
406
407
      if ($this->error || !isset($files['tmp_name'])) {
408
        return false;
409
      }
410
411
      /* check image for valid mime types and return mime */
412
      $image->getImageMime($files['tmp_name']);
413
      /* validate image mime type */
414
      if (!in_array($image->mime, $image->mimeTypes)) {
415
        $image->error = sprintf('Invalid File! Only (%s) image types are allowed', implode(', ', $image->mimeTypes));
416
        return false;
417
      }
418
419
      /* initialize image properties */
420
      $image->width = $image->getWidth();
421
      $image->height = $image->getHeight();
422
      $image->getFullPath();
423
      
424
      /* get image sizes */
425
      list($minSize, $maxSize) = $image->size;
426
427
      /* check image size based on the settings */
428
      if ($files['size'] < $minSize || $files['size'] > $maxSize) {
429
        $min = $minSize . ' bytes ('.intval($minSize / 1000) .' kb)';
430
        $max = $maxSize . ' bytes ('.intval($maxSize / 1000) .' kb)';
431
        $image->error = 'Image size should be minumum ' . $min . ', upto maximum ' . $max;
432
        return false;
433
      }
434
435
      /* check image dimension */
436
      list($allowedWidth, $allowedHeight) = $image->dimensions;
437
438
      if ($image->height > $allowedHeight || $image->width > $allowedWidth ) {
439
        $image->error = 'Image height/width should be less than ' . $allowedHeight . '/' . $allowedWidth . ' pixels';
440
        return false;
441
      }
442
443
      $isSaved = $image->isSaved($files['tmp_name'], $image->fullPath);
444
445
      return $isSaved ? $image : false;
446
    }
447
448
449
    /**
450
     * Final upload method to be called, isolated for testing purposes
451
     *
452
     * @param $tmp_name int the temporary location of the image file
453
     * @param $destination int upload destination
454
     *
455
     * @return bool
456
     */
457
    protected function isSaved($tmp_name, $destination)
458
    {
459
      return move_uploaded_file($tmp_name, $destination);
460
    }
461
}