Magento 2 Product list aggregate rating fix

During our work with the Magento 2 Rich Snippets module, we noticed numerous errors on the product list page when switched to list mode. The errors were generated by the standard Magento 2 Luma theme, when attempting to aggregate rating markup rendering for each product in the product list. This article outlines a configurable solution to address this issue. We will have an opportunity to disable this fix for any custom theme which doesn’t have this error or in case the next Magento 2 version has a fix for this included.

Aggregate Rating Errors in Magento2 product list mode

Let’s begin by understanding the root cause of the issue. Aggregate rating markup should be located inside the product scope tag of the Schema.org markup:

<div itemscope="" itemtype="http://schema.org/Product">
	...................
</div>

This is present on the product view page, but it’s missing from the product list page.

If we try to debug, where the ratings come from, we will find that it is generated by Magento\Review\Block\Product\ReviewRenderer::getReviewsSummaryHtml() method. So, we need to plug in this method and wrap the html generated as the method output with a tag, that is specified above. We will also need to do so for product list page only and make sure that we will be able to easily disable our fix for the custom theme or after Magento update.

Let’s start from the extension declaration. You may skip this part and add the system configuration and Magento 2 plugin to an existing extension. First of all we will need a module.xml :

<?xml version="1.0"?>
<!-- 
	Location: magento2_root/app/code/Vendorname/Extensionname/etc/module.xml
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendorname_Extensionname" setup_version="0.0.1">
    </module>
</config>

We will also need a registration.php:

<?php
// Location: magento2_root/app/code/Vendorname/Extensionname/registration.php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Vendorname_Extensionname',
    __DIR__
);

The last but not least is magento2_root/app/code/Vendorname/Extensionname/composer.json:

{
  "name": "vendorname/extensionname",
  "description": "N/A",
  "require": {
    "php": "~5.5.0|~5.6.0|~7.0.0"
  },
  "type": "magento2-module",
  "version": "0.0.1-beta",
  "license": [
    "OSL-3.0",
    "AFL-3.0"
  ],
  "autoload": {
    "files": [ "registration.php" ],
    "psr-4": {
      "Vendorname\\Extensionname\\": ""
    }
  }
}

We’ve completed the setup. Next, we’ll introduce a configuration that facilitates easy toggling of our fix on or off:

<?xml version="1.0"?>
<!--
/**
 * Location: magento2_root/app/code/Vendorname/Extensionname/etc/adminhtml/system.xml
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="example_tab" translate="label" sortOrder="1000">
            <label>Example tab config</label>
        </tab>
        <section id="example_section" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Example config section</label>
            <tab>example_tab</tab>
            <resource>Vendorname_Extensionname::config</resource>
            <group id="category" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Category Page Product Reviews Fix</label>
                <field id="review_fix_enabled" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Category Page Product Reviews Fix</label>
                    <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model>
                    <comment><![CDATA[Wrap native product review summary with target product markup, which is missing in native functionality.]]></comment>
                </field>
            </group>
        </section>
    </system>
</config>

It is useful to have a default value for our configuration:

<?xml version="1.0"?>
<!--
/**
 * Location: magento2_root/app/code/Vendorname/Extensionname/etc/config.xml
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <example_section>
            <category>
                <review_fix_enabled>1</review_fix_enabled>
            </category>
        </example_section>
    </default>
</config>

Let’s add our Magento 2 plugin to di.xml :

<?xml version="1.0"?>
<!--
/**
 * Location: magento2_root/app/code/Vendorname/Extensionname/etc/di.xml
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Review\Block\Product\ReviewRenderer">
        <plugin name="vendornma-extensionname-product-list-rating-wrapper"
                type="Vendorname\Extensionname\Plugin\Block\Product\ReviewRenderer" sortOrder="1000"/>
    </type>
</config>

Additionally, we’ll require a helper for extracting the config value:

<?php
// Location: magento2_root/app/code/Vendorname/Extensionname/Helper/Data.php

namespace Vendorname\Extensionname\Helper;
use Magento\Framework\App\Helper\Context;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * Returns system configuration value
     *
     * @param $key
     * @param null $store
     * @return mixed
     */
    public function getConfigurationValue($key, $store = null)
    {
        return $this->scopeConfig->getValue(
            'example_section/' . $key,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
            $store
        );
    }
}

Now, we are ready to add a plugin file. We will plugin after the main method run and will wrap its output with a proper markup if the route is correct and our fix is enabled in the config:

<?php
// Location: magento2_root/app/code/Vendorname/Extensionname/Plugin/Block/Produc/ReviewRenderer.php

namespace Vendorname\Extensionname\Plugin\Block\Product;

use Vendorname\Extensionname\Helper\Data as Helper;
use Magento\Review\Block\Product\ReviewRenderer as SubjectBlock;

class ReviewRenderer
{
    /**
     * @var \Vendorname\Extensionname\Helper\Data
     */
    protected $helper;

    public function __construct(
        Helper $helper
    )
    {
        $this->helper = $helper;
    }

    public function afterGetReviewsSummaryHtml(SubjectBlock $subject, $result = '')
    {
        if ($result != ''
            && !is_null($subject->getRequest())
            && $subject->getRequest()->getFullActionName() == 'catalog_category_view'
            && $this->helper->getConfigurationValue('category/review_fix_enabled') == 1
            && $product = $subject->getProduct()) {
            $result = '<div itemscope itemtype="http://schema.org/Product"><div itemprop="name" content="'
                . htmlspecialchars($product->getName()) . '"></div>' . $result . '</div>';
        }

        return $result;
    }
}

Note, that we are also adding a product name to our markup.

That’s all we need. We hope this article saves you time and assists in enhancing your Magento 2 SEO performance. We will include this fix in our next release of the Rich Snippets extension. Thanks for reading!