Show featured label - Magento 2
Tags: magento2
Published: 2020-10-04 15:44:05

The following will be helpful if there is a request to show a product label in product detail page and all product listing sections in the site.
Listing sections being:
- Product detail page - product content area (catalog_product_view.xml)
- Category pages (catalog_category_view.xml)
- Page builder product widget (catalog_widget_product_list.xml)
- Upsell and related products (catalog_product_view.xml)
- Crossell (checkout_cart_index.xml)
- Search (catalogsearch_result_index.xml)
- Advance Search (catalogsearch_advanced_result.xml)


1) Create custom attribute

app/code/Vendor/FeaturedLabel/Setup/Patch/Data/AddFeaturedLabelProductAttribute.php
```
namespace Vendor\FeaturedLabel\Setup\Patch\Data;

use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;

class AddFeaturedLabelProductAttribute implements DataPatchInterface, PatchRevertableInterface
{
    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;
    /**
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Constructor
     *
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        EavSetupFactory $eavSetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function apply()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->addAttribute(
            Product::ENTITY,
            'featured_label',
            [
                'type' => 'int',
                'label' => 'Featured Label',
                'input' => 'select',
                'source' => '',
                'required' => false,
                'backend' => ArrayBackend::class,
                'sort_order' => '30',
                'global' => ScopedAttributeInterface::SCOPE_STORE,
                'default' => null,
                'visible' => true,
                'user_defined' => true,
                'searchable' => true,
                'filterable' => true,
                'comparable' => false,
                'visible_on_front' => false,
                'unique' => false,
                'apply_to' => '',
                'group' => 'General',
                'used_in_product_listing' => true,
                'is_used_in_grid' => true,
                'is_visible_in_grid' => false,
                'is_filterable_in_grid' => false,
                'option' => ''
            ]
        );

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public function revert()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->removeAttribute(Product::ENTITY, 'featured_label');

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * {@inheritdoc}
     */
    public function getAliases()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDependencies()
    {
        return [
        
        ];
    }
}
```

2) Create one template to show the labels

app/code/Vendor/FeaturedLabel/view/frontend/templates/product/featured_label.phtml
```
/** @var $block \Magento\Catalog\Block\Product\View */
/* @var $product Vendor\FeaturedLabel\ViewModel\Product */
/* @var $featuredLabel Vendor\FeaturedLabel\ViewModel\FeaturedLabel */

$product = $block->getData('product');
$featuredLabel = $block->getData('featured_label_widget');
?>
<?php $currentProduct = $product->getCurrentProduct() ?: $product; ?>

<?php if ($featuredLabel->getFeaturedLabel($currentProduct)): ?>
    <div class="featured-label">
        <?= $block->escapeHtml(__($featuredLabel->getFeaturedLabel($currentProduct))) ?>
    </div>
<?php endif; ?>
```   

3) Create viewModel for labels
Note: the current product is passed as product inteface

app/code/Vendor/FeaturedLabel/ViewModel/FeaturedLabel.php
```
namespace Vendor\FeaturedLabel\ViewModel;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\View\Element\Block\ArgumentInterface;

class FeaturedLabel implements ArgumentInterface
{
    /**
     * @param ProductInterface $product
     * @return false|\Magento\Framework\Phrase
     */
    public function getFeaturedLabel(ProductInterface $product)
    {
        if ($product->getAttributeText('featured_label')) {
            return $product->getAttributeText('featured_label');
        } 
}
```    

4) View model to get the current product

app/code/Vendor/Catalog/ViewModel/Product.php
```
namespace Vendor\Catalog\ViewModel;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\Block\ArgumentInterface;

class Product implements ArgumentInterface
{
    /**
     * @var Registry
     */
    private $registry;

    /**
     * @var ProductInterface
     */
    private $product;

    public function __construct(
        Registry $registry,
        array $data = []
    ) {
		$this->registry = $registry;
	}

    public function getCurrentProduct(): ProductInterface
    {
        if (is_null($this->product)) {
            $this->product = $this->registry->registry('current_product');
        }
        return $this->product;
	}
}
```        

