3

In Magento 2 I am looking to create a module that adds a custom 404 page specifically for not-found products in the catalog, however keep the default 404 page for all other pages.

My initial thoughts are to create a controller with di on \Magento\Catalog\Controller\Product\View that checks if the product does not exist and changes the template to a custom 404 response if returned true.

Something like (not actual code)

public function execute()
{
    if (!$this->productExists()) {
        $this->setTemplate('custom-404');
    }
}

Is there a better way to do this? Let me know if you need additional info.

3 Answers 3

7

Magento 2 provides some classes to handle no router: vendor/magento/framework/App/Router/NoRouteHandler.php vendor/magento/framework/App/Router/NoRouteHandlerInterface.php vendor/magento/framework/App/Router/NoRouteHandlerList.php

Create an example module:

app/code/Boolfly/NotFoundPages/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Boolfly_NotFoundPages" setup_version="0.0.1" />
</config>

app/code/Boolfly/NotFoundPages/etc/frontend/routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="notfoundpages" frontName="notfoundpages">
            <module name="Boolfly_NotFoundPages" />
        </route>
    </router>
</config>

app/code/Boolfly/NotFoundPages/etc/frontend/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Router\NoRouteHandlerList">
        <arguments>
            <argument name="handlerClassesList" xsi:type="array">
                <item name="notfoundpages" xsi:type="array">
                    <item name="class" xsi:type="string">Boolfly\NotFoundPages\Controller\NoRouteHandler</item>
                    <item name="sortOrder" xsi:type="string">50</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

app/code/Boolfly/NotFoundPages/Controller/NoRoute.php

<?php

namespace Boolfly\NotFoundPages\Controller;

use Magento\Framework\App\Action\Context as Context;
use Magento\Framework\View\Result\PageFactory as PageFactory;

class NoRoute extends \Magento\Framework\App\Action\Action
{
    protected $resultPageFactory;

    /**
     * NoRoute constructor.
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(Context $context, PageFactory $resultPageFactory)
    {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }

    /**
     * @return \Magento\Framework\View\Result\Page
     */
    public function execute()
    {
        $resultLayout = $this->resultPageFactory->create();
        $resultLayout->setStatusHeader(404, '1.1', 'Not Found');
        $resultLayout->setHeader('Status', '404 File not found');
        return $resultLayout;
    }
}

app/code/Boolfly/NotFoundPages/Controller/NoRouteHandler.php

<?php

namespace Boolfly\NotFoundPages\Controller;

use Magento\Framework\App\RequestInterface as Request;

class NoRouteHandler implements \Magento\Framework\App\Router\NoRouteHandlerInterface
{
    /**
     * @param Request $request
     * @return bool
     */
    public function process(Request $request)
    {
        $pathInfo = $request->getPathInfo();
        $parts = explode('/', trim($pathInfo, '/'));

        $moduleName = isset($parts[0]) ? $parts[0] : '';
        $actionPath = isset($parts[1]) ? $parts[1] : '';
        $actionName = isset($parts[2]) ? $parts[2] : '';

        if($moduleName == 'catalog' && $actionPath == 'product' && $actionName == 'view') {
            $request->setModuleName('notfoundpages')
                    ->setControllerName('noroute')
                    ->setActionName('product');
        } else {
            $request->setModuleName('notfoundpages')
                ->setControllerName('noroute')
                ->setActionName('other');
        }
        return false;
    }
}

app/code/Boolfly/NotFoundPages/Controller/NoRoute/Product.php

<?php

namespace Boolfly\NotFoundPages\Controller\NoRoute;

class Product extends \Boolfly\NotFoundPages\Controller\NoRoute
{

}

app/code/Boolfly/NotFoundPages/Controller/NoRoute/Other.php

<?php

namespace Boolfly\NotFoundPages\Controller\NoRoute;

class Other extends \Boolfly\NotFoundPages\Controller\NoRoute
{

}

app/code/Boolfly/NotFoundPages/view/frontend/layout/notfoundpages_noroute_product.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template" name="no.route.product" as="no.route.product"
                   template="Boolfly_NotFoundPages::product.phtml" />
        </referenceContainer>
    </body>
</page>

app/code/Boolfly/NotFoundPages/view/frontend/layout/notfoundpages_noroute_other.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template" name="no.route.other" as="no.route.other" template="Boolfly_NotFoundPages::other.phtml" />
        </referenceContainer>
    </body>
</page>

app/code/Boolfly/NotFoundPages/view/frontend/templates/product.phtml

<?php echo __('Product Page Not Found.'); ?>

app/code/Boolfly/NotFoundPages/view/frontend/templates/other.phtml

<?php echo __('Other Page Not Found.') ;?>

Now, we can try:

Other pages: url: magen2.loc/dddddd

enter image description here

Product Page: url: magen2.loc/catalog/view/45555

enter image description here

Note: we will catch the url of product with format: {base url}/catalog/product/view/{....}

4
  • any better way to get the module name other than exploding the path? I feel like this will cause issues if a product has a url rewrite. Commented May 31, 2016 at 20:07
  • It's really hard to handle custom 404 page not found when using url rewrite. As we know, a cms page has its url, this url likes a url rewrite of product. So, seem that it's impossible to catch two cases. Commented Jun 5, 2016 at 5:10
  • Is this a good way to do it? It will run the extra loop of router each time, for each page does not matter either product or any other. This is not a recommended approach. Commented Aug 23, 2019 at 4:58
  • i've this error 'Front controller reached 100 router match iterations'
    – hkguile
    Commented Apr 2, 2020 at 11:00
0

And one more thing as additional to Khoa's answer. To avoid possible issues, you must return true from your process method in custom noRoute handler in case if you match request. It will interrupt further no route handlers processing, which performs in method below.

\Magento\Framework\App\Router\DefaultRouter::match
0

create a plugin to check the product status "afterGetStatus" method for class \Magento\Catalog\Model\Product, if the product status is disabled, then create a custom 404 page and forward the controller to that. Find below the code to do that, I have checked it and it is working.

di.xml

 <type name="Magento\Catalog\Model\Product">
    <plugin name="change_product" type="Rahul\Disabledproductredirect\Plugin\Product" sortOrder="1" />
  </type>

Plugin/Product.php

 namespace Rahul\Disabledproductredirect\Plugin;
use Magento\Framework\Controller\Result\Forward; use Magento\Framework\App\Request\Http;

class Product {

private $forward;
private $request;


public function __construct(Forward $forward,Http $request)
{

    $this->forward = $forward;
    $this->request  = $request;
}

public function afterGetStatus(\Magento\Catalog\Model\Product $subject, $result)
{
    //return $result;
    if($result == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED && $this->request->getControllerName()=='product' && $this->request->getActionName() == 'view')
    {

       $this->forward->forward('noroute');

    }

    return $result;
}
}
1
  • Don't forget to add your custom 404 page identifier instead of "noroute" Commented Aug 23, 2019 at 5:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.