unil-lettres /
dilps-tiresias
| 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
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
|
|||||
| 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
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
Loading history...
|
|||||
| 148 | 11 | $this->setFilename($filename); |
|||
| 149 | } |
||||
| 150 | } |
||||
| 151 |