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
fonte
? 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
Se você quiser ir com uma helperclasse, consulte /programming/47237179/magento-2-i-want-to-add-ifconfig-in-override-block-xml?rq=1
Asrar

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

eInyzant
fonte
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.

Daniel
fonte
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

Anton Kril
fonte
Porém, ele não funcionará com "referenceBlock". Só funcionará quando você estiver adicionando um novo bloco.
Nikita Abrashnev 23/01