From c5574ab036247d9e6443d7f96a7e16f7d1db259f Mon Sep 17 00:00:00 2001 From: Edward Crocombe <edward@veretech.systems> Date: Sun, 3 Sep 2023 19:59:59 +1000 Subject: [PATCH] Feature #3038 Use display_pattern on product view, product compare pages (disabled by default) --- .../Helper/ProductAttribute.php | 94 ++++++++++++++- .../Product/Compare/ListComparePlugin.php | 113 ++++++++++++++++++ .../Catalog/Product/View/AttributesPlugin.php | 80 +++++++++++++ .../etc/adminhtml/system.xml | 5 + .../etc/config.xml | 1 + src/module-elasticsuite-catalog/etc/di.xml | 9 ++ 6 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 src/module-elasticsuite-catalog/Plugin/Catalog/Product/Compare/ListComparePlugin.php create mode 100644 src/module-elasticsuite-catalog/Plugin/Catalog/Product/View/AttributesPlugin.php diff --git a/src/module-elasticsuite-catalog/Helper/ProductAttribute.php b/src/module-elasticsuite-catalog/Helper/ProductAttribute.php index 36cc774d9..4b2d1b62e 100644 --- a/src/module-elasticsuite-catalog/Helper/ProductAttribute.php +++ b/src/module-elasticsuite-catalog/Helper/ProductAttribute.php @@ -17,6 +17,8 @@ use Magento\Framework\App\Helper\Context; use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as AttributeCollectionFactory; +use Magento\Framework\Locale\LocaleFormatter; +use Magento\Store\Model\ScopeInterface; /** * ElasticSuite product attributes helper. @@ -27,15 +29,103 @@ */ class ProductAttribute extends AbstractAttribute { + /** + * @var string Config path to determine if we should use display battern on frontend. + */ + const XML_PATH_FRONTEND_PRODUCT_DISPLAY_PATTERN_ENABLED + = 'smile_elasticsuite_catalogsearch_settings/catalogsearch/frontend_product_display_pattern_enabled'; + + /** + * @var string Attribute column containing pattern. + */ + const DISPLAY_PATTERN_COLUMN = 'display_pattern'; + + /** + * @var string Attribute column containing precision. + */ + const DISPLAY_PRECISION_COLUMN = 'display_precision'; + + /** + * @var string Default display pattern if not otherwise specified. + */ + const DEFAULT_DISPLAY_PATTERN = '%s'; + + /** + * @var string Round value to this many decimal places if not otherwise specified. + */ + const DEFAULT_DISPLAY_PRECISION = 2; + + /** + * @var int The maximum possible replacements for each pattern in each subject string. + */ + const REPLACEMENT_LIMIT = 1; + + /** + * @var \Magento\Framework\Locale\LocaleFormatter $localeFormatter + */ + protected $localeFormatter; + /** * Constructor. * * @param Context $context Helper context. * @param AttributeFactory $attributeFactory Factory used to create attributes. * @param AttributeCollectionFactory $collectionFactory Attribute collection factory. + * @param LocaleFormatter $localeFormatter Format numbers to a locale */ - public function __construct(Context $context, AttributeFactory $attributeFactory, AttributeCollectionFactory $collectionFactory) - { + public function __construct( + Context $context, + AttributeFactory $attributeFactory, + AttributeCollectionFactory $collectionFactory, + LocaleFormatter $localeFormatter + ) { + $this->localeFormatter = $localeFormatter; parent::__construct($context, $attributeFactory, $collectionFactory); } + + /** + * Is Display Pattern on Frontend Enabled ? + * + * @return bool + */ + public function isFrontendProductDisplayPatternEnabled(): bool + { + return (bool) $this->scopeConfig->getValue( + self::XML_PATH_FRONTEND_PRODUCT_DISPLAY_PATTERN_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Format Product Attribute Value with Display Pattern + * + * Value's stored as numeric (i.e `8` or `355`) and the attribute has a display pattern (i.e `% %s` or `%s mm`) + * will be formatted as `% 8` or `355 mm` + * + * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute Product Attribute to format + * @param int|float|string $value Product Attribute Value to format + * + * @return string Formatted string + */ + public function formatProductAttributeValueDisplayPattern($attribute, $value) + { + // Translate attribute pattern, or default, without variable. + // @codingStandardsIgnoreStart + $pattern = $attribute->getData(self::DISPLAY_PATTERN_COLUMN) + ? (string) __($attribute->getData(self::DISPLAY_PATTERN_COLUMN)) + : (string) self::DEFAULT_DISPLAY_PATTERN; + // @codingStandardsIgnoreEnd + + // Get attribute display precision or default. + // @codingStandardsIgnoreStart + $precision = is_numeric($attribute->getData(self::DISPLAY_PRECISION_COLUMN) ?? '') + ? (int) abs($attribute->getData(self::DISPLAY_PRECISION_COLUMN)) + : (int) self::DEFAULT_DISPLAY_PRECISION; + // @codingStandardsIgnoreEnd + + // Round value to precision and format to locale string. + $amount = (string) $this->localeFormatter->formatNumber(round((float) $value, $precision)); + // Insert number value into translated string. + return (string) preg_replace('/' . self::DEFAULT_DISPLAY_PATTERN . '/', $amount, $pattern, self::REPLACEMENT_LIMIT); + } } diff --git a/src/module-elasticsuite-catalog/Plugin/Catalog/Product/Compare/ListComparePlugin.php b/src/module-elasticsuite-catalog/Plugin/Catalog/Product/Compare/ListComparePlugin.php new file mode 100644 index 000000000..eb096ba21 --- /dev/null +++ b/src/module-elasticsuite-catalog/Plugin/Catalog/Product/Compare/ListComparePlugin.php @@ -0,0 +1,113 @@ +<?php +/** + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer + * versions in the future. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Edward Crocombe <ecrocombe@outlook.com> + * @copyright 2020 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Plugin\Catalog\Product\View; + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Smile\ElasticsuiteCatalog\Helper\ProductAttribute; + +/** + * Catalog Product List Compare plugin. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Edward Crocombe <ecrocombe@outlook.com> + */ +class ListComparePlugin +{ + /** + * @var null|\Magento\Catalog\Api\Data\ProductAttributeInterface[] + */ + private $attributes; + + /** + * @var \Smile\ElasticsuiteCatalog\Helper\ProductAttribute + */ + private $productAttributeHelper; + + /** + * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository + */ + private $productAttributeRepository; + + /** + * Constructor. + * + * @param ProductAttribute $productAttributeHelper ElasticSuite product attributes helper. + * @param ProductAttributeRepositoryInterface $productAttributeRepository Formats numbers to a locale + */ + public function __construct( + ProductAttribute $productAttributeHelper, + ProductAttributeRepositoryInterface $productAttributeRepository + ) { + $this->productAttributeHelper = $productAttributeHelper; + $this->productAttributeRepository = $productAttributeRepository; + } + + /** + * Add display pattern for frontend display + * + * @param \Magento\Catalog\Block\Product\Compare\ListCompare $subject Plugin Subject + * @param \Magento\Framework\Phrase|string $result Plugin Result + * @param \Magento\Catalog\Model\Product $product Product + * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute Product Attribute + * + * @return \Magento\Framework\Phrase|string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetProductAttributeValue( + \Magento\Catalog\Block\Product\Compare\ListCompare $subject, + $result, + $product, + $attribute + ) { + if (!$this->productAttributeHelper->isFrontendProductDisplayPatternEnabled()) { + return $result; + } + + $value = $attribute->getFrontend()->getValue($product); + if (is_numeric($value) && strlen($attribute->getData('display_pattern') ?? '') > 0) { + $result = $this->productAttributeHelper->formatProductAttributeValueDisplayPattern($attribute, $value); + } + + return $result; + } + + /** + * Retrieve Product Compare Attributes + * + * Default getAttributes retrieves columns from eav_attribute table only, + * both the display_pattern and display_precision values are on the catalog_eav_attribute table. + * + * @param \Magento\Catalog\Block\Product\Compare\ListCompare $subject Plugin Subject + * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[] $result Plugin Result + * + * @return \Magento\Catalog\Api\Data\ProductAttributeInterface[] + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetAttributes(\Magento\Catalog\Block\Product\Compare\ListCompare $subject, $result) + { + if (!$this->productAttributeHelper->isFrontendProductDisplayPatternEnabled()) { + return $result; + } + + if ($this->attributes === null) { + $this->attributes = []; + foreach (array_keys($result) as $attributeCode) { + $this->attributes[$attributeCode] = $this->productAttributeRepository->get($attributeCode); + } + } + + return $this->attributes; + } +} diff --git a/src/module-elasticsuite-catalog/Plugin/Catalog/Product/View/AttributesPlugin.php b/src/module-elasticsuite-catalog/Plugin/Catalog/Product/View/AttributesPlugin.php new file mode 100644 index 000000000..ef4a215d1 --- /dev/null +++ b/src/module-elasticsuite-catalog/Plugin/Catalog/Product/View/AttributesPlugin.php @@ -0,0 +1,80 @@ +<?php +/** + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer + * versions in the future. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Edward Crocombe <ecrocombe@outlook.com> + * @copyright 2020 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Plugin\Catalog\Product\View; + +use Smile\ElasticsuiteCatalog\Helper\ProductAttribute; + +/** + * Catalog Product View Attributes plugin. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Edward Crocombe <ecrocombe@outlook.com> + */ +class AttributesPlugin +{ + /** + * @var \Smile\ElasticsuiteCatalog\Helper\ProductAttribute + */ + private $productAttributeHelper; + + /** + * Constructor. + * + * @param ProductAttribute $productAttributeHelper ElasticSuite product attributes helper. + */ + public function __construct( + ProductAttribute $productAttributeHelper + ) { + $this->productAttributeHelper = $productAttributeHelper; + } + + /** + * Add display pattern for frontend display + * + * @param \Magento\Catalog\Block\Product\View\Attributes $subject Plugin Subject + * @param array $result Additional data + * @param string[] $excludeAttr Attribute Codes to exclude + * + * @return array Additional data + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetAdditionalData(\Magento\Catalog\Block\Product\View\Attributes $subject, $result, array $excludeAttr = []) + { + if (!$this->productAttributeHelper->isFrontendProductDisplayPatternEnabled()) { + return $result; + } + + $product = $subject->getProduct(); + $attributes = $product->getAttributes(); + foreach ($attributes as $attribute) { + // If attribute is already in array, then isVisibleOnFrontend = `true`. + if (isset($result[$attribute->getAttributeCode()])) { + // @codingStandardsIgnoreStart + $value = isset($result[$attribute->getAttributeCode()]['value']) + ? $result[$attribute->getAttributeCode()]['value'] + : ''; + // @codingStandardsIgnoreEnd + + if (is_numeric($value) && strlen($attribute->getData('display_pattern') ?? '') > 0) { + $result[$attribute->getAttributeCode()]['value'] + = $this->productAttributeHelper->formatProductAttributeValueDisplayPattern($attribute, $value); + } + } + } + + return $result; + } +} diff --git a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml index 1a1fd2b7a..5540707dc 100644 --- a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml +++ b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml @@ -75,6 +75,11 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment><![CDATA[If enabled, when necessary to support the presence of outlier values in the navigation context (for instance, a very high price amidst a majority of low prices), the price slider behavior changes so that the middle of the slider range corresponds to the median price instead of the price at the middle of the range.]]></comment> </field> + <field id="frontend_product_display_pattern_enabled" translate="label" type="select" sortOrder="45" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Enable Display Pattern on Frontend</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment><![CDATA[If enabled, will also show the Attributes Value using Display Pattern on Product Pages (Product Detail and Compare), not just in Layered Navigation. ]]></comment> + </field> <field id="index_child_product_sku" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enable indexing child product SKU in dedicated subfield</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/src/module-elasticsuite-catalog/etc/config.xml b/src/module-elasticsuite-catalog/etc/config.xml index 74424e032..e42c08e6c 100644 --- a/src/module-elasticsuite-catalog/etc/config.xml +++ b/src/module-elasticsuite-catalog/etc/config.xml @@ -38,6 +38,7 @@ <category_filter_use_url_rewrites>1</category_filter_use_url_rewrites> <expanded_facets>3</expanded_facets> <adaptive_slider_enabled>0</adaptive_slider_enabled> + <frontend_product_display_pattern_enabled>0</frontend_product_display_pattern_enabled> <index_child_product_sku>0</index_child_product_sku> <compute_child_product_discount>0</compute_child_product_discount> </catalogsearch> diff --git a/src/module-elasticsuite-catalog/etc/di.xml b/src/module-elasticsuite-catalog/etc/di.xml index e1ba86a2f..866329bd4 100644 --- a/src/module-elasticsuite-catalog/etc/di.xml +++ b/src/module-elasticsuite-catalog/etc/di.xml @@ -425,4 +425,13 @@ <!-- This plugin is defined in magento/module-inventory-catalog --> <plugin name="outOfStockSorting" disabled="true"/> </type> + + <!-- Show product attributes on frontend with display pattern --> + <type name="Magento\Catalog\Block\Product\View\Attributes"> + <plugin name="format_product_view_attribute_display_pattern" type="Smile\ElasticsuiteCatalog\Plugin\Catalog\Product\View\AttributesPlugin" /> + </type> + <type name="Magento\Catalog\Block\Product\Compare\ListCompare"> + <plugin name="format_product_compare_attribute_display_pattern" type="Smile\ElasticsuiteCatalog\Plugin\Catalog\Product\View\ListComparePlugin" /> + </type> + </config>