Completed
Branch prettify (695764)
by samayo
01:20
created

Image::offsetGet()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
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     3.2.1
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
            return null;
103
        }
104
105
        $this->_files = $_files;
106
    }
107
108
    /**
109
     * @param mixed $offset
110
     * @param mixed $value
111
     * @return null
112
     */
113
    public function offsetSet($offset, $value){}
114
115
    /**
116
     * @param mixed $offset
117
     * @return null
118
     */
119
    public function offsetExists($offset){}
120
121
    /**
122
     * @param mixed $offset
123
     * @return null
124
     */
125
    public function offsetUnset($offset){}
126
127
    /**
128
     * Gets array value \ArrayAccess
129
     *
130
     * @param mixed $offset
131
     *
132
     * @return string|boolean
133
     */
134
    public function offsetGet($offset)
135
    {   
136
        // return error if requested
137
        if ($offset == 'error') {
138
            return $this->error;
139
        }
140
        
141
        // return false if $image['key'] isn't found
142
        if (!isset($this->_files[$offset])) {
143
            return false;
144
        }
145
146
        $this->_files = $this->_files[$offset];
147
148
        // check for common upload errors
149
150
        if(isset($this->_files['error'])){
151
            $this->error = $this->commonUploadErrors[$this->_files['error']];
152
        }
153
154
        return true;
155
    }
156
157
    /**
158
     * Sets max image height and width limit
159
     *
160
     * @param $maxWidth int max width value
161
     * @param $maxHeight int max height value
162
     *
163
     * @return $this
164
     */
165
    public function setDimension($maxWidth, $maxHeight)
166
    {
167
        $this->dimensions = array($maxWidth, $maxHeight);
168
        return $this;
169
    }
170
171
    /**
172
     * Returns the full path of the image ex 'location/image.mime'
173
     *
174
     * @return string
175
     */
176
    public function getFullPath()
177
    {
178
        $this->fullPath = $this->location . '/' . $this->name . '.' . $this->mime;
179
        return $this->fullPath;
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
        /* gather image info for json storage */
214
        return json_encode (
215
            array(
216
                'name'      => $this->name,
217
                'mime'      => $this->mime,
218
                'height'    => $this->height,
219
                'width'     => $this->width,
220
                'size'      => $this->_files['size'],
221
                'location'  => $this->location,
222
                'fullpath'  => $this->fullPath
223
            )
224
        );
225
    }
226
227
    /**
228
     * Returns the image mime type
229
     *
230
     * @return null|string
231
     */
232
    public function getMime()