5) Add the label to product detail page - product content area, upsell, related products

app/code/Vendor/FeaturedLabel/view/frontend/layout/catalog_product_view.xml
```
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="product.info.media">
            <block class="Magento\Catalog\Block\Product\View"
                   name="product.featured.label"
                   template="Vendor_FeaturedLabel::product/featured_label.phtml"
                   before="-">
                <arguments>
                    <argument name="product" xsi:type="object">Vendor\FeaturedLabel\ViewModel\Product</argument>
                    <argument name="featured_label_widget" xsi:type="object">Vendor\FeaturedLabel\ViewModel\FeaturedLabel</argument>
                </arguments>
            </block>
        </referenceContainer>

        <referenceBlock name="catalog.product.related" template="Magento_Catalog::product/list/items.phtml">
            <block name="product.related.featured.label"
                   as="featured.label"
                   template="Vendor_FeaturedLabel::product/featured_label.phtml">
                <arguments>
                    <argument name="product" xsi:type="object">Vendor\FeaturedLabel\ViewModel\Product</argument>
                    <argument name="featured_label_widget" xsi:type="object">Vendor\FeaturedLabel\ViewModel\FeaturedLabel</argument>
                </arguments>
            </block>
        </referenceBlock>

        <referenceBlock name="product.info.upsell" template="Magento_Catalog::product/list/items.phtml">
            <block name="product.upsell.featured.label"
                   as="featured.label"
                   template="Vendor_FeaturedLabel::product/featured_label.phtml">
                <arguments>
                    <argument name="product" xsi:type="object">Vendor\FeaturedLabel\ViewModel\Product</argument>
                    <argument name="featured_label_widget" xsi:type="object">Vendor\FeaturedLabel\ViewModel\FeaturedLabel</argument>
                </arguments>
            </block>
        </referenceBlock>
    </body>
</page>
``` 

6) Category pages app/code/Vendor/FeaturedLabel/view/frontend/layout/catalog_category_view.xml
```
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="category.products.list" template="Vendor_Catalog::product/list.phtml">
            <block name="category.product.featured.label"
                   as="featured.label"
                   template="Vendor_FeaturedLabel::product/featured_label.phtml">
                <arguments>
                    <argument name="product" xsi:type="object">Vendor\Catalog\ViewModel\Product</argument>
                    <argument name="featured_label_widget" xsi:type="object">Vendor\FeaturedLabel\ViewModel\FeaturedLabel</argument>
                </arguments>
            </block>
        </referenceBlock>
    </body>
</page>
```

7) Page builder product widget
a) app/code/Vendor/Catalog/Block/Product/ProductsList.php
```
namespace Vendor\Catalog\Block\Product;

use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Block\Product\Context as ProductContext;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\CatalogWidget\Block\Product\ProductsList as DefaultProductsList;
use Magento\CatalogWidget\Model\Rule;
use Magento\Framework\App\Http\Context;
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Url\EncoderInterface;
use Magento\Framework\View\Element\BlockInterface;
use Magento\Framework\View\LayoutFactory;
use Magento\Rule\Model\Condition\Sql\Builder;
use Magento\Widget\Block\BlockInterface as WidgetBlockInterface;
use Magento\Widget\Helper\Conditions;

class ProductsList extends DefaultProductsList implements WidgetBlockInterface, IdentityInterface
{
    /**
     * @var LayoutFactory
     */
    private $layoutFactory;

    /**
     * @var Json
     */
    private $json;

    /**
     * @var EncoderInterface
     */
    private $urlEncoder;

    /**
     * @var array
     */
    private $layoutBlocks = [];

    /**
     * ProductsList constructor.
     * @param ProductContext $context
     * @param CollectionFactory $productCollectionFactory
     * @param Visibility $catalogProductVisibility
     * @param Context $httpContext
     * @param Builder $sqlBuilder
     * @param Rule $rule
     * @param Conditions $conditionsHelper
     * @param CategoryRepositoryInterface $categoryRepository
     * @param LayoutFactory $layoutFactory
     * @param Json $json
     * @param EncoderInterface $urlEncoder
     * @param array $data
     */
    public function __construct(
        ProductContext $context,
        CollectionFactory $productCollectionFactory,
        Visibility $catalogProductVisibility,
        Context $httpContext,
        Builder $sqlBuilder,
        Rule $rule,
        Conditions $conditionsHelper,
        CategoryRepositoryInterface $categoryRepository,
        LayoutFactory $layoutFactory,
        Json $json,
        EncoderInterface $urlEncoder,
        array $data = []
    ) {
        $this->layoutFactory = $layoutFactory;
        $this->json = $json;
        $this->urlEncoder = $urlEncoder;

        parent::__construct(
            $context,
            $productCollectionFactory,
            $catalogProductVisibility,
            $httpContext,
            $sqlBuilder,
            $rule,
            $conditionsHelper,
            $categoryRepository,
            $data,
            $json,
            $layoutFactory,
            $urlEncoder
        );
    }

    /**
     * @param $name
     * @return mixed|null
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getBlockFromLayout($name)
    {
        $layoutBlock = isset($this->layoutBlocks[$name]) ? $this->layoutBlocks[$name] : null;

        if (!$layoutBlock) {
            $layout = $this->layoutFactory->create(['cacheable' => false]);
            $layout->getUpdate()->addHandle('catalog_widget_product_list')->load();
            $layout->generateXml();
            $layout->generateElements();
            $layoutBlock = $layout->getBlock($name);
            $this->layoutBlocks[$name] = $layoutBlock;
        }

        return $layoutBlock;
    }
}
```

