6

I have a Megamenu extension installed replacing the default navigation menu. This however when added breaks breadcrumbs on product pages.

I have debugged by also removing both navigation and this also breaks the navigation so I believe the issue lies with default navigation being required somehow for breadcrumbs to work now. I could of sworn however this was working on 2.2.4 earlier this week as had below issue:

Issue with Magento 2.2.4 - quotation mark in product name break product pages

This i resolved however am sure did not break breadcrumbs as the time. Reverting does not fix breadcrumbs either.

How do breadcrumbs work now? They seem to load asynchronously and not sure they did before.

From what i can tell the below function in breadcrumbs.js renders the breadcrumbs:

    _render: function () {
        var html,
            crumbs = breadcrumbList,
            template = mageTemplate(tpl);

        this._decorate(crumbs);

        html = template({
            'breadcrumbs': crumbs
        });

        if (html.length) {
            $(this.element).html(html);
        }
    },

However im not sure how it grabs the data for the html variable and why it breaks when i remove the default navigation.

12
  • are u fixed this issue?
    – Mahi M
    Jul 23, 2018 at 12:59
  • I just adapted an earlier plugin i made to also fix this. This is listed in my answer.
    – harri
    Jul 23, 2018 at 14:45
  • 1
    this is correct one github.com/harrigo/EverCrumbs
    – Mahi M
    Jul 24, 2018 at 4:29
  • Still i can't get the breadcrumbs in view page? Any idea about that?
    – Mahi M
    Jul 24, 2018 at 8:33
  • Yeah that is the one, that is quite odd. When did this start happening?
    – harri
    Jul 24, 2018 at 8:41

8 Answers 8

19

With help of some posts on stackexchange, I found out this solution, This approach does not require you to override with di.xml

YOUR_THEME\Magento_Catalog\layout\catalog_product_view.xml

<referenceContainer name="content.top">
    <block class="Vendor\Module\Block\Breadcrumbs" name="breadcrumbs" as="breadcrumbs" />
</referenceContainer>

Create a module with the following block:

Vendor/Module/Block/Breadcrumbs.php

<?php

namespace Vendor\Module\Block;

use Magento\Catalog\Helper\Data;
use Magento\Framework\View\Element\Template\Context;
use Magento\Store\Model\Store;
use Magento\Framework\Registry;

class Breadcrumbs extends \Magento\Theme\Block\Html\Breadcrumbs
{
    /**
     * Catalog data
     *
     * @var Data
     */
    protected $_catalogData = null;

    /**
     * @param Context $context
     * @param Data $catalogData
     * @param array $data
     */
    public function __construct(Context $context, Data $catalogData, Registry $registry, array $data = [])
    {
        $this->_catalogData = $catalogData;
        $this->registry = $registry;
        parent::__construct($context, $data);
    }

    /**
     * Retrieve HTML title value separator (with space)
     *
     * @param null|string|bool|int|Store $store
     * @return string
     */
    public function getTitleSeparator($store = null)
    {
        $separator = (string)$this->_scopeConfig->getValue('catalog/seo/title_separator', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $store);
        return ' ' . $separator . ' ';
    }
    
    public function getCrumbs() {
        return $this->_crumbs;
    }

