Passed
Pull Request — master (#132)
by Arman
02:54
created

UploadedFile::save()   B

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.0
13
 */
14
15
namespace Quantum\Libraries\Storage;
16
17
use Quantum\Exceptions\FileUploadException;
18
use Quantum\Exceptions\FileSystemException;
19
use Quantum\Exceptions\LangException;
20
use Quantum\Exceptions\AppException;
21
use Quantum\Exceptions\DiException;
22
use Gumlet\ImageResizeException;
23
use ReflectionException;
24
use Gumlet\ImageResize;
25
use Quantum\Di\Di;
26
use SplFileInfo;
27
use finfo;
28
29
/**
30
 * Class File
31
 * @package Quantum\Libraries\Upload
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
     * File constructor.
118
     * @param array $meta
119
     * @throws DiException
120
     * @throws ReflectionException
121
     */
122
    public function __construct(array $meta)
123
    {
124
        $this->localFileSystem = Di::get(FileSystem::class);
125
126
        $this->originalName = $meta['name'];
127
        $this->errorCode = $meta['error'];
128
129
        parent::__construct($meta['tmp_name']);
130
    }
131
132
    /**
133
     * Get name
134
     * @return string
135
     */
136
    public function getName(): string
137
    {
138
        if (!$this->name) {
139
            $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

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

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

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

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