Completed
Branch prettify (6793f8)
by samayo
01:02
created

Image::getWidth()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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