MediaUploader::fetchMedia()   F
last analyzed

Complexity

Conditions 23
Paths 1225

Size

Total Lines 98
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 63
c 0
b 0
f 0
nc 1225
nop 2
dl 0
loc 98
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Xhelp;
4
5
/*
6
 * You may not change or alter any portion of this comment or credits
7
 * of supporting developers from this source code or any supporting source code
8
 * which is considered copyrighted (c) material of the original comment or credit authors.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
 */
14
15
/**
16
 * @copyright    XOOPS Project (https://xoops.org)
17
 * @license      GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
18
 * @author       XOOPS Development Team, Kazumi Ono (AKA onokazu)
19
 */
20
21
/**
22
 * !
23
 * Example
24
 *
25
 * require_once __DIR__ . '/uploader.php';
26
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
27
 * $maxfilesize = 50000;
28
 * $maxfilewidth = 120;
29
 * $maxfileheight = 120;
30
 * $uploader = new Xhelp\MediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
31
 * if ($uploader->fetchMedia($_POST['uploade_file_name'])) {
32
 * if (!$uploader->upload()) {
33
 * echo $uploader->getErrors();
34
 * } else {
35
 * echo '<h4>File uploaded successfully!</h4>'
36
 * echo 'Saved as: ' . $uploader->getSavedFileName() . '<br>';
37
 * echo 'Full path: ' . $uploader->getSavedDestination();
38
 * }
39
 * } else {
40
 * echo $uploader->getErrors();
41
 * }
42
 */
43
44
/**
45
 * Upload Media files
46
 *
47
 * Example of usage:
48
 * <code>
49
 * require_once __DIR__ . '/uploader.php';
50
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
51
 * $maxfilesize = 50000;
52
 * $maxfilewidth = 120;
53
 * $maxfileheight = 120;
54
 * $uploader = new Xhelp\MediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
55
 * if ($uploader->fetchMedia($_POST['uploade_file_name'])) {
56
 *            if (!$uploader->upload()) {
57
 *               echo $uploader->getErrors();
58
 *            } else {
59
 *               echo '<h4>File uploaded successfully!</h4>'
60
 *               echo 'Saved as: ' . $uploader->getSavedFileName() . '<br>';
61
 *               echo 'Full path: ' . $uploader->getSavedDestination();
62
 *            }
63
 * } else {
64
 *            echo $uploader->getErrors();
65
 * }
66
 * </code>
67
 *
68
 * @author        Kazumi Ono <[email protected]>
69
 * @copyright (c) 2000-2003 XOOPS Project (https://xoops.org)
70
 */
71
72
/**
73
 * class MediaUploader
74
 */
