13

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.

17

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');
            }
        }
    }
}
| improve this answer | |
  • 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 Jan 27 '16 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 Jan 28 '16 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 May 10 '16 at 9:20
  • 2
    Really?! We have to do an observer to remove or not conditionally a block? this is ridiculous, just saying. – slayerbleast May 2 '17 at 8:03
  • 1
    Observers should not manipulate data I think ... – Alex Jul 4 '19 at 10:03
6

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

| improve this answer | |
  • I think rewriting the Framework is the best solution, don't know why magento don't have this by default. – slayerbleast May 3 '17 at 8:22
  • Magento2.3.4 - Rewriting the Framework is working for mode - default and production. ifconfig not working for developer mode. :-| – Ronak Patel May 13 at 17:21
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="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.

| improve this answer | |
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

| improve this answer | |
  • 1
    It won't work with "referenceBlock" though. It will only work when you're adding a new block. – Nikita Abrashnev Jan 23 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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