HasImage   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Test Coverage

Coverage 85.71%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 19
eloc 56
c 2
b 0
f 0
dl 0
loc 132
ccs 48
cts 56
cp 0.8571
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A hasImage() 0 3 1
A getPath() 0 4 1
A getFilename() 0 4 1
A setFilename() 0 4 1
A setFile() 0 15 3
A deleteFile() 0 11 5
A getMime() 0 13 3
A generateUniqueFilename() 0 5 2
A validateMimeType() 0 25 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Traits;
6
7
use Application\Api\FileException;
8
use Doctrine\ORM\Mapping as ORM;
9
use Exception;
10
use GraphQL\Doctrine\Attribute as API;
11
use Psr\Http\Message\UploadedFileInterface;
12
use Throwable;
13
14
/**
15
 * Trait for all objects with a name.
16
 */
17
trait HasImage
18
{
19
    #[ORM\Column(type: 'string', length: 2000)]
20
    private string $filename = '';
21
22
    /**
23
     * Set the image file.
24
     */
25 9
    #[API\Input(type: '?GraphQL\Upload\UploadType')]
26
    public function setFile(UploadedFileInterface $file): void
27
    {
28
        try {
29 9
            $this->generateUniqueFilename($file->getClientFilename());
0 ignored issues
show
Bug introduced by
It seems like $file->getClientFilename() can also be of type null; however, parameter $originalFilename of Application\Traits\HasIm...enerateUniqueFilename() 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

29
            $this->generateUniqueFilename(/** @scrutinizer ignore-type */ $file->getClientFilename());
Loading history...
30
31 9
            $path = $this->getPath();
32 9
            if (file_exists($path)) {
33
                throw new Exception('A file already exist with the same name: ' . $this->getFilename());
34
            }
35 9
            $file->moveTo($path);
36
37 9
            $this->validateMimeType();
38
        } catch (Throwable $e) {
39
            throw new FileException($file, $e);
40
        }
41
    }
42
43
    /**
44
     * Set filename (without path).
45
     */
46 16
    #[API\Exclude]
47
    public function setFilename(string $filename): void
48
    {
49 16
        $this->filename = $filename;
50
    }
51
52
    /**
53
     * Get filename (without path).
54
     */
55 18
    #[API\Exclude]
56
    public function getFilename(): string
57
    {
58 18
        return $this->filename;
59
    }
60
61 7
    public function hasImage(): bool
62
    {
63 7
        return !empty($this->filename);
64
    }
65
66
    /**
67
     * Get absolute path to image on disk.
68
     */
69 18
    #[API\Exclude]
70
    public function getPath(): string
71
    {
72 18
        return realpath('.') . '/' . self::IMAGE_PATH . $this->getFilename();
0 ignored issues
show
Bug introduced by
The constant Application\Traits\HasImage::IMAGE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
73
    }
74
75
    /**
76
     * Automatically called by Doctrine when the object is deleted
77
     * Is called after database update because we can have issues on remove operation (like integrity test)
78
     * and it's preferable to keep a related file on drive before removing it definitely.
79
     */
80 3
    #[ORM\PostRemove]
81
    public function deleteFile(): void
82
    {
83 3
        $path = $this->getPath();
84
        global $container;
85 3
        $config = $container->get('config');
86 3
        $unlink = $config['files']['unlink'];
87
88 3
        if (file_exists($path) && is_file($path)) {
89 3
            if ($this->getFilename() !== 'dw4jV3zYSPsqE2CB8BcP8ABD0.jpg' && $unlink) {
90 1
                unlink($path);
91
            }
92
        }
93
    }
94
95 10
    public function getMime(): string
96
    {
97 10
        $path = $this->getPath();
98 10
        $mime = mime_content_type($path);
99 10
        if ($mime === false) {
100
            throw new Exception('Could not get mimetype for path: ' . $path);
101
        }
102
103 10
        if ($mime === 'image/svg') {
104
            $mime = 'image/svg+xml';
105
        }
106
107 10
        return $mime;
108
    }
109
110
    /**
111
     * Delete file and throw exception if MIME type is invalid.
112
     */
113 9
    private function validateMimeType(): void
114
    {
115 9
        $mime = $this->getMime();
116
117
        // Validate image mimetype
118 9
        $acceptedMimeTypes = [
119 9
            'image/bmp',
120 9
            'image/x-ms-bmp',
121 9
            'image/gif',
122 9
            'image/jpeg',
123 9
            'image/pjpeg',
124 9
            'image/png',
125 9
            'image/svg+xml',
126 9
            'image/tiff',
127 9
            'image/vnd.adobe.photoshop',
128 9
            'image/webp',
129 9
            'image/heif',
130 9
            'image/heic',
131 9
        ];
132
133 9
        if (!in_array($mime, $acceptedMimeTypes, true)) {
134
            $path = $this->getPath();
135
            unlink($path);
136
137
            throw new Exception('Invalid file type of: ' . $mime);
138
        }
139
    }
140
141
    /**
142
     * Generate unique filename while trying to preserver original extension.
143
     */
144 11
    private function generateUniqueFilename(string $originalFilename): void
145
    {
146 11
        $extension = pathinfo($originalFilename, PATHINFO_EXTENSION);
147 11
        $filename = uniqid() . ($extension ? '.' . $extension : '');
0 ignored issues
show
Bug introduced by
Are you sure $extension of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

147
        $filename = uniqid() . ($extension ? '.' . /** @scrutinizer ignore-type */ $extension : '');
Loading history...
148 11
        $this->setFilename($filename);
149
    }
150
}
151