Magento 2: Remova o bloco, dependendo da configuração


13

Estou tentando remover um bloco de uma determinada página (seja front-end ou back-end), mas apenas se um determinado sinalizador de configuração estiver definido como true.
Vamos dar um exemplo.
Quero remover o bloco com o nome dashboarddo painel do administrador.

O bloco é definido no adminhtml_dashboard_index.xmlarquivo do Magento_Backendmódulo:

<referenceContainer name="content">
    <block class="Magento\Backend\Block\Dashboard" name="dashboard"/>
</referenceContainer>

Graças à resposta de Adam, posso fazer isso noadminhtml_dashboard_index.xml

<body>
    <referenceBlock name="dashboard" remove="true"  />
</body>

Mas quero aumentar um pouco e remover esse bloco apenas se a configuração do caminho dashboard/settings/removetiver o valor 1.
Uma abordagem de layout xml seria incrível, mas também adotarei uma abordagem de observador.


? Marius, você sabe a mesma coisa está disponível para Events.xml Quer dizer, eu quero executar o meu observador se a configuração é permitir
— Keyur Shah

Respostas:


17

Também não consegui encontrar uma maneira de fazer isso com o layout, mas aqui está um exemplo de como você pode fazer isso com observadores (desde que eles estendam o bloco Template) ...

Crie seu events.xml em 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>

Crie o observador

<?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);
            }
        }
    }
}

Basicamente, o _toHtml verifica se há um modelo. Se não houver, retorna ''.

EDITAR

Depois de mais algumas escavações, encontrei uma maneira de fazer isso ainda mais na cadeia.

<?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>

E o observador ...

<?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');
            }
        }
    }
}

Isso pode funcionar, mas apenas para blocos que usam modelos. Isso se aplica ao exemplo que forneci, mas ainda assim, se houver blocos que estendem o AbstractBlock e não o modelo, isso não funcionará. +1 para o bom ponto de partida.
— Marius

Você está certo. Depois de mais algumas escavações, descobri que você pode fazer isso mais cedo no processo. Resposta atualizada. Deixei meu original lá para referência.
— Smartie

Obrigado, esta é uma resposta útil. O problema é que isso significa que a lógica será acionada a cada carregamento da página, pois está usando o evento "layout_generate_blocks_after". Você sabe como executá-lo apenas em determinadas cargas de página, por exemplo, carregando uma página de categoria (o evento é "catalog_controller_category_init_after", mas o layout não pode ser acessado)?
— 1028 Alex

2
Realmente?! Temos que fazer um observador para remover ou não condicionalmente um bloco? isso é ridículo, apenas dizendo.
— Slayerbleast

1
Os observadores não devem manipular os dados que eu penso ...
— Alex

5

Normalmente isso deve ser feito com a <action />tag:

<referenceContainer name="content">
    <action method="unsetChild" ifconfig="dashboard/settings/remove">
        <argument xsi:type="string">dashboard</argument>
    </action>
</referenceContainer>

EDIT:

O único problema é unsetChild aceita apenas alias. Você não pode usar o nome do bloco.

Outra solução: reescreva o Magento Framework para poder usar o ifconfig com remove = "true"

1- Crie seu próprio módulo.

2- Adicione um novo arquivo para substituir Magento quadro: (por exemplo: /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- Adicione o arquivo di.xml para substituir o arquivo magento:

<?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- Agora você pode usar o ifconfig no layout combinado com remove:

<referenceBlock name="content" remove="true" ifconfig="path/to/myconfig" />

Este exemplo é para Block, mas você pode fazer o mesmo para container se substituir o método containerReference () de /Magento/Framework/View/Layout/Reader/Container.php


Eu acho que reescrever o Framework é a melhor solução, não sei por que o magento não tem isso por padrão.
— Slayerbleast

3

Nas diretrizes técnicas :

14.1 Todos os valores (incluindo objetos) passados ​​para um evento NÃO DEVEM ser modificados no observador de eventos. Em vez disso, os plugins DEVEM SER usados ​​para modificar a entrada ou saída de uma função.

14.3 Eventos não devem alterar um estado de objetos observáveis.

Então, aqui está uma solução de plug-in para isso:

Declare o 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>

Defina o 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;
    }
}

Como na resposta de Smartie Eu tentei o plugin mais acima na cadeia em \Magento\Framework\View\Layout\Builder::buildcom um afterBuild()método, mas isso vai levar a uma recursão infinita porque \Magento\Framework\View\Layout::getBlocke \Magento\Framework\View\Layout::unsetElementambos chamada \Magento\Framework\View\Layout\Builder::buildnovamente.


2

O atributo "ifconfig" de um nó "bloco" no layout permite vincular o bloco ao valor na configuração da loja.

O processamento "ifconfig" ocorre em \Magento\Framework\View\Layout\GeneratorPool::buildStructure


Porém, ele não funcionará com "referenceBlock". Só funcionará quando você estiver adicionando um novo bloco.
— Nikita Abrashnev 23/01
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.