75
class MediaUploader
76
{
77
    public $mediaName;
78
    public $mediaType;
79
    public $mediaSize;
80
    public $mediaTmpName;
81
    public $mediaError;
82
    public $uploadDir        = '';
83
    public $allowedMimeTypes = [];
84
    public $maxFileSize      = 0;
85
    public $maxWidth;
86
    public $maxHeight;
87
    public $targetFileName;
88
    public $prefix;
89
    public $ext;
90
    public $dimension;
91
    public $errors           = [];
92
    public $savedDestination;
93
    public $savedFileName;
94
    /**
95
     * No admin check for uploads
96
     */
97
    public $noadmin_sizecheck;
98
99
    /**
100
     * Constructor
101
     *
102
     * @param string    $uploadDir
103
     * @param array|int $allowedMimeTypes
104
     * @param int       $maxFileSize
105
     * @param int       $maxWidth
106
     * @param int       $maxHeight
107
     * @internal param int $cmodvalue
108
     */
109
    public function __construct(string $uploadDir, $allowedMimeTypes, int $maxFileSize, int $maxWidth = 0, int $maxHeight = 0)
110
    {
111
        if (\is_array($allowedMimeTypes)) {
112
            $this->allowedMimeTypes = &$allowedMimeTypes;
113
        }
114
        $this->uploadDir   = $uploadDir;
115
        $this->maxFileSize = $maxFileSize;
116
        if (null !== $maxWidth) {
0 ignored issues
show
introduced by
The condition null !== $maxWidth is always true.
Loading history...
117
            $this->maxWidth = $maxWidth;
118
        }
119
        if (null !== $maxHeight) {
0 ignored issues
show
introduced by
The condition null !== $maxHeight is always true.
Loading history...
120
            $this->maxHeight = $maxHeight;
121
        }
122
    }
123
124
    /**
125
     * @param string $value
126
     */
127
    public function noAdminSizeCheck(string $value): void
128
    {
129
        $this->noadmin_sizecheck = $value;
130
    }
131
132
    /**
133
     * Fetch the uploaded file
134
     *
135
     * @param string $media_name Name of the file field
136
     * @param null   $index      Index of the file (if more than one uploaded under that name)
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $index is correct as it would always require null to be passed?
Loading history...
137
     * @return bool
138
     */
139
    public function fetchMedia(string $media_name, $index = null): bool
140
    {
141
        global $_FILES;
142
143
        if (!isset($_FILES[$media_name])) {
144
            $this->setErrors('You either did not choose a file to upload or the server has insufficient read/writes to upload this file.!');
145
146
            return false;
147
        }
148
149
        if (\is_array($_FILES[$media_name]['name']) && null !== $index) {
0 ignored issues
show
introduced by
The condition null !== $index is always false.
Loading history...
150
            $index              = (int)$index;
151
            $this->mediaName    = @get_magic_quotes_gpc() ? \stripslashes($_FILES[$media_name]['name'][$index]) : $_FILES[$media_name]['name'][$index];
152
            $this->mediaType    = $_FILES[$media_name]['type'][$index];
153
            $this->mediaSize    = $_FILES[$media_name]['size'][$index];
154
            $this->mediaTmpName = $_FILES[$media_name]['tmp_name'][$index];
155
            $this->mediaError   = !empty($_FILES[$media_name]['error'][$index]) ? $_FILES[$media_name]['errir'][$index] : 0;
156
        } else {
157
            $media_name         = @$_FILES[$media_name];
158
            $this->mediaName    = @get_magic_quotes_gpc() ? \stripslashes($media_name['name']) : $media_name['name'];
159
            $this->mediaName    = $media_name['name'];
160
            $this->mediaType    = $media_name['type'];
161
            $this->mediaSize    = $media_name['size'];
162
            $this->mediaTmpName = $media_name['tmp_name'];
163
            $this->mediaError   = !empty($media_name['error']) ? $media_name['error'] : 0;
164
        }
165
        $this->dimension = \getimagesize($this->mediaTmpName);
166
167
        $this->errors = [];
168
169
        if ((int)$this->mediaSize < 0) {
170
            $this->setErrors('Invalid File Size');
171
172
            return false;
173
        }
174
        if ('' === $this->mediaName) {
175
            $this->setErrors('Filename Is Empty');
176
177
            return false;
178
        }
179
180
        if ('none' === $this->mediaTmpName) {
181
            $this->setErrors('No file uploaded, this is a error');
182
183
            return false;
184
        }
185
186
        if (!$this->checkMaxFileSize()) {
187
            $this->setErrors(\sprintf('File Size: %u. Maximum Size Allowed: %u', $this->mediaSize, $this->maxFileSize));
188
        }
189
190
        if (\is_array($this->dimension)) {
0 ignored issues
show
introduced by
The condition is_array($this->dimension) is always true.
Loading history...
191
            if (!$this->checkMaxWidth($this->dimension[0])) {
192
                $this->setErrors(\sprintf('File width: %u. Maximum width allowed: %u', $this->dimension[0], $this->maxWidth));
193
            }
194
            if (!$this->checkMaxHeight($this->dimension[1])) {
195
                $this->setErrors(\sprintf('File height: %u. Maximum height allowed: %u', $this->dimension[1], $this->maxHeight));
196
            }
197
        }
198
199
        if (\count($this->errors) > 0) {
200
            return false;
201
        }
202
203
        if (!$this->checkMimeType()) {
204
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
205
        }
206
207
        if (!\is_uploaded_file($this->mediaTmpName)) {
208
            switch ($this->mediaError) {
209
                case 0: // no error; possible file attack!
210
                    $this->setErrors('There was a problem with your upload. Error: 0');
211
                    break;
212
                case 1: // uploaded file exceeds the upload_max_filesize directive in php.ini
213
                    //if ($this->noAdminSizeCheck)
214
                    //{
215
                    //    return true;
216
                    //}
217
                    $this->setErrors('The file you are trying to upload is too big. Error: 1');
218
                    break;
219
                case 2: // uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form
220
                    $this->setErrors('The file you are trying to upload is too big. Error: 2');
221
                    break;
222
                case 3: // uploaded file was only partially uploaded
223
                    $this->setErrors('The file you are trying upload was only partially uploaded. Error: 3');
224
                    break;
225
                case 4: // no file was uploaded
226
                    $this->setErrors('No file selected for upload. Error: 4');
227
                    break;
228
                default: // a default error, just in case!  :)
229
                    $this->setErrors('No file selected for upload. Error: 5');
230
                    break;
231
            }
232
233
            return false;
234
        }
235
236
        return true;
237
    }
238
239
    /**
240
     * Set the target filename
241
     *
242
     * @param string $value
243
     */
244
    public function setTargetFileName(string $value): void
245
    {
246
        $this->targetFileName = \trim($value);
247
    }
248
249
    /**
250
     * Set the prefix
251
     *
252
     * @param string $value
253
     */
254
    public function setPrefix(string $value): void
255
    {
256
        $this->prefix = \trim($value);
257
    }
258
259
    /**
260
     * Get the uploaded filename
261
     *
262
     * @return string
263
     */
264
    public function getMediaName(): string
265
    {
266
        return $this->mediaName;
267
    }
268
269
    /**
270
     * Get the type of the uploaded file
271
     *
272
     * @return string
273
     */
274
    public function getMediaType(): string
275
    {
276
        return $this->mediaType;
277
    }
278
279
    /**
280
     * Get the size of the uploaded file
281
     *
282
     * @return int
283
     */
284
    public function getMediaSize(): int
285
    {
286
        return $this->mediaSize;
287
    }
288
289
    /**
290
     * Get the temporary name that the uploaded file was stored under
291
     *
292
     * @return string
293
     */
294
    public function getMediaTmpName(): string
295
    {
296
        return $this->mediaTmpName;
297
    }
298
299
    /**
300
     * Get the saved filename
301
     *
302
     * @return string
303
     */
304
    public function getSavedFileName(): string
305
    {
306
        return $this->savedFileName;
307
    }
308
309
    /**
310
     * Get the destination the file is saved to
311
     *
312
     * @return string
313
     */
314
    public function getSavedDestination(): string
315
    {
316
        return $this->savedDestination;
317
    }
318
319
    /**
320
     * Check the file and copy it to the destination
321
     *
322
     * @param int $chmod
323
     * @return bool
324
     */
325
    public function upload(int $chmod = 0644): bool
326
    {
327
        if ('' === $this->uploadDir) {
328
            $this->setErrors('Upload directory not set');
329
330
            return false;
331
        }
332
333
        if (!\is_dir($this->uploadDir)) {
334
            $this->setErrors('Failed opening directory: ' . $this->uploadDir);
335
        }
336
337
        if (!\is_writable($this->uploadDir)) {
338
            $this->setErrors('Failed opening directory with write permission: ' . $this->uploadDir);
339
        }
340
341
        if (!$this->checkMaxFileSize()) {
342
            $this->setErrors(\sprintf('File Size: %u. Maximum Size Allowed: %u', $this->mediaSize, $this->maxFileSize));
343
        }
344
345
        if (\is_array($this->dimension)) {
346
            if (!$this->checkMaxWidth($this->dimension[0])) {
347
                $this->setErrors(\sprintf('File width: %u. Maximum width allowed: %u', $this->dimension[0], $this->maxWidth));
348
            }
349
            if (!$this->checkMaxHeight($this->dimension[1])) {
350
                $this->setErrors(\sprintf('File height: %u. Maximum height allowed: %u', $this->dimension[1], $this->maxHeight));
351
            }
352
        }
353
354
        if (!$this->checkMimeType()) {
355
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
356
        }
357
358
        if (!$this->_copyFile($chmod)) {
359
            $this->setErrors('Failed uploading file: ' . $this->mediaName);
360
        }
361
362
        if (\count($this->errors) > 0) {
363
            return false;
364
        }
365
366
        return true;
367
    }
368
369
    /**
370
     * Copy the file to its destination
371
     *
372
     * @param int $chmod
373
     * @return bool
374
     */
375
    public function _copyFile(int $chmod): bool
376
    {
377
        $matched = [];
378
        if (!\preg_match('/\.([a-zA-Z0-9]+)$/', $this->mediaName, $matched)) {
379
            return false;
380
        }
381
        if (null !== $this->targetFileName) {
382
            $this->savedFileName = $this->targetFileName;
383
        } elseif (null !== $this->prefix) {
384
            $this->savedFileName = \uniqid($this->prefix, true) . '.' . \mb_strtolower($matched[1]);
385
        } else {
386
            $this->savedFileName = \mb_strtolower($this->mediaName);
387
        }
388
        $this->savedFileName    = \preg_replace('!\s+!', '_', $this->savedFileName);
389
        $this->savedDestination = $this->uploadDir . $this->savedFileName;
390
        if (\is_file($this->savedDestination) && !!\is_dir($this->savedDestination)) {
391
            $this->setErrors('File ' . $this->mediaName . ' already exists on the server. Please rename this file and try again.<br>');
392
393
            return false;
394
        }
395
        if (!\move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
396
            return false;
397
        }
398
        @\chmod($this->savedDestination, $chmod);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

398
        /** @scrutinizer ignore-unhandled */ @\chmod($this->savedDestination, $chmod);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
399
400
        return true;
401
    }
402
403
    /**
404
     * Is the file the right size?
405
     *
406
     * @return bool
407
     */
408
    public function checkMaxFileSize(): bool
409
    {
410
        if ($this->noadmin_sizecheck) {
411
            return true;
412
        }
413
        if ($this->mediaSize > $this->maxFileSize) {
414
            return false;
415
        }
416
417
        return true;
418
    }
419
420
    /**
421
     * Is the picture the right width?
422
     *
423
     * @param int $dimension
424
     * @return bool
425
     */
426
    public function checkMaxWidth(int $dimension): bool
427
    {
428
        if (null === $this->maxWidth) {
429
            return true;
430
        }
431
        if ($dimension > $this->maxWidth) {
432
            return false;
433
        }
434
435
        return true;
436
    }
437
438
    /**
439
     * Is the picture the right height?
440
     *
441
     * @param int $dimension
442
     * @return bool
443
     */
444
    public function checkMaxHeight(int $dimension): bool
445
    {
446
        if (null === $this->maxHeight) {
447
            return true;
448
        }
449
        if ($dimension > $this->maxWidth) {
450
            return false;
451
        }
452
453
        return true;
454
    }
455
456
    /**
457
     * Is the file the right Mime type
458
     *
459
     * (is there a right type of mime? ;-)
460
     *
461
     * @return bool
462
     */
463
    public function checkMimeType(): bool
464
    {
465
        if (\count($this->allowedMimeTypes) > 0 && !\in_array($this->mediaType, $this->allowedMimeTypes)) {
466
            return false;
467
        }
468
469
        return true;
470
    }
471
472
    /**
473
     * Add an error
474
     *
475
     * @param string $error
476
     */
477
    public function setErrors(string $error): void
478
    {
479
        $this->errors[] = \trim($error);
480
    }
481
482
    /**
483
     * Get generated errors
484
     *
485
     * @param bool $ashtml Format using HTML?
486
     * @return array |string    Array of array messages OR HTML string
487
     */
488
    public function &getErrors(bool $ashtml = true)
489
    {
490
        if (!$ashtml) {
491
            return $this->errors;
492
        }
493
494
        $ret = '';
495
        if (\count($this->errors) > 0) {
496
            $ret = '<h4>Errors Returned While Uploading</h4>';
497
            foreach ($this->errors as $error) {
498
                $ret .= $error . '<br>';
499
            }
500
        }
501
502
        return $ret;
503
    }
504
}
505