vendor/pimcore/pimcore/models/Document/Editable/Image.php line 27

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore\Model\Document\Editable;
  15. use Pimcore\Model;
  16. use Pimcore\Model\Asset;
  17. use Pimcore\Model\Element;
  18. use Pimcore\Model\Element\ElementDescriptor;
  19. use Pimcore\Tool\Serialize;
  20. /**
  21. * @method \Pimcore\Model\Document\Editable\Dao getDao()
  22. */
  23. class Image extends Model\Document\Editable implements IdRewriterInterface, EditmodeDataInterface
  24. {
  25. /**
  26. * ID of the referenced image
  27. *
  28. * @internal
  29. *
  30. * @var int|null
  31. */
  32. protected $id;
  33. /**
  34. * The ALT text of the image
  35. *
  36. * @internal
  37. *
  38. * @var string|null
  39. */
  40. protected $alt;
  41. /**
  42. * Contains the imageobject itself
  43. *
  44. * @internal
  45. *
  46. * @var Asset\Image|ElementDescriptor|null
  47. */
  48. protected $image;
  49. /**
  50. * @internal
  51. *
  52. * @var bool
  53. */
  54. protected $cropPercent = false;
  55. /**
  56. * @internal
  57. *
  58. * @var float
  59. */
  60. protected $cropWidth;
  61. /**
  62. * @internal
  63. *
  64. * @var float
  65. */
  66. protected $cropHeight;
  67. /**
  68. * @internal
  69. *
  70. * @var float
  71. */
  72. protected $cropTop;
  73. /**
  74. * @internal
  75. *
  76. * @var float
  77. */
  78. protected $cropLeft;
  79. /**
  80. * @internal
  81. *
  82. * @var array
  83. */
  84. protected $hotspots = [];
  85. /**
  86. * @internal
  87. *
  88. * @var array
  89. */
  90. protected $marker = [];
  91. /**
  92. * The Thumbnail config of the image
  93. *
  94. * @internal
  95. *
  96. * @var string|null
  97. */
  98. protected $thumbnail;
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function getType()
  103. {
  104. return 'image';
  105. }
  106. /**
  107. * {@inheritdoc}
  108. *
  109. * @return mixed
  110. */
  111. public function getData()
  112. {
  113. return [
  114. 'id' => $this->id,
  115. 'alt' => $this->alt,
  116. 'cropPercent' => $this->cropPercent,
  117. 'cropWidth' => $this->cropWidth,
  118. 'cropHeight' => $this->cropHeight,
  119. 'cropTop' => $this->cropTop,
  120. 'cropLeft' => $this->cropLeft,
  121. 'hotspots' => $this->hotspots,
  122. 'marker' => $this->marker,
  123. 'thumbnail' => $this->thumbnail,
  124. ];
  125. }
  126. /**
  127. * {@inheritdoc}
  128. *
  129. * @return array
  130. */
  131. public function getDataForResource()
  132. {
  133. return [
  134. 'id' => $this->id,
  135. 'alt' => $this->alt,
  136. 'cropPercent' => $this->cropPercent,
  137. 'cropWidth' => $this->cropWidth,
  138. 'cropHeight' => $this->cropHeight,
  139. 'cropTop' => $this->cropTop,
  140. 'cropLeft' => $this->cropLeft,
  141. 'hotspots' => $this->hotspots,
  142. 'marker' => $this->marker,
  143. 'thumbnail' => $this->thumbnail,
  144. ];
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function getDataEditmode() /** : mixed */
  150. {
  151. $image = $this->getImage();
  152. if ($image instanceof Asset\Image) {
  153. $rewritePath = function ($data) {
  154. if (!is_array($data)) {
  155. return [];
  156. }
  157. foreach ($data as &$element) {
  158. if (array_key_exists('data', $element) && is_array($element['data']) && count($element['data']) > 0) {
  159. foreach ($element['data'] as &$metaData) {
  160. if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  161. $metaData = get_object_vars($metaData);
  162. }
  163. if (in_array($metaData['type'], ['object', 'asset', 'document'])
  164. && $el = Element\Service::getElementById($metaData['type'], $metaData['value'])) {
  165. $metaData['value'] = $el;
  166. }
  167. if ($metaData['value'] instanceof Element\ElementInterface) {
  168. $metaData['value'] = $metaData['value']->getRealFullPath();
  169. }
  170. }
  171. }
  172. }
  173. return $data;
  174. };
  175. $marker = $rewritePath($this->marker);
  176. $hotspots = $rewritePath($this->hotspots);
  177. return [
  178. 'id' => $this->id,
  179. 'path' => $image->getRealFullPath(),
  180. 'alt' => $this->alt,
  181. 'cropPercent' => $this->cropPercent,
  182. 'cropWidth' => $this->cropWidth,
  183. 'cropHeight' => $this->cropHeight,
  184. 'cropTop' => $this->cropTop,
  185. 'cropLeft' => $this->cropLeft,
  186. 'hotspots' => $hotspots,
  187. 'marker' => $marker,
  188. 'thumbnail' => $this->thumbnail,
  189. 'predefinedDataTemplates' => $this->getConfig()['predefinedDataTemplates'] ?? null,
  190. ];
  191. }
  192. return null;
  193. }
  194. /**
  195. * @return array
  196. */
  197. public function getConfig()
  198. {
  199. $config = parent::getConfig();
  200. if (isset($config['thumbnail']) && !isset($config['focal_point_context_menu_item'])) {
  201. $thumbConfig = Asset\Image\Thumbnail\Config::getByAutoDetect($config['thumbnail']);
  202. if ($thumbConfig) {
  203. foreach ($thumbConfig->getItems() as $item) {
  204. if ($item['method'] == 'cover') {
  205. $config['focal_point_context_menu_item'] = true;
  206. $this->config['focal_point_context_menu_item'] = true;
  207. break;
  208. }
  209. }
  210. }
  211. }
  212. return $config;
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. public function frontend()
  218. {
  219. if (!is_array($this->config)) {
  220. $this->config = [];
  221. }
  222. $image = $this->getImage();
  223. if ($image instanceof Asset\Image) {
  224. $thumbnailName = $this->config['thumbnail'] ?? null;
  225. if ($thumbnailName || $this->cropPercent) {
  226. // create a thumbnail first
  227. $autoName = false;
  228. $thumbConfig = $image->getThumbnailConfig($thumbnailName);
  229. if (!$thumbConfig && $this->cropPercent) {
  230. $thumbConfig = new Asset\Image\Thumbnail\Config();
  231. }
  232. if ($this->cropPercent) {
  233. $this->applyCustomCropping($thumbConfig);
  234. $autoName = true;
  235. }
  236. if (isset($this->config['highResolution']) && $this->config['highResolution'] > 1) {
  237. $thumbConfig->setHighResolution($this->config['highResolution']);
  238. }
  239. // autogenerate a name for the thumbnail because it's different from the original
  240. if ($autoName) {
  241. $thumbConfig->generateAutoName();
  242. }
  243. $deferred = true;
  244. if (isset($this->config['deferred'])) {
  245. $deferred = $this->config['deferred'];
  246. }
  247. $thumbnail = $image->getThumbnail($thumbConfig, $deferred);
  248. } else {
  249. // we're using the thumbnail class only to generate the HTML
  250. $thumbnail = $image->getThumbnail();
  251. }
  252. $attributes = array_merge($this->config, [
  253. 'alt' => $this->alt,
  254. 'title' => $this->alt,
  255. ]);
  256. $removeAttributes = [];
  257. if (isset($this->config['removeAttributes']) && is_array($this->config['removeAttributes'])) {
  258. $removeAttributes = $this->config['removeAttributes'];
  259. }
  260. // thumbnail's HTML is always generated by the thumbnail itself
  261. return $thumbnail->getHtml($attributes);
  262. }
  263. return '';
  264. }
  265. /**
  266. * {@inheritdoc}
  267. */
  268. public function setDataFromResource($data)
  269. {
  270. if (strlen($data) > 2) {
  271. $data = Serialize::unserialize($data);
  272. }
  273. $rewritePath = function ($data) {
  274. if (!is_array($data)) {
  275. return [];
  276. }
  277. foreach ($data as &$element) {
  278. if (array_key_exists('data', $element) && is_array($element['data']) && count($element['data']) > 0) {
  279. foreach ($element['data'] as &$metaData) {
  280. // this is for backward compatibility (Array vs. MarkerHotspotItem)
  281. if (is_array($metaData)) {
  282. $metaData = new Element\Data\MarkerHotspotItem($metaData);
  283. }
  284. }
  285. }
  286. }
  287. return $data;
  288. };
  289. if (array_key_exists('marker', $data) && is_array($data['marker']) && count($data['marker']) > 0) {
  290. $data['marker'] = $rewritePath($data['marker']);
  291. }
  292. if (array_key_exists('hotspots', $data) && is_array($data['hotspots']) && count($data['hotspots']) > 0) {
  293. $data['hotspots'] = $rewritePath($data['hotspots']);
  294. }
  295. $this->id = $data['id'] ?? null;
  296. $this->alt = $data['alt'] ?? null;
  297. $this->cropPercent = $data['cropPercent'] ?? null;
  298. $this->cropWidth = $data['cropWidth'] ?? null;
  299. $this->cropHeight = $data['cropHeight'] ?? null;
  300. $this->cropTop = $data['cropTop'] ?? null;
  301. $this->cropLeft = $data['cropLeft'] ?? null;
  302. $this->marker = $data['marker'] ?? null;
  303. $this->hotspots = $data['hotspots'] ?? null;
  304. $this->thumbnail = $data['thumbnail'] ?? null;
  305. return $this;
  306. }
  307. /**
  308. * {@inheritdoc}
  309. */
  310. public function setDataFromEditmode($data)
  311. {
  312. $rewritePath = function ($data) {
  313. if (!is_array($data)) {
  314. return [];
  315. }
  316. foreach ($data as &$element) {
  317. if (array_key_exists('data', $element) && is_array($element['data']) && count($element['data']) > 0) {
  318. foreach ($element['data'] as &$metaData) {
  319. $metaData = new Element\Data\MarkerHotspotItem($metaData);
  320. if (in_array($metaData['type'], ['object', 'asset', 'document'])) {
  321. $el = Element\Service::getElementByPath($metaData['type'], $metaData->getValue());
  322. $metaData['value'] = $el;
  323. }
  324. }
  325. }
  326. }
  327. return $data;
  328. };
  329. if (is_array($data)) {
  330. if (array_key_exists('marker', $data) && is_array($data['marker']) && count($data['marker']) > 0) {
  331. $data['marker'] = $rewritePath($data['marker']);
  332. }
  333. if (array_key_exists('hotspots', $data) && is_array($data['hotspots']) && count($data['hotspots']) > 0) {
  334. $data['hotspots'] = $rewritePath($data['hotspots']);
  335. }
  336. $this->id = $data['id'] ?? null;
  337. $this->alt = $data['alt'] ?? null;
  338. $this->cropPercent = $data['cropPercent'] ?? null;
  339. $this->cropWidth = $data['cropWidth'] ?? null;
  340. $this->cropHeight = $data['cropHeight'] ?? null;
  341. $this->cropTop = $data['cropTop'] ?? null;
  342. $this->cropLeft = $data['cropLeft'] ?? null;
  343. $this->marker = $data['marker'] ?? null;
  344. $this->hotspots = $data['hotspots'] ?? null;
  345. $this->thumbnail = $data['thumbnail'] ?? null;
  346. }
  347. return $this;
  348. }
  349. /**
  350. * @return string
  351. */
  352. public function getText()
  353. {
  354. return $this->alt;
  355. }
  356. /**
  357. * @param string $text
  358. */
  359. public function setText($text)
  360. {
  361. $this->alt = $text;
  362. }
  363. /**
  364. * @return string
  365. */
  366. public function getAlt()
  367. {
  368. return $this->getText();
  369. }
  370. /**
  371. * @return string|null
  372. */
  373. public function getThumbnailConfig()
  374. {
  375. return $this->thumbnail;
  376. }
  377. /**
  378. * @return string
  379. */
  380. public function getSrc()
  381. {
  382. $image = $this->getImage();
  383. if ($image instanceof Asset) {
  384. return $image->getFullPath();
  385. }
  386. return '';
  387. }
  388. /**
  389. * @return Asset\Image|null
  390. */
  391. public function getImage()
  392. {
  393. if (!$this->image instanceof Asset\Image) {
  394. $this->image = $this->getId() ? Asset\Image::getById($this->getId()) : null;
  395. }
  396. return $this->image;
  397. }
  398. /**
  399. * @param Asset\Image|ElementDescriptor|null $image
  400. *
  401. * @return $this
  402. */
  403. public function setImage($image)
  404. {
  405. $this->image = $image;
  406. if ($image instanceof Asset\Image) {
  407. $this->setId($image->getId());
  408. }
  409. return $this;
  410. }
  411. /**
  412. * @param int|null $id
  413. *
  414. * @return $this
  415. */
  416. public function setId($id)
  417. {
  418. $this->id = $id ? (int)$id : null;
  419. return $this;
  420. }
  421. /**
  422. * @return int|null
  423. */
  424. public function getId()
  425. {
  426. return $this->id;
  427. }
  428. /**
  429. * @param array|string|Asset\Image\Thumbnail\Config $conf
  430. * @param bool $deferred
  431. *
  432. * @return Asset\Image\Thumbnail|string
  433. */
  434. public function getThumbnail($conf, $deferred = true)
  435. {
  436. $image = $this->getImage();
  437. if ($image instanceof Asset\Image) {
  438. $thumbConfig = $image->getThumbnailConfig($conf);
  439. if ($thumbConfig && $this->cropPercent) {
  440. $this->applyCustomCropping($thumbConfig);
  441. $thumbConfig->generateAutoName();
  442. }
  443. return $image->getThumbnail($thumbConfig, $deferred);
  444. }
  445. return '';
  446. }
  447. /**
  448. * @param Asset\Image\Thumbnail\Config $thumbConfig
  449. *
  450. * @return void
  451. */
  452. private function applyCustomCropping($thumbConfig)
  453. {
  454. $cropConfig = [
  455. 'width' => $this->cropWidth,
  456. 'height' => $this->cropHeight,
  457. 'y' => $this->cropTop,
  458. 'x' => $this->cropLeft,
  459. ];
  460. $thumbConfig->addItemAt(0, 'cropPercent', $cropConfig);
  461. // also crop media query specific configs
  462. if ($thumbConfig->hasMedias()) {
  463. foreach ($thumbConfig->getMedias() as $mediaName => $mediaItems) {
  464. $thumbConfig->addItemAt(0, 'cropPercent', $cropConfig, $mediaName);
  465. }
  466. }
  467. }
  468. /**
  469. * {@inheritdoc}
  470. */
  471. public function isEmpty()
  472. {
  473. $image = $this->getImage();
  474. if ($image instanceof Asset\Image) {
  475. return false;
  476. }
  477. return true;
  478. }
  479. /**
  480. * {@inheritdoc}
  481. */
  482. public function getCacheTags(Model\Document\PageSnippet $ownerDocument, array $tags = []): array
  483. {
  484. $image = $this->getImage();
  485. if ($image instanceof Asset) {
  486. if (!array_key_exists($image->getCacheTag(), $tags)) {
  487. $tags = $image->getCacheTags($tags);
  488. }
  489. }
  490. $getMetaDataCacheTags = function ($data, $tags) {
  491. if (!is_array($data)) {
  492. return $tags;
  493. }
  494. foreach ($data as $element) {
  495. if (array_key_exists('data', $element) && is_array($element['data']) && count($element['data']) > 0) {
  496. foreach ($element['data'] as $metaData) {
  497. if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  498. $metaData = get_object_vars($metaData);
  499. }
  500. if ($metaData['value'] instanceof Element\ElementInterface) {
  501. if (!array_key_exists($metaData['value']->getCacheTag(), $tags)) {
  502. $tags = $metaData['value']->getCacheTags($tags);
  503. }
  504. }
  505. }
  506. }
  507. }
  508. return $tags;
  509. };
  510. $tags = $getMetaDataCacheTags($this->marker, $tags);
  511. $tags = $getMetaDataCacheTags($this->hotspots, $tags);
  512. return $tags;
  513. }
  514. /**
  515. * @return array
  516. */
  517. public function resolveDependencies()
  518. {
  519. $dependencies = [];
  520. $image = $this->getImage();
  521. if ($image instanceof Asset\Image) {
  522. $key = 'asset_' . $image->getId();
  523. $dependencies[$key] = [
  524. 'id' => $image->getId(),
  525. 'type' => 'asset',
  526. ];
  527. }
  528. $getMetaDataDependencies = function ($data, $dependencies) {
  529. if (!is_array($data)) {
  530. return $dependencies;
  531. }
  532. foreach ($data as $element) {
  533. if (array_key_exists('data', $element) && is_array($element['data']) && count($element['data']) > 0) {
  534. foreach ($element['data'] as $metaData) {
  535. if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  536. $metaData = get_object_vars($metaData);
  537. }
  538. if ($metaData['value'] instanceof Element\ElementInterface) {
  539. $dependencies[$metaData['type'] . '_' . $metaData['value']->getId()] = [
  540. 'id' => $metaData['value']->getId(),
  541. 'type' => $metaData['type'],
  542. ];
  543. }
  544. }
  545. }
  546. }
  547. return $dependencies;
  548. };
  549. $dependencies = $getMetaDataDependencies($this->marker, $dependencies);
  550. $dependencies = $getMetaDataDependencies($this->hotspots, $dependencies);
  551. return $dependencies;
  552. }
  553. /**
  554. * @param float $cropHeight
  555. *
  556. * @return $this
  557. */
  558. public function setCropHeight($cropHeight)
  559. {
  560. $this->cropHeight = $cropHeight;
  561. return $this;
  562. }
  563. /**
  564. * @return float
  565. */
  566. public function getCropHeight()
  567. {
  568. return $this->cropHeight;
  569. }
  570. /**
  571. * @param float $cropLeft
  572. *
  573. * @return $this
  574. */
  575. public function setCropLeft($cropLeft)
  576. {
  577. $this->cropLeft = $cropLeft;
  578. return $this;
  579. }
  580. /**
  581. * @return float
  582. */
  583. public function getCropLeft()
  584. {
  585. return $this->cropLeft;
  586. }
  587. /**
  588. * @param bool $cropPercent
  589. *
  590. * @return $this
  591. */
  592. public function setCropPercent($cropPercent)
  593. {
  594. $this->cropPercent = $cropPercent;
  595. return $this;
  596. }
  597. /**
  598. * @return bool
  599. */
  600. public function getCropPercent()
  601. {
  602. return $this->cropPercent;
  603. }
  604. /**
  605. * @param float $cropTop
  606. *
  607. * @return $this
  608. */
  609. public function setCropTop($cropTop)
  610. {
  611. $this->cropTop = $cropTop;
  612. return $this;
  613. }
  614. /**
  615. * @return float
  616. */
  617. public function getCropTop()
  618. {
  619. return $this->cropTop;
  620. }
  621. /**
  622. * @param float $cropWidth
  623. *
  624. * @return $this
  625. */
  626. public function setCropWidth($cropWidth)
  627. {
  628. $this->cropWidth = $cropWidth;
  629. return $this;
  630. }
  631. /**
  632. * @return float
  633. */
  634. public function getCropWidth()
  635. {
  636. return $this->cropWidth;
  637. }
  638. /**
  639. * @param array $hotspots
  640. */
  641. public function setHotspots($hotspots)
  642. {
  643. $this->hotspots = $hotspots;
  644. }
  645. /**
  646. * @return array
  647. */
  648. public function getHotspots()
  649. {
  650. return $this->hotspots;
  651. }
  652. /**
  653. * @param array $marker
  654. */
  655. public function setMarker($marker)
  656. {
  657. $this->marker = $marker;
  658. }
  659. /**
  660. * @return array
  661. */
  662. public function getMarker()
  663. {
  664. return $this->marker;
  665. }
  666. /**
  667. * {@inheritdoc}
  668. */
  669. public function rewriteIds($idMapping) /** : void */
  670. {
  671. if (array_key_exists('asset', $idMapping) && array_key_exists($this->getId(), $idMapping['asset'])) {
  672. $this->setId($idMapping['asset'][$this->getId()]);
  673. // reset marker & hotspot information
  674. $this->setHotspots([]);
  675. $this->setMarker([]);
  676. $this->setCropPercent(false);
  677. $this->setImage(null);
  678. }
  679. }
  680. /**
  681. * @return array
  682. */
  683. public function __sleep()
  684. {
  685. $finalVars = [];
  686. $parentVars = parent::__sleep();
  687. $blockedVars = ['image'];
  688. foreach ($parentVars as $key) {
  689. if (!in_array($key, $blockedVars)) {
  690. $finalVars[] = $key;
  691. }
  692. }
  693. return $finalVars;
  694. }
  695. }