Question

I have looked everywhere for any reviews extension that supported grouped products and could not find any. So trying to hash it out myself.

I need the reviews block on a grouped product to also show reviews for the simple products that are on the page.

The collections classes Magento\Review\Model\ResourceModel\Review\Product\Collection and Magento\Review\Model\ResourceModel\Review\Collection build a query via method AddEntityFilter using exact match.

It seems that "all" that is needed here is to create a new block that calls these factory methods and passes a list of IDs - and then "override" the query in AddEntityFilter from entity_pk_value=? to entity_pk_value in (?).

That would remain backwards compatible with single value so there is no risk of conflict with anything else relying on these classes. So it could be a plugin or preference override.

I am currently getting a death walk of errors with everything I try - it seems plugins are very hard around query objects because half the methods are protected.

I can't tell if my death walk of errors are because I dont understand "around" plugins well enough and am making rookie mistakes, or because I am trying something that cannot be done by plugin

Here's what didn't work so far for me

Module Alpine\FixGrouped

Alpine\FixGrouped\view\templates\review\product\view\list.phtml is identical to the core version, except for calling a different block class

layout Alpine\FixGrouped\view\frontend\layout\review_product_list.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="catalog_product_view_type_grouped"/>
    <body>
        <referenceContainer name="product.info.details">
                <block class="Alpine\FixGrouped\Block\Review\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Alpine_FixGrouped::review/product/view/list.phtml" ifconfig="catalog/review/active"/>
        </referenceContainer>
    </body>
</page>

beginning of template: (all identical except @var line)

   /** @var Alpine\FixGrouped\Block\Review\Product\View\GroupedListView $block */

    $_items = $block->getReviewsCollection()->getItems();
    ...

Next the custom block - adds a "getGroupIds" method and uses that in the building of the collection instead of single getProductId

namespace Alpine\FixGrouped\Block\Review\Product\View;

class GroupedListView extends \Magento\Review\Block\Product\View
{

    /**
     * Get grouped product ids
     *
     * @return string|int|null
     */
    public function getGroupIds()
    {
        $product = $this->_coreRegistry->registry('product');

        // get children IDs
        if ($product) {

            $children = $product->getTypeInstance()->getChildrenIds($product->getId());
            $idlist = [$product->getId()];
            $idlist[] = $children[0];
            return implode(',',$idlist);
        }

        return null;
    }

    /**
     * Get collection of reviews
     *
     * @return ReviewCollection
     */
    public function getReviewsCollection()
    {
        if (null === $this->_reviewsCollection) {
            $this->_reviewsCollection = $this->_reviewsColFactory->create()->addStoreFilter(
                $this->_storeManager->getStore()->getId()
            )->addStatusFilter(
                \Magento\Review\Model\Review::STATUS_APPROVED
            )->addEntityFilter(
                'product',
                $this->getGroupIds()
            )->setDateOrder();
        }
        return $this->_reviewsCollection;
    }

Obviously now the addEntityFilter method in the review collection needs to be adapted to work with a list.

So I tried was plugin but I admit I am out of my depth here, never done an "around" replacement plugin before.

in frontend\di.xml

<type name="Magento\Review\Model\ResourceModel\Review\Collection">
    <plugin sortOrder="1" name="FixGroupedPluginModelReviewCollection" type="Alpine\FixGrouped\Plugin\Model\Review\ResourceModel\Review\Collection" disabled="false" />
</type>

and the code

namespace Alpine\FixGrouped\Plugin\Model\Review\ResourceModel\Review

/**
 * Review collection resource model
 *
 * @api
 * @since 100.0.2
 */
class Collection extends Magento\Review\Model\ResourceModel\Review\Collection
{

