I'm trying to remove a block from a certain page (be it frontend or backend) but only if a certain config flag is set to true.
Let's take an example.
I want to remove the block with the name dashboard from the admin dashboard.
The block is defined in adminhtml_dashboard_index.xml file from the Magento_Backend module:
<referenceContainer name="content">
<block class="Magento\Backend\Block\Dashboard" name="dashboard"/>
</referenceContainer>
Thanks to Adam's answer I can do this in the adminhtml_dashboard_index.xml
<body>
<referenceBlock name="dashboard" remove="true" />
</body>
But I want to take it up a notch and remove this block only if the config setting with the path dashboard/settings/remove has the value 1.
A layout xml approach would be awesome, but I will take an observer approach also.
5 Answers 5
I couldn't find a way to do this with layout either but here is an example of a way you can do it with observers (providing they extend the Template block)...
Create your events.xml in etc/events.xml
<?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="view_block_abstract_to_html_before">
<observer name="remove_block" instance="[Vendor]\[ModuleName]\Model\Observer\RemoveBlock" />
</event>
</config>
Create the observer
<?php
namespace [Vendor]\[ModuleName]\Model\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class RemoveBlock implements ObserverInterface
{
protected $_scopeConfig;
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
) {
$this->_scopeConfig = $scopeConfig;
}
public function execute(Observer $observer)
{
/** @var \Magento\Framework\View\Element\Template $block */
$block = $observer->getBlock();
if ($block->getType() == 'Magento\Backend\Block\Dashboard') {
$remove = $this->_scopeConfig->getValue(
'dashboard/settings/remove',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
if ($remove) {
$block->setTemplate(false);
}
}
}
}
Basically the _toHtml checks to see if there is a template. If there isn't it returns ''.
EDIT
After some more digging i have found a way to do this further up the chain.
<?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_generate_blocks_after">
<observer name="remove_block" instance="[Vendor]\[ModuleName]\Model\Observer\RemoveBlock" />
</event>
</config>
And the observer...
<?php
namespace [Vendor]\[ModuleName]\Model\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class RemoveBlock implements ObserverInterface
{
protected $_scopeConfig;
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
) {
$this->_scopeConfig = $scopeConfig;
}
public function execute(Observer $observer)
{
/** @var \Magento\Framework\View\Layout $layout */
$layout = $observer->getLayout();
$block = $layout->getBlock('dashboard');
if ($block) {
$remove = $this->_scopeConfig->getValue(
'dashboard/settings/remove',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
if ($remove) {
$layout->unsetElement('dashboard');
}
}
}
}
-
This might work, but only for blocks that use templates. It applies to the example I provided, but still, if there are blocks that extend the AbstractBlock and not the Template block this will not work. +1 for the good starting point.Marius– Marius2016年01月27日 22:42:45 +00:00Commented Jan 27, 2016 at 22:42
-
You are correct. After some more digging i found you can do this earlier in the process. Answer updated. I have left my original there for reference.Smartie– Smartie2016年01月28日 09:01:46 +00:00Commented Jan 28, 2016 at 9:01
-
Thanks this is a useful answer. The problem is it means the logic will be fired on every page load as it's using the event "layout_generate_blocks_after". Do you know how to only run it on certain page loads e.g. loading a category page (event is "catalog_controller_category_init_after" but the layout cannot be accessed)?Alex– Alex2016年05月10日 09:20:20 +00:00Commented May 10, 2016 at 9:20
-
2Really?! We have to do an observer to remove or not conditionally a block? this is ridiculous, just saying.Marc Pont– Marc Pont2017年05月02日 08:03:06 +00:00Commented May 2, 2017 at 8:03
-
2Observers should not manipulate data I think ...Alex– Alex2019年07月04日 10:03:54 +00:00Commented Jul 4, 2019 at 10:03
Normally it should be done with <action /> tag :
<referenceContainer name="content">
<action method="unsetChild" ifconfig="dashboard/settings/remove">
<argument xsi:type="string">dashboard</argument>
</action>
</referenceContainer>
EDIT :
Only problem is unsetChild only accept alias. You cannot use block name.
Other solution: rewrite Magento Framework to be able to use ifconfig with remove="true"
1- Create your own module.
2- Add a new file to override Magento Framework : (eg : /Vendor/Module/Override/Magento/Framework/View/Layout/Reader/Block.php)
namespace Vendor\Module\Override\Magento\Framework\View\Layout\Reader;
use Magento\Framework\App;
use Magento\Framework\Data\Argument\InterpreterInterface;
use Magento\Framework\View\Layout;
/**
* Block structure reader
*/
class Block extends \Magento\Framework\View\Layout\Reader\Block
{
/**
* @var \Magento\Framework\App\ScopeResolverInterface
*/
protected $scopeResolver;
/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $scopeConfig;
/**
* Constructor
*
* @param Layout\ScheduledStructure\Helper $helper
* @param Layout\Argument\Parser $argumentParser
* @param Layout\ReaderPool $readerPool
* @param InterpreterInterface $argumentInterpreter
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\App\ScopeResolverInterface $scopeResolver
* @param string|null $scopeType
*/
public function __construct(
Layout\ScheduledStructure\Helper $helper,
Layout\Argument\Parser $argumentParser,
Layout\ReaderPool $readerPool,
InterpreterInterface $argumentInterpreter,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Framework\App\ScopeResolverInterface $scopeResolver,
$scopeType = null
) {
parent::__construct($helper,
$argumentParser,
$readerPool,
$argumentInterpreter,
$scopeType
);
$this->scopeConfig = $scopeConfig;
$this->scopeResolver = $scopeResolver;
}
protected function scheduleReference(
Layout\ScheduledStructure $scheduledStructure,
Layout\Element $currentElement
) {
$elementName = $currentElement->getAttribute('name');
$elementRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN);
if ($elementRemove) {
$configPath = (string)$currentElement->getAttribute('ifconfig');
if (empty($configPath)
|| $this->scopeConfig->isSetFlag($configPath, $this->scopeType, $this->scopeResolver->getScope())
) {
$scheduledStructure->setElementToRemoveList($elementName);
}
} else {
$data = $scheduledStructure->getStructureElementData($elementName, []);
$data['attributes'] = $this->mergeBlockAttributes($data, $currentElement);
$this->updateScheduledData($currentElement, $data);
$this->evaluateArguments($currentElement, $data);
$scheduledStructure->setStructureElementData($elementName, $data);
}
}
}
3- Add di.xml file to override magento file :
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Framework\View\Layout\Reader\Block"
type="Vendor\Module\Override\Magento\Framework\View\Layout\Reader\Block" />
</config>
4- Now you can use ifconfig in layout combined with remove :
<referenceBlock name="content" remove="true" ifconfig="path/to/myconfig" />
This example is for Block, but you can do the same for container if you override the method containerReference() of /Magento/Framework/View/Layout/Reader/Container.php
-
I think rewriting the Framework is the best solution, don't know why magento don't have this by default.Marc Pont– Marc Pont2017年05月03日 08:22:16 +00:00Commented May 3, 2017 at 8:22
-
Magento2.3.4 - Rewriting the Framework is working for mode - default and production. ifconfig not working for developer mode. :-|Ronak Patel– Ronak Patel2020年05月13日 17:21:27 +00:00Commented May 13, 2020 at 17:21
From the technical guidelines:
14.1. All values (including objects) passed to an event MUST NOT be modified in the event observer. Instead, plugins SHOULD BE used for modifying the input or output of a function.
14.3. Events SHOULD NOT change a state of observable objects.
So here is a plugin solution for this:
Declare the plugin:
<?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\Framework\View\Element\AbstractBlock">
<plugin name="remove_block" type="[Vendor]\[Module]\Plugin\RemoveBlock" />
</type>
</config>
Define the plugin:
<?php
namespace Vendor\Module\Plugin;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\View\Element\AbstractBlock;
class RemoveBlock
{
const BLOCK_NAME = 'block_to_be_removed';
const CONFIG_PATH = 'your/path';
private $_scopeConfig;
public function __construct(ScopeConfigInterface $scopeConfig) {
$this->_scopeConfig = $scopeConfig;
}
public function afterToHtml(AbstractBlock $subject, $result)
{
if ($subject->getNameInLayout() === self::BLOCK_NAME && $this->_scopeConfig->getValue(self::class)) {
return '';
}
return $result;
}
}
Like in the answer from Smartie I tried to plugin further up the chain into \Magento\Framework\View\Layout\Builder::build with an afterBuild() method but this will lead into an endless recursion because \Magento\Framework\View\Layout::getBlock and \Magento\Framework\View\Layout::unsetElement both call \Magento\Framework\View\Layout\Builder::build again.
"ifconfig" attribute of a "block" node in layout allows you to link block to value in store configuration.
"ifconfig" processing happens in \Magento\Framework\View\Layout\GeneratorPool::buildStructure
-
2It won't work with "referenceBlock" though. It will only work when you're adding a new block.Nikita Abrashnev– Nikita Abrashnev2020年01月23日 12:55:26 +00:00Commented Jan 23, 2020 at 12:55
You can remove the block, and then conditionally add your block. For example, in your adminhtml_dashboard_index.xml you can put:
<referenceContainer name="content">
<block class="Magento\Backend\Block\Dashboard" name="dashboard" remove="true"/>
<block class="Magento\Backend\Block\Dashboard" name="my_dashboard" ifconfig="dashboard/settings/remove"/>
</referenceContainer>
Note that you should change the logic of dashboard/settings/remove setting. It should be dashboard/settings/show, and if it is set to 1 your block will be shown.
helperclass, see https://stackoverflow.com/questions/47237179/magento-2-i-want-to-add-ifconfig-in-override-block-xml?rq=1