Magento 2: Supprimer le bloc en fonction d'un paramètre de configuration

13

J'essaie de supprimer un bloc d'une certaine page (que ce soit le frontend ou le backend) mais uniquement si un certain indicateur de configuration est défini sur true.
Prenons un exemple.
Je veux supprimer le bloc avec le nom dashboarddu tableau de bord d'administration.

Le bloc est défini dans le adminhtml_dashboard_index.xmlfichier du Magento_Backendmodule:

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

Grâce à la réponse d'Adam, je peux le faire dans leadminhtml_dashboard_index.xml

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

Mais je veux prendre un cran et supprimer ce bloc uniquement si le paramètre de configuration avec le chemin dashboard/settings/removea la valeur 1.
Une approche de mise en page XML serait géniale, mais je prendrai également une approche d'observateur.

Marius
la source
Marius, vous savez que la même chose est disponible pour events.xml? Je veux dire que je veux exécuter mon observateur si la configuration est activée
Keyur Shah
Si vous voulez aller avec une helperclasse, voir /programming/47237179/magento-2-i-want-to-add-ifconfig-in-override-block-xml?rq=1
Asrar

Réponses:

17

Je n'ai pas non plus trouvé de moyen de le faire avec la mise en page, mais voici un exemple de façon de le faire avec des observateurs (à condition qu'ils étendent le bloc Template) ...

Créez votre events.xml dans 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>

Créer l'observateur

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

Fondamentalement, le _toHtml vérifie s'il existe un modèle. S'il n'y en a pas, il revient ''.

ÉDITER

Après avoir creusé un peu plus, j'ai trouvé un moyen de le faire plus haut dans la chaîne.

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

Et l'observateur ...

<?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
la source
Cela peut fonctionner, mais uniquement pour les blocs qui utilisent des modèles. Cela s'applique à l'exemple que j'ai fourni, mais quand même, s'il y a des blocs qui étendent AbstractBlock et non le bloc Template, cela ne fonctionnera pas. +1 pour le bon point de départ.
Marius
Vous avez raison. Après avoir creusé un peu plus, j'ai trouvé que vous pouvez le faire plus tôt dans le processus. Réponse mise à jour. J'ai laissé mon original là pour référence.
Smartie
Merci, c'est une réponse utile. Le problème est que cela signifie que la logique sera déclenchée à chaque chargement de page car elle utilise l'événement "layout_generate_blocks_after". Savez-vous comment l'exécuter uniquement sur certains chargements de page, par exemple en chargeant une page de catégorie (l'événement est "catalog_controller_category_init_after" mais la mise en page n'est pas accessible)?
Alex
2
Vraiment?! Faut-il faire un observateur pour retirer ou non conditionnellement un bloc? c'est ridicule, juste dire.
slayerbleast
1
Les observateurs ne devraient pas manipuler les données je pense ...
Alex
5

Normalement, cela devrait être fait avec la <action />balise:

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

ÉDITER :

Seul problème est unsetChild n'accepte que l'alias. Vous ne pouvez pas utiliser de nom de bloc.

Autre solution: réécrire Magento Framework pour pouvoir utiliser ifconfig avec remove = "true"

1- Créez votre propre module.

2- Ajouter un nouveau fichier pour remplacer Framework Magento: (par exemple: /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- Ajouter un fichier di.xml pour remplacer le fichier 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- Vous pouvez maintenant utiliser ifconfig dans la mise en page combinée avec remove:

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

Cet exemple concerne le bloc, mais vous pouvez faire de même pour le conteneur si vous remplacez la méthode containerReference () de /Magento/Framework/View/Layout/Reader/Container.php

eInyzant
la source
Je pense que réécrire le Framework est la meilleure solution, je ne sais pas pourquoi magento n'a pas cela par défaut.
slayerbleast
3

D'après les directives techniques :

14.1. Toutes les valeurs (y compris les objets) passées à un événement NE DOIVENT PAS être modifiées dans l'observateur d'événement. Au lieu de cela, les plugins DEVRAIENT ÊTRE utilisés pour modifier l'entrée ou la sortie d'une fonction.

14.3. Les événements NE DEVRAIENT PAS changer l'état des objets observables.

Voici donc une solution de plugin pour cela:

Déclarez le 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>

Définissez le 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;
    }
}

Comme dans la réponse de Smartie, j'ai essayé de plugin plus loin dans la chaîne \Magento\Framework\View\Layout\Builder::buildavec une afterBuild()méthode mais cela conduira à une récursivité sans fin car \Magento\Framework\View\Layout::getBlocket les \Magento\Framework\View\Layout::unsetElementdeux appellent à \Magento\Framework\View\Layout\Builder::buildnouveau.

Daniel
la source
2

L'attribut "ifconfig" d'un nœud "bloc" dans la disposition vous permet de lier le bloc à la valeur dans la configuration du magasin.

le traitement "ifconfig" se produit dans \Magento\Framework\View\Layout\GeneratorPool::buildStructure

Anton Kril
la source
Cependant, cela ne fonctionnera pas avec "referenceBlock". Cela ne fonctionnera que lorsque vous ajouterez un nouveau bloc.
Nikita Abrashnev