Magento 2: Remove block depending on a config setting

The question:

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="MagentoBackendBlockDashboard" 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.

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

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]ModelObserverRemoveBlock" />
    </event>
</config>

Create the observer

<?php

namespace [Vendor][ModuleName]ModelObserver;

use MagentoFrameworkEventObserver;
use MagentoFrameworkEventObserverInterface;

class RemoveBlock implements ObserverInterface
{
    protected $_scopeConfig;

    public function __construct(
        MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig
    ) {
        $this->_scopeConfig = $scopeConfig;
    }

    public function execute(Observer $observer)
    {
        /** @var MagentoFrameworkViewElementTemplate $block */
        $block = $observer->getBlock();
        if ($block->getType() == 'MagentoBackendBlockDashboard') {
            $remove = $this->_scopeConfig->getValue(
                'dashboard/settings/remove',
                MagentoStoreModelScopeInterface::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]ModelObserverRemoveBlock" />
    </event>
</config>

And the observer…

<?php

namespace [Vendor][ModuleName]ModelObserver;

use MagentoFrameworkEventObserver;
use MagentoFrameworkEventObserverInterface;

class RemoveBlock implements ObserverInterface
{
    protected $_scopeConfig;

    public function __construct(
        MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig
    ) {
        $this->_scopeConfig = $scopeConfig;
    }

    public function execute(Observer $observer)
    {
        /** @var MagentoFrameworkViewLayout $layout */
        $layout = $observer->getLayout();
        $block = $layout->getBlock('dashboard');
        if ($block) {
            $remove = $this->_scopeConfig->getValue(
                'dashboard/settings/remove',
                MagentoStoreModelScopeInterface::SCOPE_STORE
            );

            if ($remove) {
                $layout->unsetElement('dashboard');
            }
        }
    }
}

Method 2

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 VendorModuleOverrideMagentoFrameworkViewLayoutReader;

use MagentoFrameworkApp;
use MagentoFrameworkDataArgumentInterpreterInterface;
use MagentoFrameworkViewLayout;

/**
 * Block structure reader
 */
class Block extends MagentoFrameworkViewLayoutReaderBlock
{
    /**
     * @var MagentoFrameworkAppScopeResolverInterface
     */
    protected $scopeResolver;

    /**
     * @var MagentoFrameworkAppConfigScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * Constructor
     *
     * @param LayoutScheduledStructureHelper $helper
     * @param LayoutArgumentParser $argumentParser
     * @param LayoutReaderPool $readerPool
     * @param InterpreterInterface $argumentInterpreter
     * @param MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig
     * @param MagentoFrameworkAppScopeResolverInterface $scopeResolver
     * @param string|null $scopeType
     */
    public function __construct(
        LayoutScheduledStructureHelper $helper,
        LayoutArgumentParser $argumentParser,
        LayoutReaderPool $readerPool,
        InterpreterInterface $argumentInterpreter,
        MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig,
        MagentoFrameworkAppScopeResolverInterface $scopeResolver,
        $scopeType = null
    ) {
        parent::__construct($helper,
            $argumentParser,
            $readerPool,
            $argumentInterpreter,
            $scopeType
        );
        $this->scopeConfig = $scopeConfig;
        $this->scopeResolver = $scopeResolver;
    }

    protected function scheduleReference(
        LayoutScheduledStructure $scheduledStructure,
        LayoutElement $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="MagentoFrameworkViewLayoutReaderBlock"
       type="VendorModuleOverrideMagentoFrameworkViewLayoutReaderBlock" />    
</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

Method 3

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="MagentoFrameworkViewElementAbstractBlock">
        <plugin name="remove_block" type="[Vendor][Module]PluginRemoveBlock" />
    </type>
</config>

Define the plugin:

<?php

namespace VendorModulePlugin;


use MagentoFrameworkAppConfigScopeConfigInterface;
use MagentoFrameworkViewElementAbstractBlock;

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 MagentoFrameworkViewLayoutBuilder::build with an afterBuild() method but this will lead into an endless recursion because MagentoFrameworkViewLayout::getBlock and MagentoFrameworkViewLayout::unsetElement both call MagentoFrameworkViewLayoutBuilder::build again.

Method 4

“ifconfig” attribute of a “block” node in layout allows you to link block to value in store configuration.

“ifconfig” processing happens in MagentoFrameworkViewLayoutGeneratorPool::buildStructure


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

Leave a Comment