    /**
     * Preparing layout
     *
     * @return \Magento\Catalog\Block\Breadcrumbs
     */
    protected function _prepareLayout() {

        $title = [];
        if ($breadcrumbsBlock = $this->getLayout()->getBlock('breadcrumbs')) {
            $breadcrumbsBlock->addCrumb(
                    'home', [
                'label' => __('Home'),
                'title' => __('Go to Home Page'),
                'link' => $this->_storeManager->getStore()->getBaseUrl()
                    ]
            );
            $path = $this->_catalogData->getBreadcrumbPath();
            $product = $this->registry->registry('current_product');

            if ($product && count($path) == 1) {
                $categoryCollection = clone $product->getCategoryCollection();
                $categoryCollection->clear();
                $categoryCollection->addAttributeToSort('level', $categoryCollection::SORT_ORDER_DESC)->addAttributeToFilter('path', array('like' => "1/" . $this->_storeManager->getStore()->getRootCategoryId() . "/%"));
                $categoryCollection->setPageSize(1);
                $breadcrumbCategories = $categoryCollection->getFirstItem()->getParentCategories();

                foreach ($breadcrumbCategories as $category) {
                    $catbreadcrumb = array("label" => $category->getName(), "link" => $category->getUrl());
                    $breadcrumbsBlock->addCrumb("category" . $category->getId(), $catbreadcrumb);
                    $title[] = $category->getName();
                }
                //add current product to breadcrumb
                $prodbreadcrumb = array("label" => $product->getName(), "link" => "");
                $breadcrumbsBlock->addCrumb("product" . $product->getId(), $prodbreadcrumb);
                $title[] = $product->getName();
            } else {
                foreach ($path as $name => $breadcrumb) {
                    $breadcrumbsBlock->addCrumb($name, $breadcrumb);
                    $title[] = $breadcrumb['label'];
                }
            }
            $this->pageConfig->getTitle()->set(join($this->getTitleSeparator(), array_reverse($title)));
            return parent::_prepareLayout();
        }
        $path = $this->_catalogData->getBreadcrumbPath();
        foreach ($path as $name => $breadcrumb) {
            $title[] = $breadcrumb['label'];
        }
        $this->pageConfig->getTitle()->set(join($this->getTitleSeparator(), array_reverse($title)));
        return parent::_prepareLayout();
    }
}

YOUR_THEME\Magento_Catalog\templates\product\breadcrumbs.phtml

<?php if ($crumbs && is_array($crumbs)) : ?>
    <div class="breadcrumbs">
        <ul class="items">
        <?php foreach ($crumbs as $crumbName => $crumbInfo) : ?>

            <?php
                $label = $block->escapeHtml($crumbInfo['label']);
                $link = $block->escapeUrl($crumbInfo['link']);
            ?>

            <li class="item <?php /* @escapeNotVerified */ echo $block->escapeHtml($crumbName) ?>">
            <?php if ($link) : ?>
                    <a href="<?php /* @escapeNotVerified */ echo $link ?>"
                       title="<?= $block->escapeHtml($crumbInfo['title']) ?>">
                    <?= $label ?>
                    </a>
                    <?php elseif ($crumbInfo['last']) : ?>
                    <strong><?= $label ?></strong>
                <?php else: ?>
                    <?= $label ?>
                <?php endif; ?>
            </li>
        <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>
7
  • 1
    your solution is working perfectly:)
    – akgola
    Oct 9, 2018 at 13:13
  • Only thing missing here is ordering by level and also position
    – Joel Davey
    Nov 20, 2018 at 22:05
  • Hi @nuwaus could u please look into my issue magento.stackexchange.com/questions/257932/… Jan 17, 2019 at 8:09
  • Hi nuwaus i have tried u r answer but getting "Invalid template file: 'html/breadcrumbs.phtml' can u please suggest where i need to add breadcrumbs.phtml? Jan 17, 2019 at 8:21
  • @Nagaraju Kasa, In magento breadcrumbs.phtml is at this location : vendor/magento/module-theme/view/frontend/templates/html If you want to override it, then you have to put it in your theme folder and path will be as below : Magento_Theme/templates/html/breadcrumbs.phtml Hope this will help you. :) Jan 30, 2019 at 10:22
3

In design/frontend/VendorTheme/ThemeName/Magento_Catalog/layout/catalog_product_view.xml

<?xml version="1.0"?>
<page>
<body>
    <referenceBlock name="breadcrumbs" remove="true" />
    <referenceContainer name="page.top">
        <block class="VendorName\Breadcrumbs\Block\CatalogBreadcrumbs" name="product.view.breadcrumbs" template="VendorName_Breadcrumbs::breadcrumbs.phtml" />       
    </referenceContainer>
</body>
</page>

In app/design/frontend/VendorTheme/ThemeName/Magento_Theme/templates/html/breadcrumbs.phtml:

