Completed
Branch master (69506e)
by samayo
01:13
created

src/bulletproof.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * BULLETPROOF 
4
 *
5
 * Bulletproof is a single-class library to upload images in PHP with security.
6
 *
7
 * PHP support 5.3+
8
 *
9
 * @package     bulletproof
10
 * @version     3.1.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 A json format of all information about an image
51
     */
52
    protected $serialize = array();
53
54
    /**
55
     * @var array The min and max image size allowed for upload (in bytes)
56
     */
57
    protected $size = array(100, 500000);
58
59
    /**
60
     * @var array The max height and width image allowed
61
     */
62
    protected $dimensions = array(5000, 5000);
63
64
    /**
65
     * @var array The mime types allowed for upload
66
     */
67
    protected $mimeTypes = array('jpeg', 'png', 'gif');
68
69
    /**
70
     * @var array list of known image types
71
     */
72
    protected $imageMimes = array(
73
        1 => 'gif', 'jpeg', 'png', 'swf', 'psd',
74
        'bmp', 'tiff', 'tiff', 'jpc', 'jp2', 'jpx',
75
        'jb2', 'swc', 'iff', 'wbmp', 'xbm', 'ico'
76
    );
77
78
    /**
79
     * @var array storage for the $_FILES global array
80
     */
81
    private $_files = array();
82
83
    /**
84
     * @var string storage for any errors
85
     */
86
    private $error = '';
87
88
    /**
89
     * @var array error messages strings
90
     */
91
    protected $common_upload_errors = array(
92
        UPLOAD_ERR_OK         => '',
93
        UPLOAD_ERR_INI_SIZE   => 'Image is larger than the specified amount set by the server',
94
        UPLOAD_ERR_FORM_SIZE  => 'Image is larger than the specified amount specified by browser',
95
        UPLOAD_ERR_PARTIAL    => 'Image could not be fully uploaded. Please try again later',
96
        UPLOAD_ERR_NO_FILE    => 'Image is not found',
97
        UPLOAD_ERR_NO_TMP_DIR => 'Can\'t write to disk, due to server configuration ( No tmp dir found )',
98
        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk. Please check you file permissions',
99
        UPLOAD_ERR_EXTENSION  => 'A PHP extension has halted this file upload process'
100
    );
101
102
    /**
103
     * @param array $_files represents the $_FILES array passed as dependency
104
     */
105
    public function __construct(array $_files = array())
106
    {
107
        /* check if php_exif is enabled */
108
        if (!function_exists('exif_imagetype')) {
109
            $this->error = 'Function \'exif_imagetype\' Not found. Please enable \'php_exif\' in your PHP.ini';
110
            return null;
111
        }
112
113
        $this->_files = $_files;
114
    }
115
116
    /**
117
     * Gets the real image mime type
118
     *
119
     * @param $tmp_name string The upload tmp directory
120
     *
121
     * @return null|string
122
     */
123
    protected function getImageMime($tmp_name)
124
    {
125
        $mime = @$this->imageMimes[exif_imagetype($tmp_name)];
126
127
        if (!$mime) {
128
            return null;
129
        }
130
131
        return $mime;
132
    }
133
134
    /**
135
     * @param mixed $offset
136
     * @param mixed $value
137
     */
138
    public function offsetSet($offset, $value)
139
    {
140
    }
141
142
    /**
143
     * @param mixed $offset
144
     * @return null
145
     */
146
    public function offsetExists($offset)
147
    {
148
    }
149
150
    /**
151
     * @param mixed $offset
152
     */
153
    public function offsetUnset($offset)
154
    {
155
    }
156
157
    /**
158
     * Gets array value \ArrayAccess
159
     *
160
     * @param mixed $offset
161
     *
162
     * @return bool
0 ignored issues
show
Should the return type not be string|null|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
163
     */
164
    public function offsetGet($offset)