    /**
     * Add entity filter
     *
     * @param int|string $entity
     * @param int|string $pkValue
     * @return $this
     */
    public function aroundAddEntityFilter(\Magento\Review\Model\ResourceModel\Review\Collection $target, \Closure $ignore, $entity, $pkValue)
    {
        $reviewEntityTable = $target->getReviewEntityTable();
        if (is_numeric($entity)) {
            $target->addFilter('entity', $target->getConnection()->quoteInto('main_table.entity_id=?', $entity), 'string');
        } elseif (is_string($entity)) {
            $target->_select->join(
                $reviewEntityTable,
                'main_table.entity_id=' . $reviewEntityTable . '.entity_id',
                ['entity_code']
            );

            $target->addFilter(
                'entity',
                $target->getConnection()->quoteInto($reviewEntityTable . '.entity_code=?', $entity),
                'string'
            );
        }

        $target->addFilter(
            'entity_pk_value',
            $target->getConnection()->quoteInto('main_table.entity_pk_value in (?)', $pkValue),
            'string'
        );

        return $target;
    }

The result at the moment is that no error appears anywhere but also no reviews appear. Different approaches have caused outright error - including warning of protected methods - so at least some of the "plugin" code is executed

Was it helpful?

Solution

try again with the plugin approach and replace $target->_select->join( with $target->getSelect()->join(

and $target->getReviewEntityTable(); with $target->getTable('review_entity');
This should get rid of the protected method calls.

OTHER TIPS

After a month of end of day head banging I had no luck trying to override the getReviewsCollection blocks where criteria are hard coded, neither as preference nor as plugin (things like $target->getReviewsColFactory->create() still wouldn't work no matter how many diagonal approaches I tried).

So I ended up adding logic to the plugin in the Collection classes @marius helped me fix . It's not where it should be but this does add the children reviews to the parents page - further plugins needed for the summary and the ratings, though.

Putting this out here should someone else be a grouped-product-fixing masochist

namespace Alpine\FixGrouped\Plugin\Model\Review\ResourceModel\Review;
// was namespace Magento\Review\Model\ResourceModel\Review;
use Psr\Log\LoggerInterface;
use Magento\GroupedProduct\Model\ResourceModel\Product\Link;

class Collection 
{
    /**
     * Add entity filter
     *
     * @param int|string $entity
     * @param int|string $pkValue
     * @return $this
     */
    public function aroundAddEntityFilter(\Magento\Review\Model\ResourceModel\Review\Collection $target, \Closure $ignore, $entity, $pkValue)
    {

        $reviewEntityTable = $target->getTable('review_entity');
        if (is_numeric($entity)) {
            $target->addFilter('entity', $target->getConnection()->quoteInto('main_table.entity_id=?', $entity), 'string');
        } elseif (is_string($entity)) {
            $target->getSelect()->join(
                $reviewEntityTable,
                'main_table.entity_id=' . $reviewEntityTable . '.entity_id',
                ['entity_code']
            );
            $target->addFilter(
                'entity',
                $target->getConnection()->quoteInto($reviewEntityTable . '.entity_code=?', $entity),
                'string'
            );
        }

        $idlist = $pkValue;
        if ( (is_string($entity) && $entity=='product') || ( is_numeric($entity) && $entity==1) ) {
            // should i test  is_numeric($pkValue)?
            $children = $this->getGroupedChildren($pkValue);
            if (!empty($children[3])) 
            {
                foreach(array_keys($children[3]) as $key) {$idlist .= ','.$key;}
            }
        }

        $target->addFilter(
            'entity_pk_value',
            'main_table.entity_pk_value in ('.$idlist.')',
            'string'
        );
        return $target;
    }

    /**
     * Retrieve Required children ids
     * Return grouped array, ex array(
     *   group => array(ids)
     * )
     *
     * @param int $parentId
     * @return array
     */
    public function getGroupedChildren($parentId) 
    {
        $groupedworker = \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\GroupedProduct\Model\ResourceModel\Product\Link::class);
        return $groupedworker->getChildrenIds(
            $parentId,
            \Magento\GroupedProduct\Model\ResourceModel\Product\Link::LINK_TYPE_GROUPED
        );
    }
}

PS: Here we are at the "review collections lookup" for a product so I just fetch the IDs (already fetched since we are on the product page) and put them in the condition entity_pk_value in ('.$idlist.')

For the product collections I suspect a subquery condition will be needed, as in "entity_pk_value=$entityId or entity_pk_value in (SELECT linked_product_id FROM ".$linktable." WHERE product_id=$entityId and link_type_id=3)"

PPS: I wasted a lot of time tracing things through Review and Framework classes before I realised that the original class' use of quoteInto in the line $target->getConnection()->quoteInto('main_table.entity_pk_value in (?)', $pkValue) was breaking things by quoting my whole variable...

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top