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