Configurable.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Swatches\Block\Product\Renderer;
  7. use Magento\Catalog\Block\Product\Context;
  8. use Magento\Catalog\Helper\Product as CatalogProduct;
  9. use Magento\Catalog\Model\Product;
  10. use Magento\Catalog\Model\Product\Image\UrlBuilder;
  11. use Magento\ConfigurableProduct\Helper\Data;
  12. use Magento\ConfigurableProduct\Model\ConfigurableAttributeData;
  13. use Magento\Customer\Helper\Session\CurrentCustomer;
  14. use Magento\Framework\Json\EncoderInterface;
  15. use Magento\Framework\Pricing\PriceCurrencyInterface;
  16. use Magento\Framework\Stdlib\ArrayUtils;
  17. use Magento\Store\Model\ScopeInterface;
  18. use Magento\Swatches\Helper\Data as SwatchData;
  19. use Magento\Swatches\Helper\Media;
  20. use Magento\Swatches\Model\Swatch;
  21. use Magento\Framework\App\ObjectManager;
  22. use Magento\Swatches\Model\SwatchAttributesProvider;
  23. /**
  24. * Swatch renderer block
  25. *
  26. * @api
  27. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  28. * @since 100.0.2
  29. */
  30. class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable implements
  31. \Magento\Framework\DataObject\IdentityInterface
  32. {
  33. /**
  34. * Path to template file with Swatch renderer.
  35. */
  36. const SWATCH_RENDERER_TEMPLATE = 'Magento_Swatches::product/view/renderer.phtml';
  37. /**
  38. * Path to default template file with standard Configurable renderer.
  39. */
  40. const CONFIGURABLE_RENDERER_TEMPLATE = 'Magento_ConfigurableProduct::product/view/type/options/configurable.phtml';
  41. /**
  42. * Action name for ajax request
  43. */
  44. const MEDIA_CALLBACK_ACTION = 'swatches/ajax/media';
  45. /**
  46. * Name of swatch image for json config
  47. */
  48. const SWATCH_IMAGE_NAME = 'swatchImage';
  49. /**
  50. * Name of swatch thumbnail for json config
  51. */
  52. const SWATCH_THUMBNAIL_NAME = 'swatchThumb';
  53. /**
  54. * @var Product
  55. */
  56. protected $product;
  57. /**
  58. * @var SwatchData
  59. */
  60. protected $swatchHelper;
  61. /**
  62. * @var Media
  63. */
  64. protected $swatchMediaHelper;
  65. /**
  66. * Indicate if product has one or more Swatch attributes
  67. *
  68. * @deprecated 100.1.0 unused
  69. *
  70. * @var boolean
  71. */
  72. protected $isProductHasSwatchAttribute;
  73. /**
  74. * @var SwatchAttributesProvider
  75. */
  76. private $swatchAttributesProvider;
  77. /**
  78. * @var UrlBuilder
  79. */
  80. private $imageUrlBuilder;
  81. /**
  82. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  83. * @param Context $context
  84. * @param ArrayUtils $arrayUtils
  85. * @param EncoderInterface $jsonEncoder
  86. * @param Data $helper
  87. * @param CatalogProduct $catalogProduct
  88. * @param CurrentCustomer $currentCustomer
  89. * @param PriceCurrencyInterface $priceCurrency
  90. * @param ConfigurableAttributeData $configurableAttributeData
  91. * @param SwatchData $swatchHelper
  92. * @param Media $swatchMediaHelper
  93. * @param array $data
  94. * @param SwatchAttributesProvider|null $swatchAttributesProvider
  95. * @param UrlBuilder|null $imageUrlBuilder
  96. */
  97. public function __construct(
  98. Context $context,
  99. ArrayUtils $arrayUtils,
  100. EncoderInterface $jsonEncoder,
  101. Data $helper,
  102. CatalogProduct $catalogProduct,
  103. CurrentCustomer $currentCustomer,
  104. PriceCurrencyInterface $priceCurrency,
  105. ConfigurableAttributeData $configurableAttributeData,
  106. SwatchData $swatchHelper,
  107. Media $swatchMediaHelper,
  108. array $data = [],
  109. SwatchAttributesProvider $swatchAttributesProvider = null,
  110. UrlBuilder $imageUrlBuilder = null
  111. ) {
  112. $this->swatchHelper = $swatchHelper;
  113. $this->swatchMediaHelper = $swatchMediaHelper;
  114. $this->swatchAttributesProvider = $swatchAttributesProvider
  115. ?: ObjectManager::getInstance()->get(SwatchAttributesProvider::class);
  116. $this->imageUrlBuilder = $imageUrlBuilder ?? ObjectManager::getInstance()->get(UrlBuilder::class);
  117. parent::__construct(
  118. $context,
  119. $arrayUtils,
  120. $jsonEncoder,
  121. $helper,
  122. $catalogProduct,
  123. $currentCustomer,
  124. $priceCurrency,
  125. $configurableAttributeData,
  126. $data
  127. );
  128. }
  129. /**
  130. * Get Key for caching block content
  131. *
  132. * @return string
  133. * @since 100.1.0
  134. */
  135. public function getCacheKey()
  136. {
  137. return parent::getCacheKey() . '-' . $this->getProduct()->getId();
  138. }
  139. /**
  140. * Get block cache life time
  141. *
  142. * @return int
  143. * @since 100.1.0
  144. */
  145. protected function getCacheLifetime()
  146. {
  147. return parent::hasCacheLifetime() ? parent::getCacheLifetime() : 3600;
  148. }
  149. /**
  150. * Get Swatch config data
  151. *
  152. * @return string
  153. */
  154. public function getJsonSwatchConfig()
  155. {
  156. $attributesData = $this->getSwatchAttributesData();
  157. $allOptionIds = $this->getConfigurableOptionsIds($attributesData);
  158. $swatchesData = $this->swatchHelper->getSwatchesByOptionsId($allOptionIds);
  159. $config = [];
  160. foreach ($attributesData as $attributeId => $attributeDataArray) {
  161. if (isset($attributeDataArray['options'])) {
  162. $config[$attributeId] = $this->addSwatchDataForAttribute(
  163. $attributeDataArray['options'],
  164. $swatchesData,
  165. $attributeDataArray
  166. );
  167. }
  168. }
  169. return $this->jsonEncoder->encode($config);
  170. }
  171. /**
  172. * Get number of swatches from config to show on product listing.
  173. * Other swatches can be shown after click button 'Show more'
  174. *
  175. * @return string
  176. */
  177. public function getNumberSwatchesPerProduct()
  178. {
  179. return $this->_scopeConfig->getValue(
  180. 'catalog/frontend/swatches_per_product',
  181. ScopeInterface::SCOPE_STORE
  182. );
  183. }
  184. /**
  185. * Set product to block
  186. *
  187. * @param Product $product
  188. * @return $this
  189. */
  190. public function setProduct(Product $product)
  191. {
  192. $this->product = $product;
  193. return $this;
  194. }
  195. /**
  196. * Override parent function
  197. *
  198. * @return Product
  199. */
  200. public function getProduct()
  201. {
  202. if (!$this->product) {
  203. $this->product = parent::getProduct();
  204. }
  205. return $this->product;
  206. }
  207. /**
  208. * @return array
  209. */
  210. protected function getSwatchAttributesData()
  211. {
  212. return $this->swatchHelper->getSwatchAttributesAsArray($this->getProduct());
  213. }
  214. /**
  215. * @deprecated 100.2.0 Method isProductHasSwatchAttribute() is used instead of this.
  216. *
  217. * @codeCoverageIgnore
  218. * @return void
  219. */
  220. protected function initIsProductHasSwatchAttribute()
  221. {
  222. $this->isProductHasSwatchAttribute = $this->swatchHelper->isProductHasSwatch($this->getProduct());
  223. }
  224. /**
  225. * Check that product has at least one swatch attribute
  226. *
  227. * @return bool
  228. * @since 100.1.5
  229. */
  230. protected function isProductHasSwatchAttribute()
  231. {
  232. $swatchAttributes = $this->swatchAttributesProvider->provide($this->getProduct());
  233. return count($swatchAttributes) > 0;
  234. }
  235. /**
  236. * Add Swatch Data for attribute
  237. *
  238. * @param array $options
  239. * @param array $swatchesCollectionArray
  240. * @param array $attributeDataArray
  241. * @return array
  242. */
  243. protected function addSwatchDataForAttribute(
  244. array $options,
  245. array $swatchesCollectionArray,
  246. array $attributeDataArray
  247. ) {
  248. $result = [];
  249. foreach ($options as $optionId => $label) {
  250. if (isset($swatchesCollectionArray[$optionId])) {
  251. $result[$optionId] = $this->extractNecessarySwatchData($swatchesCollectionArray[$optionId]);
  252. $result[$optionId] = $this->addAdditionalMediaData($result[$optionId], $optionId, $attributeDataArray);
  253. $result[$optionId]['label'] = $label;
  254. }
  255. }
  256. return $result;
  257. }
  258. /**
  259. * Add media from variation
  260. *
  261. * @param array $swatch
  262. * @param integer $optionId
  263. * @param array $attributeDataArray
  264. * @return array
  265. */
  266. protected function addAdditionalMediaData(array $swatch, $optionId, array $attributeDataArray)
  267. {
  268. if (isset($attributeDataArray['use_product_image_for_swatch'])
  269. && $attributeDataArray['use_product_image_for_swatch']
  270. ) {
  271. $variationMedia = $this->getVariationMedia($attributeDataArray['attribute_code'], $optionId);
  272. if (! empty($variationMedia)) {
  273. $swatch['type'] = Swatch::SWATCH_TYPE_VISUAL_IMAGE;
  274. $swatch = array_merge($swatch, $variationMedia);
  275. }
  276. }
  277. return $swatch;
  278. }
  279. /**
  280. * Retrieve Swatch data for config
  281. *
  282. * @param array $swatchDataArray
  283. * @return array
  284. */
  285. protected function extractNecessarySwatchData(array $swatchDataArray)
  286. {
  287. $result['type'] = $swatchDataArray['type'];
  288. if ($result['type'] == Swatch::SWATCH_TYPE_VISUAL_IMAGE && !empty($swatchDataArray['value'])) {
  289. $result['value'] = $this->swatchMediaHelper->getSwatchAttributeImage(
  290. Swatch::SWATCH_IMAGE_NAME,
  291. $swatchDataArray['value']
  292. );
  293. $result['thumb'] = $this->swatchMediaHelper->getSwatchAttributeImage(
  294. Swatch::SWATCH_THUMBNAIL_NAME,
  295. $swatchDataArray['value']
  296. );
  297. } else {
  298. $result['value'] = $swatchDataArray['value'];
  299. }
  300. return $result;
  301. }
  302. /**
  303. * Generate Product Media array
  304. *
  305. * @param string $attributeCode
  306. * @param integer $optionId
  307. * @return array
  308. */
  309. protected function getVariationMedia($attributeCode, $optionId)
  310. {
  311. $variationProduct = $this->swatchHelper->loadFirstVariationWithSwatchImage(
  312. $this->getProduct(),
  313. [$attributeCode => $optionId]
  314. );
  315. if (!$variationProduct) {
  316. $variationProduct = $this->swatchHelper->loadFirstVariationWithImage(
  317. $this->getProduct(),
  318. [$attributeCode => $optionId]
  319. );
  320. }
  321. $variationMediaArray = [];
  322. if ($variationProduct) {
  323. $variationMediaArray = [
  324. 'value' => $this->getSwatchProductImage($variationProduct, Swatch::SWATCH_IMAGE_NAME),
  325. 'thumb' => $this->getSwatchProductImage($variationProduct, Swatch::SWATCH_THUMBNAIL_NAME),
  326. ];
  327. }
  328. return $variationMediaArray;
  329. }
  330. /**
  331. * @param Product $childProduct
  332. * @param string $imageType
  333. * @return string
  334. */
  335. protected function getSwatchProductImage(Product $childProduct, $imageType)
  336. {
  337. if ($this->isProductHasImage($childProduct, Swatch::SWATCH_IMAGE_NAME)) {
  338. $swatchImageId = $imageType;
  339. $imageAttributes = ['type' => Swatch::SWATCH_IMAGE_NAME];
  340. } elseif ($this->isProductHasImage($childProduct, 'image')) {
  341. $swatchImageId = $imageType == Swatch::SWATCH_IMAGE_NAME ? 'swatch_image_base' : 'swatch_thumb_base';
  342. $imageAttributes = ['type' => 'image'];
  343. }
  344. if (!empty($swatchImageId) && !empty($imageAttributes['type'])) {
  345. return $this->imageUrlBuilder->getUrl($childProduct->getData($imageAttributes['type']), $swatchImageId);
  346. }
  347. }
  348. /**
  349. * @param Product $product
  350. * @param string $imageType
  351. * @return bool
  352. */
  353. protected function isProductHasImage(Product $product, $imageType)
  354. {
  355. return $product->getData($imageType) !== null && $product->getData($imageType) != SwatchData::EMPTY_IMAGE_VALUE;
  356. }
  357. /**
  358. * @param array $attributeData
  359. * @return array
  360. * @since 100.0.3
  361. */
  362. protected function getConfigurableOptionsIds(array $attributeData)
  363. {
  364. $ids = [];
  365. foreach ($this->getAllowProducts() as $product) {
  366. /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute $attribute */
  367. foreach ($this->helper->getAllowAttributes($this->getProduct()) as $attribute) {
  368. $productAttribute = $attribute->getProductAttribute();
  369. $productAttributeId = $productAttribute->getId();
  370. if (isset($attributeData[$productAttributeId])) {
  371. $ids[$product->getData($productAttribute->getAttributeCode())] = 1;
  372. }
  373. }
  374. }
  375. return array_keys($ids);
  376. }
  377. /**
  378. * Produce and return block's html output
  379. *
  380. * @return string
  381. * @since 100.2.0
  382. */
  383. public function toHtml()
  384. {
  385. $this->setTemplate(
  386. $this->getRendererTemplate()
  387. );
  388. return parent::toHtml();
  389. }
  390. /**
  391. * Return HTML code
  392. *
  393. * @return string
  394. */
  395. protected function _toHtml()
  396. {
  397. return $this->getHtmlOutput();
  398. }
  399. /**
  400. * Return renderer template
  401. *
  402. * Template for product with swatches is different from product without swatches
  403. *
  404. * @return string
  405. */
  406. protected function getRendererTemplate()
  407. {
  408. return $this->isProductHasSwatchAttribute() ?
  409. self::SWATCH_RENDERER_TEMPLATE : self::CONFIGURABLE_RENDERER_TEMPLATE;
  410. }
  411. /**
  412. * @deprecated 100.1.5 Now is used _toHtml() directly
  413. * @return string
  414. */
  415. protected function getHtmlOutput()
  416. {
  417. return parent::_toHtml();
  418. }
  419. /**
  420. * @return string
  421. */
  422. public function getMediaCallback()
  423. {
  424. return $this->getUrl(self::MEDIA_CALLBACK_ACTION, ['_secure' => $this->getRequest()->isSecure()]);
  425. }
  426. /**
  427. * Return unique ID(s) for each object in system
  428. *
  429. * @return string[]
  430. * @since 100.1.0
  431. */
  432. public function getIdentities()
  433. {
  434. if ($this->product instanceof \Magento\Framework\DataObject\IdentityInterface) {
  435. return $this->product->getIdentities();
  436. } else {
  437. return [];
  438. }
  439. }
  440. /**
  441. * Get Swatch image size config data.
  442. *
  443. * @return string
  444. * @since 100.2.5
  445. */
  446. public function getJsonSwatchSizeConfig()
  447. {
  448. $imageConfig = $this->swatchMediaHelper->getImageConfig();
  449. $sizeConfig = [];
  450. $sizeConfig[self::SWATCH_IMAGE_NAME]['width'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['width'];
  451. $sizeConfig[self::SWATCH_IMAGE_NAME]['height'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['height'];
  452. $sizeConfig[self::SWATCH_THUMBNAIL_NAME]['height'] = $imageConfig[Swatch::SWATCH_THUMBNAIL_NAME]['height'];
  453. $sizeConfig[self::SWATCH_THUMBNAIL_NAME]['width'] = $imageConfig[Swatch::SWATCH_THUMBNAIL_NAME]['width'];
  454. return $this->jsonEncoder->encode($sizeConfig);
  455. }
  456. }