1

We have a cms page with a content like this:

<p>Lorem Ipsum</p>

<div>{{block class="Vendor\Module\Block\Feature" block_id="feature_wrapper_block"}}</div>

<p>More Lorem Ipsum</p>

The block referenced in the cms page mostly consists of a template which, among other things, echoes other blocks. These other blocks obviously need to exist in order to be injected.

My first attempt was to declare them in the default.xml layout so that they are available, then use $block->getLayout()->getBlock('block_name')->toHtml() in the phtml template to echo the other blocks. While this seems to work fine the problem is this approach will probably hurt performance a bit since I've put the declaration in default.xml the blocks are injected into every page.

My next idea was to put this in a layout handle which would add the blocks but that does not work because I'm unable to tell Magento to use that layout handle:

  1. Using $this->getLayout()->getUpdate()->addHandle('add_blocks'); in the block injected into the cms page does not work because the handle is evaluated after the block itself which means the other blocks created in the handle are not available while they are echoed in the phtml and thus everything explodes.
  2. Adding {​​​​{​​​​​​layout handle="add_quickorder_blocks"}​​​​​​}​​​​​​ in the cms page itself does not work because again, the {{block}} directive is evaluated before the {{layout}} directive and thus fails to render the block because the layout handles were not yet evaluated.
  3. I also tried adding a layout handle directly to the cms page but that didn't work at all and is not a good solution either since the customer wants to be able to change the url of the cms page.
  4. Using an observer to add the layout handle works, but then the handle is present on every page again.
  5. I tried combining the observer approach with a check whether the current cms page content contains the {{block class=... template direction but that didn't feel like a good solution and didn't work in all cases.

Another idea would be to add a new cms page layout which adds the required blocks but that sounds a bit overkill tbh. It might be the most efficient solution at the end of the day though.

What is the proper way to do this?

1
  • Not sure if using a widget instead of a simple block would be of any benefit.
    – Alex
    Commented Jun 13, 2022 at 16:06

1 Answer 1

0

I think view method addPageLayoutHandles can help you to solve your issue

How you can use

  1. Create layout file delimited with 2 underscores like vendor_cms_logic.xml and specify your changes

  2. With plugin for your page call

    $view->addPageLayoutHandles([
        'cms' => [
            'handle' => 'vendor', 
            'value'  => 'logic',
        ],
    ]);
    

The system should cache this layout update only for specific entities

[UPDATE]

I've created some simple test and tested it on 2.4.3 and 2.4.4 and it works as expected.

It's frustrating to get downvotes if you haven't checked the solution carefully and properly

1. Feature block

app/code/Acme/StackExchange/Block/Feature.php

<?php
declare(strict_types=1);

namespace Acme\StackExchange\Block;

use Magento\Framework\View\Element\Template;

class Feature extends Template
{
    // some custom logic if needed
}

app/code/Acme/StackExchange/view/frontend/templates/feature.phtml

<?php
/**
 * @var \Acme\StackExchange\Block\Feature $block
 * @var \Magento\Framework\View\TemplateEngine\Php $this
 * @var \Magento\Framework\Escaper $escaper
 * @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer
 */

$bestsellers = $block->getLayout()->getBlock('product-bestsellers');

?>
The heavy block from layout ...
<?php if ($bestsellers): ?>
    <?= $bestsellers->toHtml(); ?>
<?php endif; ?>

2. CMS Page Content

Just update CMS content with Feature block like

{{block class="Acme\StackExchange\Block\Feature" template="Acme_StackExchange::feature.phtml"}}

3. Layout update

app/code/Acme/StackExchange/view/frontend/layout/acme_cms_feature.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="cms_page">
            <block class="Acme\StackExchange\Block\Bestsellers" name="product-bestsellers"
                   template="Acme_StackExchange::bestsellers.phtml">
                <arguments>
                    <argument name="view_model" xsi:type="object">Magento\Catalog\ViewModel\Product\Listing\PreparePostData</argument>
                </arguments>
            </block>
        </referenceBlock>
    </body>
</page>

4. Observer for inject layout

app/code/Acme/StackExchange/etc/frontend/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="cms_page_render">
        <observer name="acme_stackexchange_observer_cms_page_render"
                  instance="Acme\StackExchange\Observer\Frontend\Cms\PageRender" />
    </event>
</config>

app/code/Acme/StackExchange/Observer/Frontend/Cms/PageRender.php

<?php
declare(strict_types=1);

namespace Acme\StackExchange\Observer\Frontend\Cms;

use Magento\Cms\Model\Page;
use Magento\Framework\App\ViewInterface;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Registry;

class PageRender implements ObserverInterface
{
    protected Registry      $registry;
    protected ViewInterface $view;

    public function __construct(
        Registry $registry,
        ViewInterface $view
    ) {
        $this->registry = $registry;
        $this->view     = $view;
    }

    /**
     * @inheritDoc
     */
    public function execute(Observer $observer)
    {
        /** @var Page $page */
        $page = $observer->getEvent()->getData('page');

        // if content contains my feature block in content add related layout
        if (str_contains($page->getContent() ?? '', 'class="Acme\StackExchange\Block\Feature"')) {
            $this->view->addPageLayoutHandles(['cms' => 'feature'], 'acme');
        }
    }
}
5
  • Isn't that what I did in my first try?
    – kolaente
    Commented Jun 14, 2022 at 6:39
  • What is Magento version, plugin for which class::method are you use? Commented Jun 14, 2022 at 7:42
  • I'm running this on Magento 2.4.4. I actually had a solution similiar to the example you edited into your answer but couldn't get it to work (The downvote is not mine). I'll update my question accordingly.
    – kolaente
    Commented Jun 15, 2022 at 8:56
  • Sorry for down voting without leaving a comment, that was rude from me.
    – Alex
    Commented Jun 15, 2022 at 9:01
  • @kolaente, can you please provide more details what exactly doesn't work, because i've tested provided code on 2.4.4. Maybe make sense to review logic in your layout blocks, maybe make sense to use cache=false attribute, also issue may related to isolation logic, etc Commented Jun 15, 2022 at 10:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.