Uploader   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 140
c 1
b 0
f 0
dl 0
loc 391
rs 3.2
wmc 65

19 Methods

Rating   Name   Duplication   Size   Complexity  
A checkMaxWidth() 0 14 5
A setErrors() 0 3 1
A checkExtension() 0 8 3
A getMediaName() 0 3 1
C fetchMedia() 0 44 12
A getMediaSize() 0 3 1
A getSavedFileName() 0 3 1
A _copyFile() 0 20 5
A getMediaTmpName() 0 3 1
A getErrors() 0 14 4
A getSavedDestination() 0 3 1
A setTargetFileName() 0 3 1
A getMediaType() 0 3 1
A setPrefix() 0 3 1
A checkMaxHeight() 0 14 5
A checkMaxFileSize() 0 7 2
A checkMimeType() 0 7 3
A __construct() 0 24 6
B upload() 0 46 11

How to fix   Complexity   

Complex Class

Complex classes like Uploader 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 Uploader, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Songlist;
4
5
use function mb_substr;
6
7
use const DS;
8
9
/**
10
 * Class SonglistMediaUploader
11
 */
12
class Uploader
13
{
14
    public $mediaName;
15
    public $mediaType;
16
    public $mediaSize;
17
    public $mediaTmpName;
18
    public $mediaError;
19
    public $uploadDir         = '';
20
    public $allowedMimeTypes  = [];
21
    public $allowedExtensions = [];
22
    public $maxFileSize       = 3299999999;
23
    public $maxWidth;
24
    public $maxHeight;
25
    public $targetFileName;
26
    public $prefix;
27
    public $errors            = [];
28
    public $savedDestination;
29
    public $savedFileName;
30
31
    /**
32
     * Constructor
33
     *
34
     * @param string $uploadDir
35
     * @param array  $allowedMimeTypes
36
     * @param int    $maxFileSize
37
     * @param int    $maxWidth
38
     * @param int    $maxHeight
39
     * @param array  $allowedExtensions
40
     * @internal param int $cmodvalue
41
     */
42
    public function __construct($uploadDir, $allowedMimeTypes, $maxFileSize, $maxWidth = null, $maxHeight = null, $allowedExtensions = null)
43
    {
44
        if (\is_array($allowedMimeTypes)) {
0 ignored issues
show
introduced by
The condition is_array($allowedMimeTypes) is always true.
Loading history...
45
            $this->allowedMimeTypes = &$allowedMimeTypes;
46
        }
47
//        $this->uploadDir = $uploadDir . (DS != mb_substr($uploadDir, mb_strlen($uploadDir) - 1, 1) ? DS : '');
48
//        if (\is_dir($uploadDir)) {
49
//            foreach (\explode(DS, $uploadDir) as $folder) {
50
//                $path .= DS . $folder;
51
//                if (!\mkdir($path, 0777) && !\is_dir($path)) {
52
//                    throw new \RuntimeException(\sprintf('Directory "%s" was not created', $path));
53
//                }
54
//            }
55
//        }
56
        $this->uploadDir = $uploadDir;
57
        $this->maxFileSize = (int)$maxFileSize;
58
        if (isset($maxWidth)) {
59
            $this->maxWidth = (int)$maxWidth;
60
        }
61
        if (isset($maxHeight)) {
62
            $this->maxHeight = (int)$maxHeight;
63
        }
64
        if (isset($allowedExtensions) && \is_array($allowedExtensions)) {
65
            $this->allowedExtensions = &$allowedExtensions;
66
        }
67
    }
68
69
    /**
70
     * Fetch the uploaded file
71
     *
72
     * @param       $index_name
73
     * @param int   $index Index of the file (if more than one uploaded under that name)
74
     * @return bool
75
     * @internal param string $media_name Name of the file field
76
     */
77
    public function fetchMedia($index_name, $index = null): bool
78
    {
79
        if (!isset($_FILES[$index_name])) {
80
            $this->setErrors('File not found');
81
82
            return false;
83
        }
84
85
        if (\is_array($_FILES[$index_name]['name'][$index]) && isset($index)) {
86
            $this->mediaName    = $_FILES[$index_name]['name'][$index];
87
            $this->mediaType    = $_FILES[$index_name]['type'][$index];
88
            $this->mediaSize    = $_FILES[$index_name]['size'][$index];
89
            $this->mediaTmpName = $_FILES[$index_name]['tmp_name'][$index];
90
            $this->mediaError   = !empty($_FILES[$index_name]['error'][$index]) ? $_FILES[$index_name]['errir'][$index] : 0;
91
        } else {
92
            $this->mediaName    = $_FILES[$index_name]['name'];
93
            $this->mediaType    = $_FILES[$index_name]['type'];
94
            $this->mediaSize    = $_FILES[$index_name]['size'];
95
            $this->mediaTmpName = $_FILES[$index_name]['tmp_name'];
96
            $this->mediaError   = !empty($_FILES[$index_name]['error']) ? $_FILES[$index_name]['error'] : 0;
97
        }
98
        $this->errors = [];
99
        if ((int)$this->mediaSize < 0) {
100
            $this->setErrors('Invalid File Size');
101
102
            return false;
103
        }
104
        if ('' == $this->mediaName) {
105
            $this->setErrors('Filename Is Empty');
106
107
            return false;
108
        }
109
        if ('none' === $this->mediaTmpName || !\is_uploaded_file($this->mediaTmpName) || 0 == $this->mediaSize) {
110
            $this->setErrors('No file uploaded');
111
112
            return false;
113
        }
114
        if ($this->mediaError > 0) {
115
            $this->setErrors('Error occurred: Error #' . $this->mediaError);
116
117
            return false;
118
        }
119
120
        return true;
121
    }
122
123
    /**
124
     * Set the target filename
125
     *
126
     * @param string $value
127
     **/
128
    public function setTargetFileName(string $value): void
129
    {
130
        $this->targetFileName = \trim($value);
131
    }
132
133
    /**
134
     * Set the prefix
135
     *
136
     * @param string $value
137
     **/
138
    public function setPrefix($value): void
139
    {
140
        $this->prefix = \trim($value);
141
    }
142
143
    /**
144
     * Get the uploaded filename
145
     *
146
     * @return  string
147
     **/
148
    public function getMediaName(): string
149
    {
150
        return $this->mediaName;
151
    }
152
153
    /**
154
     * Get the type of the uploaded file
155
     *
156
     * @return  string
157
     **/
158
    public function getMediaType(): string
159
    {
160
        return $this->mediaType;
161
    }
162
163
    /**
164
     * Get the size of the uploaded file
165
     *
166
     * @return  int
167
     **/
168
    public function getMediaSize(): int
169
    {
170
        return $this->mediaSize;
171
    }
172
173
    /**
174
     * Get the temporary name that the uploaded file was stored under
175
     *
176
     * @return  string
177
     **/
178
    public function getMediaTmpName(): string
179
    {
180
        return $this->mediaTmpName;
181
    }
182
183
    /**
184
     * Get the saved filename
185
     *
186
     * @return  string
187
     **/
188
    public function getSavedFileName(): string
189
    {
190
        return $this->savedFileName;
191
    }
192
193
    /**
194
     * Get the destination the file is saved to
195
     *
196
     * @return  string
197
     **/
198
    public function getSavedDestination(): string
199
    {
200
        return $this->savedDestination;
201
    }
202
203
    /**
204
     * Check the file and copy it to the destination
205
     *
206
     * @param int $chmod
207
     * @return bool
208
     */
209
    public function upload($chmod = 0644): bool
210
    {
211
        if ('' == $this->uploadDir) {
212
            $this->setErrors('Upload directory not set');
213
214
            return false;
215
        }
216
        if (!\is_dir($this->uploadDir)) {
217
            $this->setErrors('Failed opening directory: ' . $this->uploadDir);
218
219
            return false;
220
        }
221
        if (!\is_writable($this->uploadDir)) {
222
            $this->setErrors('Failed opening directory with write permission: ' . $this->uploadDir);
223
224
            return false;
225
        }
226
        if (!$this->checkMimeType()) {
227
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
228
229
            return false;
230
        }
231
        if (!$this->checkExtension()) {
232
            $this->setErrors('Extension not allowed');
233
234
            return false;
235
        }
236
        if (!$this->checkMaxFileSize()) {
237
            $this->setErrors('File size too large: ' . $this->mediaSize);
238
        }
239
        if (!$this->checkMaxWidth()) {
240
            $this->setErrors(\sprintf('File width must be smaller than %u', $this->maxWidth));
241
        }
242
        if (!$this->checkMaxHeight()) {
243
            $this->setErrors(\sprintf('File height must be smaller than %u', $this->maxHeight));
244
        }
245
        if (\count($this->errors) > 0) {
246
            return false;
247
        }
248
        if (!$this->_copyFile($chmod)) {
249
            $this->setErrors('Failed uploading file: ' . $this->mediaName);
250
251
            return false;
252
        }
253
254
        return true;
255
    }
256
257
    /**
258
     * Copy the file to its destination
259
     *
260
     * @param $chmod
261
     * @return bool
262
     */
263
    public function _copyFile($chmod): bool
264
    {
265
        $matched = [];
266
        if (!\preg_match('/\.([a-zA-Z0-9]+)$/', $this->mediaName, $matched)) {
267
            return false;
268
        }
269
        if (isset($this->targetFileName)) {
270
            $this->savedFileName = $this->targetFileName;
271
        } elseif (isset($this->prefix)) {
272
            $this->savedFileName = \uniqid($this->prefix, true) . '.' . \mb_strtolower($matched[1]);
273
        } else {
274
            $this->savedFileName = \mb_strtolower($this->mediaName);
275
        }
276
        $this->savedDestination = $this->uploadDir . '/' . $this->savedFileName;
277
        if (!\move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
278
            return false;
279
        }
280
        @\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

280
        /** @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...
281
282
        return true;
283
    }
284
285
    /**
286
     * Is the file the right size?
287
     *
288
     * @return  bool
289
     **/
290
    public function checkMaxFileSize(): bool
291
    {
292
        if ($this->mediaSize > $this->maxFileSize) {
293
            return false;
294
        }
295
296
        return true;
297
    }
298
299
    /**
300
     * Is the picture the right width?
301
     *
302
     * @return  bool
303
     **/
304
    public function checkMaxWidth(): bool
305
    {
306
        if (!isset($this->maxWidth) || $this->maxWidth < 1) {
307
            return true;
308
        }
309
        if (false !== $dimension = \getimagesize($this->mediaTmpName)) {
310
            if ($dimension[0] > $this->maxWidth) {
311
                return false;
312
            }
313
        } else {
314
            \trigger_error(\sprintf('Failed fetching image size of %s, skipping max width check..', $this->mediaTmpName), \E_USER_WARNING);
315
        }
316
317
        return true;
318
    }
319
320
    /**
321
     * Is the picture the right height?
322
     *
323
     * @return  bool
324
     **/
325
    public function checkMaxHeight(): bool
326
    {
327
        if (!isset($this->maxHeight) || $this->maxHeight < 1) {
328
            return true;
329
        }
330
        if (false !== $dimension = \getimagesize($this->mediaTmpName)) {
331
            if ($dimension[1] > $this->maxHeight) {
332
                return false;
333
            }
334
        } else {
335
            \trigger_error(\sprintf('Failed fetching image size of %s, skipping max height check..', $this->mediaTmpName), \E_USER_WARNING);
336
        }
337
338
        return true;
339
    }
340
341
    /**
342
     * Is the file the right Mime type
343
     *
344
     * (is there a right type of mime? ;-)
345
     *
346
     * @return  bool
347
     **/
348
    public function checkMimeType(): bool
349
    {
350
        if (\count($this->allowedMimeTypes) > 0 && !\in_array($this->mediaType, $this->allowedMimeTypes, true)) {
351
            return false;
352
        }
353
354
        return true;
355
    }
356
357
    /**
358
     * Is the file the right extension
359
     *
360
     * @return  bool
361
     **/
362
    public function checkExtension(): bool
363
    {
364
        $ext = mb_substr(mb_strrchr($this->mediaName, '.'), 1);
365
        if (!empty($this->allowedExtensions) && !\in_array(mb_strtolower($ext), $this->allowedExtensions, true)) {
366
            return false;
367
        }
368
369
        return true;
370
    }
371
372
    /**
373
     * Add an error
374
     *
375
     * @param string $error
376
     **/
377
    public function setErrors($error): void
378
    {
379
        $this->errors[] = \trim($error);
380
    }
381
382
    /**
383
     * Get generated errors
384
     *
385
     * @param bool $ashtml Format using HTML?
386
     *
387
     * @return  array|string    Array of array messages OR HTML string
388
     */
389
    public function &getErrors($ashtml = true)
390
    {
391
        if (!$ashtml) {
392
            return $this->errors;
393
        }
394
        $ret = '';
395
        if (\count($this->errors) > 0) {
396
            $ret = '<h4>Errors Returned While Uploading</h4>';
397
            foreach ($this->errors as $error) {
398
                $ret .= $error . '<br>';
399
            }
400
        }
401
402
        return $ret;
403
    }
404
}
405