<?php $crumbs = $block->getBreadcrumb(); ?>
<?php if ($crumbs && is_array($crumbs)) : ?>
    <div class="breadcrumbs">
        <ul class="items">
            <?php foreach ($crumbs as $crumbName => $crumbInfo) : ?>
                <li class="item <?= /* @escapeNotVerified */ $crumbName ?>">
                    <?php if ($crumbInfo['link']) : ?>
                        <a href="<?= /* @escapeNotVerified */ $crumbInfo['link'] ?>" title="<?= $block->escapeHtml($crumbInfo['title']) ?>"><?= $block->escapeHtml($crumbInfo['label']) ?></a>
                    <?php else: ?>
                        <?= $block->escapeHtml($crumbInfo['label']) ?>
                    <?php endif; ?>
                </li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

In Block/CatalogBreadcrumbs.php:

<?php

namespace VendorName\Breadcrumbs\Block;

use Magento\Catalog\Helper\Data;
use Magento\Framework\View\Element\Template\Context;

class CatalogBreadcrumbs extends \Magento\Theme\Block\Html\Breadcrumbs
{

    /**
     * Catalog data
     *
     * @var Data
     */
    protected $_catalogData = null;
    protected $path = array();

    /**
     * @param Context $context
     * @param Data $catalogData
     * @param array $data
     */
    public function __construct(Context $context, Data $catalogData, array $data = [])
    {
        $this->_catalogData = $catalogData;
        parent::__construct($context, $data);
    }

    public function getTitleSeparator($store = null)
    {
        $separator = (string) $this->_scopeConfig->getValue('catalog/seo/title_separator', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $store);
        return ' ' . $separator . ' ';
    }

    public function getBreadcrumb()
    {
        $this->addCrumb(
                'home', [
            'label' => __('Home'),
            'title' => __('Go to Home Page'),
            'link' => $this->getBaseUrl()
                ]
        );
        foreach ((array) $this->path as $name => $breadcrumb) {
            $this->addCrumb($name, $breadcrumb);
        }
        return $this->getCrumbs();
    }

    protected function _prepareLayout()
    {
        $this->path = $this->_catalogData->getBreadcrumbPath();
        $title = [];
        foreach ((array) $this->path as $name => $breadcrumb) {
            $title[] = $breadcrumb['label'];
        }
        return $this->pageConfig->getTitle()->set(join($this->getTitleSeparator(), array_reverse($title)));
        //return parent::_prepareLayout();
    }

    public function getCrumbs()
    {
        return $this->_crumbs;
    }

    public function getBaseUrl()
    {
        return $this->_storeManager->getStore()->getBaseUrl();
    }

}

If you got it, let me know.

1
2

Thank you it work for me ! juste some improvement to get the key in phtml like default breadcrumb

public function getCrumbs()
{
    $evercrumbs = array();

    $evercrumbs[] = array(
        'label' => 'Home',
        'title' => 'Go to Home Page',
        'link' => $this->_storeManager->getStore()->getBaseUrl(),
        'key' => 'home'
    );

    $path = $this->_catalogData->getBreadcrumbPath();
    $product = $this->registry->registry('current_product');
    $categoryCollection = clone $product->getCategoryCollection();
    $categoryCollection->clear();
    $categoryCollection->addAttributeToSort('level', $categoryCollection::SORT_ORDER_DESC)->addAttributeToFilter('path', array('like' => "1/" . $this->_storeManager->getStore()->getRootCategoryId() . "/%"));
    $categoryCollection->setPageSize(1);
    $breadcrumbCategories = $categoryCollection->getFirstItem()->getParentCategories();
    foreach ($breadcrumbCategories as $category) {
        $evercrumbs[] = array(
            'label' => $category->getName(),
            'title' => $category->getName(),
            'link' => $category->getUrl(),
            'key' => $category->getUrlKey()
        );
    }


    $evercrumbs[] = array(
            'label' => $product->getName(),
            'title' => $product->getName(),
            'link' => '',
            'key' => $product->getUrlKey()
        );

    return $evercrumbs;
}