165
    {
166
        if ($offset == 'error') {
167
            return $this->error;
168
        }
169
170
        if(!isset($this->_files[$offset])){
171
            return ;
172
        }
173
174
        /* check for common upload errors */
175
        if ($this->_files[$offset]['error'] == 2) {
176
            $this->error = "Image is larger than specified by the browser";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Image is larger than specified by the browser does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
177
            return ;
178
        }
179
180
        if ($this->error || $this->error = $this->commonUploadErrors($this->_files[$offset]['error'])) {
181
            return false;
182
        }
183
184
        if (isset($this->_files[$offset]) && file_exists($this->_files[$offset]['tmp_name'])) {
185
            $this->_files = $this->_files[$offset];
186
            return true;
187
        }
188
189
        return false;
190
    }
191
192
193
    /**
194
     * Provide image name if not provided
195
     *
196
     * @param null $isNameProvided
197
     * @return $this
198
     */
199
    public function setName($isNameProvided = null)
200
    {
201
        if ($isNameProvided) {
202
            $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
203
        }
204
205
        return $this;
206
    }
207
208
    /**
209
     * Define a mime type for uploading
210
     *
211
     * @param array $fileTypes
212
     *
213
     * @return $this
214
     */
215
    public function setMime(array $fileTypes)
216
    {
217
        $this->mimeTypes = $fileTypes;
218
        return $this;
219
    }
220
221
    /**
222
     * Define a min and max image size for uploading
223
     *
224
     * @param $min int minimum value in bytes
225
     * @param $max int maximum value in bytes
226
     *
227
     * @return $this
228
     */
229
    public function setSize($min, $max)
230
    {
231
        $this->size = array($min, $max);
232
        return $this;
233
    }
234
235
    /**
236
     * Creates a location for upload storage
237
     *
238
     * @param $dir string the folder name to create
239
     * @param int $permission chmod permission
240
     *
241
     * @return $this
242
     */
243
    public function setLocation($dir = 'bulletproof', $permission = 0666)
244
    {
245
246
        if (!file_exists($dir) && !is_dir($dir) && !$this->location) {
247
            $createFolder = @mkdir('' . $dir, (int)$permission, true);
248
            if (!$createFolder) {
249
                $this->error = 'Error! Folder ' . $dir . ' could not be created';
250
                return false;
251
            }
252
        }
253
        
254
        /* check if we can create a file in the directory */
255
        if (!is_writable($dir)) {
256
            $this->error =  "The images directory \"" . $dir . "\" is not writable!";
257
            return false;
258
        }
259
260
        $this->location = $dir;
261
        return $this;
262
    }
263
264
    /**
265
     * Sets acceptable max image height and width
266
     *
267
     * @param $maxWidth int max width value
268
     * @param $maxHeight int max height value
269
     *
270
     * @return $this
271
     */
272
    public function setDimension($maxWidth, $maxHeight)
