UploadedFile   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 97
dl 0
loc 368
rs 9.36
c 2
b 0
f 0
wmc 38

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getMd5() 0 3 1
A applyModifications() 0 6 1
A moveUploadedFile() 0 9 3
A getNameWithExtension() 0 3 1
A getErrorCode() 0 3 1
A getErrorMessage() 0 3 1
A setName() 0 4 1
A isUploaded() 0 3 1
A getName() 0 7 2
A setRemoteFileSystem() 0 4 1
B save() 0 39 11
A getExtension() 0 7 2
A getMimeType() 0 11 2
A modify() 0 14 3
A isImage() 0 3 1
A getRemoteFileSystem() 0 3 1
A getDimensions() 0 11 2
A whitelisted() 0 7 2
A __construct() 0 8 1
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.8
13
 */
14
15
namespace Quantum\Libraries\Storage;
16
17
use Quantum\Libraries\Storage\Contracts\FilesystemAdapterInterface;
18
use Quantum\Libraries\Storage\Exceptions\FileSystemException;
19
use Quantum\Libraries\Storage\Exceptions\FileUploadException;
20
use Quantum\Libraries\Storage\Factories\FileSystemFactory;
21
use Quantum\Libraries\Lang\Exceptions\LangException;
22
use Quantum\Environment\Exceptions\EnvException;
23
use Quantum\Config\Exceptions\ConfigException;
24
use Quantum\App\Exceptions\BaseException;
25
use Quantum\Di\Exceptions\DiException;
26
use Gumlet\ImageResizeException;
27
use ReflectionException;
28
use Gumlet\ImageResize;
29
use SplFileInfo;
30
use finfo;
31
32
/**
33
 * Class File
34
 * @package Quantum\Libraries\Storage
35
 */
