Model/Product/Feed/Builder.php (241 lines of code) (raw):

<?php /** * Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved */ namespace Facebook\BusinessExtension\Model\Product\Feed; use Facebook\BusinessExtension\Helper\FBEHelper; use Facebook\BusinessExtension\Model\Feed\EnhancedCatalogHelper; use Facebook\BusinessExtension\Model\Product\Feed\Builder\Inventory; use Facebook\BusinessExtension\Model\Product\Feed\Builder\Tools as BuilderTools; use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Framework\Exception\LocalizedException; class Builder { const ATTR_RETAILER_ID = 'id'; const ATTR_ITEM_GROUP_ID = 'item_group_id'; const ATTR_DESCRIPTION = 'description'; const ATTR_RICH_DESCRIPTION = 'rich_text_description'; const ATTR_URL = 'link'; const ATTR_IMAGE_URL = 'image_link'; const ATTR_ADDITIONAL_IMAGE_URL = 'additional_image_link'; const ATTR_BRAND = 'brand'; const ATTR_SIZE = 'size'; const ATTR_COLOR = 'color'; const ATTR_CONDITION = 'condition'; const ATTR_AVAILABILITY = 'availability'; const ATTR_INVENTORY = 'inventory'; const ATTR_PRICE = 'price'; const ATTR_SALE_PRICE = 'sale_price'; const ATTR_NAME = 'title'; const ATTR_PRODUCT_TYPE = 'product_type'; const ATTR_PRODUCT_CATEGORY = 'google_product_category'; /** * @var FBEHelper */ protected $fbeHelper; /** * @var string */ protected $defaultBrand; /** * @var CategoryCollectionFactory */ protected $categoryCollectionFactory; /** * @var BuilderTools */ protected $builderTools; /** * @var Inventory */ protected $inventory; /** * @var EnhancedCatalogHelper */ protected $enhancedCatalogHelper; /** * @param FBEHelper $fbeHelper * @param CategoryCollectionFactory $categoryCollectionFactory * @param BuilderTools $builderTools * @param Inventory $inventory * @param EnhancedCatalogHelper $enhancedCatalogHelper */ public function __construct( FBEHelper $fbeHelper, CategoryCollectionFactory $categoryCollectionFactory, BuilderTools $builderTools, Inventory $inventory, EnhancedCatalogHelper $enhancedCatalogHelper ) { $this->fbeHelper = $fbeHelper; $this->categoryCollectionFactory = $categoryCollectionFactory; $this->builderTools = $builderTools; $this->inventory = $inventory; $this->enhancedCatalogHelper = $enhancedCatalogHelper; } /** * @return string */ protected function getDefaultBrand() { if (!$this->defaultBrand) { $this->defaultBrand = $this->trimAttribute(self::ATTR_BRAND, $this->fbeHelper->getStoreName()); } return $this->defaultBrand; } /** * @param Product $product * @return string */ protected function getProductUrl(Product $product) { $parentUrl = $product->getParentProductUrl(); // use parent product URL if a simple product has a parent and is not visible individually $url = (!$product->isVisibleInSiteVisibility() && $parentUrl) ? $parentUrl : $product->getProductUrl(); return $this->builderTools->replaceLocalUrlWithDummyUrl($url); } /** * @param Product $product * @return array */ protected function getProductImages(Product $product) { $mainImage = $product->getImage(); $additionalImages = []; if (!empty($product->getMediaGalleryImages())) { foreach ($product->getMediaGalleryImages() as $img) { if ($img['file'] === $mainImage) { continue; } $additionalImages[] = $this->builderTools->replaceLocalUrlWithDummyUrl($img['url']); } } return [ 'main_image' => $this->builderTools->replaceLocalUrlWithDummyUrl( $this->fbeHelper->getBaseUrlMedia() . 'catalog/product' . $mainImage ), 'additional_images' => array_slice($additionalImages, 0, 10), ]; } /** * @param Product $product * @return string */ protected function getProductPrice(Product $product) { return $this->builderTools->formatPrice($product->getPrice()); } /** * @param Product $product * @return string */ protected function getProductSalePrice(Product $product) { return $product->getSpecialPrice() ? $this->builderTools->formatPrice($product->getSpecialPrice()) : ''; } /** * @param Product $product * @return string * @throws LocalizedException */ protected function getCategoryPath(Product $product) { $categoryIds = $product->getCategoryIds(); if (empty($categoryIds)) { return ''; } $categoryNames = []; $categories = $this->categoryCollectionFactory->create() ->addAttributeToSelect('name') ->addAttributeToFilter('entity_id', $categoryIds) ->setOrder('position', 'ASC'); /** @var CategoryInterface $category */ foreach ($categories as $category) { $categoryNames[] = $category->getName(); } return implode(' > ', $categoryNames); } /** * @param $attrName * @param $attrValue * @return string */ protected function trimAttribute($attrName, $attrValue) { $attrValue = trim($attrValue); // Facebook Product attributes // ref: https://developers.facebook.com/docs/commerce-platform/catalog/fields switch ($attrName) { case self::ATTR_RETAILER_ID: case self::ATTR_URL: case self::ATTR_IMAGE_URL: case self::ATTR_CONDITION: case self::ATTR_AVAILABILITY: case self::ATTR_INVENTORY: case self::ATTR_PRICE: case self::ATTR_SIZE: case self::ATTR_COLOR: if ($attrValue) { return $attrValue; } break; case self::ATTR_BRAND: if ($attrValue) { // brand max size: 70 return mb_strlen($attrValue) > 70 ? mb_substr($attrValue, 0, 70) : $attrValue; } break; case self::ATTR_NAME: if ($attrValue) { // title max size: 100 return mb_strlen($attrValue) > 100 ? mb_substr($attrValue, 0, 100) : $attrValue; } break; case self::ATTR_DESCRIPTION: if ($attrValue) { // description max size: 5000 return mb_strlen($attrValue) > 5000 ? mb_substr($attrValue, 0, 5000) : $attrValue; } break; case self::ATTR_PRODUCT_TYPE: // product_type max size: 750 if ($attrValue) { return mb_strlen($attrValue) > 750 ? mb_substr($attrValue, mb_strlen($attrValue) - 750, 750) : $attrValue; } break; } return ''; } /** * @param Product $product * @return string */ protected function getDescription(Product $product) { // 'Description' is required by default but can be made // optional through the magento admin panel. // Try using the short description and title if it doesn't exist. $description = $this->trimAttribute( self::ATTR_DESCRIPTION, $product->getDescription() ); if (!$description) { $description = $this->trimAttribute( self::ATTR_DESCRIPTION, $product->getShortDescription() ); } $title = $product->getName(); $productTitle = $this->trimAttribute(self::ATTR_NAME, $title); $description = $description ?: $productTitle; // description can't be all uppercase $description = $this->builderTools->htmlDecode($description); $description = addslashes($this->builderTools->lowercaseIfAllCaps($description)); return $description; } /** * @param Product $product * @return string */ protected function getCondition(Product $product) { $condition = null; if ($product->getData('condition')) { $condition = $this->trimAttribute(self::ATTR_CONDITION, $product->getAttributeText('condition')); } return ($condition && in_array($condition, ['new', 'refurbished', 'used'])) ? $condition : 'new'; } /** * @param Product $product * @param $attribute * @return string|false */ private function getCorrectText(Product $product, $attribute) { if ($product->getData($attribute)) { $text = $product->getAttributeText($attribute); if (!$text) { $text = $product->getData($attribute); } return $text; } return false; } /** * @param Product $product * @return string|null */ protected function getBrand(Product $product) { $brand = $this->getCorrectText($product, 'brand'); if (!$brand) { $brand = $this->getCorrectText($product, 'manufacturer'); } if (!$brand) { $brand = $this->getDefaultBrand(); } return $this->trimAttribute(self::ATTR_BRAND, $brand); } /** * @param Product $product * @return string */ protected function getItemGroupId(Product $product) { $configurableSettings = $product->getConfigurableSettings() ?: []; return array_key_exists('item_group_id', $configurableSettings) ? $configurableSettings['item_group_id'] : ''; } /** * @param Product $product * @return string */ protected function getColor(Product $product) { $configurableSettings = $product->getConfigurableSettings() ?: []; return array_key_exists('color', $configurableSettings) ? $configurableSettings['color'] : ''; } /** * @param $product * @return string */ protected function getSize($product) { $configurableSettings = $product->getConfigurableSettings() ?: []; return array_key_exists('size', $configurableSettings) ? $configurableSettings['size'] : ''; } /** * @param Product $product * @return array * @throws LocalizedException */ public function buildProductEntry(Product $product) { $this->inventory->initInventoryForProduct($product); $productType = $this->trimAttribute(self::ATTR_PRODUCT_TYPE, $this->getCategoryPath($product)); $title = $product->getName(); $productTitle = $this->trimAttribute(self::ATTR_NAME, $title); $images = $this->getProductImages($product); $imageUrl = $this->trimAttribute(self::ATTR_IMAGE_URL, $images['main_image']); $entry = [ self::ATTR_RETAILER_ID => $this->trimAttribute(self::ATTR_RETAILER_ID, $product->getId()), self::ATTR_ITEM_GROUP_ID => $this->getItemGroupId($product), self::ATTR_NAME => $productTitle, self::ATTR_DESCRIPTION => $this->getDescription($product), self::ATTR_AVAILABILITY => $this->inventory->getAvailability(), self::ATTR_INVENTORY => $this->inventory->getInventory(), self::ATTR_BRAND => $this->getBrand($product), self::ATTR_PRODUCT_CATEGORY => $product->getGoogleProductCategory() ?? '', self::ATTR_PRODUCT_TYPE => $productType, self::ATTR_CONDITION => $this->getCondition($product), self::ATTR_PRICE => $this->getProductPrice($product), self::ATTR_SALE_PRICE => $this->getProductSalePrice($product), self::ATTR_COLOR => $this->getColor($product), self::ATTR_SIZE => $this->getSize($product), self::ATTR_URL => $this->getProductUrl($product), self::ATTR_IMAGE_URL => $imageUrl, self::ATTR_ADDITIONAL_IMAGE_URL => $images['additional_images'], ]; $this->enhancedCatalogHelper->assignECAttribute($product, $entry); return $entry; } }