b) app/code/Vendor/FeaturedLabel/etc/widget.xml
```
<widgets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
    <widget id="products_list" class="Vendor\Catalog\Block\Product\ProductsList">
        <label translate="true">Catalog Products List</label>
        <description translate="true">List of Products</description>
    </widget>
</widgets>
```

c) app/code/Vendor/FeaturedLabel/view/adminhtml/web/js/content-type/products/mass-converter/carousel-widget-directive.js
```
define([
    'Magento_PageBuilder/js/content-type/products/mass-converter/carousel-widget-directive',
    'Magento_PageBuilder/js/utils/object'
], function (OriginalCarouselWidgetDirective, _object) {
    'use strict';

    function CarouselWidgetDirective() {
        OriginalCarouselWidgetDirective.apply(this, arguments);
    }

    CarouselWidgetDirective.prototype = Object.create(OriginalCarouselWidgetDirective.prototype);

    /**
     * Override the toDom function to pass new 'type' and 'template' parameters.
     *
     * @param data
     * @param config
     * @returns {*}
     */
    CarouselWidgetDirective.prototype.toDom = function toDom(data, config) {
        var attributes = {
            type: "Vendor\\Catalog\\Block\\Product\\ProductsList",
            template: "Vendor_FeaturedLabel::product/widget/content/carousel.phtml",
            anchor_text: "",
            id_path: "",
            show_pager: 0,
            products_count: data.carousel_products_count,
            condition_option: data.condition_option,
            condition_option_value: "",
            type_name: "Catalog Products Carousel",
            conditions_encoded: this.encodeWysiwygCharacters(data.conditions_encoded || "")
        };

        if (data.sort_order) {
            attributes.sort_order = data.sort_order;
        }

        if (typeof data[data.condition_option] === "string") {
            attributes.condition_option_value = this.encodeWysiwygCharacters(data[data.condition_option]);
        }

        if (attributes.conditions_encoded.length === 0) {
            return data;
        }

        _object.set(data, config.html_variable, this.buildDirective(attributes));
        return data;
    };

    return CarouselWidgetDirective;
});
```

