Skip to content

Commit 1a481b4

Browse files
author
mastiuhin-olexandr
committed
MC-23553: Invalid Template Strings when Translate Inline enabled
1 parent 55c4ff8 commit 1a481b4

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

lib/internal/Magento/Framework/Escaper.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
67

78
namespace Magento\Framework;
89

@@ -258,8 +259,16 @@ private function escapeAttributeValue($name, $value)
258259
public function escapeHtmlAttr($string, $escapeSingleQuote = true)
259260
{
260261
if ($escapeSingleQuote) {
261-
return $this->getEscaper()->escapeHtmlAttr((string) $string);
262+
$translateInline = $this->getTranslateInline();
263+
264+
if ($translateInline->isAllowed()) {
265+
return $this->inlineSensitiveEscapeHthmlAttr((string) $string);
266+
}
267+
268+
return $this->getEscaper()
269+
->escapeHtmlAttr((string) $string);
262270
}
271+
263272
return htmlspecialchars((string)$string, $this->htmlSpecialCharsFlag, 'UTF-8', false);
264273
}
265274

@@ -476,4 +485,32 @@ private function getTranslateInline()
476485

477486
return $this->translateInline;
478487
}
488+
489+
/**
490+
* Inline sensitive escape attribute value.
491+
*
492+
* @param string $text
493+
* @return string
494+
*/
495+
private function inlineSensitiveEscapeHthmlAttr(string $text): string
496+
{
497+
$escaper = $this->getEscaper();
498+
$firstCharacters = substr($text, 0, 3);
499+
$lastCharacters = substr($text, -3, 3);
500+
501+
if ($firstCharacters === '{{{' && $lastCharacters === '}}}') {
502+
$textLength = strlen($text);
503+
$text = substr($text, 3, $textLength - 6);
504+
$strings = explode('}}{{', $text);
505+
$escapedStrings = [];
506+
507+
foreach ($strings as $string) {
508+
$escapedStrings[] = $escaper->escapeHtmlAttr($string);
509+
}
510+
511+
return '{{{' . implode('}}{{', $escapedStrings) . '}}}';
512+
}
513+
514+
return $escaper->escapeHtmlAttr($text);
515+
}
479516
}

lib/internal/Magento/Framework/Test/Unit/EscaperTest.php

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Framework\ZendEscaper;
1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Log\LoggerInterface;
16+
use Magento\Framework\Translate\Inline\StateInterface;
1617

1718
/**
1819
* \Magento\Framework\Escaper test case
@@ -24,6 +25,11 @@ class EscaperTest extends TestCase
2425
*/
2526
protected $escaper;
2627

28+
/**
29+
* @var ObjectManager
30+
*/
31+
private $objectManagerHelper;
32+
2733
/**
2834
* @var ZendEscaper
2935
*/
@@ -44,14 +50,14 @@ class EscaperTest extends TestCase
4450
*/
4551
protected function setUp(): void
4652
{
47-
$objectManagerHelper = new ObjectManager($this);
53+
$this->objectManagerHelper = new ObjectManager($this);
4854
$this->escaper = new Escaper();
4955
$this->zendEscaper = new ZendEscaper();
50-
$this->translateInline = $objectManagerHelper->getObject(Inline::class);
56+
$this->translateInline = $this->objectManagerHelper->getObject(Inline::class);
5157
$this->loggerMock = $this->getMockForAbstractClass(LoggerInterface::class);
52-
$objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'escaper', $this->zendEscaper);
53-
$objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'logger', $this->loggerMock);
54-
$objectManagerHelper->setBackwardCompatibleProperty(
58+
$this->objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'escaper', $this->zendEscaper);
59+
$this->objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'logger', $this->loggerMock);
60+
$this->objectManagerHelper->setBackwardCompatibleProperty(
5561
$this->escaper,
5662
'translateInline',
5763
$this->translateInline
@@ -169,6 +175,58 @@ public function testEscapeHtml($data, $expected, $allowedTags = [])
169175
$this->assertEquals($expected, $actual);
170176
}
171177

178+
/**
179+
* Tests escapeHtmlAttr method when Inline translate is configured.
180+
*
181+
* @param string $input
182+
* @param string $output
183+
* @return void
184+
* @dataProvider escapeHtmlAttrWithInlineProvider
185+
*/
186+
public function testEscapeHtmlAttrWithInline(string $input, string $output): void
187+
{
188+
$this->objectManagerHelper->setBackwardCompatibleProperty(
189+
$this->translateInline,
190+
'isAllowed',
191+
true
192+
);
193+
$stateMock = $this->createMock(StateInterface::class);
194+
$stateMock->method('isEnabled')
195+
->willReturn(true);
196+
$this->objectManagerHelper->setBackwardCompatibleProperty(
197+
$this->translateInline,
198+
'state',
199+
$stateMock
200+
);
201+
202+
203+
$actual = $this->escaper->escapeHtmlAttr($input);
204+
$this->assertEquals($output, $actual);
205+
}
206+
207+
/**
208+
* Data provider for escapeHtmlAttrWithInline test.
209+
*
210+
* @return array
211+
*/
212+
public function escapeHtmlAttrWithInlineProvider(): array
213+
{
214+
return [
215+
[
216+
'{{{Search entire store here...}}}',
217+
'{{{Search entire store here...}}}',
218+
],
219+
[
220+
'{{{Product search}}{{Translated to language}}{{themeMagento/Luma}}}',
221+
'{{{Product search}}{{Translated to language}}{{themeMagento/Luma}}}',
222+
],
223+
[
224+
'Simple string',
225+
'Simple string',
226+
],
227+
];
228+
}
229+
172230
/**
173231
* @covers \Magento\Framework\Escaper::escapeHtml
174232
* @dataProvider escapeHtmlInvalidDataProvider

0 commit comments

Comments
 (0)