11

I want to remove a block from the layout in magento 2 that is declared in a third party extension, but the block does not have a name.
Can I do that?

The block is declared like this

<referenceContainer name="before.body.end">
    <block class="Magento\Backend\Block\Template" template="[Vendor_Module]::template.phtml"/>
</referenceContainer>

I cannot use

<referenceBlock name="..." remove="true" /> 

because, as you can see there is no name on it.

  • marius, i have idea.if we using event and remove block by match template name [Vendor_Module]::template.phtml – Amit Bera Dec 21 '17 at 11:06
  • I have the same idea (see comments on the answer) but I will use it only as a desperate measure. I was hoping for a simple solution. If you have some code, post it as an answer. – Marius Dec 21 '17 at 11:20
  • ha ha that we donot have simple solution.let me try give u an answer using event – Amit Bera Dec 21 '17 at 11:27
5

I found this issue in class Magento\Framework\View\Layout\ScheduledStructure\Helper

There is function _generateAnonymousName:

protected function _generateAnonymousName($class)
{
    $position = strpos($class, '\\Block\\');
    $key = $position !== false ? substr($class, $position + 7) : $class;
    $key = strtolower(trim($key, '_'));
    return $key . $this->counter++;
}

It's call from scheduleStructure function:

    public function scheduleStructure(
    Layout\ScheduledStructure $scheduledStructure,
    Layout\Element $currentNode,
    Layout\Element $parentNode
) {
    // if it hasn't a name it must be generated
    if (!(string)$currentNode->getAttribute('name')) {
        $name = $this->_generateAnonymousName($parentNode->getElementName() . '_schedule_block'); // CALL HERE
        $currentNode->setAttribute('name', $name);
    }
    $path = $name = (string)$currentNode->getAttribute('name');

    // Prepare scheduled element with default parameters [type, alias, parentName, siblingName, isAfter]
    $row = [
        self::SCHEDULED_STRUCTURE_INDEX_TYPE           => $currentNode->getName(),
        self::SCHEDULED_STRUCTURE_INDEX_ALIAS          => '',
        self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME    => '',
        self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME   => null,
        self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER       => true,
    ];

    $parentName = $parentNode->getElementName();
    //if this element has a parent element, there must be reset [alias, parentName, siblingName, isAfter]
    if ($parentName) {
        $row[self::SCHEDULED_STRUCTURE_INDEX_ALIAS] = (string)$currentNode->getAttribute('as');
        $row[self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME] = $parentName;

        list($row[self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME],
            $row[self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER]) = $this->_beforeAfterToSibling($currentNode);

        // materialized path for referencing nodes in the plain array of _scheduledStructure
        if ($scheduledStructure->hasPath($parentName)) {
            $path = $scheduledStructure->getPath($parentName) . '/' . $path;
        }
    }

    $this->_overrideElementWorkaround($scheduledStructure, $name, $path);
    $scheduledStructure->setPathElement($name, $path);
    $scheduledStructure->setStructureElement($name, $row);
    return $name;
}

With this case, Block name can be:

  • before.body.end_schedule_block1
  • before.body.end_schedule_block2
  • ...

I think you should define totals block without the name on container and order block name need remove on the container.

  • I don't think this will work. There is no way to predict the generated name since on different pages there can be multiple blocks added in the body.before.end container in different order. – Marius Dec 21 '17 at 10:37
  • This case only applies to block/container without a name. If all of them without the name, so difficult to define some block/container need removed. – Thao Pham Dec 21 '17 at 10:42
  • yep...my problem exactly – Marius Dec 21 '17 at 10:45
  • We should rewrite $name = $this->_generateAnonymousName($parentNode->getElementName() . '_schedule_block'); , Should be pass class & template to parameter ? – Thao Pham Dec 21 '17 at 10:58
  • 2
    seems like an overhead to rewrite something like that. I'm looking for a simple solution (if any) or an answer like 'not possible very easily'. I think I can observe the layout generate blocks event or something to remove it there, but again seems too much overhead. I'm keeping that as a backup solution. – Marius Dec 21 '17 at 11:01
2

I am really give u bad idea.

Here the idea is not stop output of your block

Using event view_block_abstract_to_html_after

<?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_after">
        <observer name="myObserverName" instance="Stack\Work\Observer\MyObserver" />
    </event>
</config>

And using this observer disable output of your block

<?php
namespace Stack\Work\Observer;
use Magento\Framework\Event\ObserverInterface;

class MyObserver implements ObserverInterface
{
  public function __construct()
  {
    //Observer initialization code...
    //You can use dependency injection to get any class this observer may need.
  }

  public function execute(\Magento\Framework\Event\Observer $observer)
  {
    $block = $observer->getData('block');

    if('[Vendor_Module]::template.phtml' == $block->getTemplate()){
        $transport = $observer->getData('transport');
        $transport->setHtml('');

    }
  }
}
  • this is not actually such a bad idea. Indeed there is an overkill observing all the blocks, but I'm willing to use it over other options. I will try and let you know. – Marius Dec 22 '17 at 7:46
  • coool. man.... see what happen – Amit Bera Dec 22 '17 at 8:22
  • 1
    It works, but I tried to optimize it a bit, not to execute the code for every block. So I ended up with my answer. Thanks for the idea. – Marius Dec 22 '17 at 8:32
  • I see answer, that really good man :) – Amit Bera Dec 22 '17 at 8:48
2

I got an idea from Amit's answer and ended up with a working solution that does not look very intrusive and it's not an overkill since my code is executed only once.

I've created an observer on the event layout_generate_blocks_after that is executed after the layouts are loaded and the blocks are generated.

This can have a drawback because the block I'm trying to remove still gets instantiated, but in my case I just needed to remove it from the page.

So I have the file etc/adminhtml/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="layout_generate_blocks_after">
        <observer name="remove-the-block" instance="[MyVendor]\[MyModule]\Observer\RemoveBlock" />
    </event>
</config>

and my observer class:

<?php
namespace [MyVendor]\[MyModule]\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class RemoveBlock implements ObserverInterface
{
    const TEMPLATE_TO_REMOVE = '[OtherVendor]_[OtherModule]::template.phtml';
    public function execute(Observer $observer)
    {
        /** @var \Magento\Framework\View\Layout $layout */
        $layout = $observer->getLayout();
        $blocks = $layout->getAllBlocks();
        foreach ($blocks as $key => $block) {
            /** @var \Magento\Framework\View\Element\Template $block */
            if ($block->getTemplate() == self::TEMPLATE_TO_REMOVE) {
                $layout->unsetElement($key);
            }
        }
    }
}

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.