233
    {
234
        if (!$this->mime) {
235
            return $this->getImageMime($this->_files['tmp_name']);
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
        $mime = @ $this->acceptedMimes[exif_imagetype($tmp_name)];
263
264
        if (!$mime) {
265
            return null;
266
        }
267
268
        return $mime;
269
    }
270
271
    /**
272
     * Returns error string or false if no errors occurred
273
     *
274
     * @return string|false
275
     */
276
    public function getError()
277
    {
278
        return $this->error != '' ? $this->error : false;
279
    }
280
281
    /**
282
     * Returns the image name
283
     *
284
     * @return string
285
     */
286
    public function getName()
287
    {
288
        if (!$this->name) {
289
            return uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
290
        }
291
292
        return $this->name;
293
    }
294
295
    /**
296
     * Provide image name if not provided
297
     *
298
     * @param null $isNameProvided
299
     * @return $this
300
     */
301
    public function setName($isNameProvided = null)
302
    {
303
        if ($isNameProvided) {
304
            $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
305
        }
306
307
        return $this;
308
    }
309
310
    /**
311
     * Returns the image width
312
     *
313
     * @return int
314
     */
315
    public function getWidth()
316
    {
317
        if ($this->width != null) {
318
            return $this->width;
319
        }
320
321
        list($width) = getImageSize($this->_files['tmp_name']);
322
        return $width;
323
    }
324
325
    /**
326
     * Returns the image height in pixels
327
     *
328
     * @return int
329
     */
330
    public function getHeight()
331
    {
332
        if ($this->height != null) {
333
            return $this->height;
334
        }
335
336
        list(, $height) = getImageSize($this->_files['tmp_name']);
337
        return $height;
338
    }
339
340
    /**
341
     * Returns the storage / folder name
342
     *
343
     * @return string
344
     */
345
    public function getLocation()
346
    {
347
        if (!$this->location) {
348
            $this->setLocation();
349
        }
350
351
        return $this->location;
352
    }
353
354
    /**
355
     * Creates a location for upload storage
356
     *
357
     * @param $dir string the folder name to create
358
     * @param int $permission chmod permission
359
     *
360
     * @return $this
361
     */
362
    public function setLocation($dir = 'bulletproof', $permission = 0666)
363
    {
364
        if (!file_exists($dir) && !is_dir($dir) && !$this->location) {
365
            $createFolder = @mkdir('' . $dir, (int) $permission, true);
366
            if (!$createFolder) {
367
                $this->error = 'Error! Folder ' . $dir . ' could not be created';
368
                return false;
369
            }
370
        }
371
372
        /* check if we can create a file in the directory */
373
        if (!is_writable($dir)) {
374
            $this->error = 'The images directory \'' . $dir . '\' is not writable!';
375
            return false;
376
        }
377
378
        $this->location = $dir;
379
        return $this;
380
    }
381
382
    /**
383
     * This methods validates and uploads the image
384
     * @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...
385
     */
386
    public function upload()
387
    {
388
        /* modify variable names for convenience */
389
        $image = $this;
390
        $files = $this->_files;
391
392
        if ($this->error || !isset($files['tmp_name'])) {
393
            return false;
394
        }
395
396
        /* check image for valid mime types and return mime */
397
        $image->mime = $image->getImageMime($files['tmp_name']);
398
        /* validate image mime type */
399
        if (!in_array($image->mime, $image->mimeTypes)) {
400
            $ext = implode(', ', $image->mimeTypes);
401
            $image->error = sprintf('Invalid File! Only (%s) image types are allowed', $ext);
402
            return false;
403
        }
404
405
        /* initialize image properties */
406
        $image->name = $image->getName();
407
        $image->width = $image->getWidth();
408
        $image->height = $image->getHeight();
409
        $image->location = $image->getLocation();
410
        
411
        $image->getFullPath();
412
413
        /* get image sizes */
414
        list($minSize, $maxSize) = $image->size;
415
416
        /* check image size based on the settings */
417
        if ($files['size'] < $minSize || $files['size'] > $maxSize) {
418
            $min = intval($minSize / 1000) ?: 1;
419
            $max = intval($maxSize / 1000) ?: 1;
420
            $image->error = 'Image size should be at least ' . $min . ' KB, and no more than ' . $max . ' KB';
421
            return false;
422
        }
423
424
        /* check image dimension */
425
        list($allowedWidth, $allowedHeight) = $image->dimensions;
426
427
        if ($image->height > $allowedHeight || $image->width > $allowedWidth) {
428
            $image->error = 'Image height/width should be less than ' . $allowedHeight . '/' . $allowedWidth . ' pixels';
429
            return false;
430
        }
431
432
        if ($image->height < 2 || $image->width < 2) {
433
            $image->error = 'Image height/width too small or corrupted.';
434
            return false;
435
        }
436
437
        if ($image->error === '') {
438
            $moveUpload = $image->moveUploadedFile($files['tmp_name'], $image->fullPath);
439
            if (false !== $moveUpload) {
440
                return $image;
441
            }
442
        }
443
444
        $image->error = 'Upload failed, Unknown error occured';
445
        return 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
    public function moveUploadedFile($tmp_name, $destination)
458
    {
459
        return move_uploaded_file($tmp_name, $destination);
460
    }
461
}