Failed Conditions
Push — master ( bf4629...7712d5 )
by Adrien
27:59 queued 18:08
created

MemoryDrawing::identifyRenderingFunction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Worksheet;
4
5
use GdImage;
6
use PhpOffice\PhpSpreadsheet\Exception;
7
use PhpOffice\PhpSpreadsheet\Shared\File;
8
9
class MemoryDrawing extends BaseDrawing
10
{
11
    // Rendering functions
12
    const RENDERING_DEFAULT = 'imagepng';
13
    const RENDERING_PNG = 'imagepng';
14
    const RENDERING_GIF = 'imagegif';
15
    const RENDERING_JPEG = 'imagejpeg';
16
17
    // MIME types
18
    const MIMETYPE_DEFAULT = 'image/png';
19
    const MIMETYPE_PNG = 'image/png';
20
    const MIMETYPE_GIF = 'image/gif';
21
    const MIMETYPE_JPEG = 'image/jpeg';
22
23
    const SUPPORTED_MIME_TYPES = [
24
        self::MIMETYPE_GIF,
25
        self::MIMETYPE_JPEG,
26
        self::MIMETYPE_PNG,
27
    ];
28
29
    /**
30
     * Image resource.
31
     */
32
    private null|GdImage $imageResource = null;
33
34
    /**
35
     * Rendering function.
36
     */
37
    private string $renderingFunction;
38
39
    /**
40
     * Mime type.
41
     */
42
    private string $mimeType;
43
44
    /**
45
     * Unique name.
46
     */
47
    private string $uniqueName;
48
49
    /**
50
     * Create a new MemoryDrawing.
51
     */
52 23
    public function __construct()
53
    {
54
        // Initialise values
55 23
        $this->renderingFunction = self::RENDERING_DEFAULT;
56 23
        $this->mimeType = self::MIMETYPE_DEFAULT;
57 23
        $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
58
59
        // Initialize parent
60 23
        parent::__construct();
61
    }
62
63 6
    public function __destruct()
64
    {
65 6
        if ($this->imageResource) {
66 6
            @imagedestroy($this->imageResource);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for imagedestroy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

66
            /** @scrutinizer ignore-unhandled */ @imagedestroy($this->imageResource);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
67 6
            $this->imageResource = null;
68
        }
69
    }
70
71 1
    public function __clone()
72
    {
73 1
        parent::__clone();
74 1
        $this->cloneResource();
75
    }
76
77 1
    private function cloneResource(): void
78
    {
79 1
        if (!$this->imageResource) {
80
            return;
81
        }
82
83 1
        $width = (int) imagesx($this->imageResource);
84 1
        $height = (int) imagesy($this->imageResource);
85
86 1
        if (imageistruecolor($this->imageResource)) {
87 1
            $clone = imagecreatetruecolor($width, $height);
88 1
            if (!$clone) {
89
                throw new Exception('Could not clone image resource');
90
            }
91
92 1
            imagealphablending($clone, false);
93 1
            imagesavealpha($clone, true);
94
        } else {
95
            $clone = imagecreate($width, $height);
96
            if (!$clone) {
97
                throw new Exception('Could not clone image resource');
98
            }
99
100
            // If the image has transparency...
101
            $transparent = imagecolortransparent($this->imageResource);
102
            if ($transparent >= 0) {
103
                $rgb = imagecolorsforindex($this->imageResource, $transparent);
104
                if (empty($rgb)) {
105
                    throw new Exception('Could not get image colors');
106
                }
107
108
                imagesavealpha($clone, true);
109
                $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']);
110
                if ($color === false) {
111
                    throw new Exception('Could not get image alpha color');
112
                }
113
114
                imagefill($clone, 0, 0, $color);
115
            }
116
        }
117
118
        //Create the Clone!!
119 1
        imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height);
120
121 1
        $this->imageResource = $clone;
122
    }
123
124
    /**
125
     * @param resource $imageStream Stream data to be converted to a Memory Drawing
126
     *
127
     * @throws Exception
128
     */
129 1
    public static function fromStream($imageStream): self
130
    {
131 1
        $streamValue = stream_get_contents($imageStream);
132 1
        if ($streamValue === false) {
133
            throw new Exception('Unable to read data from stream');
134
        }
135
136 1
        return self::fromString($streamValue);
137
    }
138
139
    /**
140
     * @param string $imageString String data to be converted to a Memory Drawing
141
     *
142
     * @throws Exception
143
     */
144 4
    public static function fromString(string $imageString): self
145
    {
146 4
        $gdImage = @imagecreatefromstring($imageString);
147 4
        if ($gdImage === false) {
148 1
            throw new Exception('Value cannot be converted to an image');
149
        }
150
151 3
        $mimeType = self::identifyMimeType($imageString);
152 3
        if (imageistruecolor($gdImage) || imagecolortransparent($gdImage) >= 0) {
153 3
            imagesavealpha($gdImage, true);
154
        }
155 3
        $renderingFunction = self::identifyRenderingFunction($mimeType);
156
157 3
        $drawing = new self();
158 3
        $drawing->setImageResource($gdImage);
1 ignored issue
show
Bug introduced by
It seems like $gdImage can also be of type resource; however, parameter $value of PhpOffice\PhpSpreadsheet...ing::setImageResource() does only seem to accept GdImage|null, 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

158
        $drawing->setImageResource(/** @scrutinizer ignore-type */ $gdImage);
Loading history...
159 3
        $drawing->setRenderingFunction($renderingFunction);
160 3
        $drawing->setMimeType($mimeType);
161
162 3
        return $drawing;
163
    }