and in phtml file replace <li class="item <?= /* @escapeNotVerified */ $crumbName ?>">

by <li class="item <?= /* @escapeNotVerified */ $crumbInfo['key'] ?>">

I also have error with the file module.xml in your github so i change it to

<?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="Harrigo_EverCrumbs" active="true" setup_version="1.0.1"/>

2
  • I will look after and update the module thanks.
    – harri
    Jun 14, 2018 at 11:51
  • Thats a wiered module.xml however what was going wrong?
    – harri
    Jun 14, 2018 at 12:51
1

It looks like the braedcrumbs are now heavily reliant on the default navigation and so all custom menus may need to adapt to fix this. I think this was an attempt to fix caching of incorrect breadcrumbs.

Instead of fixing my megamenu and because the default breadcrumbs are still poor for me as when searching they never show for a product i have ripped the default breadcrumbs and replaced with my own.

public function getCrumbs()
{
    $evercrumbs = array();

    $evercrumbs[] = array(
        'label' => 'Home',
        'title' => 'Go to Home Page',
        'link' => $this->_storeManager->getStore()->getBaseUrl()
    );

    $path = $this->_catalogData->getBreadcrumbPath();
    $product = $this->registry->registry('current_product');

    $categoryCollection = clone $product->getCategoryCollection();
    $categoryCollection->clear();
    $categoryCollection->addAttributeToSort('level', $categoryCollection::SORT_ORDER_DESC)->addAttributeToFilter('path', array('like' => "1/" . $this->_storeManager->getStore()->getRootCategoryId() . "/%"));
    $categoryCollection->setPageSize(1);
    $breadcrumbCategories = $categoryCollection->getFirstItem()->getParentCategories();
    foreach ($breadcrumbCategories as $category) {
        $evercrumbs[] = array(
            'label' => $category->getName(),
            'title' => $category->getName(),
            'link' => $category->getUrl()
        );
    }

    $evercrumbs[] = array(
            'label' => $product->getName(),
            'title' => $product->getName(),
            'link' => ''
        );

    return $evercrumbs;
}

And then replaced default breadcrumbs with my own:

<?xml version="1.0"?>
<page>
    <body>
        <referenceBlock name="breadcrumbs" remove="true" />
        <referenceContainer name="page.top">
            <block class="Harrigo\EverCrumbs\Block\Evercrumbs" name="evercrumbs" as="evercrumbs" template="Harrigo_EverCrumbs::evercrumbs.phtml" />
        </referenceContainer>
    </body>
</page>

And the template for the block:

<?php $crumbs = $block->getCrumbs(); ?>
<?php if ($crumbs && is_array($crumbs)) : ?>
<div class="breadcrumbs">
    <ul class="items">
        <?php foreach ($crumbs as $crumbName => $crumbInfo) : ?>
            <li class="item <?= /* @escapeNotVerified */ $crumbName ?>">
            <?php if ($crumbInfo['link']) : ?>
                <a href="<?= /* @escapeNotVerified */ $crumbInfo['link'] ?>" title="<?= $block->escapeHtml($crumbInfo['title']) ?>"><?= $block->escapeHtml($crumbInfo['label']) ?></a>
            <?php else: ?>
                <?= $block->escapeHtml($crumbInfo['label']) ?>
            <?php endif; ?>
            </li>
        <?php endforeach; ?>
    </ul>
</div>
<?php endif; ?>

I have updated my EverCrumbs module if you wish to simply install and use this or look over the full code used:

https://github.com/harrigo/EverCrumbs

