Completed
Branch prettify (52520b)
by samayo
01:47
created

Image::setMime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
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     4.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 $fullPath;
42
43
    /**
44
     * @var string The folder or image storage location
45
     */
46
    protected $location;
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 $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
    /**
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
      if (!function_exists('exif_imagetype')) {
102
          $this->error = 'Function \'exif_imagetype\' Not found. Please enable \'php_exif\' in your php.ini';
103
      }
104
105
      $this->_files = $_files;
106
    }
107
108
    /**
109
     * @param mixed $offset
110
     * @param mixed $value
111
     */
112
    public function offsetSet($offset, $value) {}
113
114
    /**
115
     * @param mixed $offset
116
     */
117
    public function offsetExists($offset){}
118
119
    /**
120
     * @param mixed $offset
121
     */
122
    public function offsetUnset($offset){}
123
124
    /**
125
     * Gets array value \ArrayAccess.
126
     *
127
     * @param mixed $offset
128
     *
129
     * @return string|bool
130
     */
131
    public function offsetGet($offset)
132
    {
133
      // return false if $_FILES['key'] isn't found
134
      if (!isset($this->_files[$offset])) {
135
        $this->error = sprintf('No file input found with name: (%s)', $offset);
136
        return false;
137
      }
138
139
      $this->_files = $this->_files[$offset];
140
141
      // check for common upload errors
142
      if (isset($this->_files['error'])) {
143
        $this->error = $this->commonUploadErrors[$this->_files['error']];
144
      }
145
146
      return true;
147
    }
148
149
    /**
150
     * Sets max image height and width limit.
151
     *
152
     * @param $maxWidth int max width value
153
     * @param $maxHeight int max height value
154
     *
155
     * @return $this
156
     */
157
    public function setDimension($maxWidth, $maxHeight)
158
    {
159
      if ((int) $maxWidth && (int) $maxHeight) {
160
        $this->dimensions = array($maxWidth, $maxHeight);
161
      } else {
162
        $this->error = 'Invalid dimension! Values must be integers';
163
      }
164
165
      return $this;
166
    }
167
168
    /**
169
     * Returns the full path of the image ex 'location/image.mime'.
170
     *
171
     * @return string
172
     */
173
    public function getFullPath()
174
    {
175
      return $this->fullPath = $this->getLocation().'/'.$this->getName().'.'.$this->getMime();
176
    }
177
178
    /**
179
     * Returns the image size in bytes.
180
     *
181
     * @return int
182
     */
183
    public function getSize()
184
    {
185
      return (int) $this->_files['size'];
186
    }
187
188
    /**
189
     * Define a min and max image size for uploading.
190
     *
191
     * @param $min int minimum value in bytes
192
     * @param $max int maximum value in bytes
193
     *
194
     * @return $this
195
     */
196
    public function setSize($min, $max)
197
    {
198
      $this->size = array($min, $max);
199
      return $this;
200
    }
201
202
    /**
203
     * Returns a JSON format of the image width, height, name, mime ...
204
     *
205
     * @return string
206
     */
207
    public function getJson()
208
    {
209
      return json_encode(
210
        array(
211
          'name' => $this->name,
212
          'mime' => $this->mime,
213
          'height' => $this->height,
214
          'width' => $this->width,
215
          'size' => $this->_files['size'],
216
          'location' => $this->location,
217
          'fullpath' => $this->fullPath,
218
        )
219
      );
220
    }
221
222
    /**
223
     * Returns the image mime type.
224
     *
225
     * @return null|string
226
     */
227
    public function getMime()
228
    {
229
      if (!$this->mime) {
230
        $this->mime = $this->getImageMime($this->_files['tmp_name']);
231
      }
232
233
      return $this->mime;
234
    }
235
236
    /**
237
     * Define a mime type for uploading.
238
     *
239
     * @param array $fileTypes
240
     *
241
     * @return $this
242
     */
243
    public function setMime(array $fileTypes)
244
    {
245
      $this->mimeTypes = $fileTypes;
246
      return $this;
247
    }
248
249
    /**
250
     * Gets the real image mime type.
251
     *
252
     * @param $tmp_name string The upload tmp directory
253
     *
254
     * @return null|string
255
     */
256
    protected function getImageMime($tmp_name)
257
    {
258
      $this->mime = @$this->acceptedMimes[exif_imagetype($tmp_name)];
259
      if (!$this->mime) {
260
        return null;
261
      }
262
263
      return $this->mime;
264
    }
265
266
    /**
267
     * Returns error string or false if no errors occurred.
268
     *
269
     * @return string|false
270
     */
271
    public function getError()
272
    {
273
      return $this->error;
274
    }
275
276
    /**
277
     * Returns the image name.
278
     *
279
     * @return string
280
     */
281
    public function getName()
282
    {
283
      if (!$this->name) {
284
        $this->name = uniqid('', true).'_'.str_shuffle(implode(range('e', 'q')));
285
      }
286
287
      return $this->name;
288
    }