36
class UploadedFile extends SplFileInfo
37
{
38
39
    /**
40
     * Local File System
41
     * @var FilesystemAdapterInterface
42
     */
43
    protected $localFileSystem;
44
45
    /**
46
     * Remove File System
47
     * @var FilesystemAdapterInterface
48
     */
49
    protected $remoteFileSystem = null;
50
51
    /**
52
     * Original file name provided by client
53
     * @var string
54
     */
55
    protected $originalName = null;
56
57
    /**
58
     * File name (without extension)
59
     * @var string
60
     */
61
    protected $name = null;
62
63
    /**
64
     * File extension
65
     * @var string
66
     */
67
    protected $extension = null;
68
69
    /**
70
     * File mime type
71
     * @var string
72
     */
73
    protected $mimetype = null;
74
75
    /**
76
     * ImageResize function name
77
     * @var string
78
     */
79
    protected $imageModifierFuncName = null;
80
81
    /**
82
     * ImageResize function arguments
83
     * @var array
84
     */
85
    protected $params = [];
86
87
    /**
88
     * Upload error code messages
89
     * @var array
90
     */
91
    protected $errorMessages = [
92
        1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
93
        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
94
        3 => 'The uploaded file was only partially uploaded',
95
        4 => 'No file was uploaded',
96
        6 => 'Missing a temporary folder',
97
        7 => 'Failed to write file to disk',
98
        8 => 'A PHP extension stopped the file upload'
99
    ];
100
101
    /**
102
     * Blacklisted extensions
103
     * @var array
104
     */
105
    protected $blacklistedExtensions = [
106
        'php([0-9])?', 'pht', 'phar', 'phpt', 'pgif', 'phtml', 'phtm', 'phps',
107
        'cgi', 'inc', 'env', 'htaccess', 'htpasswd', 'config', 'conf',
108
        'bat', 'exe', 'msi', 'cmd', 'dll', 'sh', 'com', 'app', 'sys', 'drv',
109
        'pl', 'jar', 'jsp', 'js', 'vb', 'vbscript', 'wsf', 'asp', 'py',
110
        'cer', 'csr', 'crt',
111
    ];
112
113
    /**
114
     * Upload error code
115
     * @var int
116
     */
117
    protected $errorCode;
118
119
    /**
120
     * @param array $meta
121
     * @throws BaseException
122
     * @throws ConfigException
123
     * @throws DiException
124
     * @throws ReflectionException
125
     */
126
    public function __construct(array $meta)
127
    {
128
        $this->localFileSystem = FileSystemFactory::get();
0 ignored issues
show
Documentation Bug introduced by
It seems like Quantum\Libraries\Storag...ileSystemFactory::get() of type Quantum\Libraries\Storage\FileSystem is incompatible with the declared type Quantum\Libraries\Storag...esystemAdapterInterface of property $localFileSystem.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
129
130
        $this->originalName = $meta['name'];
131
        $this->errorCode = $meta['error'];
132
133
        parent::__construct($meta['tmp_name']);
134
    }
135
136
    /**
137
     * Get name
138
     * @return string
139
     */
140
    public function getName(): string
141
    {
142
        if (!$this->name) {
143
            $this->name = $this->localFileSystem->fileName($this->originalName);
0 ignored issues
show
Bug introduced by
The method fileName() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

143
            /** @scrutinizer ignore-call */ 
144
            $this->name = $this->localFileSystem->fileName($this->originalName);
Loading history...
144
        }
145
146
        return $this->name;
147
    }
148
149
    /**
150
     * Set name (without extension)
151
     * @param string $name
152
     * @return $this
153
     */
154
    public function setName(string $name): UploadedFile
155
    {
156
        $this->name = $name;
157
        return $this;
158
    }
159
160
    /**
161
     * Sets the remote file system adapter
162
     * @param FilesystemAdapterInterface $remoteFileSystem
163
     * @return $this
164
     */
165
    public function setRemoteFileSystem(FilesystemAdapterInterface $remoteFileSystem): UploadedFile
166
    {
167
        $this->remoteFileSystem = $remoteFileSystem;
168
        return $this;
169
    }
170
171
    /**
172
     * Gets the remote file system adapter
173
     * @return FilesystemAdapterInterface|null
174
     */
175
    public function getRemoteFileSystem(): ?FilesystemAdapterInterface
176
    {
177
        return $this->remoteFileSystem;
178
    }
179
180
    /**
181
     * Get file extension (without leading dot)
182
     * @return string
183
     */
184
    public function getExtension(): string
185
    {
186
        if (!$this->extension) {
187
            $this->extension = strtolower($this->localFileSystem->extension($this->originalName));
0 ignored issues
show
Bug introduced by
The method extension() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

187
            $this->extension = strtolower($this->localFileSystem->/** @scrutinizer ignore-call */ extension($this->originalName));
Loading history...
188
        }
189
190
        return $this->extension;
191
    }
192
193
    /**
194
     * Get file name with extension
195
     * @return string
196
     */
197
    public function getNameWithExtension(): string
198
    {
199
        return $this->getName() . '.' . $this->getExtension();
200
    }
201
202
    /**
203
     * Get mime type
204
     * @return string
205
     */
206
    public function getMimeType(): string
207
    {
208
        if (!$this->mimetype) {
209
            $fileInfo = new finfo(FILEINFO_MIME);
210
            $mimetype = $fileInfo->file($this->getPathname());
211
            $mimetypeParts = preg_split('/\s*[;,]\s*/', $mimetype);
212
            $this->mimetype = strtolower($mimetypeParts[0]);
213
            unset($fileInfo);
214
        }
215
216
        return $this->mimetype;
217
    }
218
219
    /**
220
     * Get md5
221
     * @return string
222
     */
223
    public function getMd5(): string
224
    {
225
        return md5_file($this->getPathname());
226
    }
227
228
    /**
229
     * Get image dimensions
230
     * @return array
231
     * @throws FileUploadException
232
     */
233
    public function getDimensions(): array
234
    {
235
        if (!$this->isImage($this->getPathname())) {
236
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
237
        }
238
239
        list($width, $height) = getimagesize($this->getPathname());
240
241
        return [
242
            'width' => $width,
243
            'height' => $height
244
        ];
245
    }
246
247
    /**
248
     * Save the uploaded file
249
     * @param string $dest
250
     * @param bool $overwrite
251
     * @return bool
252
     * @throws BaseException
253
     * @throws FileSystemException
254
     * @throws FileUploadException
255
     * @throws ImageResizeException
256
     * @throws EnvException
257
     */
258
    public function save(string $dest, bool $overwrite = false): bool
259
    {
260
        if ($this->errorCode !== UPLOAD_ERR_OK) {
261
            throw new FileUploadException($this->getErrorMessage());
262
        }
263
264
        if (!$this->localFileSystem->isFile($this->getPathname())) {
265
            throw FileUploadException::fileNotFound($this->getPathname());
266
        }
267
268
        if (!$this->whitelisted($this->getExtension())) {
269
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
270
        }
271
272
        $filePath = $dest . DS . $this->getNameWithExtension();
273
274
        if (!$this->remoteFileSystem) {
275
            if (!$this->localFileSystem->isDirectory($dest)) {
276
                throw FileSystemException::directoryNotExists($dest);
277
            }
278
279
            if (!$this->localFileSystem->isWritable($dest)) {
0 ignored issues
show
Bug introduced by
The method isWritable() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

279
            if (!$this->localFileSystem->/** @scrutinizer ignore-call */ isWritable($dest)) {
Loading history...
280
                throw FileSystemException::directoryNotWritable($dest);
281
            }
282
283
            if ($overwrite === false && $this->localFileSystem->exists($filePath)) {
284
                throw FileSystemException::fileAlreadyExists();
285
            }
286
        }
287
288
        if (!$this->moveUploadedFile($filePath)) {
289
            return false;
290
        }
291
292
        if ($this->imageModifierFuncName) {
293
            $this->applyModifications($filePath);
294
        }
295
296
        return true;
297
    }
298
299
    /**
300
     * Sets modification function on image
301
     * @param string $funcName
302
     * @param array $params
303
     * @return $this
304
     * @throws BaseException
305
     * @throws FileUploadException
306
     * @throws LangException
307
     */
308
    public function modify(string $funcName, array $params): UploadedFile
309
    {
310
        if (!$this->isImage($this->getPathname())) {
311
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
312
        }
313
314
        if (!method_exists(ImageResize::class, $funcName)) {
315
            throw BaseException::methodNotSupported($funcName, ImageResize::class);
316
        }
317
318
        $this->imageModifierFuncName = $funcName;
319
        $this->params = $params;
320
321
        return $this;
322
    }
323
324
    /**
325
     * Gets the error code
326
     * @return int
327
     */
328
    public function getErrorCode(): int
329
    {
330
        return $this->errorCode;
331
    }
332
333
    /**
334
     * Gets the error message from code
335
     * @return string
336
     */
337
    public function getErrorMessage(): string
338
    {
339
        return $this->errorMessages[$this->errorCode];
340
    }
341
342
    /**
343
     * Tells whether the file was uploaded
344
     * @return bool
345
     */
346
    public function isUploaded(): bool
347
    {
348
        return is_uploaded_file($this->getPathname());
349
    }
350
351
    /**
352
     * Checks if the given file is image
353
     * @param $filePath
354
     * @return bool
355
     */
356
    public function isImage($filePath): bool
357
    {
358
        return !!getimagesize($filePath);
359
    }
360
361
    /**
362
     * Moves an uploaded file to a new location
363
     * @param string $filePath
364
     * @return bool
365
     */
366
    protected function moveUploadedFile(string $filePath): bool
367
    {
368
        if ($this->remoteFileSystem) {
369
            return (bool)$this->remoteFileSystem->put($filePath, $this->localFileSystem->get($this->getPathname()));
0 ignored issues
show
Bug introduced by
It seems like $this->localFileSystem->get($this->getPathname()) can also be of type false; however, parameter $content of Quantum\Libraries\Storag...AdapterInterface::put() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

369
            return (bool)$this->remoteFileSystem->put($filePath, /** @scrutinizer ignore-type */ $this->localFileSystem->get($this->getPathname()));
Loading history...
370
        } else {
371
            if ($this->isUploaded()) {
372
                return move_uploaded_file($this->getPathname(), $filePath);
373
            } else {
374
                return $this->localFileSystem->copy($this->getPathname(), $filePath);
375
            }
376
        }
377
    }
378
379
    /**
380
     * Whitelist the file extension
381
     * @param string $extension
382
     * @return bool
383
     */
384
    protected function whitelisted(string $extension): bool
385
    {
386
        if (!preg_match('/(' . implode('|', $this->blacklistedExtensions) . ')$/i', $extension)) {
387
            return true;
388
        }
389
390
        return false;
391
    }
392
393
    /**
394
     * Applies modifications on image
395
     * @param $filePath
396
     * @throws ImageResizeException
397
     */
398
    protected function applyModifications($filePath)
399
    {
400
        $image = new ImageResize($filePath);
401
        call_user_func_array([$image, $this->imageModifierFuncName], $this->params);
402
403
        $image->save($filePath);
404
    }
405
}