Question

The problem:

I'm running into an issue with the page/block caching and the minicart. In the template cart/minicart.phtml we have a permission check to see if the current customer is allowed to add products to the cart like so:

<?php if ($customerViewModel->hasPermission(PermissionManagerInterface::ACL_RESOURCE_4)): ?>
    // Render the minicart
<?php endif; ?>

The problem here is that this block is being cached on the first visit. So if the first customer who visits the page is allowed to add products to the cart then customers who don't and logged out visitors will see the cart button in the header.

Fix attempt 1: Override the block and set _isScopePrivate to true

While this fixed the initial issue with the always-visible (or never visible) cart button, a new issue came up: the contents of the cart is never rendered, presumably because the mage-init/knockoutJS stuff is now not executed properly. The dialog which should contain the items is always an empty white box.

Fix attempt 2: Setting cacheable to false

Aside from getting the issue of the full_page cache now never picking up the pages, this did not do anything to the block output. It's still either always visible or never visible.

The current workaround

Currently, I have disabled the block_html cache and this "fixes" all the issues above. But I would prefer if there is a way to make the block just appear conditionally based on the output of the hasPermission check.

TL;DR

Is there a way to conditionally show/hide the minicart block based on the output of a hasPermission method.

Was it helpful?

Solution

I've found 2 plugins in the core that appear to be doing what I'm looking for.

In short, they change the cache key and the returned cache data based on the customer group instead of disabling the cache altogether.

These are the 2 plugins:

  • Magento\CatalogPermissions\Model\Plugin\Theme\Block\Html\Topmenu::beforeToHtml
  • Magento\CatalogPermissions\Plugin\Theme\Block\Html\Topmenu::afterGetCacheKeyInfo

However, since we're not using the CatalogPermissions module in our project, I've copied the methods over to a separate plugin in a custom module and slightly modified the logic to match our needs.

The end result:

File: Vendor\Module\Plugin\Theme\Block\Html\Topmenu

<?php

declare(strict_types=1);

namespace Vendor\Module\Plugin\Theme\Block\Html;

use Vendor\Customer\Api\Data\AclCustomerInterface;
use Vendor\Customer\Model\PermissionManager;

use Magento\Customer\Model\Session as CustomerSession;
use Magento\Theme\Block\Html\Topmenu as TopmenuBlock;

class Topmenu
{
    /**
     * @var CustomerSession
     */
    protected $customerSession;

    /**
     * @var PermissionManager
     */
    protected $permissionManager;

    /**
     * @param CustomerSession $customerSession
     * @param PermissionManager $permissionManager
     */
    public function __construct(
        CustomerSession $customerSession,
        PermissionManager $permissionManager
    ) {
        $this->customerSession = $customerSession;
        $this->permissionManager = $permissionManager;
    }

    /**
     * Add Customer type code cache key.
     *
     * @param TopmenuBlock $subject
     * @param array $result
     * @return array
     */
    public function afterGetCacheKeyInfo(TopmenuBlock $subject, $result): array
    {
        $result['customer_type_code'] = $this->getCustomerTypeCode();

        return $result;
    }

    /**
     * Plugin that generates unique block cache key depending on customer group.
     *
     * @param TopmenuBlock $subject
     * @return array|null
     */
    public function beforeToHtml(TopmenuBlock $subject): ?array
    {
        $typeCode = $this->getCustomerTypeCode();

        $key = $subject->getCacheKeyInfo();
        $key = array_values($key);
        $key[] = $typeCode;
        $key = implode('|', $key);
        $key = sha1($key);

        $subject->setData('cache_key', $key);

        return null;
    }

    /**
     * @return string
     */
    protected function getCustomerTypeCode(): string
    {
        /** @var AclCustomerInterface $customer */
        $customer = $this->customerSession->getCustomerData();

        return $customer ? $customer->getTypeCode() : '';
    }
}

File: 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">
    <type name="Magento\Theme\Block\Html\Topmenu">
        <plugin name="module_permissions_topmenu" type="Vendor\Module\Plugin\Theme\Block\Html\Topmenu"/>
    </type>
</config>

(I've obfuscated the vendor and module name as I'm not sure I'm allowed to share those)

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