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.