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;
}
}