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.

4

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/{....}

  • 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. – Xenocide8998 May 31 '16 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. – Khoa TruongDinh Jun 5 '16 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. – Rahul Anand Aug 23 at 4:58
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;
}
}
  • Don't forget to add your custom 404 page identifier instead of "noroute" – Rahul Anand Aug 23 at 5:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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