289
290
    /**
291
     * Provide image name if not provided.
292
     *
293
     * @param null $isNameProvided
294
     *
295
     * @return $this
296
     */
297
    public function setName($isNameProvided = null)
298
    {
299
      if ($isNameProvided) {
300
        $this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
301
      }
302
303
      return $this;
304
    }
305
306
    /**
307
     * Returns the image width.
308
     *
309
     * @return int
310
     */
311
    public function getWidth()
312
    {
313
      if ($this->width != null) {
314
        return $this->width;
315
      }
316
317
      list($width) = getimagesize($this->_files['tmp_name']);
318
319
      return $width;
320
    }
321
322
    /**
323
     * Returns the image height in pixels.
324
     *
325
     * @return int
326
     */
327
    public function getHeight()
328
    {
329
      if ($this->height != null) {
330
        return $this->height;
331
      }
332
333
      list(, $height) = getimagesize($this->_files['tmp_name']);
334
335
      return $height;
336
    }
337
338
    /**
339
     * Returns the storage / folder name.
340
     *
341
     * @return string
342
     */
343
    public function getLocation()
344
    {
345
      if (!$this->location) {
346
        $this->setLocation();
347
      }
348
349
      return $this->location;
350
    }
351
352
    /**
353
     * Validate directory/permission before creating a folder.
354
     *
355
     * @param $dir string the folder name to check
356
     *
357
     * @return bool
358
     */
359
    private function isDirectoryValid($dir)
360
    {
361
      return !file_exists($dir) && !is_dir($dir) || is_writable($dir);
362
    }
363
364
    /**
365
     * Creates a location for upload storage.
366
     *
367
     * @param $dir string the folder name to create
368
     * @param int $permission chmod permission
369
     *
370
     * @return $this
371
     */
372
    public function setLocation($dir = 'bulletproof', $permission = 0666)
373
    {
374
      $isDirectoryValid = $this->isDirectoryValid($dir);
375
376
      if (!$isDirectoryValid) {
377
        $this->error = 'Can not create a directory  \''.$dir.'\', please check write permission';
378
        return false;
379
      }
380
381
      $create = !is_dir($dir) ? @mkdir(''.$dir, (int) $permission, true) : true;
382
383
      if (!$create) {
384
        $this->error = 'Error! directory \''.$dir.'\' could not be created';
385
        return false;
386
      }
387
388
      $this->location = $dir;
389
390
      return $this;
391
    }
392
393
    /**
394
     * Validate image size, dimension or mimetypes
395
     *
396
     * @return true|false
0 ignored issues
show
Documentation introduced by
Should the return type not be 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...
397
     */
398
    protected function contraintsValidator()
399
    {
400
      /* check image for valid mime types and return mime */
401
      $this->getImageMime($this->_files['tmp_name']);
402
      /* validate image mime type */
403
      if (!in_array($this->mime, $this->mimeTypes)) {
404
        $this->error = sprintf('Invalid File! Only (%s) image types are allowed', implode(', ', $this->mimeTypes));
405
        return false;
406
      }
407
408
      /* get image sizes */
409
      list($minSize, $maxSize) = $this->size;
410
411
      /* check image size based on the settings */
412
      if ($this->_files['size'] < $minSize || $this->_files['size'] > $maxSize) {
413
        $min = $minSize.' bytes ('.intval($minSize / 1000).' kb)';
414
        $max = $maxSize.' bytes ('.intval($maxSize / 1000).' kb)';
415
        $this->error = 'Image size should be minumum '.$min.', upto maximum '.$max;
416
        return false;
417
      }
418
419
      /* check image dimension */
420
      list($maxWidth, $maxHeight) = $this->dimensions;
421
422
      /* initialize image properties */
423
      $this->width = $this->getWidth();
424
      $this->height = $this->getHeight();
425
426
      if ($this->height > $maxHeight || $this->width > $maxWidth) {
427
        $this->error = 'Image height/width should be less than '.$maxHeight.'/'.$maxWidth.' pixels';
428
        return false;
429
      }
430
431
      return true;
432
    }
433
434
    /**
435
     * Validate and save (upload) file
436
     *
437
     * @return false|Image
438
     */
439
    public function upload()
440
    {
441
      if ($this->error || !isset($this->_files['tmp_name'])) {
442
        return false;
443
      }
444
445
      $isValid = $this->contraintsValidator();
446
447
      if (!$isValid) {
448
        return false;
449
      }
450
451
      $isSaved = $this->isSaved($this->_files['tmp_name'], $this->getFullPath());
452
453
      return $isSaved ? $this : false;
454
    }
455
456
    /**
457
     * Final upload method to be called, isolated for testing purposes.
458
     *
459
     * @param $tmp_name int the temporary location of the image file
460
     * @param $destination int upload destination
461
     *
462
     * @return bool
463
     */
464
    protected function isSaved($tmp_name, $destination)
465
    {
466
      return move_uploaded_file($tmp_name, $destination);
467
    }
468
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
469