15

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.

2

5 Answers 5

19

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');
            }
        }
    }
}
5
  • 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
    Commented 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
    Commented 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
    Commented May 10, 2016 at 9:20
  • 2
    Really?! We have to do an observer to remove or not conditionally a block? this is ridiculous, just saying.
    – Marc Pont
    Commented May 2, 2017 at 8:03
  • 2
    Observers should not manipulate data I think ...
    – Alex
    Commented Jul 4, 2019 at 10:03
8

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

2
  • I think rewriting the Framework is the best solution, don't know why magento don't have this by default.
    – Marc Pont
    Commented 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. :-| Commented May 13, 2020 at 17:21
7

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.

2

"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

1
  • 2
    It won't work with "referenceBlock" though. It will only work when you're adding a new block. Commented Jan 23, 2020 at 12:55
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.