Completed
Branch master (a42ab4)
by samayo
64:27 queued 29:30
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
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * BulletProof
4
 *
5
 * Bulletproof is a single-class, secure and user-friendly Image uploader in php.
6
 *
7
 * PHP support 5.3+
8
 *
9
 * @package     bulletproof
10
 * @version     3.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 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 bool|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|mixed
163
     */
164
    public function offsetGet($offset)
165
    {
166
        if ($offset == "error") {
167
            return $this->error;
168
        }
169
170
        if (isset($this->_files[$offset]) && file_exists($this->_files[$offset]["tmp_name"])) {
171
            $this->_files = $this->_files[$offset];
172
            return true;
173
        }
174
175
        return false;
176
    }
177
178
179
    /**
180
     * Provide image name if not provided
181
     *
182
     * @param null $isNameProvided
183
     * @return $this
184
     */
185
    public function setName($isNameProvided = null)
186
    {
187
        if ($isNameProvided) {
188
            $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
189
        }
190
191
        return $this;
192
    }
193
194
    /**
195
     * Define a mime type for uploading
196
     *
197
     * @param array $fileTypes
198
     *
199
     * @return $this
200
     */
201
    public function setMime(array $fileTypes)
202
    {
203
        $this->mimeTypes = $fileTypes;
204
        return $this;
205
    }
206
207
    /**
208
     * Define a min and max image size for uploading
209
     *
210
     * @param $min int minimum value in bytes
211
     * @param $max int maximum value in bytes
212
     *
213
     * @return $this
214
     */
215
    public function setSize($min, $max)
216
    {
217
        $this->size = array($min, $max);
218
        return $this;
219
    }
220
221
    /**
222
     * Creates a location for upload storage
223
     *
224
     * @param $dir string the folder name to create
225
     * @param int $permission chmod permission
226
     *
227
     * @return $this
228
     */
229
    public function setLocation($dir = "bulletproof", $permission = 0666)
230
    {
231
        if (!file_exists($dir) && !is_dir($dir) && !$this->location) {
232
            $createFolder = @mkdir("" . $dir, (int)$permission, true);
233
            if (!$createFolder) {
234
                $this->error = "Error! Folder " . $dir . " could not be created";
235
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by BulletProof\Image::setLocation of type BulletProof\Image.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
236
            }
237
        }
238
239
        $this->location = $dir;
240
        return $this;
241
    }
242
243
    /**
244
     * Sets acceptable max image height and width
245
     *
246
     * @param $maxWidth int max width value
247
     * @param $maxHeight int max height value
248
     *
249
     * @return $this
250
     */
251
    public function setDimension($maxWidth, $maxHeight)
252
    {
253
        $this->dimensions = array($maxWidth, $maxHeight);
254
        return $this;
255
    }
256
257
    /**
258
     * Returns the image name
259
     *
260
     * @return string
261
     */
262
    public function getName()
263
    {
264
        if (!$this->name) {
265
            return uniqid(true) . "_" . str_shuffle(implode(range("e", "q")));
266
        }
267
268
        return $this->name;
269
    }
270
271
    /**
272
     * Returns the full path of the image ex "location/image.mime"
273
     *
274
     * @return string
275
     */
276
    public function getFullPath()
277
    {
278
        $this->fullPath = $this->location . "/" . $this->name . "." . $this->mime;
279
        return $this->fullPath;
280
    }
281
282
    /**
283
     * Returns the image size in bytes
284
     *
285
     * @return int
286
     */
287
    public function getSize()
288
    {
289
        return (int)$this->_files["size"];
290
    }
291
292
    /**
293
     * Returns the image height in pixels
294
     *
295
     * @return int
296
     */
297
    public function getHeight()
