Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.89% |
156 / 161 |
|
88.46% |
23 / 26 |
CRAP | |
0.00% |
0 / 1 |
Image | |
96.89% |
156 / 161 |
|
88.46% |
23 / 26 |
68 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getStyle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSourceType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMediaId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isWatermark | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setIsWatermark | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageCreateFunction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageFunction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageQuality | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageExtension | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isMemImage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMediaIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMediaIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getImageString | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
10 | |||
getImageStringData | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
checkImage | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
7 | |||
setSourceType | |
83.33% |
15 / 18 |
|
0.00% |
0 / 1 |
7.23 | |||
getArchiveImageSize | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
5 | |||
setFunctions | |
97.44% |
38 / 39 |
|
0.00% |
0 / 1 |
11 | |||
setProportionalSize | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * This file is part of PHPWord - A pure PHP library for reading and writing |
5 | * word processing documents. |
6 | * |
7 | * PHPWord is free software distributed under the terms of the GNU Lesser |
8 | * General Public License version 3 as published by the Free Software Foundation. |
9 | * |
10 | * For the full copyright and license information, please read the LICENSE |
11 | * file that was distributed with this source code. For the full list of |
12 | * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. |
13 | * |
14 | * @see https://github.com/PHPOffice/PHPWord |
15 | * |
16 | * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 |
17 | */ |
18 | |
19 | namespace PhpOffice\PhpWord\Element; |
20 | |
21 | use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; |
22 | use PhpOffice\PhpWord\Exception\InvalidImageException; |
23 | use PhpOffice\PhpWord\Exception\UnsupportedImageTypeException; |
24 | use PhpOffice\PhpWord\Settings; |
25 | use PhpOffice\PhpWord\Shared\ZipArchive; |
26 | use PhpOffice\PhpWord\Style\Image as ImageStyle; |
27 | |
28 | /** |
29 | * Image element. |
30 | */ |
31 | class Image extends AbstractElement |
32 | { |
33 | /** |
34 | * Image source type constants. |
35 | */ |
36 | const SOURCE_LOCAL = 'local'; // Local images |
37 | const SOURCE_GD = 'gd'; // Generated using GD |
38 | const SOURCE_ARCHIVE = 'archive'; // Image in archives zip://$archive#$image |
39 | const SOURCE_STRING = 'string'; // Image from string |
40 | |
41 | /** |
42 | * Image source. |
43 | * |
44 | * @var string |
45 | */ |
46 | private $source; |
47 | |
48 | /** |
49 | * Source type: local|gd|archive. |
50 | * |
51 | * @var string |
52 | */ |
53 | private $sourceType; |
54 | |
55 | /** |
56 | * Image style. |
57 | * |
58 | * @var ?ImageStyle |
59 | */ |
60 | private $style; |
61 | |
62 | /** |
63 | * Is watermark. |
64 | * |
65 | * @var bool |
66 | */ |
67 | private $watermark; |
68 | |
69 | /** |
70 | * Name of image. |
71 | * |
72 | * @var string |
73 | */ |
74 | private $name; |
75 | |
76 | /** |
77 | * Image type. |
78 | * |
79 | * @var string |
80 | */ |
81 | private $imageType; |
82 | |
83 | /** |
84 | * Image create function. |
85 | * |
86 | * @var string |
87 | */ |
88 | private $imageCreateFunc; |
89 | |
90 | /** |
91 | * Image function. |
92 | * |
93 | * @var null|callable(resource): void |
94 | */ |
95 | private $imageFunc; |
96 | |
97 | /** |
98 | * Image extension. |
99 | * |
100 | * @var string |
101 | */ |
102 | private $imageExtension; |
103 | |
104 | /** |
105 | * Image quality. |
106 | * |
107 | * Functions imagepng() and imagejpeg() have an optional parameter for |
108 | * quality. |
109 | * |
110 | * @var null|int |
111 | */ |
112 | private $imageQuality; |
113 | |
114 | /** |
115 | * Is memory image. |
116 | * |
117 | * @var bool |
118 | */ |
119 | private $memoryImage; |
120 | |
121 | /** |
122 | * Image target file name. |
123 | * |
124 | * @var string |
125 | */ |
126 | private $target; |
127 | |
128 | /** |
129 | * Image media index. |
130 | * |
131 | * @var int |
132 | */ |
133 | private $mediaIndex; |
134 | |
135 | /** |
136 | * Has media relation flag; true for Link, Image, and Object. |
137 | * |
138 | * @var bool |
139 | */ |
140 | protected $mediaRelation = true; |
141 | |
142 | /** |
143 | * Create new image element. |
144 | * |
145 | * @param string $source |
146 | * @param mixed $style |
147 | * @param bool $watermark |
148 | * @param string $name |
149 | */ |
150 | public function __construct($source, $style = null, $watermark = false, $name = null) |
151 | { |
152 | $this->source = $source; |
153 | $this->style = $this->setNewStyle(new ImageStyle(), $style, true); |
154 | $this->setIsWatermark($watermark); |
155 | $this->setName($name); |
156 | |
157 | $this->checkImage(); |
158 | } |
159 | |
160 | /** |
161 | * Get Image style. |
162 | * |
163 | * @return ?ImageStyle |
164 | */ |
165 | public function getStyle() |
166 | { |
167 | return $this->style; |
168 | } |
169 | |
170 | /** |
171 | * Get image source. |
172 | * |
173 | * @return string |
174 | */ |
175 | public function getSource() |
176 | { |
177 | return $this->source; |
178 | } |
179 | |
180 | /** |
181 | * Get image source type. |
182 | * |
183 | * @return string |
184 | */ |
185 | public function getSourceType() |
186 | { |
187 | return $this->sourceType; |
188 | } |
189 | |
190 | /** |
191 | * Sets the image name. |
192 | * |
193 | * @param string $value |
194 | */ |
195 | public function setName($value): void |
196 | { |
197 | $this->name = $value; |
198 | } |
199 | |
200 | /** |
201 | * Get image name. |
202 | * |
203 | * @return null|string |
204 | */ |
205 | public function getName() |
206 | { |
207 | return $this->name; |
208 | } |
209 | |
210 | /** |
211 | * Get image media ID. |
212 | * |
213 | * @return string |
214 | */ |
215 | public function getMediaId() |
216 | { |
217 | return md5($this->source); |
218 | } |
219 | |
220 | /** |
221 | * Get is watermark. |
222 | * |
223 | * @return bool |
224 | */ |
225 | public function isWatermark() |
226 | { |
227 | return $this->watermark; |
228 | } |
229 | |
230 | /** |
231 | * Set is watermark. |
232 | * |
233 | * @param bool $value |
234 | */ |
235 | public function setIsWatermark($value): void |
236 | { |
237 | $this->watermark = $value; |
238 | } |
239 | |
240 | /** |
241 | * Get image type. |
242 | * |
243 | * @return string |
244 | */ |
245 | public function getImageType() |
246 | { |
247 | return $this->imageType; |
248 | } |
249 | |
250 | /** |
251 | * Get image create function. |
252 | * |
253 | * @return string |
254 | */ |
255 | public function getImageCreateFunction() |
256 | { |
257 | return $this->imageCreateFunc; |
258 | } |
259 | |
260 | /** |
261 | * Get image function. |
262 | * |
263 | * @return null|callable(resource): void |
264 | */ |
265 | public function getImageFunction(): ?callable |
266 | { |
267 | return $this->imageFunc; |
268 | } |
269 | |
270 | /** |
271 | * Get image quality. |
272 | */ |
273 | public function getImageQuality(): ?int |
274 | { |
275 | return $this->imageQuality; |
276 | } |
277 | |
278 | /** |
279 | * Get image extension. |
280 | * |
281 | * @return string |
282 | */ |
283 | public function getImageExtension() |
284 | { |
285 | return $this->imageExtension; |
286 | } |
287 | |
288 | /** |
289 | * Get is memory image. |
290 | * |
291 | * @return bool |
292 | */ |
293 | public function isMemImage() |
294 | { |
295 | return $this->memoryImage; |
296 | } |
297 | |
298 | /** |
299 | * Get target file name. |
300 | * |
301 | * @return string |
302 | */ |
303 | public function getTarget() |
304 | { |
305 | return $this->target; |
306 | } |
307 | |
308 | /** |
309 | * Set target file name. |
310 | * |
311 | * @param string $value |
312 | */ |
313 | public function setTarget($value): void |
314 | { |
315 | $this->target = $value; |
316 | } |
317 | |
318 | /** |
319 | * Get media index. |
320 | * |
321 | * @return int |
322 | */ |
323 | public function getMediaIndex() |
324 | { |
325 | return $this->mediaIndex; |
326 | } |
327 | |
328 | /** |
329 | * Set media index. |
330 | * |
331 | * @param int $value |
332 | */ |
333 | public function setMediaIndex($value): void |
334 | { |
335 | $this->mediaIndex = $value; |
336 | } |
337 | |
338 | /** |
339 | * Get image string. |
340 | */ |
341 | public function getImageString(): ?string |
342 | { |
343 | $source = $this->source; |
344 | $actualSource = null; |
345 | $imageBinary = null; |
346 | $isTemp = false; |
347 | |
348 | // Get actual source from archive image or other source |
349 | // Return null if not found |
350 | if ($this->sourceType == self::SOURCE_ARCHIVE) { |
351 | $source = substr($source, 6); |
352 | [$zipFilename, $imageFilename] = explode('#', $source); |
353 | |
354 | $zip = new ZipArchive(); |
355 | if ($zip->open($zipFilename) !== false) { |
356 | if ($zip->locateName($imageFilename) !== false) { |
357 | $isTemp = true; |
358 | $zip->extractTo(Settings::getTempDir(), $imageFilename); |
359 | $actualSource = Settings::getTempDir() . DIRECTORY_SEPARATOR . $imageFilename; |
360 | } |
361 | } |
362 | $zip->close(); |
363 | } else { |
364 | $actualSource = $source; |
365 | } |
366 | |
367 | // Can't find any case where $actualSource = null hasn't captured by |
368 | // preceding exceptions. Please uncomment when you find the case and |
369 | // put the case into Element\ImageTest. |
370 | // if ($actualSource === null) { |
371 | // return null; |
372 | // } |
373 | |
374 | // Read image binary data and convert to hex/base64 string |
375 | if ($this->sourceType == self::SOURCE_GD) { |
376 | $imageResource = call_user_func($this->imageCreateFunc, $actualSource); |
377 | if ($this->imageType === 'image/png') { |
378 | // PNG images need to preserve alpha channel information |
379 | imagesavealpha($imageResource, true); |
380 | } |
381 | ob_start(); |
382 | $callback = $this->imageFunc; |
383 | $callback($imageResource); |
384 | $imageBinary = ob_get_contents(); |
385 | ob_end_clean(); |
386 | } elseif ($this->sourceType == self::SOURCE_STRING) { |
387 | $imageBinary = $this->source; |
388 | } else { |
389 | $fileHandle = fopen($actualSource, 'rb', false); |
390 | $fileSize = filesize($actualSource); |
391 | if ($fileHandle !== false && $fileSize > 0) { |
392 | $imageBinary = fread($fileHandle, $fileSize); |
393 | fclose($fileHandle); |
394 | } |
395 | } |
396 | |
397 | // Delete temporary file if necessary |
398 | if ($isTemp === true) { |
399 | @unlink($actualSource); |
400 | } |
401 | |
402 | return $imageBinary; |
403 | } |
404 | |
405 | /** |
406 | * Get image string data. |
407 | * |
408 | * @param bool $base64 |
409 | * |
410 | * @return null|string |
411 | * |
412 | * @since 0.11.0 |
413 | */ |
414 | public function getImageStringData($base64 = false) |
415 | { |
416 | $imageBinary = $this->getImageString(); |
417 | if ($imageBinary === null) { |
418 | return null; |
419 | } |
420 | |
421 | if ($base64) { |
422 | return base64_encode($imageBinary); |
423 | } |
424 | |
425 | return bin2hex($imageBinary); |
426 | } |
427 | |
428 | /** |
429 | * Check memory image, supported type, image functions, and proportional width/height. |
430 | */ |
431 | private function checkImage(): void |
432 | { |
433 | $this->setSourceType(); |
434 | |
435 | // Check image data |
436 | if ($this->sourceType == self::SOURCE_ARCHIVE) { |
437 | $imageData = $this->getArchiveImageSize($this->source); |
438 | } elseif ($this->sourceType == self::SOURCE_STRING) { |
439 | $imageData = @getimagesizefromstring($this->source); |
440 | } else { |
441 | $imageData = @getimagesize($this->source); |
442 | } |
443 | if (!is_array($imageData)) { |
444 | throw new InvalidImageException(sprintf('Invalid image: %s', $this->source)); |
445 | } |
446 | [$actualWidth, $actualHeight, $imageType] = $imageData; |
447 | |
448 | // Check image type support |
449 | $supportedTypes = [IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG]; |
450 | if ($this->sourceType != self::SOURCE_GD && $this->sourceType != self::SOURCE_STRING) { |
451 | $supportedTypes = array_merge($supportedTypes, [IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM]); |
452 | } |
453 | if (!in_array($imageType, $supportedTypes)) { |
454 | throw new UnsupportedImageTypeException(); |
455 | } |
456 | |
457 | // Define image functions |
458 | $this->imageType = image_type_to_mime_type($imageType); |
459 | $this->setFunctions(); |
460 | $this->setProportionalSize($actualWidth, $actualHeight); |
461 | } |
462 | |
463 | /** |
464 | * Set source type. |
465 | */ |
466 | private function setSourceType(): void |
467 | { |
468 | if (stripos(strrev($this->source), strrev('.php')) === 0) { |
469 | $this->memoryImage = true; |
470 | $this->sourceType = self::SOURCE_GD; |
471 | } elseif (strpos($this->source, 'zip://') !== false) { |
472 | $this->memoryImage = false; |
473 | $this->sourceType = self::SOURCE_ARCHIVE; |
474 | } elseif (filter_var($this->source, FILTER_VALIDATE_URL) !== false) { |
475 | $this->memoryImage = true; |
476 | if (strpos($this->source, 'https') === 0) { |
477 | $fileContent = file_get_contents($this->source); |
478 | $this->source = $fileContent; |
479 | $this->sourceType = self::SOURCE_STRING; |
480 | } else { |
481 | $this->sourceType = self::SOURCE_GD; |
482 | } |
483 | } elseif ((strpos($this->source, chr(0)) === false) && @file_exists($this->source)) { |
484 | $this->memoryImage = false; |
485 | $this->sourceType = self::SOURCE_LOCAL; |
486 | } else { |
487 | $this->memoryImage = true; |
488 | $this->sourceType = self::SOURCE_STRING; |
489 | } |
490 | } |
491 | |
492 | /** |
493 | * Get image size from archive. |
494 | * |
495 | * @since 0.12.0 Throws CreateTemporaryFileException. |
496 | * |
497 | * @param string $source |
498 | * |
499 | * @return null|array |
500 | */ |
501 | private function getArchiveImageSize($source) |
502 | { |
503 | $imageData = null; |
504 | $source = substr($source, 6); |
505 | [$zipFilename, $imageFilename] = explode('#', $source); |
506 | |
507 | $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordImage'); |
508 | if (false === $tempFilename) { |
509 | throw new CreateTemporaryFileException(); // @codeCoverageIgnore |
510 | } |
511 | |
512 | $zip = new ZipArchive(); |
513 | if ($zip->open($zipFilename) !== false) { |
514 | if ($zip->locateName($imageFilename) !== false) { |
515 | $imageContent = $zip->getFromName($imageFilename); |
516 | if ($imageContent !== false) { |
517 | file_put_contents($tempFilename, $imageContent); |
518 | $imageData = getimagesize($tempFilename); |
519 | unlink($tempFilename); |
520 | } |
521 | } |
522 | $zip->close(); |
523 | } |
524 | |
525 | return $imageData; |
526 | } |
527 | |
528 | /** |
529 | * Set image functions and extensions. |
530 | */ |
531 | private function setFunctions(): void |
532 | { |
533 | switch ($this->imageType) { |
534 | case 'image/png': |
535 | $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefrompng'; |
536 | $this->imageFunc = function ($resource): void { |
537 | imagepng($resource, null, $this->imageQuality); |
538 | }; |
539 | $this->imageExtension = 'png'; |
540 | $this->imageQuality = -1; |
541 | |
542 | break; |
543 | case 'image/gif': |
544 | $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromgif'; |
545 | $this->imageFunc = function ($resource): void { |
546 | imagegif($resource); |
547 | }; |
548 | $this->imageExtension = 'gif'; |
549 | $this->imageQuality = null; |
550 | |
551 | break; |
552 | case 'image/jpeg': |
553 | case 'image/jpg': |
554 | $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromjpeg'; |
555 | $this->imageFunc = function ($resource): void { |
556 | imagejpeg($resource, null, $this->imageQuality); |
557 | }; |
558 | $this->imageExtension = 'jpg'; |
559 | $this->imageQuality = 100; |
560 | |
561 | break; |
562 | case 'image/bmp': |
563 | case 'image/x-ms-bmp': |
564 | $this->imageType = 'image/bmp'; |
565 | $this->imageFunc = null; |
566 | $this->imageExtension = 'bmp'; |
567 | $this->imageQuality = null; |
568 | |
569 | break; |
570 | case 'image/tiff': |
571 | $this->imageType = 'image/tiff'; |
572 | $this->imageFunc = null; |
573 | $this->imageExtension = 'tif'; |
574 | $this->imageQuality = null; |
575 | |
576 | break; |
577 | } |
578 | } |
579 | |
580 | /** |
581 | * Set proportional width/height if one dimension not available. |
582 | * |
583 | * @param int $actualWidth |
584 | * @param int $actualHeight |
585 | */ |
586 | private function setProportionalSize($actualWidth, $actualHeight): void |
587 | { |
588 | $styleWidth = $this->style->getWidth(); |
589 | $styleHeight = $this->style->getHeight(); |
590 | if (!($styleWidth && $styleHeight)) { |
591 | if ($styleWidth == null && $styleHeight == null) { |
592 | $this->style->setWidth($actualWidth); |
593 | $this->style->setHeight($actualHeight); |
594 | } elseif ($styleWidth) { |
595 | $this->style->setHeight($actualHeight * ($styleWidth / $actualWidth)); |
596 | } else { |
597 | $this->style->setWidth($actualWidth * ($styleHeight / $actualHeight)); |
598 | } |
599 | } |
600 | } |
601 | } |