Image.php 16 KB

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