5
  • I have installed your extension, but breadcrumb is not correct shows when product added in multiple categories.
    – sky
    Jul 17, 2018 at 5:46
  • Yeah that is my main issue. It would be wrong on normal magento although also depending on which page it was originally cached on so is still an improvement.
    – harri
    Jul 17, 2018 at 7:25
  • Hi @harri can u please help me on this issue ? magento.stackexchange.com/questions/257932/… Jan 17, 2019 at 8:32
  • @NagarajuKasa This looks like a similar problem mentioned in this question here. As stated i think in my question the breadcrumbs may potentially being cached and if visited from search or directly no category is present weather or not you later visit from a category these old crumbs will get stuck in FPC. Have you tried flushing cache and visiting from category to see if they work then. Do the reverse and the breadcrumbs may not work. If so try my module and all should work,
    – harri
    Jan 17, 2019 at 10:42
  • Actually i think my question is different buy my answer should solve your issue none the less. I think i did have a question once where i solve your issue and this question updated my module to work with 2.2.5 or 2.2.6 as breadcrumbs relied on having default nav
    – harri
    Jan 17, 2019 at 10:44
1

Know that since the update the product page has its own breadcrumbs :

vendor/magento/module-catalog/view/frontend/templates/product/breadcrumbs.phtml

I have made a module to fix this:

https://github.com/CompactCodeEU/FixProductBreadcrumbs

Composer Install Instructions

composer config repositories.compact-code-fix-product-breadcrumbs git [email protected]:CompactCodeEU/FixProductBreadcrumbs.git

composer require "compactcode/fixproductbreadcrumbs:dev-master"

php bin/magento setup:upgrade
6
0

I've found solution, approved by Magento team. Because if you use approaches with block rewrites or plugins and you have some extensions that use Breadcrumbs block or Magento\Catalog\Controller\Product\View.php, you'll have problems. So, you have to customize just app/code/Magento/Catalog/view/frontend/web/js/product/breadcrumbs.js. So, here are full code:

    /**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

define([
    'jquery',
    'Magento_Theme/js/model/breadcrumb-list'
], function ($, breadcrumbList) {
    'use strict';

    return function (widget) {
        $.widget('mage.breadcrumbs', widget, {
            options: {
                categoryUrlSuffix: '',
                useCategoryPathInUrl: false,
                product: '',
                menuContainer: '[data-action="navigation"] > ul'
            },

            /** @inheritdoc */
            _render: function () {
                this._appendCatalogCrumbs();
                this._super();
            },

            /**
             * Append category and product crumbs.
             *
             * @private
             */
            _appendCatalogCrumbs: function () {
                var categoryCrumbs = this._resolveCategoryCrumbs();

                categoryCrumbs.forEach(function (crumbInfo) {
                    breadcrumbList.push(crumbInfo);
                });

                if (this.options.product) {
                    breadcrumbList.push(this._getProductCrumb());
                }
            },

            /**
             * Resolve categories crumbs.
             *
             * @return Array
             * @private
             */
            _resolveCategoryCrumbs: function () {
                var menuItem = this._resolveCategoryMenuItem(),
                    categoryCrumbs = [];

                if (menuItem !== null && menuItem.length) {
                    categoryCrumbs.unshift(this._getCategoryCrumb(menuItem));

                    while ((menuItem = this._getParentMenuItem(menuItem)) !== null) {
                        categoryCrumbs.unshift(this._getCategoryCrumb(menuItem));
                    }
                }

                return categoryCrumbs;
            },

            /**
             * Returns crumb data.
             *
             * @param {Object} menuItem
             * @return {Object}
             * @private
             */
            _getCategoryCrumb: function (menuItem) {

                return {
                    'name': 'category',
                    'label': menuItem.text(),
                    'link': menuItem.attr('href'),
                    'title': ''
                };
            },

            /**
             * Returns product crumb.
             *
             * @return {Object}
             * @private
             */
            _getProductCrumb: function () {
                return {
                    'name': 'product',
                    'label': this.options.product,
                    'link': '',
                    'title': ''
                };
            },

            /**
             * Find parent menu item for current.
             *
             * @param {Object} menuItem
             * @return {Object|null}
             * @private
             */
            _getParentMenuItem: function (menuItem) {
                var classes,
                    classNav,
                    parentClass,
                    parentMenuItem = null;

                if (!menuItem) {
                    return null;
                }

                classes = menuItem.parent().attr('class');
                classNav = classes.match(/(nav\-)[0-9]+(\-[0-9]+)+/gi);

                if (classNav) {
                    classNav = classNav[0];
                    parentClass = classNav.substr(0, classNav.lastIndexOf('-'));

                    if (parentClass.lastIndexOf('-') !== -1) {
                        parentMenuItem = $(this.options.menuContainer).find('.' + parentClass + ' > a');
                        parentMenuItem = parentMenuItem.length ? parentMenuItem : null;
                    }
                }

                return parentMenuItem;
            },

            /**
             * Returns category menu item.
             *
             * Tries to resolve category from url or from referrer as fallback and
             * find menu item from navigation menu by category url.
             *
             * @return {Object|null}
             * @private
             */
            _resolveCategoryMenuItem: function () {
                var categoryUrl = this._resolveCategoryUrl(),
                    menu = $(this.options.menuContainer),
                    categoryMenuItem = null;

                if (categoryUrl && menu.length) {
                    categoryMenuItem = menu.find('a[href="' + categoryUrl + '"]');
                }

                return categoryMenuItem;
            },

            /**
             * Returns category url.
             *
             * @return {String}
             * @private
             */
            _resolveCategoryUrl: function () {
                var categoryUrl;

                if (this.options.useCategoryPathInUrl) {
                    // In case category path is used in product url - resolve category url from current url.
                    categoryUrl = window.location.href.split('?')[0];
                    categoryUrl = categoryUrl.substring(0, categoryUrl.lastIndexOf('/')) +
                        this.options.categoryUrlSuffix;
                } else {
                    // In other case - try to resolve it from referrer (without parameters).
                    categoryUrl = document.referrer;

                    if (categoryUrl.indexOf('?') > 0) {
                        categoryUrl = categoryUrl.substr(0, categoryUrl.indexOf('?'));
                    }
                }

                return categoryUrl;
            }
        });

        return $.mage.breadcrumbs;
    };
});

