Image   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 459
Duplicated Lines 0 %

Importance

Changes 10
Bugs 3 Features 0
Metric Value
wmc 48
eloc 114
c 10
b 3
f 0
dl 0
loc 459
rs 8.5599

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setSize() 0 4 1
A getImageMime() 0 8 2
A offsetUnset() 0 1 1
A getError() 0 3 1
A getStorage() 0 7 2
A offsetSet() 0 1 1
A getJson() 0 11 1
A __construct() 0 10 2
A offsetGet() 0 17 4
A isDirectoryValid() 0 3 3
A setDimension() 0 5 1
A getMime() 0 7 2
A getName() 0 3 1
A setName() 0 9 2
A setStorage() 0 19 4
A setMime() 0 4 1
A getPath() 0 3 1
A getWidth() 0 9 2
A getSize() 0 3 1
A offsetExists() 0 1 1
A getHeight() 0 9 2
B constraintValidator() 0 41 7
A upload() 0 12 4
A isSaved() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Image often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Image, and based on these observations, apply Extract Interface, too.

1
<?php 
2
/**
3
 * BULLETPROOF.
4
 * 
5
 * A single-class PHP library to upload images securely.
6
 * 
7
 * PHP support 8.1+
8
 * 
9
 * @version     5.0.0
10
 * @author      https://twitter.com/_samayo
11
 * @link        https://github.com/samayo/bulletproof
12
 * @license     MIT
13
 */
14
namespace Bulletproof;
15
16
class Image implements \ArrayAccess
17
{
18
    /**
19
     * @var string The new image name, to be provided or will be generated
20
     */
21
    protected $name;
22
23
    /**
24
     * @var int The image width in pixels
25
     */
26
    protected $width;
27
28
    /**
29
     * @var int The image height in pixels
30
     */
31
    protected $height;
32
33
    /**
34
     * @var string The image mime type (extension)
35
     */
36
    protected $mime;
37
38
    /**
39
     * @var string The full image path (dir + image + mime)
40
     */
41
    protected $path;
42
43
    /**
44
     * @var string The folder or image storage storage
45
     */
46
    protected $storage;
47
48
    /**
49
     * @var array The min and max image size allowed for upload (in bytes)
50
     */
51
    protected $size = array(100, 500000);
52
53
    /**
54
     * @var array The max height and width image allowed
55
     */
56
    protected $dimensions = array(5000, 5000);
57
58
    /**
59
     * @var array The mime types allowed for upload
60
     */
61
    protected $mimeTypes = array('jpeg', 'png', 'gif', 'jpg');
62
63
    /**
64
     * @var array list of known image types
65
     */
66
    protected $acceptedMimes = array(
67
      1 => 'gif', 'jpeg', 'png', 'swf', 'psd',
68
      'bmp', 'tiff', 'tiff', 'jpc', 'jp2', 'jpx',
69
      'jb2', 'swc', 'iff', 'wbmp', 'xbm', 'ico', 'webp'
70
    );
71
72
    /**
73
     * @var array error messages strings
74
     */
75
    protected $commonErrors = 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
    /**
87
     * @var array storage for the global array
88
     */
89
    private $_files = array();
90
91
    /**
92
     * @var string storage for any errors
93
     */
94
    private $error = '';
95
96
    /**
97
     * @param array $_files represents the $_FILES array passed as dependency
98
     */
99
    public function __construct(array $_files = array())
100
    {
101
102
        /* check if php_exif is enabled */
103
        if (!function_exists('exif_imagetype')) {
104
          $this->error = 'Function \'exif_imagetype\' Not found. Please enable \'php_exif\' in your PHP.ini';
105
          return false;
106
      }
107
108
      $this->_files = $_files;
109
    }
110
111
    /**
112
     * \ArrayAccess unused method
113
     * 
114
     * @param mixed $offset
115
     * @param mixed $value
116
     */
117
    public function offsetSet($offset, $value) : void {}
118
119
    /**
120
     * \ArrayAccess unused method
121
     * 
122
     * @param mixed $offset
123
     */
124
    public function offsetExists($offset) : bool {}
125
126
    /**
127
     * \ArrayAccess unused method
128
     * 
129
     * @param mixed $offset
130
     */
131
    public function offsetUnset($offset) : void {}
132
133
    /**
134
     * \ArrayAccess - get array value from object
135
     *
136
     * @param mixed $offset
137
     *
138
     * @return string|bool
139
     */
140
    public function offsetGet($offset) : mixed
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
          
146
          return false;
147
        }
