diff --git a/Observer/ResponseBefore.php b/Observer/ResponseBefore.php index a4c7374..803d0e7 100644 --- a/Observer/ResponseBefore.php +++ b/Observer/ResponseBefore.php @@ -21,9 +21,9 @@ public function execute(\Magento\Framework\Event\Observer $observer) } $response = $observer->getEvent()->getData('response'); - $response->setBody(preg_replace( + $response->setBody(preg_replace_callback( '//', - " + fn() => " {$this->getPreloadsHTML()} ", diff --git a/Test/Unit/Observer/ResponseBeforeTest.php b/Test/Unit/Observer/ResponseBeforeTest.php index f87733c..7c5fb38 100644 --- a/Test/Unit/Observer/ResponseBeforeTest.php +++ b/Test/Unit/Observer/ResponseBeforeTest.php @@ -158,6 +158,33 @@ public function testModifiesResponseInFrontendAreaWithLinks(): void $this->subject->execute($this->observerMock); } + /** + * Test that hrefs containing "$" + digits (e.g. "/img$1.jpg") are not corrupted. + * preg_replace() treats $0-$99 in its replacement argument as backreferences + * regardless of source; preg_replace_callback() does not have this problem. + */ + public function testDoesNotCorruptHrefsContainingDollarDigitSequences(): void + { + $linkMock = $this->createMock(LinkInterface::class); + $linkMock->method('getAttrs')->willReturn(['rel' => 'preload', 'href' => '/img$1.jpg']); + + $this->appStateMock->method('getAreaCode')->willReturn(Area::AREA_FRONTEND); + $this->linkStoreMock->method('get')->willReturn([$linkMock]); + + $this->secureHtmlRendererMock->method('renderTag') + ->willReturn(''); + + $this->responseMock->method('getBody')->willReturn(self::SAMPLE_RESPONSE_HTML); + + $this->responseMock->expects($this->once()) + ->method('setBody') + ->with($this->callback(function ($body) { + return str_contains($body, '/img$1.jpg'); + })); + + $this->subject->execute($this->observerMock); + } + /** * Test that only the first tag is replaced. */