And here is a link to merge request: ENGCOM-1707

2
  • I have tried your solution, but the _resolveCategoryMenuItem return null. Can you please share a better solution for this.
    – Ramya
    Feb 10, 2020 at 7:50
  • JS for breadcrumbing is probably the stupidest idea magento devs ever had. Aug 2, 2022 at 11:40
0

In my instance, I created a plugin to update the Magento\Catalog\ViewModel\Product\Breadcrumbs::getJsonConfigurationHtmlEscaped() function return value since I only needed to update the default widget options in this js file: /vendor/magento/module-catalog/view/frontend/web/js/product/breadcrumbs.js

The main issue was the default menu selectors (categoryItemSelector and menuContainer) did not match the current menu structure. Another route would be to fix the new menu structure and update the classes to match the default values. You could always do a js file override or mixin as well.

0

If you can overwrite di.xml file, it can be done with following code:

Vendor/Module/etc/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">
    <preference for="Magento\Catalog\Helper\Data"
                type="Vendor\Module\Helper\Data"/>
</config>

Vendor/Module/Helper/Data.php

<?php
namespace Vendor\Module\Helper;

class Data extends \Magento\Catalog\Helper\Data
{
    public function getBreadcrumbPath()
    {
        $product = $this->_coreRegistry->registry('current_product');

        $path = [];
        $categoryCollection = clone $product->getCategoryCollection();
        $categoryCollection->clear();
        $categoryCollection->addAttributeToSort('level', $categoryCollection::SORT_ORDER_DESC)
            ->addAttributeToFilter(
                'path',
                ['like' => "1/".$this->_storeManager->getStore()->getRootCategoryId()."/%"]
            );
        $categoryCollection->setPageSize(1);
        $breadcrumbCategories = $categoryCollection->getFirstItem()->getParentCategories();

        foreach ($breadcrumbCategories as $category) {
            $path['category'.$category->getId()] = ["label" => $category->getName(), "link" => $category->getUrl()];
        }

        if ($product) {
            $path['product'] = ["label" => $product->getName(), "link" => ""];
        }
        $this->_categoryPath = $path;

        return $this->_categoryPath;
    }
}

Your Answer

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

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