DEV Community

Paboda Hettiarachchi
Paboda Hettiarachchi

Posted on • Edited on

1

Show featured label - Magento 2

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 [

        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

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; ?>
Enter fullscreen mode Exit fullscreen mode

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');
        } 
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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; ?>
Enter fullscreen mode Exit fullscreen mode

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; ?>
Enter fullscreen mode Exit fullscreen mode

b) Page builder product widget (carosal)

<?php if ($featureLabel = $block->getBlockFromLayout('category.products.list.featured.label')): ?>
    <?= /* @noEscape */ $featureLabel->setData('product', $_item)->toHtml() ?>
<?php endif; ?>
Enter fullscreen mode Exit fullscreen mode

c) Page builder product widget (grid)

<?php if ($featuredLabel = $block->getBlockFromLayout('category.products.list.featured.label')): ?>
    <?= /* @noEscape */ $featuredLabel->setData('product', $_item)->toHtml() ?>
<?php endif; ?>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay