Completed
Branch dev (aa27ce)
by samayo
01:18
created

Image::isValid()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 0
1
<?php 
2
/**
3
 * BULLETPROOF.
4
 * 
5
 * A single-class PHP library to upload images securely.
6
 * 
7
 * PHP support 5.3+
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'
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
      }
106
107
      $this->_files = $_files;
108
    }
109
110
    /**
111
     * \ArrayAccess unused method
112
     * 
113
     * @param mixed $offset
114
     * @param mixed $value
115
     */
116
    public function offsetSet($offset, $value){}
117
118
    /**
119
     * \ArrayAccess unused method
120
     * 
121
     * @param mixed $offset
122
     */
123
    public function offsetExists($offset){}
124
125
    /**
126
     * \ArrayAccess unused method
127
     * 
128
     * @param mixed $offset
129
     */
130
    public function offsetUnset($offset){}
131
132
    /**
133
     * \ArrayAccess - get array value from object
134
     *
135
     * @param mixed $offset
136
     *
137
     * @return string|bool
138
     */
139
    public function offsetGet($offset)
140
    {
141
        /* return false if $image['key'] isn't found */
142
        if (!isset($this->_files[$offset])) {
143
          $this->error = sprintf('No file input found with name: (%s)', $offset);
144
          
145
          return false;
146
        }
147
148
      $this->_files = $this->_files[$offset];
149
150
       /* check for common upload errors */
151
      if (isset($this->_files['error'])) {
152
        $this->error = $this->commonErrors[$this->_files['error']];
153
      }
154
155
      return $this->error ? false : true; 
156
    }
157
158
    /**
159
     * Sets max image height and width limit.
160
     *
161
     * @param $maxWidth int max width value
162
     * @param $maxHeight int max height value
163
     *
164
     * @return $this
165
     */
166
    public function setDimension($maxWidth, $maxHeight)
167
    {
168
      $this->dimensions = array($maxWidth, $maxHeight);
169
170
      return $this;
171
    }
172
173
    /**
174
     * Returns the full path of the image ex 'storage/image.mime'.
175
     *
176
     * @return string
177
     */
178
    public function getPath()
179
    {
180
      return $this->path = $this->getStorage() . '/' . $this->getName() . '.' . $this->getMime();
181
    }
182
183
    /**
184
     * Returns the image size in bytes.
185
     *
186
     * @return int
187
     */
188
    public function getSize()
189
    {
190
      return (int) $this->_files['size'];
191
    }
192
193
    /**
194
     * Define a min and max image size for uploading.
195
     *
196
     * @param $min int minimum value in bytes
197
     * @param $max int maximum value in bytes
198
     *
199
     * @return $this
200
     */
201
    public function setSize($min, $max)
202
    {
203
      $this->size = array($min, $max);
204
      return $this;
205
    }
206
207
    /**
208
     * Returns a JSON format of the image width, height, name, mime ...
209
     *
210
     * @return string
211
     */
212
    public function getJson()
213
    {
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
          'storage' => $this->storage,
222
          'path' => $this->path,
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
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
236
      }
237
238
      return $this->mime;
239
    }
240
241
    /**
242
     * Define a mime type for uploading.
243
     *
244
     * @param array $fileTypes
245
     *
246
     * @return $this
247
     */
248
    public function setMime(array $fileTypes)
249
    {
250
      $this->mimeTypes = $fileTypes;
251
      return $this;
252
    }
253
254
    /**
255
     * Gets the real image mime type.
256
     *
257
     * @param $tmp_name string The upload tmp directory
258
     *
259
     * @return null|string
260
     */
261
    protected function getImageMime($tmp_name)
262
    {
263
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
264
      if (!$this->mime) {
265
        return null;
266
      }
267
268
      return $this->mime;
269
    }
270
271
    /**
272
     * Returns error string
273
     *
274
     * @return string
275
     */
276
    public function getError()
277
    {
278
      return $this->error;
279
    }
280
281
    /**
282
     * Returns the image name.
283
     *
284
     * @return string
285
     */
286
    public function getName()
287
    {
288
      return $this->name;
289
    }
