Pergunta

I want to add a CSS class to the body when the customer is logged in or not. I thought this would work:

<body>
    <customer_logged_in>
        <attribute name="class" value="customer-logged-in"/>
    </customer_logged_in>
    <customer_logged_out>
        <attribute name="class" value="customer-logged-out"/>
    </customer_logged_out>

But that doesn't seem to work. Is there any way to do this via the layout?

Foi útil?

Solução

This can be easily done by hooking into the page renderer. After all, that's the class responsible for rendering the <body>-tag.

It always starts easy...

If you look at \Magento\Framework\View\Result\Page::render(), you'll see that the argument bodyAttributes is build like this:

'bodyAttributes' => $this->pageConfigRenderer->renderElementAttributes($config::ELEMENT_TYPE_BODY),

Magento\Framework\View\Page\Config\Renderer::renderElementAttributes() is a public method which we can hook into:

etc/frontend/di.xml:

<type name="Magento\Framework\View\Page\Config\Renderer">
    <plugin name="add_class_to_body"
            type="Vendor\Module\Plugin\Magento\Framework\View\Page\Config\Renderer"/>
</type>

And Renderer.php:

/**
 * @var Session
 */
protected $customerSession;

/**
 * Renderer constructor.
 * @param Session $customerSession
 */
public function __construct(
    Session $customerSession
)
{
    $this->customerSession = $customerSession;
}

/**
 * @param \Magento\Framework\View\Page\Config\Renderer $subject
 * @param callable $proceed
 * @param string $elementType
 * @return string
 */
public function aroundRenderElementAttributes(
    \Magento\Framework\View\Page\Config\Renderer $subject,
    callable $proceed,
    string $elementType
)
{
    $result = $proceed($elementType);

    if ($elementType === \Magento\Framework\View\Page\Config::ELEMENT_TYPE_BODY) {
        // Prepend CSS class:
        if ($this->customerSession->isLoggedIn()) {
            $result = str_replace(
                'class="', 
                'class="logged-in ', 
                $result
            );
        }
    }

    return $result;
}

Now, this should do the trick.

Well, as long as you're not using full page cache...

Full Page Cache and customer sessions

Full Page cache has a very interesting way of working that you should be aware of. That is: if all elements in the page qualify for caching, the customer session is 'depersonalized'. This is done to make sure that no customer details accidentally end up in the cached pages. After all, caching is your best friend and your worst enemeny.

The method responsible for this is Magento\PageCache\Model\DepersonalizeChecker::checkIfDepersonalize().

What this means for us, is that whenever a page is qualified for full page caching, our customer session is depersonalized. So we can no longer check $this->customerSession->isLoggedIn() to determine whether or not to add the class to the body.

To fix this you have to write a plugin for the depersonalizer as well:

di.xml:

<type name="Magento\PageCache\Model\DepersonalizeChecker">
    <plugin name="check_logged_in"
            type="Vendor\Module\Plugin\Magento\PageCache\Model\DepersonalizeChecker"/>
</type>

DepersonalizeChecker.php:

/**
 * @var Session
 */
protected $customerSession;

/**
 * DepersonalizeChecker constructor.
 * @param Session $customerSession
 */
public function __construct(
    Session $customerSession
)
{
    $this->customerSession = $customerSession;
}

/**
 * @param \Magento\PageCache\Model\DepersonalizeChecker $subject
 * @param bool $result
 * @return bool
 */
public function afterCheckIfDepersonalize(
    \Magento\PageCache\Model\DepersonalizeChecker $subject,
    bool $result
)
{
    if ($result === true) {
        return $this->customerSession->isLoggedIn() ? false : $result;
    }

    return $result;
}

BEWARE OF WHAT YOU ARE DOING HERE! Because basically you are disabling full-page cache entirely for every logged in customer. So if you are going to mess with the outcome of the depersonalizer, you'd be best of by adding some extra additions:

  • Perhaps there are only some customer groups that require a body class?
  • Perhaps it's only needed on certain pages?
  • Perhaps it's only needed according to some other parameters?

In conclusion

The combination of the two plugins should get you going into implementing this feature. It's funny how a simple task like 'adding a class to the body if condition x == y' can introduce a lot more complexity than - for example - adding a new shipping carrier or something.

It's just important that you know what you're doing.

Outras dicas

I think the best approach would be to manually add handles customer-logged-in and customer-logged-out since they are not anymore present by default in Magento 2, and to utilize class attribute to add a particular class for these handles.

eg.

<body>
    <attribute name="class" value="customer-logged-in"/>

...

Here is the simple module which adds customer-logged-in and customer-logged-out handles: http://frankclark.xyz/modules/magento-2-get-customer_logged_in-and-customer_logged_out-layout-handles

The approach I use is to add these classes with JavaScript, it has the advantage of not messing around with any of M2's backend logic, Full Page Cache and is less likely to cause any issues.

var htmlBody = $("body[data-container='body']");
if ($(".customer-welcome").length) {
    htmlBody.addClass("customer-logged-in");
} else {
    htmlBody.addClass("customer-logged-out");
}

It adds the correct CSS class to the <body> tag by checking the document body to see if the .customer-welcome element is present, which for my theme is only in the document body if the customer is logged in (and I believe Magento default themes too).

You can directly do that by using below code. Its not best way but works fine. Put below code in any common file like footer.phtml

<?php 
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$customerSession = $objectManager->get('Magento\Customer\Model\Session');
if($customerSession->isLoggedIn()) { ?>
<script type="text/javascript">


require(['jquery'],function($){
        $( window ).load(function()  {
            $('body').addClass('customer-logged-in');
        });
    });


</script>
<?php } ?>

I've achieved id by doing the following:

Create an events.xml file in your module. Vendor\Package\etc\frontend\events.xml and add the content:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="layout_load_before">
        <observer name="add_layout_handles" instance="Vendor\Package\Observer\AddHandles" />
    </event>
</config>

Now, create the file Vendor\Package\Observer\AddHandles.php

<?php

namespace Vendor\Package\Observer;

use Magento\Customer\Model\Session as CustomerSession;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\View\Page\Config;
use Magento\Newsletter\Model\Subscriber;

/**
 * Class AddHandles
 * @package Vendor\Package\Observer
 */
class AddHandles implements ObserverInterface
{
    /**
     * @var CustomerSession
     */
    protected $customerSession;
    /**
     * @var Config
     */
    protected $pageConfig;
    /**
     * @var Subscriber
     */
    protected $subscriber;

    /**
     * AddHandles constructor.
     * @param CustomerSession $customerSession
     * @param Config $pageConfig
     * @param Subscriber $subscriber
     */
    public function __construct(
        CustomerSession $customerSession,
        Config $pageConfig,
        Subscriber $subscriber
    ) {
        $this->customerSession = $customerSession;
        $this->pageConfig = $pageConfig;
        $this->subscriber= $subscriber;
    }

    /**
     * @param Observer $observer
     */
    public function execute(Observer $observer)
    {
        if ($this->customerSession->isLoggedIn()) {
            $this->pageConfig->addBodyClass('customer_logged_in');

            if($this->subscriber->loadByEmail($this->customerSession->getCustomer()->getEmail())->isSubscribed()) {
                $this->pageConfig->addBodyClass('is-subscribed');
            }
        }
    }
}

Flush the cache and it should work. In your frontend tag you should see something like that if your user is logged in and subscribes to the newsletter:

enter image description here

Licenciado em: CC-BY-SA com atribuição
Não afiliado a magento.stackexchange
scroll top