Skip to content

Commit 159197c

Browse files
committed
Merge remote-tracking branch 'origin/imported-magento-magento2-32002' into 2.4-develop-pr127
2 parents 64d3e2e + 6341225 commit 159197c

File tree

4 files changed

+113
-14
lines changed

4 files changed

+113
-14
lines changed

app/code/Magento/Translation/Model/Inline/Parser.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ private function _prepareTagAttributesForContent(&$content)
475475
} else {
476476
$trAttr = ' ' . $this->_getHtmlAttribute(
477477
self::DATA_TRANSLATE,
478-
'[' . str_replace("\"", "'", join(',', $trArr)) . ']'
478+
'[' . str_replace("\"", """, join(',', $trArr)) . ']'
479479
);
480480
}
481481
$trAttr = $this->_addTranslateAttribute($trAttr);

app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,12 @@ public function testProcessResponseBodyStringProcessingAttributesCorrectly()
147147
{
148148
$testContent = file_get_contents(__DIR__ . '/_files/datatranslate_fixture.html');
149149
$processedAttributes = [
150-
"data-translate=\"[{'shown':'* Required Fields','translated':'* Required Fields',"
151-
. "'original':'* Required Fields','location':'Tag attribute (ALT, TITLE, etc.)'}]\"",
152-
"data-translate=\"[{'shown':'Email','translated':'Email','original':'Email',"
153-
. "'location':'Tag attribute (ALT, TITLE, etc.)'}]\"",
154-
"data-translate=\"[{'shown':'Password','translated':'Password','original':'Password',"
155-
. "'location':'Tag attribute (ALT, TITLE, etc.)'}]\""
150+
"data-translate=\"[{"shown":"* Required Fields","translated":"* Required Fields","
151+
. ""original":"* Required Fields","location":"Tag attribute (ALT, TITLE, etc.)"}]\"",
152+
"data-translate=\"[{"shown":"Email","translated":"Email","original":"Email","
153+
. ""location":"Tag attribute (ALT, TITLE, etc.)"}]\"",
154+
"data-translate=\"[{"shown":"Password","translated":"Password","original":"Password","
155+
. ""location":"Tag attribute (ALT, TITLE, etc.)"}]\""
156156
];
157157
$this->translateInlineMock->method('getAdditionalHtmlAttribute')->willReturn(null);
158158

lib/internal/Magento/Framework/Escaper.php

Lines changed: 43 additions & 2 deletions
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

@@ -257,10 +258,17 @@ private function escapeAttributeValue($name, $value)
257258
*/
258259
public function escapeHtmlAttr($string, $escapeSingleQuote = true)
259260
{
261+
$string = (string)$string;
262+
260263
if ($escapeSingleQuote) {
261-
return $this->getEscaper()->escapeHtmlAttr((string) $string);
264+
$translateInline = $this->getTranslateInline();
265+
266+
return $translateInline->isAllowed()
267+
? $this->inlineSensitiveEscapeHthmlAttr($string)
268+
: $this->getEscaper()->escapeHtmlAttr($string);
262269
}
263-
return htmlspecialchars((string)$string, $this->htmlSpecialCharsFlag, 'UTF-8', false);
270+
271+
return htmlspecialchars($string, $this->htmlSpecialCharsFlag, 'UTF-8', false);
264272
}
265273

266274
/**
@@ -476,4 +484,37 @@ private function getTranslateInline()
476484

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

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 escapeHtmlAttributeWithInlineTranslateEnabledDataProvider
185+
*/
186+
public function testEscapeHtmlAttributeWithInlineTranslateEnabled(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 escapeHtmlAttributeWithInlineTranslateEnabledDataProvider(): array
213+
{
214+
return [
215+
[
216+
'{{{Search entire store here...}}}',
217+
'{{{Search&#x20;entire&#x20;store&#x20;here...}}}',
218+
],
219+
[
220+
'{{{Product search}}{{Translated to language}}{{themeMagento/Luma}}}',
221+
'{{{Product&#x20;search}}{{Translated&#x20;to&#x20;language}}{{themeMagento&#x2F;Luma}}}',
222+
],
223+
[
224+
'Simple string',
225+
'Simple&#x20;string',
226+
],
227+
];
228+
}
229+
172230
/**
173231
* @covers \Magento\Framework\Escaper::escapeHtml
174232
* @dataProvider escapeHtmlInvalidDataProvider

0 commit comments

Comments
 (0)