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.
*/