148
149
      $this->_files = $this->_files[$offset];
150
151
       /* check for common upload errors */
152
      if (isset($this->_files['error'])) {
153
        $this->error = $this->commonErrors[$this->_files['error']];
154
      }
155
156
      return $this->error ? false : true; 
157
    }
158
159
    /**
160
     * Sets max image height and width limit.
161
     *
162
     * @param $maxWidth int max width value
163
     * @param $maxHeight int max height value
164
     *
165
     * @return $this
166
     */
167
    public function setDimension($maxWidth, $maxHeight)
168
    {
169
      $this->dimensions = array($maxWidth, $maxHeight);
170
171
      return $this;
172
    }
173
174
    /**
175
     * Returns the full path of the image ex 'storage/image.mime'.
176
     *
177
     * @return string
178
     */
179
    public function getPath()
180
    {
181
      return $this->path = $this->getStorage() . '/' . $this->getName() . '.' . $this->getMime();
182
    }
183
184
    /**
185
     * Returns the image size in bytes.
186
     *
187
     * @return int
188
     */
189
    public function getSize()
190
    {
191
      return (int) $this->_files['size'];
192
    }
193
194
    /**
195
     * Define a min and max image size for uploading.
196
     *
197
     * @param $min int minimum value in bytes
198
     * @param $max int maximum value in bytes
199
     *
200
     * @return $this
201
     */
202
    public function setSize($min, $max)
203
    {
204
      $this->size = array($min, $max);
205
      return $this;
206
    }
207
208
    /**
209
     * Returns a JSON format of the image width, height, name, mime ...
210
     *
211
     * @return string
212
     */
213
    public function getJson()
214
    {
215
      return json_encode(
216
        array(
217
          'name' => $this->name,
218
          'mime' => $this->mime,
219
          'height' => $this->height,
220
          'width' => $this->width,
221
          'size' => $this->_files['size'],
222
          'storage' => $this->storage,
223
          'path' => $this->path,
224
        )
225
      );
226
    }
227
228
    /**
229
     * Returns the image mime type.
230
     *
231
     * @return null|string
232
     */
233
    public function getMime()
234
    {
235
      if (!$this->mime) {
236
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
237
      }
238
239
      return $this->mime;
240
    }
241
242
    /**
243
     * Define a mime type for uploading.
244
     *
245
     * @param array $fileTypes
246
     *
247
     * @return $this
248
     */
249
    public function setMime(array $fileTypes)
250
    {
251
      $this->mimeTypes = $fileTypes;
252
      return $this;
253
    }
254
255
    /**
256
     * Gets the real image mime type.
257
     *
258
     * @param $tmp_name string The upload tmp directory
259
     *
260
     * @return null|string
261
     */
262
    protected function getImageMime($tmp_name)
263
    {
264
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
265
      if (!$this->mime) {
266
        return null;
267
      }
268
269
      return $this->mime;
270
    }
271
272
    /**
273
     * Returns error string
274
     *
275
     * @return string
276
     */
277
    public function getError()
278
    {
279
      return $this->error;
280
    }
281
282
    /**
283
     * Returns the image name.
284
     *
285
     * @return string
286
     */
287
    public function getName()
288
    {
289
      return $this->name;
290
    }
291
292
    /**
293
     * Provide image name if not provided.
294
     *
295
     * @param null|string $isNameProvided
296
     *
297
     * @return $this
298
     */
299
    public function setName($isNameProvided = null)
