The question:
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?
The Solutions:
Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.
Method 1
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 MagentoFrameworkViewResultPage::render()
, you’ll see that the argument bodyAttributes
is build like this:
'bodyAttributes' => $this->pageConfigRenderer->renderElementAttributes($config::ELEMENT_TYPE_BODY),
MagentoFrameworkViewPageConfigRenderer::renderElementAttributes()
is a public method which we can hook into:
etc/frontend/di.xml
:
<type name="MagentoFrameworkViewPageConfigRenderer">
<plugin name="add_class_to_body"
type="VendorModulePluginMagentoFrameworkViewPageConfigRenderer"/>
</type>
And Renderer.php
:
/**
* @var Session
*/
protected $customerSession;
/**
* Renderer constructor.
* @param Session $customerSession
*/
public function __construct(
Session $customerSession
)
{
$this->customerSession = $customerSession;
}
/**
* @param MagentoFrameworkViewPageConfigRenderer $subject
* @param callable $proceed
* @param string $elementType
* @return string
*/
public function aroundRenderElementAttributes(
MagentoFrameworkViewPageConfigRenderer $subject,
callable $proceed,
string $elementType
)
{
$result = $proceed($elementType);
if ($elementType === MagentoFrameworkViewPageConfig::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 MagentoPageCacheModelDepersonalizeChecker::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="MagentoPageCacheModelDepersonalizeChecker">
<plugin name="check_logged_in"
type="VendorModulePluginMagentoPageCacheModelDepersonalizeChecker"/>
</type>
DepersonalizeChecker.php
:
/**
* @var Session
*/
protected $customerSession;
/**
* DepersonalizeChecker constructor.
* @param Session $customerSession
*/
public function __construct(
Session $customerSession
)
{
$this->customerSession = $customerSession;
}
/**
* @param MagentoPageCacheModelDepersonalizeChecker $subject
* @param bool $result
* @return bool
*/
public function afterCheckIfDepersonalize(
MagentoPageCacheModelDepersonalizeChecker $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.
Method 2
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
Method 3
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).
Method 4
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 = MagentoFrameworkAppObjectManager::getInstance();
$customerSession = $objectManager->get('MagentoCustomerModelSession');
if($customerSession->isLoggedIn()) { ?>
<script type="text/javascript">
require(['jquery'],function($){
$( window ).load(function() {
$('body').addClass('customer-logged-in');
});
});
</script>
<?php } ?>
Method 5
I’ve achieved id by doing the following:
Create an events.xml file in your module. VendorPackageetcfrontendevents.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="VendorPackageObserverAddHandles" />
</event>
</config>
Now, create the file VendorPackageObserverAddHandles.php
<?php
namespace VendorPackageObserver;
use MagentoCustomerModelSession as CustomerSession;
use MagentoFrameworkEventObserver;
use MagentoFrameworkEventObserverInterface;
use MagentoFrameworkViewPageConfig;
use MagentoNewsletterModelSubscriber;
/**
* Class AddHandles
* @package VendorPackageObserver
*/
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:
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0