UploadedFile::move()   B
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 17
nc 5
nop 2
1
<?php
2
/*
3
 * This file is part of the Borobudur-Http package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Http\File;
12
13
use Borobudur\Http\File\Exception\FileException;
14
use Borobudur\Http\File\Exception\FileNotFoundException;
15
16
/**
17
 * @author      Iqbal Maulana <[email protected]>
18
 * @created     8/2/15
19
 */
20
final class UploadedFile extends \SplFileInfo
21
{
22
    use FileTrait;
23
24
    /**
25
     * Error message mapping.
26
     *
27
     * @var array
28
     */
29
    private static $errorMessages = array(
30
        UPLOAD_ERR_INI_SIZE   => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
31
        UPLOAD_ERR_FORM_SIZE  => 'The file "%s" exceeds the upload limit defined in your form.',
32
        UPLOAD_ERR_PARTIAL    => 'The file "%s" was only partially uploaded.',
33
        UPLOAD_ERR_NO_FILE    => 'No file was uploaded.',
34
        UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
35
        UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
36
        UPLOAD_ERR_EXTENSION  => 'File upload was stopped by a PHP extension.',
37
    );
38
39
    /**
40
     * @var string
41
     */
42
    private $originalName;
43
    
44
    /**
45
     * @var string
46
     */
47
    private $mimeType;
48
49
    /**
50
     * @var int
51
     */
52
    private $size;
53
54
    /**
55
     * @var int
56
     */
57
    private $error;
58
59
    /**
60
     * @var bool
61
     */
62
    private $test;
63
64
    /**
65
     * @var bool
66
     */
67
    private $mimeTypeValid;
68
69
    /**
70
     * @var AcceptedMimeType
71
     */
72
    private $acceptedMimeTypes;
73
74
    /**
75
     * Constructor.
76
     *
77
     * @param string $file
78
     * @param string $originalName
79
     * @param string $mimeType
80
     * @param int    $size
81
     * @param int    $error
82
     * @param bool   $test
83
     */
84
    public function __construct($file, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
85
    {
86
        if (false === $test && false === is_file($file)) {
87
            throw new FileNotFoundException($file);
88
        }
89
90
        $this->originalName = $originalName;
91
        $this->mimeType = $mimeType;
92
        $this->size = $size;
93
        $this->error = $error ?: UPLOAD_ERR_OK;
94
        $this->test = (bool) $test;
95
        $this->acceptedMimeTypes = new AcceptedMimeType();
96
97
        parent::__construct($file);
98
    }
99
100
    /**
101
     * Factory create UploadedFile from array.
102
     *
103
     * @param array $uploadedFile
104
     *
105
     * @return UploadedFile
106
     */
107
    public static function fromArray(array $uploadedFile)
108
    {
109
        return new UploadedFile(
110
            $uploadedFile['tmp_name'],
111
            $uploadedFile['name'],
112
            mime_content_type($uploadedFile['tmp_name']),
113
            $uploadedFile['size'],
114
            $uploadedFile['error']
115
        );
116
    }
117
118
    /**
119
     * Get maximum file size of an uploaded file base on php.ini
120
     *
121
     * @return int
122
     */
123
    public static function getMaxFileSize()
124
    {
125
        $iniMax = strtolower(ini_get('upload_max_filesize'));
126
127
        if ('' === $iniMax) {
128
            return PHP_INT_MAX;
129
        }
130
131
        return self::convertFileSize($iniMax, self::defineFileSize($iniMax));
132
    }
133
134
    /**
135
     * Convert file size from string to int.
136
     *
137
     * @param string $iniMax
138
     * @param int    $max
139
     *
140
     * @return int
141
     */
142
    private static function convertFileSize($iniMax, $max)
143
    {
144
        switch (substr($iniMax, -1)) {
145
            case 't':
146
                $max *= 1024;
147
            // terabyte calculation
148
            case 'g':
149
                $max *= 1024;
150
            // gigabyte calculation
151
            case 'm':
152
                $max *= 1024;
153
            // megabyte calculation
154
            case 'k':
155
                $max *= 1024;
156
157
                return $max;
158
            // kilobyte calculation
159
        }
160
161
        return $max;
162
    }
163
164
    /**
165
     * Define file size.
166
     *
167
     * @param string $iniMax
168
     *
169
     * @return int|string
170
     */
171
    private static function defineFileSize($iniMax)
172
    {
173
        $max = ltrim($iniMax, '+');
174
        if (0 === strpos($max, '0x')) {
175
            return intval($max, 16);
176
        }
177
178
        if (0 === strpos($max, '0')) {
179
            return intval($max, 8);
180
        }
181
182
        return (int) $max;
183
    }
184
185
    /**
186
     * Set test state.
187
     *
188
     * @param bool $tested
189
     *
190
     * @return $this
191
     */
192
    public function setTest($tested)
193
    {
194
        $this->test = (bool) $tested;
195
196
        return $this;
197
    }
198
199
    /**
200
     * Check if file is tested.
201
     *
202
     * @return bool
203
     */
204
    public function isTested()
205
    {
206
        return $this->test;
207
    }
208
209
    /**
210
     * Get client original name.
211
     *
212
     * @return string
213
     */
214
    public function getOriginalName()
215
    {
216
        return $this->originalName;
217
    }
218
219
    /**
220
     * Get client original extension.
221
     *
222
     * @return string
223
     */
224
    public function getOriginalExtension()
225
    {
226
        return pathinfo($this->originalName, PATHINFO_EXTENSION);
227
    }
228
229
    /**
230
     * Get file mime type.
231
     *
232
     * @return string|null
233
     */
234
    public function getMimeType()
235
    {
236
        return $this->mimeType;
237
    }
238
239
    /**
240
     * Get file size.
241
     *
242
     * @return int|null
243
     */
244
    public function getSize()
245
    {
246
        return $this->size;
247
    }
248
249
    /**
250
     * Get accepted mime type.
251
     *
252
     * @return AcceptedMimeType
253
     */
254
    public function getAcceptedMimeType()
255
    {
256
        return $this->acceptedMimeTypes;
257
    }
258
259
    /**
260
     * Get upload error.
261
     *
262
     * @return int
263
     */
264
    public function getError()
265
    {
266
        return $this->error;
267
    }
268
269
    /**
270
     * Check file was uploaded successfully.
271
     *
272
     * @return bool
273
     */
274
    public function isValid()
275
    {
276
        if (false === $this->mimeTypeValid = $this->acceptedMimeTypes->isAccepted($this->getMimeType())) {
277
            return false;
278
        }
279
280
        $isOk = UPLOAD_ERR_OK === $this->error;
281
282
        return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
283
    }
284
285
    /**
286
     * Move file to new location.
287
     *
288
     * @param string      $directory Destination folder
289
     * @param string|null $name      New file name if needed
290
     *
291
     * @return UploadedFile
292
     *
293
     * @throws FileException
294
     */
295
    public function move($directory, $name = null)
296
    {
297
        if ($this->isValid()) {
298
            $target = $this->prepare($directory, $name);
299
            try {
300
                if (true === $this->test) {
301
                    rename($this->getPathname(), $target);
302
                } else {
303
                    move_uploaded_file($this->getPathname(), $target);
304
                }
305
            } catch (\RuntimeException $e) {
306
                throw new FileException(sprintf(
307
                    'Could not move file "%s" to "%s" (%s)',
308
                    $this->getPathname(),
309
                    $target,
310
                    strip_tags($e->getMessage())
311
                ));
312
            }
313
314
            $this->chmod($target);
315
316
            return $target;
317
        }
318
319
        throw new FileException($this->getErrorMessage());
320
    }
321
322
    /**
323
     * Copy file to new location.
324
     *
325
     * @param string      $directory
326
     * @param string|null $name
327
     *
328
     * @return UploadedFile
329
     *
330
     * @throws FileException
331
     */
332
    public function copy($directory, $name = null)
333
    {
334
        if ($this->isValid()) {
335
            $target = $this->prepare($directory, $name);
336
            try {
337
                copy($this->getPathname(), $target);
338
            } catch (\RuntimeException $e) {
339
                throw new FileException(sprintf(
340
                    'Could not move file "%s" to "%s" (%s)',
341
                    $this->getPathname(),
342
                    $target,
343
                    strip_tags($e->getMessage())
344
                ));
345
            }
346
347
            $this->chmod($target);
348
349
            return $target;
350
        }
351
352
        throw new FileException($this->getErrorMessage());
353
    }
354
355
    /**
356
     * Remove uploaded files.
357
     *
358
     * @return $this
359
     */
360
    public function remove()
361
    {
362
        if (file_exists($this->getPathname()) && is_writable($this->getPathname())) {
363
            try {
364
                unlink($this->getPathname());
365
            } catch (\Exception $e) {
366
                throw new FileException(sprintf(
367
                    'Could not remove file "%s" (%s)',
368
                    $this->getPathname(),
369
                    strip_tags($e->getMessage())
370
                ));
371
            }
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * Get error message based on error code.
379
     *
380
     * @return string
381
     */
382
    public function getErrorMessage()
383
    {
384
        if (false === $this->mimeTypeValid) {
385
            return sprintf(
386
                'File "%s" only accepted by mime type(s) "%s", "%s" given.',
387
                $this->getOriginalName(),
388
                implode(', ', $this->acceptedMimeTypes),
389
                $this->getMimeType()
390
            );
391
        }
392
393
        if (UPLOAD_ERR_OK == $this->error) {
394
            return null;
395
        }
396
397
        $maxFileSize = $this->error === UPLOAD_ERR_INI_SIZE ? self::getMaxFileSize() : 0;
398
399
        return sprintf($this->getMessageFormat(), $this->getOriginalName(), $maxFileSize);
400
    }
401
402
    /**
403
     * Get error message format.
404
     *
405
     * @return string
406
     */
407
    private function getMessageFormat()
408
    {
409
        if (isset(self::$errorMessages[$this->error])) {
410
            $message = self::$errorMessages[$this->error];
411
        } else {
412
            $message = 'The file "%s" was not uploaded due to an unknown error.';
413
        }
414
415
        return $message;
416
    }
417
418
    /**
419
     * Prepare file.
420
     *
421
     * @param string      $directory
422
     * @param string|null $name
423
     *
424
     * @return UploadedFile
425
     */
426
    private function prepare($directory, $name = null)
427
    {
428
        $this->createDirectory($directory);
429
430
        $directory = rtrim($directory, '/\\') . DIRECTORY_SEPARATOR;
431
        $name = null === $name ? $this->getOriginalName() : $this->getName($name);
432
        $file = $directory . $name;
433
434
        return new self($file, $name, $this->mimeType, $this->size, $this->error, true);
435
    }
436
437
    /**
438
     * Get file name.
439
     *
440
     * @param string $name
441
     *
442
     * @return string
443
     */
444
    private function getName($name)
445
    {
446
        $name = str_replace('\\', '/', $name);
447
        $parts = explode('/', $name);
448
449
        return reset($parts);
450
    }
451
}
452