Magento 2: Adding Support for Per Category Product View Updates

Published by John on August 16, 2019 Under Magento

Recently, while working on a Magento 2 site, I needed to be able to change the layout for products in a specific category. While there are a few ways to do this, such as a design update on the product page, I couldn’t find an easy way to do it via a layout xml update.

For categories, you can just created a modified layout file, like this, where ‘{CATEGORY_ID}’ is the numeric category id: catalog_category_view_id_{CATEGORY_ID}.xml

However, products do not support creating a product view template based on category, only product id(catalog_product_view_id_{PRODUCT_ID}.xml) and product type(catalog_product_view_type_{PRODUCT_TYPE}.xml).

It turns out, it is not very difficult to add support for this, by modifying the initProductLayout function of the \Magento\Catalog\Helper\Product\View class. You can find the default class here: vendor/magento/module-catalog/Helper/Product/View.php

This is a two step process, after which, you will be able to use layout handles to customize the product view, based on the category, like this: catalog_product_view_category_{CATEGORY_ID}.xml

So, your layout might look like this: app/code/Company/Plugin/view/frontend/layout/catalog_product_view_category_1.xml

And then after that, any product that is in category #1, will get updated using this layout file.

Prerequisites

This assumes you already have a Magento plugin working and are comfortable making code changes and was tested on a Magento 2.3 site.

Before doing anything, make a backup of your files and database.

No really, backup your files and database and make sure you are comfortable reverting from a backup. If you are not, you should not continue.

1: Extend the Magento\Catalog\Helper\Product\View Class

First, we will create a new helper in your plugin: app/code/Company/Plugin/Helper/Product.php

Add the below and make sure to change the namespace, ‘Company\Plugin’, accordingly:

<?php

namespace Company\Plugin\Helper\Product;

use Magento\Framework\View\Result\Page as ResultPage;

class View extends \Magento\Catalog\Helper\Product\View {

	 public function initProductLayout(ResultPage $resultPage, $product, $params = null)
	{
		$settings = $this->_catalogDesign->getDesignSettings($product);
		$pageConfig = $resultPage->getConfig();

		if ($settings->getCustomDesign()) {
			$this->_catalogDesign->applyCustomDesign($settings->getCustomDesign());
		}

		// Apply custom page layout
		if ($settings->getPageLayout()) {
			$pageConfig->setPageLayout($settings->getPageLayout());
		}

		$urlSafeSku = rawurlencode($product->getSku());

		/* Get categories from product and load into array */

		$category_ids = $product->getCategoryIds();
		$category_ids_clean = array();

		if(!is_array($category_ids)){
			$category_ids = array();
		}

		foreach($category_ids as $category_id){

			$category_id = intval($category_id);

			if($category_id <= 0){
				continue;
			}

			$category_ids_clean[] = $category_id;


		}

		/* Get categories from product and load into array */

		// Load default page handles and page configurations
		if ($params && $params->getBeforeHandles()) {
			foreach ($params->getBeforeHandles() as $handle) {

				/* Add support for category_{id} layout */

				foreach($category_ids_clean as $category_id){
					$resultPage->addPageLayoutHandles(['category' => $category_id], $handle, false);
				}

				/* Add support for category_{id} layout */

				$resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false);
				$resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle);
			}
		}

		/* Add support for category_{id} layout */

		foreach($category_ids_clean as $category_id){
			$resultPage->addPageLayoutHandles(['category' => $category_id], null, false);
		}

		/* Add support for category_{id} layout */

		$resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false);
		$resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]);

		if ($params && $params->getAfterHandles()) {
			foreach ($params->getAfterHandles() as $handle) {

				/* Add support for category_{id} layout */

				foreach($category_ids_clean as $category_id){
					$resultPage->addPageLayoutHandles(['category' => $category_id], $handle, false);
				}

				/* Add support for category_{id} layout */

				$resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false);
				$resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle);
			}
		}

		// Apply custom layout update once layout is loaded
		$update = $resultPage->getLayout()->getUpdate();
		$layoutUpdates = $settings->getLayoutUpdates();
		if ($layoutUpdates) {
			if (is_array($layoutUpdates)) {
				foreach ($layoutUpdates as $layoutUpdate) {
					$update->addUpdate($layoutUpdate);
				}
			}
		}

		$currentCategory = $this->_coreRegistry->registry('current_category');
		$controllerClass = $this->_request->getFullActionName();
		if ($controllerClass != 'catalog-product-view') {
			$pageConfig->addBodyClass('catalog-product-view');
		}
		$pageConfig->addBodyClass('product-' . $product->getUrlKey());
		if ($currentCategory instanceof \Magento\Catalog\Model\Category) {
			$pageConfig->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($currentCategory))
				->addBodyClass('category-' . $currentCategory->getUrlKey());
		}

		return $this;
	}


}


This is our helper file that we will use to overload the ‘initProductLayout’ function in the default ‘vendor/magento/module-catalog/Helper/Product/View.php’ file.

It adds support for the new layout handle, catalog_product_view_category_{CATEGORY_ID}, by getting a list of the products categories, then adding a layout handle for each one. The changes are commented by ‘/* Get categories from product and load into array */’ and ‘/* Add support for category_{id} layout */’. An example of one of these updates is here:


/* Add support for category_{id} layout */

foreach($category_ids_clean as $category_id){
	$resultPage->addPageLayoutHandles(['category' => $category_id], $handle, false);					
}

/* Add support for category_{id} layout */

This works through the $category_ids_clean array and adds a layout update for each one.

2: Tell Magento to Use the New Helper Class

Next, we need to tell magento to use this class, which we can do by creating a di.xml file here: app/code/Company/Plugin/etc/di.xml

As with the previous step, make sure to change the namespace(‘Company\Plugin’) accordingly to match your plugin.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Helper\Product\View" type="Company\Plugin\Helper\Product\View" />
</config>

3. Clear Your Cache

From the command line, clear your site’s cache, so that the new config files are parsed:


/bin/php /home/web/www/bin/magento cache:clean

4. Use the New Layout Handle

If all goes well, you should now be able to use an xml layout file to change products based on their category.

An example of how this might look is like this: app/code/Company/Plugin/view/frontend/layout/catalog_product_view_category_1.xml

Where, ‘1’ is the numeric category ID found in the admin categories section.

A Word of Caution

The site I tested this on has only a few categories for each product, with little nesting. If you make heavy use of categories, you may want to consider only doing the main top level category.

Getting Help

If you need help, feel free to post a comment or contact me. I provide Magento plugin and theme development for both Magento 1 and Magento 2, so if you aren’t a developer, let me know and we can discuss how to get this added to your site.


No Comments |

Add a Comment