6

I know my question is strange and I should change the response in the Magento way.

I have an observer subscribed to controller_front_send_response_before event.

events.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="controller_front_send_response_before">
        <observer name="roland_controller_front_send_response_before"
                  instance="Roland\HelloWorld\Model\Observers\ResponseBefore"/>
    </event>
</config>

Model/Observers/ResponseBefore.php

<?php
namespace Roland\HelloWorld\Model\Observers;

use \Magento\Framework\Event\ObserverInterface;

class ResponseBefore implements ObserverInterface {

    public function execute(\Magento\Framework\Event\Observer $observer) {

        $this->playWithResponse($observer->getEvent()
                                   ->getData('response'));

    }

    public function playWithResponse($response) {

        $head = '<script>alert("'.time().'");</script>';
        $response->setBody(preg_replace('/<\/head>/', $head . '</head>', $response->getBody(), 1));
    }

}

It works fine when Full page cache not enabled. Every time I refresh the page, I get an alert with the current server time. When Full page cache is enabled, I would like to make my code cached into Full page cache. Currently my observer called on every page load as the Full page cache comes before the controller_front_send_response_before event.

So I checked how's Magento 2 Full page cache works in magento/module-page-cache/Observer/Process/LayoutRenderElement.php

    public function execute(\Magento\Framework\Event\Observer $observer)
{
    $event = $observer->getEvent();
    /** @var \Magento\Framework\View\Layout $layout */
    $layout = $event->getLayout();
    if ($this->isFullPageCacheEnabled() && $layout->isCacheable()) {
        $name = $event->getElementName();
        /** @var \Magento\Framework\View\Element\AbstractBlock $block */
        $block = $layout->getBlock($name);
        $transport = $event->getTransport();
        if ($block instanceof \Magento\Framework\View\Element\AbstractBlock) {
            $blockTtl = $block->getTtl();
            $output = $transport->getData('output');
            if (isset($blockTtl) && $this->isVarnishEnabled()) {
                $output = $this->_wrapEsi($block, $layout);
            } elseif ($block->isScopePrivate()) {
                $output = sprintf(
                    '<!-- BLOCK %1$s -->%2$s<!-- /BLOCK %1$s -->',
                    $block->getNameInLayout(),
                    $output
                );
            }
            $transport->setData('output', $output);
        }
    }
}

I saw that it caches every block, but I haven't got any further.

Here is the part of my $event->getElementName(): mailcheck_init emailCapture copyright before.body.end page.wrapper root

I thought if I change the content in the observer before the page cache that might worth, but I have no luck.

if($event->getElementName() == 'root'){
    $transport = $event->getTransport();
    $transport->setData('output', 'test');
}

Do you have any suggestion how should I achieve the desired result?

2 Answers 2

6

So, finally I have a real solution. So I was able to track down Magento2 full page cache and turned out that it does the caching as a plugin: Magento\PageCache\Model\App\FrontController\BuiltinPlugin in the aroundDispatch.

$this->kernel->process($result);

Does the work of storing the serialized $response object.

So the solution was to create a plugin for the after event:

Roland/HelloWorld/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Controller\ResultInterface">
        <plugin name="change-result-before-cache" type="Roland\HelloWorld\Model\Plugins\ChangeResult"/>
    </type>
</config>

Roland/HelloWorld/Model/Plugins/ChangeResult.php

<?php

namespace Roland\HelloWorld\Model\Plugins;

use Magento\Framework\App\Response\Http as ResponseHttp;


class ChangeResult {

    public function afterRenderResult(ResponseHttp $response) {
        $response->setBody(preg_replace('/<\/head>/', '<script>alert("' . time() . '");</script>' . '</head>', $response->getBody(), 1));
    }

}

The result

When page cache enabled, you should always see the same timestamp for every refresh.

Related docs

How Magento2 plugins work - Alan Storm Magento 2 Full page caching - Alan Storm

1

@Roland Soós your answer is good but when I am apply facing some error below code resolve my error.

  use Magento\Framework\App\Response\Http as ResponseHttp;

Add above class

  protected $response;

  public function __construct(
       ResponseHttp $response
  ) {
       $this->response = $response;
  }

  public function afterRenderResult(\Magento\Framework\Controller\ResultInterface $process) 
  {
       $this->response->setBody(preg_replace('/<\/head>/', '<script>alert("' . time() . '");</script>' . '</head>', $this->response->getBody(), 1));
  }
1
  • Hi, this is called but it gives Fatal error: Uncaught TypeError: Argument 2 passed to Magento\PageCache\Model\Controller\Result\BuiltinPlugin::afterRenderResult() must implement interface Magento\Framework\Controller\ResultInterface, null given error. Have you faced this? Please let me know I am facing the issue. Thanks
    – Pribhav
    Commented Jun 28, 2019 at 6:35

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.