298
    {
299
        if ($this->height != null) {
300
            return $this->height;
301
        }
302
303
        list(, $height) = getImageSize($this->_files["tmp_name"]);
304
        return $height;
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 storage / folder name
324
     *
325
     * @return string
326
     */
327
    public function getLocation()
328
    {
329
        if (!$this->location) {
330
            $this->setLocation();
331
        }
332
333
        return $this->location;
334
    }
335
336
    /**
337
     * Returns a JSON format of the image width, height, name, mime ...
338
     *
339
     * @return string
340
     */
341
    public function getJson()
342
    {
343
        return json_encode($this->serialize);
344
    }
345
346
    /**
347
     * Returns the image mime type
348
     *
349
     * @return string
350
     */
351
    public function getMime()
352
    {
353
        if (!$this->mime) {
354
            return $this->getImageMime($this->_files["tmp_name"]);
355
        }
356
        return $this->mime;
357
    }
358
359
    /**
360
     * Returns error string or false if no errors occurred
361
     *
362
     * @return string|bool
363
     */
364
    public function getError()
365
    {
366
        return $this->error != "" ? $this->error : false;
367
    }
368
369
    /**
370
     * Checks for the common upload errors
371
     *
372
     * @param $e int error constant
373
     */
374
    protected function commonUploadErrors($e)
375
    {
376
        return $this->common_upload_errors[$e];
377
    }
378
379
    /**
380
     * This methods validates and uploads the image
381
     * @return bool|Image|null
382
     */
383
    public function upload()
384
    {
385
        /* modify variable names for convenience */
386
        $image = $this;
387
        $files = $this->_files;
388
389
        /* check for common upload errors */
390
        if ($image->error || $image->error = $image->commonUploadErrors($files["error"])) {
391
            return null;
392
        }
393
394
        /* check image for valid mime types and return mime */
395
        $image->mime = $image->getImageMime($files["tmp_name"]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $image->getImageMime($files['tmp_name']) can also be of type boolean. However, the property $mime is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
396
397
        /* validate image mime type */
398
        if (!in_array($image->mime, $image->mimeTypes)) {
399
            $ext = implode(", ", $image->mimeTypes);
400
            $image->error = "Invalid File! Only ($ext) image types are allowed";
401
            return null;
402
        }
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
        /* get image sizes */
412
        list($minSize, $maxSize) = $image->size;
413
414
        /* check image size based on the settings */
415
        if ($files["size"] < $minSize || $files["size"] > $maxSize) {
416
            $min = intval($minSize / 1000) ?: 1;
417
            $image->error = "Image size should be at least more than " . $min . " kb";
418
            return null;
419
        }
420
421
        /* check image dimension */
422
        list($allowedWidth, $allowedHeight) = $image->dimensions;
423
424
        if ($image->height > $allowedHeight || $image->width > $allowedWidth) {
425
            $image->error = "Image height/width should be less than " . $allowedHeight . "/" . $allowedWidth . " pixels";
426
            return null;
427
        }
428
429
        if ($image->height < 2 || $image->width < 2) {
430
            $image->error = "Image height/width too small or corrupted.";
431
            return null;
432
        }
433
434
        /* set and get folder name */
435
        $image->fullPath = $image->location . "/" . $image->name . "." . $image->mime;
436
437
        /* gather image info for json storage */
438
        $image->serialize = array(
439
            "name" => $image->name,
440
            "mime" => $image->mime,
441
            "height" => $image->height,
442
            "width" => $image->width,
443
            "size" => $files["size"],
444
            "location" => $image->location,
445
            "fullpath" => $image->fullPath
446
        );
447
448
        if ($image->error === "") {
449
            $moveUpload = $image->moveUploadedFile($files["tmp_name"], $image->fullPath);
450
            if (false !== $moveUpload) {
451
                return $image;
452
            }
453
        }
454
455
        $image->error = "Upload failed, Unknown error occured";
456
        return false;
457
    }
458
459
    /**
460
     * Final upload method to be called, isolated for testing purposes
461
     *
462
     * @param $tmp_name int the temporary location of the image file
463
     * @param $destination int upload destination
464
     *
465
     * @return bool
466
     */
467
    public function moveUploadedFile($tmp_name, $destination)
468
    {
469
        return move_uploaded_file($tmp_name, $destination);
470
    }
471
}
472