300
    {
301
      if ($isNameProvided) {
302
        $this->name = $isNameProvided;
303
      }else{
304
        $this->name = uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
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
323
      return $width;
324
    }
325
326
    /**
327
     * Returns the image height in pixels.
328
     *
329
     * @return int
330
     */
331
    public function getHeight()
332
    {
333
      if ($this->height != null) {
334
        return $this->height;
335
      }
336
337
      list(, $height) = getimagesize($this->_files['tmp_name']);
338
339
      return $height;
340
    }
341
342
    /**
343
     * Returns the storage / folder name.
344
     *
345
     * @return string
346
     */
347
    public function getStorage()
348
    {
349
      if (!$this->storage) {
350
        $this->setStorage();
351
      }
352
353
      return $this->storage;
354
    }
355
356
    /**
357
     * Validate directory/permission before creating a folder.
358
     *
359
     * @param $dir string the folder name to check
360
     *
361
     * @return bool
362
     */
363
    private function isDirectoryValid($dir)
364
    {
365
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir);
366
    }
367
368
    /**
369
     * Creates a storage for upload storage.
370
     *
371
     * @param $dir string the folder name to create
372
     * @param int $permission chmod permission
373
     *
374
     * @return $this
375
     */
376
    public function setStorage($dir = 'uploads', $permission = 0666)
377
    {
378
      $isDirectoryValid = $this->isDirectoryValid($dir);
379
380
      if (!$isDirectoryValid) {
381
        $this->error = 'Can not create a directory  \''.$dir.'\', please check write 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->storage = $dir;
393
394
      return $this;
395
    }
396
397
    /**
398
     * Validate image size, dimension or mimetypes
399
     *
400
     * @return boolean
401
     */
402
    protected function constraintValidator()
403
    { 
404
405
      // if name is provided use it, otherwise generate a unique name
406
      if (!$this->name) {
407
        $this->setName();
408
      }
409
410
      /* check image for valid mime types and return mime */
411
      $this->getImageMime($this->_files['tmp_name']);
412
413
414
      /* validate image mime type */
415
      if (!in_array($this->mime, $this->mimeTypes)) {
416
        $this->error = sprintf('Invalid File! Only (%s) image types are allowed', implode(', ', $this->mimeTypes));
417
        return false;
418
      }
419
420
      /* get image sizes */
421
      list($minSize, $maxSize) = $this->size;
422
423
424
      /* check image size based on the settings */
425
      if ($this->_files['size'] < $minSize || $this->_files['size'] > $maxSize) {
426
        $min = $minSize.' bytes ('.intval($minSize / 1000).' kb)';
427
        $max = $maxSize.' bytes ('.intval($maxSize / 1000).' kb)';
428
        $this->error = 'Image size should be minimum '.$min.', upto maximum '.$max;
429
        return false;
430
      }
431
432
      /* check image dimension */
433
      list($maxWidth, $maxHeight) = $this->dimensions;
434
      $this->width = $this->getWidth();
435
      $this->height = $this->getHeight();
436
437
      if ($this->height > $maxHeight || $this->width > $maxWidth) {
438
        $this->error = 'Image should be smaller than ' . $maxHeight . 'px in height, and smaller than ' . $maxWidth . 'px in width';
439
        return false;
440
      }
441
442
      return true;
443
    }
444
445
    /**
446
     * Validate and save (upload) file
447
     *
448
     * @return false|Image
449
     */
450
    public function upload()
451
    {
452
      if ($this->error !== '') {
453
        return false;
454
      }
455
456
      $isValid = $this->constraintValidator();
457
458
459
      $isSuccess = $isValid && $this->isSaved($this->_files['tmp_name'], $this->getPath());
460
461
      return $isSuccess ? $this : false;
462
    }
463
464
    /**
465
     * Final upload method to be called, isolated for testing purposes.
466
     *
467
     * @param $tmp_name int the temporary storage of the image file
468
     * @param $destination int upload destination
469
     *
470
     * @return bool
471
     */
472
    protected function isSaved($tmp_name, $destination)
473
    {
474
      return move_uploaded_file($tmp_name, $destination);
475
    }
476
}