273
    {
274
        $this->dimensions = array($maxWidth, $maxHeight);
275
        return $this;
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
     * Returns the full path of the image ex 'location/image.mime'
294
     *
295
     * @return string
296
     */
297
    public function getFullPath()
298
    {
299
        $this->fullPath = $this->location . '/' . $this->name . '.' . $this->mime;
300
        return $this->fullPath;
301
    }
302
303
    /**
304
     * Returns the image size in bytes
305
     *
306
     * @return int
307
     */
308
    public function getSize()
309
    {
310
        return (int)$this->_files['size'];
311
    }
312
313
    /**
314
     * Returns the image height in pixels
315
     *
316
     * @return int
317
     */
318
    public function getHeight()
319
    {
320
        if ($this->height != null) {
321
            return $this->height;
322
        }
323
324
        list(, $height) = getImageSize($this->_files['tmp_name']);
325
        return $height;
326
    }
327
328
    /**
329
     * Returns the image width
330
     *
331
     * @return int
332
     */
333
    public function getWidth()
334
    {
335
        if ($this->width != null) {
336
            return $this->width;
337
        }
338
339
        list($width) = getImageSize($this->_files['tmp_name']);
340
        return $width;
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
     * Returns a JSON format of the image width, height, name, mime ...
359
     *
360
     * @return string
361
     */
362
    public function getJson()
363
    {
364
        return json_encode($this->serialize);
365
    }
366
367
    /**
368
     * Returns the image mime type
369
     *
370
     * @return string
371
     */
372
    public function getMime()
373
    {
374
        if (!$this->mime) {
375
            return $this->getImageMime($this->_files['tmp_name']);
376
        }
377
        return $this->mime;
378
    }
379
380
    /**
381
     * Returns error string or false if no errors occurred
382
     *
383
     * @return string|bool
384
     */
385
    public function getError()
386
    {
387
        return $this->error != '' ? $this->error : false;
388
    }
389
390
    /**
391
     * Checks for the common upload errors
392
     *
393
     * @param $errors int error constant
394
     *
395
     * @return string
396
     */
397
    protected function commonUploadErrors($errors)
398
    {
399
        return $this->common_upload_errors[$errors];
400
    }
401
402
    /**
403
     * This methods validates and uploads the image
404
     * @return false|Image
405
     */
406
    public function upload()
407
    {
408
        /* modify variable names for convenience */
409
        $image = $this;
410
        $files = $this->_files;
411
412
        /* check image for valid mime types and return mime */
413
        $image->mime = $image->getImageMime($files['tmp_name']);
414
415
        /* validate image mime type */
416
        if (!in_array($image->mime, $image->mimeTypes)) {
417
            $ext = implode(', ', $image->mimeTypes);
418
            $image->error = sprintf('Invalid File! Only (%s) image types are allowed', $ext);
419
            return false;
420
        }
421
422
423
        /* initialize image properties */
424
        $image->name = $image->getName();
425
        $image->width = $image->getWidth();
426
        $image->height = $image->getHeight();
427
        $image->location = $image->getLocation();
428
429
        /* get image sizes */
430
        list($minSize, $maxSize) = $image->size;
431
432
        /* check image size based on the settings */
433
        if ($files['size'] < $minSize || $files['size'] > $maxSize) {
434
            $min = intval($minSize / 1000) ?: 1;
435
            $max = intval($maxSize / 1000) ?: 1;
436
            $image->error = "Image size should be at least " . $min . " KB, and no more than " . $max . " KB";
437
            return null;
438
        }
439
440
        /* check image dimension */
441
        list($allowedWidth, $allowedHeight) = $image->dimensions;
442
443
        if ($image->height > $allowedHeight || $image->width > $allowedWidth) {
444
            $image->error = 'Image height/width should be less than ' . $allowedHeight . '/' . $allowedWidth . ' pixels';
445
            return false;
446
        }
447
448
        if ($image->height < 2 || $image->width < 2) {
449
            $image->error = 'Image height/width too small or corrupted.';
450
            return false;
451
        }
452
453
        /* set and get folder name */
454
        $image->fullPath = $image->location . '/' . $image->name . '.' . $image->mime;
455
456
        /* gather image info for json storage */
457
        $image->serialize = array(
458
            'name' => $image->name,
459
            'mime' => $image->mime,
460
            'height' => $image->height,
461
            'width' => $image->width,
462
            'size' => $files['size'],
463
            'location' => $image->location,
464
            'fullpath' => $image->fullPath
465
        );
466
467
        if ($image->error === '') {
468
            $moveUpload = $image->moveUploadedFile($files['tmp_name'], $image->fullPath);
469
            if (false !== $moveUpload) {
470
                return $image;
471
            }
472
        }
473
474
        $image->error = 'Upload failed, Unknown error occured';
475
        return false;
476
    }
477
478
    /**
479
     * Final upload method to be called, isolated for testing purposes
480
     *
481
     * @param $tmp_name int the temporary location of the image file
482
     * @param $destination int upload destination
483
     *
484
     * @return bool
485
     */
486
    public function moveUploadedFile($tmp_name, $destination)
487
    {
488
        return move_uploaded_file($tmp_name, $destination);
489
    }
490
}
491