Magento 2: eliminar el bloque en función de una configuración

13

Estoy tratando de eliminar un bloque de una determinada página (ya sea frontend o backend) pero solo si un determinado indicador de configuración está configurado en true.
Tomemos un ejemplo.
Quiero eliminar el bloque con el nombre dashboarddel panel de administración.

El bloque se define en el adminhtml_dashboard_index.xmlarchivo del Magento_Backendmódulo:

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

Gracias a la respuesta de Adam puedo hacer esto en eladminhtml_dashboard_index.xml

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

Pero quiero llevarlo a un nivel superior y eliminar este bloque solo si la configuración con la ruta dashboard/settings/removetiene el valor 1.
Un enfoque de diseño xml sería increíble, pero también adoptaré un enfoque de observador.

Marius
fuente
Marius, ¿sabes que lo mismo está disponible para events.xml? Quiero decir que quiero ejecutar mi observador si la configuración está habilitada
Keyur Shah

Respuestas:

17

Tampoco pude encontrar una manera de hacer esto con el diseño, pero aquí hay un ejemplo de una forma en que puede hacerlo con observadores (siempre que extiendan el bloque de Plantilla) ...

Crea tu events.xml en 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>

Crea el 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);
            }
        }
    }
}

Básicamente, el _toHtml verifica si hay una plantilla. Si no lo hay, vuelve ''.

EDITAR

Después de un poco más de excavación, he encontrado una manera de hacer esto más arriba en la cadena.

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

Y el 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
fuente
Esto podría funcionar, pero solo para bloques que usan plantillas. Se aplica al ejemplo que proporcioné, pero aún así, si hay bloques que extienden el AbstractBlock y no el bloque de Plantilla, esto no funcionará. +1 para el buen punto de partida.
Marius
Estás en lo correcto. Después de investigar un poco más, descubrí que puedes hacer esto antes en el proceso. Respuesta actualizada He dejado mi original allí como referencia.
Smartie
Gracias, esta es una respuesta útil. El problema es que significa que la lógica se disparará en cada carga de la página, ya que utiliza el evento "layout_generate_blocks_after". ¿Sabes cómo ejecutarlo solo en ciertas cargas de página, por ejemplo, cargar una página de categoría (el evento es "catalog_controller_category_init_after" pero no se puede acceder al diseño)?
Alex
2
¡¿De Verdad?! ¿Tenemos que hacer un observador para eliminar o no condicionalmente un bloque? esto es ridículo, solo digo.
slayerbleast
1
Los observadores no deberían manipular los datos, creo ...
Alex
5

Normalmente se debe hacer con la <action />etiqueta:

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

EDITAR:

El único problema es unsetChild solo acepta alias. No puedes usar el nombre del bloque.

Otra solución: reescribir Magento Framework para poder usar ifconfig con remove = "true"

1- Crea tu propio módulo.

2- Agregue un nuevo archivo para anular Magento Framework: (ej . /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- Agregue el archivo di.xml para anular el archivo 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- Ahora puedes usar ifconfig en el diseño combinado con eliminar:

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

Este ejemplo es para Block, pero puede hacer lo mismo para container si anula el método containerReference () de /Magento/Framework/View/Layout/Reader/Container.php

eInyzant
fuente
Creo que reescribir el Framework es la mejor solución, no sé por qué Magento no tiene esto por defecto.
slayerbleast
3

De las pautas técnicas :

14.1 Todos los valores (incluidos los objetos) pasados ​​a un evento NO DEBEN modificarse en el observador de eventos. En cambio, los complementos DEBEN SER utilizados para modificar la entrada o salida de una función.

14.3 Los eventos NO DEBEN cambiar el estado de los objetos observables.

Así que aquí hay una solución de complemento para esto:

Declara el complemento:

<?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 el complemento:

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

Al igual que en la respuesta de Smartie Traté de plugin de más arriba en la cadena en \Magento\Framework\View\Layout\Builder::buildcon un afterBuild()método, pero esto conducirá a una recursión infinita porque \Magento\Framework\View\Layout::getBlock, y \Magento\Framework\View\Layout::unsetElementtanto la llamada \Magento\Framework\View\Layout\Builder::buildde nuevo.

Daniel
fuente
2

El atributo "ifconfig" de un nodo "bloque" en el diseño le permite vincular el bloque al valor en la configuración de la tienda.

el procesamiento "ifconfig" ocurre en \Magento\Framework\View\Layout\GeneratorPool::buildStructure

Anton Kril
fuente
Sin embargo, no funcionará con "referenceBlock". Solo funcionará cuando agregue un nuevo bloque.
Nikita Abrashnev