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;
|
|
|
|
|
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"]);
|
|
|
|
|
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
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.