d) app/code/Vendor/FeaturedLabel/view/adminhtml/web/js/content-type/products/mass-converter/widget-directive.js
```
define([
    'Magento_PageBuilder/js/content-type/products/mass-converter/widget-directive',
    'Magento_PageBuilder/js/utils/object'
], function (OriginalWidgetDirective, _object) {
    'use strict';

    function WidgetDirective() {
        OriginalWidgetDirective.apply(this, arguments);
    }

    WidgetDirective.prototype = Object.create(OriginalWidgetDirective.prototype);

    /**
     * Override the toDom function to pass new 'type' and 'template' parameters.
     *
     * @param data
     * @param config
     * @returns {*}
     */
    WidgetDirective.prototype.toDom = function toDom(data, config) {
        var attributes = {
            type: "Vendor\\Catalog\\Block\\Product\\ProductsList",
            template: "Vendor_FeaturedLabel::product/widget/content/grid.phtml",
            anchor_text: "",
            id_path: "",
            show_pager: 0,
            products_count: data.products_count,
            condition_option: data.condition_option,
            condition_option_value: "",
            type_name: "Catalog Products List",
            conditions_encoded: this.encodeWysiwygCharacters(data.conditions_encoded || "")
        };

        if (data.sort_order) {
            attributes.sort_order = data.sort_order;
        }

        if (typeof data[data.condition_option] === "string") {
            attributes.condition_option_value = this.encodeWysiwygCharacters(data[data.condition_option]);
        }

        if (attributes.conditions_encoded.length === 0) {
            return data;
        }

        _object.set(data, config.html_variable, this.buildDirective(attributes));
        return data;
    };

    return WidgetDirective;
});
```

e) app/code/Vendor/FeaturedLabel/view/adminhtml/pagebuilder/content_type/products.xml
```
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
    <type name="products">
        <appearances>
            <appearance name="grid">
                <converters>
                    <converter component="Vendor_FeaturedLabel/js/content-type/products/mass-converter/widget-directive" name="widget_directive">
                        <config>
                            <item name="html_variable" value="html"/>
                        </config>
                    </converter>
                </converters>
            </appearance>
            <appearance name="carousel">
                <converters>
                    <converter component="Vendor_FeaturedLabel/js/content-type/products/mass-converter/carousel-widget-directive" name="widget_directive">
                        <config>
                            <item name="html_variable" value="html"/>
                        </config>
                    </converter>
                </converters>
            </appearance>
        </appearances>
    </type>
</config>
```

f) app/code/Vendor/FeaturedLabel/view/frontend/layout/catalog_widget_product_list.xml
```
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <block name="category.products.list.featured.label"
               as="featured.label.top"
               template="Vendor_FeaturedLabel::product/featured_label.phtml">
            <arguments>
                <argument name="product" xsi:type="object">Vendor\Catalog\ViewModel\Product</argument>
                <argument name="featured_label_widget" xsi:type="object">Vendor\FeaturedLabel\ViewModel\FeaturedLabel</argument>
            </arguments>
        </block>
    </body>
</page>
```

8) Template files
- Product detail page - product content area (catalog_product_view.xml)
- Category pages (catalog_category_view.xml)
- Page builder product widget (catalog_widget_product_list.xml)
- Upsell and related products (catalog_product_view.xml)
- Crossell (checkout_cart_index.xml)
- Search (catalogsearch_result_index.xml)
- Advance Search (catalogsearch_advanced_result.xml)

a) Category pages / Advance Search / Search - view/frontend/templates/product/list.phtml
```
<?php if ($featuredLabel = $block->getChildBlock('featured.label')): ?>
    <?= /* @noEscape */ $featuredLabel->setData('product', $product)->toHtml() ?>
<?php endif; ?>
```
b) Crossell / Upsell and related products lists
 view/frontend/templates/product/list/items
```
<?php if ($featuredLabel = $block->getChildBlock('featured.label')): ?>
    <?= /* @noEscape */ $featuredLabel->setData('product', $item)->toHtml() ?>
<?php endif; ?>
```

b) Page builder product widget (carosal)
```
<?php if ($featureLabel = $block->getBlockFromLayout('category.products.list.featured.label')): ?>
    <?= /* @noEscape */ $featureLabel->setData('product', $_item)->toHtml() ?>
<?php endif; ?>
```

c) Page builder product widget (grid)
```
<?php if ($featuredLabel = $block->getBlockFromLayout('category.products.list.featured.label')): ?>
    <?= /* @noEscape */ $featuredLabel->setData('product', $_item)->toHtml() ?>
<?php endif; ?>
```