123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Swatches\Helper;
- use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
- use Magento\Catalog\Api\Data\ProductInterface as Product;
- use Magento\Catalog\Api\ProductRepositoryInterface;
- use Magento\Catalog\Model\Product as ModelProduct;
- use Magento\Catalog\Model\Product\Image\UrlBuilder;
- use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
- use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
- use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
- use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
- use Magento\Framework\App\ObjectManager;
- use Magento\Framework\Serialize\Serializer\Json;
- use Magento\Store\Model\StoreManagerInterface;
- use Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory as SwatchCollectionFactory;
- use Magento\Swatches\Model\Swatch;
- use Magento\Swatches\Model\SwatchAttributesProvider;
- use Magento\Swatches\Model\SwatchAttributeType;
- /**
- * Class Helper Data
- *
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
- class Data
- {
- /**
- * When we init media gallery empty image types contain this value.
- */
- const EMPTY_IMAGE_VALUE = 'no_selection';
- /**
- * Default store ID
- */
- const DEFAULT_STORE_ID = 0;
- /**
- * @var CollectionFactory
- */
- protected $productCollectionFactory;
- /**
- * @var ProductRepositoryInterface
- */
- protected $productRepository;
- /**
- * @var StoreManagerInterface
- */
- protected $storeManager;
- /**
- * @var SwatchCollectionFactory
- */
- protected $swatchCollectionFactory;
- /**
- * Product metadata pool
- *
- * @var \Magento\Framework\EntityManager\MetadataPool
- */
- private $metadataPool;
- /**
- * @var SwatchAttributesProvider
- */
- private $swatchAttributesProvider;
- /**
- * Data key which should populated to Attribute entity from "additional_data" field
- *
- * @var array
- */
- protected $eavAttributeAdditionalDataKeys = [
- Swatch::SWATCH_INPUT_TYPE_KEY,
- 'update_product_preview_image',
- 'use_product_image_for_swatch'
- ];
- /**
- * Serializer to/from JSON.
- *
- * @var Json
- */
- private $serializer;
- /**
- * @var SwatchAttributeType
- */
- private $swatchTypeChecker;
- /**
- * @var UrlBuilder
- */
- private $imageUrlBuilder;
- /**
- * @param CollectionFactory $productCollectionFactory
- * @param ProductRepositoryInterface $productRepository
- * @param StoreManagerInterface $storeManager
- * @param SwatchCollectionFactory $swatchCollectionFactory
- * @param UrlBuilder $urlBuilder
- * @param Json|null $serializer
- * @param SwatchAttributesProvider $swatchAttributesProvider
- * @param SwatchAttributeType|null $swatchTypeChecker
- */
- public function __construct(
- CollectionFactory $productCollectionFactory,
- ProductRepositoryInterface $productRepository,
- StoreManagerInterface $storeManager,
- SwatchCollectionFactory $swatchCollectionFactory,
- UrlBuilder $urlBuilder,
- Json $serializer = null,
- SwatchAttributesProvider $swatchAttributesProvider = null,
- SwatchAttributeType $swatchTypeChecker = null
- ) {
- $this->productCollectionFactory = $productCollectionFactory;
- $this->productRepository = $productRepository;
- $this->storeManager = $storeManager;
- $this->swatchCollectionFactory = $swatchCollectionFactory;
- $this->serializer = $serializer ?: ObjectManager::getInstance()->create(Json::class);
- $this->swatchAttributesProvider = $swatchAttributesProvider
- ?: ObjectManager::getInstance()->get(SwatchAttributesProvider::class);
- $this->swatchTypeChecker = $swatchTypeChecker
- ?: ObjectManager::getInstance()->create(SwatchAttributeType::class);
- $this->imageUrlBuilder = $urlBuilder;
- }
- /**
- * Assemble Additional Data for Eav Attribute
- *
- * @param Attribute $attribute
- * @return $this
- */
- public function assembleAdditionalDataEavAttribute(Attribute $attribute)
- {
- $initialAdditionalData = [];
- $additionalData = (string)$attribute->getData('additional_data');
- if (!empty($additionalData)) {
- $additionalData = $this->serializer->unserialize($additionalData);
- if (is_array($additionalData)) {
- $initialAdditionalData = $additionalData;
- }
- }
- $dataToAdd = [];
- foreach ($this->eavAttributeAdditionalDataKeys as $key) {
- $dataValue = $attribute->getData($key);
- if (null !== $dataValue) {
- $dataToAdd[$key] = $dataValue;
- }
- }
- $additionalData = array_merge($initialAdditionalData, $dataToAdd);
- $attribute->setData('additional_data', $this->serializer->serialize($additionalData));
- return $this;
- }
- /**
- * Check is media attribute available
- *
- * @param ModelProduct $product
- * @param string $attributeCode
- * @return bool
- */
- private function isMediaAvailable(ModelProduct $product, string $attributeCode): bool
- {
- $isAvailable = false;
- $mediaGallery = $product->getMediaGalleryEntries();
- foreach ($mediaGallery as $mediaEntry) {
- if (in_array($attributeCode, $mediaEntry->getTypes(), true)) {
- $isAvailable = !$mediaEntry->isDisabled();
- break;
- }
- }
- return $isAvailable;
- }
- /**
- * Load first variation
- *
- * @param string $attributeCode swatch_image|image
- * @param ModelProduct $configurableProduct
- * @param array $requiredAttributes
- * @return bool|Product
- */
- private function loadFirstVariation($attributeCode, ModelProduct $configurableProduct, array $requiredAttributes)
- {
- if ($this->isProductHasSwatch($configurableProduct)) {
- $usedProducts = $configurableProduct->getTypeInstance()->getUsedProducts($configurableProduct);
- foreach ($usedProducts as $simpleProduct) {
- if (!array_diff_assoc($requiredAttributes, $simpleProduct->getData())
- && $this->isMediaAvailable($simpleProduct, $attributeCode)
- ) {
- return $simpleProduct;
- }
- }
- }
- return false;
- }
- /**
- * Load first variation with swatch image
- *
- * @param Product $configurableProduct
- * @param array $requiredAttributes
- * @return bool|Product
- */
- public function loadFirstVariationWithSwatchImage(Product $configurableProduct, array $requiredAttributes)
- {
- return $this->loadFirstVariation('swatch_image', $configurableProduct, $requiredAttributes);
- }
- /**
- * Load first variation with image
- *
- * @param Product $configurableProduct
- * @param array $requiredAttributes
- * @return bool|Product
- */
- public function loadFirstVariationWithImage(Product $configurableProduct, array $requiredAttributes)
- {
- return $this->loadFirstVariation('image', $configurableProduct, $requiredAttributes);
- }
- /**
- * Load Variation Product using fallback
- *
- * @param Product $parentProduct
- * @param array $attributes
- * @return bool|Product
- */
- public function loadVariationByFallback(Product $parentProduct, array $attributes)
- {
- if (!$this->isProductHasSwatch($parentProduct)) {
- return false;
- }
- $productCollection = $this->productCollectionFactory->create();
- $productLinkedFiled = $this->getMetadataPool()
- ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
- ->getLinkField();
- $parentId = $parentProduct->getData($productLinkedFiled);
- $this->addFilterByParent($productCollection, $parentId);
- $configurableAttributes = $this->getAttributesFromConfigurable($parentProduct);
- $resultAttributesToFilter = [];
- foreach ($configurableAttributes as $attribute) {
- $attributeCode = $attribute->getData('attribute_code');
- if (array_key_exists($attributeCode, $attributes)) {
- $resultAttributesToFilter[$attributeCode] = $attributes[$attributeCode];
- }
- }
- $this->addFilterByAttributes($productCollection, $resultAttributesToFilter);
- $variationProduct = $productCollection->getFirstItem();
- if ($variationProduct && $variationProduct->getId()) {
- return $this->productRepository->getById($variationProduct->getId());
- }
- return false;
- }
- /**
- * Add filter by attribute
- *
- * @param ProductCollection $productCollection
- * @param array $attributes
- * @return void
- */
- private function addFilterByAttributes(ProductCollection $productCollection, array $attributes)
- {
- foreach ($attributes as $code => $option) {
- $productCollection->addAttributeToFilter($code, ['eq' => $option]);
- }
- }
- /**
- * Add filter by parent
- *
- * @param ProductCollection $productCollection
- * @param integer $parentId
- * @return void
- */
- private function addFilterByParent(ProductCollection $productCollection, $parentId)
- {
- $tableProductRelation = $productCollection->getTable('catalog_product_relation');
- $productCollection
- ->getSelect()
- ->join(
- ['pr' => $tableProductRelation],
- 'e.entity_id = pr.child_id'
- )
- ->where('pr.parent_id = ?', $parentId);
- }
- /**
- * Method getting full media gallery for current Product
- *
- * Array structure: [
- * ['image'] => 'http://url/pub/media/catalog/product/2/0/blabla.jpg',
- * ['mediaGallery'] => [
- * galleryImageId1 => simpleProductImage1.jpg,
- * galleryImageId2 => simpleProductImage2.jpg,
- * ...,
- * ]
- * ]
- *
- * @param ModelProduct $product
- *
- * @return array
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- public function getProductMediaGallery(ModelProduct $product): array
- {
- $baseImage = null;
- $gallery = [];
- $mediaGallery = $product->getMediaGalleryEntries();
- /** @var ProductAttributeMediaGalleryEntryInterface $mediaEntry */
- foreach ($mediaGallery as $mediaEntry) {
- if ($mediaEntry->isDisabled()) {
- continue;
- }
- if (!$baseImage || $this->isMainImage($mediaEntry)) {
- $baseImage = $mediaEntry;
- }
- $gallery[$mediaEntry->getId()] = $this->collectImageData($mediaEntry);
- }
- if (!$baseImage) {
- return [];
- }
- $resultGallery = $this->collectImageData($baseImage);
- $resultGallery['gallery'] = $gallery;
- return $resultGallery;
- }
- /**
- * Checks if image is main image in gallery
- *
- * @param ProductAttributeMediaGalleryEntryInterface $mediaEntry
- * @return bool
- */
- private function isMainImage(ProductAttributeMediaGalleryEntryInterface $mediaEntry): bool
- {
- return in_array('image', $mediaEntry->getTypes(), true);
- }
- /**
- * Returns image data for swatches
- *
- * @param ProductAttributeMediaGalleryEntryInterface $mediaEntry
- * @return array
- */
- private function collectImageData(ProductAttributeMediaGalleryEntryInterface $mediaEntry): array
- {
- $image = $this->getAllSizeImages($mediaEntry->getFile());
- $image[ProductAttributeMediaGalleryEntryInterface::POSITION] = $mediaEntry->getPosition();
- $image['isMain'] =$this->isMainImage($mediaEntry);
- return $image;
- }
- /**
- * Get all size images
- *
- * @param string $imageFile
- * @return array
- */
- private function getAllSizeImages($imageFile)
- {
- return [
- 'large' => $this->imageUrlBuilder->getUrl($imageFile, 'product_swatch_image_large'),
- 'medium' => $this->imageUrlBuilder->getUrl($imageFile, 'product_swatch_image_medium'),
- 'small' => $this->imageUrlBuilder->getUrl($imageFile, 'product_swatch_image_small')
- ];
- }
- /**
- * Retrieve collection of Swatch attributes
- *
- * @param Product $product
- * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute[]
- */
- private function getSwatchAttributes(Product $product)
- {
- $swatchAttributes = $this->swatchAttributesProvider->provide($product);
- return $swatchAttributes;
- }
- /**
- * Retrieve collection of Eav Attributes from Configurable product
- *
- * @param Product $product
- * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute[]
- */
- public function getAttributesFromConfigurable(Product $product)
- {
- $result = [];
- $typeInstance = $product->getTypeInstance();
- if ($typeInstance instanceof Configurable) {
- $configurableAttributes = $typeInstance->getConfigurableAttributes($product);
- /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute $configurableAttribute */
- foreach ($configurableAttributes as $configurableAttribute) {
- /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
- $attribute = $configurableAttribute->getProductAttribute();
- $result[] = $attribute;
- }
- }
- return $result;
- }
- /**
- * Retrieve all visible Swatch attributes for current product.
- *
- * @param Product $product
- * @return array
- */
- public function getSwatchAttributesAsArray(Product $product)
- {
- $result = [];
- $swatchAttributes = $this->getSwatchAttributes($product);
- foreach ($swatchAttributes as $swatchAttribute) {
- $swatchAttribute->setStoreId($this->storeManager->getStore()->getId());
- $attributeData = $swatchAttribute->getData();
- foreach ($swatchAttribute->getSource()->getAllOptions(false) as $option) {
- $attributeData['options'][$option['value']] = $option['label'];
- }
- $result[$attributeData['attribute_id']] = $attributeData;
- }
- return $result;
- }
- /**
- * @var array
- */
- private $swatchesCache = [];
- /**
- * Get swatch options by option id's according to fallback logic
- *
- * @param array $optionIds
- * @return array
- */
- public function getSwatchesByOptionsId(array $optionIds)
- {
- $swatches = $this->getCachedSwatches($optionIds);
- if (count($swatches) !== count($optionIds)) {
- $swatchOptionIds = array_diff($optionIds, array_keys($swatches));
- /** @var \Magento\Swatches\Model\ResourceModel\Swatch\Collection $swatchCollection */
- $swatchCollection = $this->swatchCollectionFactory->create();
- $swatchCollection->addFilterByOptionsIds($swatchOptionIds);
- $swatches = [];
- $fallbackValues = [];
- $currentStoreId = $this->storeManager->getStore()->getId();
- foreach ($swatchCollection as $item) {
- if ($item['type'] != Swatch::SWATCH_TYPE_TEXTUAL) {
- $swatches[$item['option_id']] = $item->getData();
- } elseif ($item['store_id'] == $currentStoreId && $item['value'] != '') {
- $fallbackValues[$item['option_id']][$currentStoreId] = $item->getData();
- } elseif ($item['store_id'] == self::DEFAULT_STORE_ID) {
- $fallbackValues[$item['option_id']][self::DEFAULT_STORE_ID] = $item->getData();
- }
- }
- if (!empty($fallbackValues)) {
- $swatches = $this->addFallbackOptions($fallbackValues, $swatches);
- }
- $this->setCachedSwatches($swatchOptionIds, $swatches);
- }
- return array_filter($this->getCachedSwatches($optionIds));
- }
- /**
- * Get cached swatches
- *
- * @param array $optionIds
- * @return array
- */
- private function getCachedSwatches(array $optionIds)
- {
- return array_intersect_key($this->swatchesCache, array_combine($optionIds, $optionIds));
- }
- /**
- * Cache swatch. If no swathes found for specific option id - set null for prevent double call
- *
- * @param array $optionIds
- * @param array $swatches
- * @return void
- */
- private function setCachedSwatches(array $optionIds, array $swatches)
- {
- foreach ($optionIds as $optionId) {
- $this->swatchesCache[$optionId] = isset($swatches[$optionId]) ? $swatches[$optionId] : null;
- }
- }
- /**
- * Add fallback options
- *
- * @param array $fallbackValues
- * @param array $swatches
- * @return array
- */
- private function addFallbackOptions(array $fallbackValues, array $swatches)
- {
- $currentStoreId = $this->storeManager->getStore()->getId();
- foreach ($fallbackValues as $optionId => $optionsArray) {
- if (isset($optionsArray[$currentStoreId]['type'], $swatches[$optionId]['type'])
- && $swatches[$optionId]['type'] === $optionsArray[$currentStoreId]['type']
- ) {
- $swatches[$optionId] = $optionsArray[$currentStoreId];
- } elseif (isset($optionsArray[$currentStoreId])) {
- $swatches[$optionId] = $optionsArray[$currentStoreId];
- } elseif (isset($optionsArray[self::DEFAULT_STORE_ID])) {
- $swatches[$optionId] = $optionsArray[self::DEFAULT_STORE_ID];
- }
- }
- return $swatches;
- }
- /**
- * Check if the Product has Swatch attributes
- *
- * @param Product $product
- * @return bool
- */
- public function isProductHasSwatch(Product $product)
- {
- return !empty($this->getSwatchAttributes($product));
- }
- /**
- * Check if an attribute is Swatch
- *
- * @param Attribute $attribute
- * @return bool
- */
- public function isSwatchAttribute(Attribute $attribute)
- {
- return $this->swatchTypeChecker->isSwatchAttribute($attribute);
- }
- /**
- * Is attribute Visual Swatch
- *
- * @param Attribute $attribute
- * @return bool
- */
- public function isVisualSwatch(Attribute $attribute)
- {
- return $this->swatchTypeChecker->isVisualSwatch($attribute);
- }
- /**
- * Is attribute Textual Swatch
- *
- * @param Attribute $attribute
- * @return bool
- */
- public function isTextSwatch(Attribute $attribute)
- {
- return $this->swatchTypeChecker->isTextSwatch($attribute);
- }
- /**
- * Get product metadata pool.
- *
- * @return \Magento\Framework\EntityManager\MetadataPool
- * @deprecared
- */
- protected function getMetadataPool()
- {
- if (!$this->metadataPool) {
- $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\EntityManager\MetadataPool::class);
- }
- return $this->metadataPool;
- }
- }
|