290
291
    /**
292
     * Provide image name if not provided.
293
     *
294
     * @param null $isNameProvided
295
     *
296
     * @return $this
297
     */
298
    public function setName($isNameProvided = null)
299
    {
300
      if ($isNameProvided) {
301
        $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
302
      }else{
303
        $this->name = uniqid('', true) . '_' . str_shuffle(implode(range('e', 'q')));
304
      }
305
306
      return $this;
307
    }
308
309
    /**
310
     * Returns the image width.
311
     *
312
     * @return int
313
     */
314
    public function getWidth()
315
    {
316
      if ($this->width != null) {
317
        return $this->width;
318
      }
319
320
      list($width) = getimagesize($this->_files['tmp_name']);
321
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
338
      return $height;
339
    }
340
341
    /**
342
     * Returns the storage / folder name.
343
     *
344
     * @return string
345
     */
346
    public function getStorage()
347
    {
348
      if (!$this->storage) {
349
        $this->setStorage();
350
      }
351
352
      return $this->storage;
353
    }
354
355
    /**
356
     * Validate directory/permission before creating a folder.
357
     *
358
     * @param $dir string the folder name to check
359
     *
360
     * @return bool
361
     */
362
    private function isDirectoryValid($dir)
363
    {
364
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir);
365
    }
366
367
    /**
368
     * Creates a storage for upload storage.
369
     *
370
     * @param $dir string the folder name to create
371
     * @param int $permission chmod permission
372
     *
373
     * @return $this
374
     */
375
    public function setStorage($dir = 'uploads', $permission = 0666)
376
    {
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
    public function validateMime() {
399
      $this->getImageMime($this->_files['tmp_name']); 
400
401
      if(!in_array($this->mime, $this->mimeTypes)) {
402
        $this->error = sprintf('Invalid File! Only (%s) image types are allowed', implode(', ', $this->mimeTypes));
403
        return false;
404
      }
405
406
      return true;
407
    }
408
409
    public function validateSize() {
410
      list($minSize, $maxSize) = $this->size; 
411
      if ($this->_files['size'] < $minSize || $this->_files['size'] > $maxSize) {
412
        $min = $minSize.' bytes ('.intval($minSize / 1000).' kb)';
413
        $max = $maxSize.' bytes ('.intval($maxSize / 1000).' kb)';
414
        $this->error = 'Image size should be minimum '.$min.', upto maximum '.$max;
415
        return false;
416
      }
417
418
      return true;
419
    }
420
421
    public function validateDimension() {
422
      /* check image dimension */
423
      list($maxWidth, $maxHeight) = $this->dimensions;
424
      $this->width = $this->getWidth();
425
      $this->height = $this->getHeight();
426
427
      if($this->height > $maxHeight) {
428
        $this->error = sprintf('Image height should be smaller than (%s) pixels', $maxHeight); 
429
430
        return false; 
431
      }
432
433
      if($this->width > $maxWidth) {
434
        $this->error = sprintf('Image width should be smaller than (%s) pixels', $maxHeight); 
435
436
        return false; 
437
      }
438
439
      return true;
440
    }
441
442
    public function isValid() {
443
444
      return $this->validateMime() && $this->validateSize() && $this->validateDimension();
445
    }
446
447
448
    /**
449
     * Validate and save (upload) file
450
     *
451
     * @return false|Image
452
     */
453
    public function upload()
454
    {
455
456
      if ($this->error !== '') {
457
        return false;
458
      }
459
460
      $this->setName();
461
462
463
      $isSuccess = $this->isValid() && $this->isSaved($this->_files['tmp_name'], $this->getPath());
464
465
      return $isSuccess ? $this : false;
466
    }
467
468
    /**
469
     * Final upload method to be called, isolated for testing purposes.
470
     *
471
     * @param $tmp_name int the temporary storage of the image file
472
     * @param $destination int upload destination
473
     *
474
     * @return bool
475
     */
476
    protected function isSaved($tmp_name, $destination)
477
    {
478
      return move_uploaded_file($tmp_name, $destination);
479
    }
480
}