UploadedFile::save()   B
last analyzed

Complexity

Conditions 11
Paths 12

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 19
c 0
b 0
f 0
nc 12
nop 2
dl 0
loc 39
rs 7.3166

How to fix   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
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.7
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\App\Exceptions\BaseException;
24
use Gumlet\ImageResizeException;
25
use Gumlet\ImageResize;
26
use SplFileInfo;
27
use finfo;
28
29
/**
30
 * Class File
31
 * @package Quantum\Libraries\Storage
32
 */
33
class UploadedFile extends SplFileInfo
34
{
35
36
    /**
37
     * Local File System
38
     * @var FilesystemAdapterInterface
39
     */
40
    protected $localFileSystem;
41
42
    /**
43
     * Remove File System
44
     * @var FilesystemAdapterInterface
45
     */
46
    protected $remoteFileSystem = null;
47
48
    /**
49
     * Original file name provided by client
50
     * @var string
51
     */
52
    protected $originalName = null;
53
54
    /**
55
     * File name (without extension)
56
     * @var string
57
     */
58
    protected $name = null;
59
60
    /**
61
     * File extension
62
     * @var string
63
     */
64
    protected $extension = null;
65
66
    /**
67
     * File mime type
68
     * @var string
69
     */
70
    protected $mimetype = null;
71
72
    /**
73
     * ImageResize function name
74
     * @var string
75
     */
76
    protected $imageModifierFuncName = null;
77
78
    /**
79
     * ImageResize function arguments
80
     * @var array
81
     */
82
    protected $params = [];
83
84
    /**
85
     * Upload error code messages
86
     * @var array
87
     */
88
    protected $errorMessages = [
89
        1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
90
        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
91
        3 => 'The uploaded file was only partially uploaded',
92
        4 => 'No file was uploaded',
93
        6 => 'Missing a temporary folder',
94
        7 => 'Failed to write file to disk',
95
        8 => 'A PHP extension stopped the file upload'
96
    ];
97
98
    /**
99
     * Blacklisted extensions
100
     * @var array
101
     */
102
    protected $blacklistedExtensions = [
103
        'php([0-9])?', 'pht', 'phar', 'phpt', 'pgif', 'phtml', 'phtm', 'phps',
104
        'cgi', 'inc', 'env', 'htaccess', 'htpasswd', 'config', 'conf',
105
        'bat', 'exe', 'msi', 'cmd', 'dll', 'sh', 'com', 'app', 'sys', 'drv',
106
        'pl', 'jar', 'jsp', 'js', 'vb', 'vbscript', 'wsf', 'asp', 'py',
107
        'cer', 'csr', 'crt',
108
    ];
109
110
    /**
111
     * Upload error code
112
     * @var int
113
     */
114
    protected $errorCode;
115
116
    /**
117
     * @param array $meta
118
     * @throws BaseException
119
     */
120
    public function __construct(array $meta)
121
    {
122
        $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...
123
124
        $this->originalName = $meta['name'];
125
        $this->errorCode = $meta['error'];
126
127
        parent::__construct($meta['tmp_name']);
128
    }
129
130
    /**
131
     * Get name
132
     * @return string
133
     */
134
    public function getName(): string
135
    {
136
        if (!$this->name) {
137
            $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

137
            /** @scrutinizer ignore-call */ 
138
            $this->name = $this->localFileSystem->fileName($this->originalName);
Loading history...
138
        }
139
140
        return $this->name;
141
    }
142
143
    /**
144
     * Set name (without extension)
145
     * @param string $name
146
     * @return $this
147
     */
148
    public function setName(string $name): UploadedFile
149
    {
150
        $this->name = $name;
151
        return $this;
152
    }
153
154
    /**
155
     * Sets the remote file system adapter
156
     * @param FilesystemAdapterInterface $remoteFileSystem
157
     * @return $this
158
     */
159
    public function setRemoteFileSystem(FilesystemAdapterInterface $remoteFileSystem): UploadedFile
160
    {
161
        $this->remoteFileSystem = $remoteFileSystem;
162
        return $this;
163
    }
164
165
    /**
166
     * Gets the remote file system adapter
167
     * @return FilesystemAdapterInterface|null
168
     */
169
    public function getRemoteFileSystem(): ?FilesystemAdapterInterface
170
    {
171
        return $this->remoteFileSystem;
172
    }
173
174
    /**
175
     * Get file extension (without leading dot)
176
     * @return string
177
     */
178
    public function getExtension(): string
179
    {
180
        if (!$this->extension) {
181
            $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

181
            $this->extension = strtolower($this->localFileSystem->/** @scrutinizer ignore-call */ extension($this->originalName));
Loading history...
182
        }
183
184
        return $this->extension;
185
    }
186
187
    /**
188
     * Get file name with extension
189
     * @return string
190
     */
191
    public function getNameWithExtension(): string
192
    {
193
        return $this->getName() . '.' . $this->getExtension();
194
    }
195
196
    /**
197
     * Get mime type
198
     * @return string
199
     */
200
    public function getMimeType(): string
201
    {
202
        if (!$this->mimetype) {
203
            $fileInfo = new finfo(FILEINFO_MIME);
204
            $mimetype = $fileInfo->file($this->getPathname());
205
            $mimetypeParts = preg_split('/\s*[;,]\s*/', $mimetype);
206
            $this->mimetype = strtolower($mimetypeParts[0]);
207
            unset($fileInfo);
208
        }
209
210
        return $this->mimetype;
211
    }
212
213
    /**
214
     * Get md5
215
     * @return string
216
     */
217
    public function getMd5(): string
218
    {
219
        return md5_file($this->getPathname());
220
    }
221
222
    /**
223
     * Get image dimensions
224
     * @return array
225
     * @throws FileUploadException
226
     */
227
    public function getDimensions(): array
228
    {
229
        if (!$this->isImage($this->getPathname())) {
230
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
231
        }
232
233
        list($width, $height) = getimagesize($this->getPathname());
234
235
        return [
236
            'width' => $width,
237
            'height' => $height
238
        ];
239
    }
240
241
    /**
242
     * Save the uploaded file
243
     * @param string $dest
244
     * @param bool $overwrite
245
     * @return bool
246
     * @throws BaseException
247
     * @throws FileSystemException
248
     * @throws FileUploadException
249
     * @throws ImageResizeException
250
     * @throws EnvException
251
     */
252
    public function save(string $dest, bool $overwrite = false): bool
253
    {
254
        if ($this->errorCode !== UPLOAD_ERR_OK) {
255
            throw new FileUploadException($this->getErrorMessage());
256
        }
257
258
        if (!$this->localFileSystem->isFile($this->getPathname())) {
259
            throw FileUploadException::fileNotFound($this->getPathname());
260
        }
261
262
        if (!$this->whitelisted($this->getExtension())) {
263
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
264
        }
265
266
        $filePath = $dest . DS . $this->getNameWithExtension();
267
268
        if (!$this->remoteFileSystem) {
269
            if (!$this->localFileSystem->isDirectory($dest)) {
270
                throw FileSystemException::directoryNotExists($dest);
271
            }
272
273
            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

273
            if (!$this->localFileSystem->/** @scrutinizer ignore-call */ isWritable($dest)) {
Loading history...
274
                throw FileSystemException::directoryNotWritable($dest);
275
            }
276
277
            if ($overwrite === false && $this->localFileSystem->exists($filePath)) {
278
                throw FileSystemException::fileAlreadyExists();
279
            }
280
        }
281
282
        if (!$this->moveUploadedFile($filePath)) {
283
            return false;
284
        }
285
286
        if ($this->imageModifierFuncName) {
287
            $this->applyModifications($filePath);
288
        }
289
290
        return true;
291
    }
292
293
    /**
294
     * Sets modification function on image
295
     * @param string $funcName
296
     * @param array $params
297
     * @return $this
298
     * @throws BaseException
299
     * @throws FileUploadException
300
     * @throws LangException
301
     */
302
    public function modify(string $funcName, array $params): UploadedFile
303
    {
304
        if (!$this->isImage($this->getPathname())) {
305
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
306
        }
307
308
        if (!method_exists(ImageResize::class, $funcName)) {
309
            throw BaseException::methodNotSupported($funcName, ImageResize::class);
310
        }
311
312
        $this->imageModifierFuncName = $funcName;
313
        $this->params = $params;
314
315
        return $this;
316
    }
317
318
    /**
319
     * Gets the error code
320
     * @return int
321
     */
322
    public function getErrorCode(): int
323
    {
324
        return $this->errorCode;
325
    }
326
327
    /**
328
     * Gets the error message from code
329
     * @return string
330
     */
331
    public function getErrorMessage(): string
332
    {
333
        return $this->errorMessages[$this->errorCode];
334
    }
335
336
    /**
337
     * Tells whether the file was uploaded
338
     * @return bool
339
     */
340
    public function isUploaded(): bool
341
    {
342
        return is_uploaded_file($this->getPathname());
343
    }
344
345
    /**
346
     * Checks if the given file is image
347
     * @param $filePath
348
     * @return bool
349
     */
350
    public function isImage($filePath): bool
351
    {
352
        return !!getimagesize($filePath);
353
    }
354
355
    /**
356
     * Moves an uploaded file to a new location
357
     * @param string $filePath
358
     * @return bool
359
     */
360
    protected function moveUploadedFile(string $filePath): bool
361
    {
362
        if ($this->remoteFileSystem) {
363
            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

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