I have 5 categories with single products each. I also have 5 stores.
I want to redirect the user directly to the product page when he clicks on the category link in the main menu for each store.
Can this be done in magento 2.1 CE?
you can write an observer for the event controller_action_predispatch_catalog_category_view
that checks if the current category has a single product.
Create this file in one of your extensions.
[Namespace]/[Module]/etc/frontend/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="controller_action_predispatch_catalog_category_view">
<observer name="[namespace]-[module]-category-view" instance="[Namespace]\[Module]\Observer\CategoryView" />
</event>
</config>
then create the observer class:
[Namespace]/[Module]/Observer/CategoryView.php
<?php
namespace [Namespace]\[Module]\Observer;
class CategoryView implements \Magento\Framework\Event\ObserverInterface
{
/**
* @var \Magento\Catalog\Model\Layer\Resolver
*/
protected $layerResolver;
/**
* @var \Magento\Framework\App\RequestInterface
*/
protected $request;
/**
* @var \Magento\Catalog\Api\CategoryRepositoryInterface
*/
protected $categoryRepository;
/**
* @var \Magento\Framework\App\ResponseInterface
*/
protected $response;
/**
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
* @param \Magento\Framework\App\RequestInterface $request
* @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
* @param \Magento\Framework\App\ResponseInterface $reponse
*/
public function __construct(
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
\Magento\Framework\App\RequestInterface $request,
\Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
\Magento\Framework\App\ResponseInterface $reponse
) {
$this->layerResolver = $layerResolver;
$this->request = $request;
$this->categoryRepository = $categoryRepository;
$this->response = $reponse;
}
public function execute(\Magento\Framework\Event\Observer $observer)
{
$categoryId = $this->request->getParam('id', false);
if (!$categoryId) {
return $this;
}
$layer = $this->layerResolver->get();
$category = $this->categoryRepository->get($categoryId);
$layer->setCurrentCategory($category);
$collection = $layer->getProductCollection();
if ($collection->count() == 1) {
/** @var \Magento\Catalog\Model\Product $product */
$product = $collection->getFirstItem();
$this->response->setRedirect($product->getProductUrl());
$this->response->sendResponse();
exit;
}
return $this;
}
}
Create a module (it's actually a plugin) in your app/code
folder, let's call the module SingleProductCategoryLink
. In the folder app/code/VendorName/SingleProductCategoryLink
you will create 6 files.
registration.php
etc/module.xml
etc/di.xml
etc/config.xml
(optional)etc/adminhtml/system.xml
Plugin/Controller/ListProduct.php
The first two should be obvious so I will not elaborate on them. The config.xml
file will allow you to set this setting in the backend per store-front.
<!-- etc/config.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<elgentos>
<single_product_category_link>
<enabled>1</enabled>
</single_product_category_link>
</elgentos>
</default>
</config>
<!-- etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
<type name="Magento\Catalog\Block\Product\ListProduct">
<plugin name="firstproduct-category-mod" type="Vendor\SingleProductCategoryLink\Block\Catalog\Product\ListProduct" sortOrder="1"/>
</type>
</config>
This one is optional but it allows you to enable the module selectively per store-front
<!-- etc/adminhtml/system.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<tab id="elgentos" translate="label" sortOrder="102">
<label>Vendor</label>
</tab>
<section id="single_product_category_link" sortOrder="102" showInDefault="1" showInWebsite="1" showInStore="1">
<tab>Vendor</tab>
<label>Single Product Category Link</label>
<resource>Vendor_SingleProductCategoryLink::config</resource>
<group id="general" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
<label>General</label>
<field id="enabled" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model>
</field>
</group>
</section>
</system>
</config>
And this is the meat of the plugin:
<?php
// Plugin/Controller/ListProduct.php
declare(strict_types=1);
namespace Vendor\SingleProductCategoryLink\Plugin\Controller\ListProduct;
use Magento\Catalog\Block\Product\ListProduct as MagentoListProduct;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Response\Http;
use Magento\Store\Model\ScopeInterface;
class ListProduct
{
public function __construct(
protected readonly Http $response,
protected readonly ScopeConfigInterface $scopeConfig
) {
}
public function afterGetLoadedProductCollection(
MagentoListProduct $subject,
AbstractCollection $resultCollection
): AbstractCollection
{
// If you included the optional config.xml file
$enabled = $this->scopeConfig->getValue('single_product_category_link/general/enabled', ScopeInterface::SCOPE_STORE);
if ($enabled && $resultCollection->getSize() === 1) {
/** @var Product $product */
$product = $resultCollection->getFirstItem();
$this->response->setRedirect($product->getProductUrl());
}
return $resultCollection;
}
}