164
165 3
    private static function identifyRenderingFunction(string $mimeType): string
166
    {
167 3
        return match ($mimeType) {
168 3
            self::MIMETYPE_PNG => self::RENDERING_PNG,
169 3
            self::MIMETYPE_JPEG => self::RENDERING_JPEG,
170 3
            self::MIMETYPE_GIF => self::RENDERING_GIF,
171 3
            default => self::RENDERING_DEFAULT,
172 3
        };
173
    }
174
175
    /**
176
     * @throws Exception
177
     */
178 3
    private static function identifyMimeType(string $imageString): string
179
    {
180 3
        $temporaryFileName = File::temporaryFilename();
181 3
        file_put_contents($temporaryFileName, $imageString);
182
183 3
        $mimeType = self::identifyMimeTypeUsingExif($temporaryFileName);
184 3
        if ($mimeType !== null) {
185 3
            unlink($temporaryFileName);
186
187 3
            return $mimeType;
188
        }
189
190
        $mimeType = self::identifyMimeTypeUsingGd($temporaryFileName);
191
        if ($mimeType !== null) {
192
            unlink($temporaryFileName);
193
194
            return $mimeType;
195
        }
196
197
        unlink($temporaryFileName);
198
199
        return self::MIMETYPE_DEFAULT;
200
    }
201
202 3
    private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string
203
    {
204 3
        if (function_exists('exif_imagetype')) {
205 3
            $imageType = @exif_imagetype($temporaryFileName);
206 3
            $mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null;
207
208 3
            return self::supportedMimeTypes($mimeType);
209
        }
210
211
        return null;
212
    }
213
214
    private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string
215
    {
216
        if (function_exists('getimagesize')) {
217
            $imageSize = @getimagesize($temporaryFileName);
218
            if (is_array($imageSize)) {
219
                $mimeType = $imageSize['mime'] ?? null; // @phpstan-ignore-line
220
221
                return self::supportedMimeTypes($mimeType);
222
            }
223
        }
224
225
        return null;
226
    }
227
228 3
    private static function supportedMimeTypes(?string $mimeType = null): ?string
229
    {
230 3
        if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) {
231 3
            return $mimeType;
232
        }
233
234
        return null;
235
    }
236
237
    /**
238
     * Get image resource.
239
     */
240 19
    public function getImageResource(): ?GdImage
241
    {
242 19
        return $this->imageResource;
243
    }
244
245
    /**
246
     * Set image resource.
247
     *
248
     * @return $this
249
     */
250 23
    public function setImageResource(?GdImage $value): static
251
    {
252 23
        $this->imageResource = $value;
253
254 23
        if ($this->imageResource !== null) {
255
            // Get width/height
256 23
            $this->width = (int) imagesx($this->imageResource);
257 23
            $this->height = (int) imagesy($this->imageResource);
258
        }
259
260 23
        return $this;
261
    }
262
263
    /**
264
     * Get rendering function.
265
     *
266
     * @return string
267
     */
268 14
    public function getRenderingFunction()
269
    {
270 14
        return $this->renderingFunction;
271
    }
272
273
    /**
274
     * Set rendering function.
275
     *
276
     * @param string $value see self::RENDERING_*
277
     *
278
     * @return $this
279
     */
280 20
    public function setRenderingFunction($value): static
281
    {
282 20
        $this->renderingFunction = $value;
283
284 20
        return $this;
285
    }
286
287
    /**
288
     * Get mime type.
289
     *
290
     * @return string
291
     */
292 16
    public function getMimeType()
293
    {
294 16
        return $this->mimeType;
295
    }
296
297
    /**
298
     * Set mime type.
299
     *
300
     * @param string $value see self::MIMETYPE_*
301
     *
302
     * @return $this
303
     */
304 20
    public function setMimeType($value): static
305
    {
306 20
        $this->mimeType = $value;
307
308 20
        return $this;
309
    }
310
311
    /**
312
     * Get indexed filename (using image index).
313
     */
314 11
    public function getIndexedFilename(): string
315
    {
316 11
        $extension = strtolower($this->getMimeType());
317 11
        $extension = explode('/', $extension);
318 11
        $extension = $extension[1];
319
320 11
        return $this->uniqueName . $this->getImageIndex() . '.' . $extension;
321
    }
322
323
    /**
324
     * Get hash code.
325
     *
326
     * @return string Hash code
327
     */
328 11
    public function getHashCode(): string
329
    {
330 11
        return md5(
331 11
            $this->renderingFunction .
332 11
            $this->mimeType .
333 11
            $this->uniqueName .
334 11
            parent::getHashCode() .
335 11
            __CLASS__
336 11
        );
337
    }
338
}
339