Skip to content

Commit 1f51e88

Browse files
ENGCOM-8132: Unable to insert widget with text value which contains }} string #29006
2 parents dc84979 + a20a955 commit 1f51e88

File tree

5 files changed

+189
-46
lines changed

5 files changed

+189
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminFillCatalogProductsListWidgetTitleActionGroup">
12+
<annotations>
13+
<description>Fill catalog products list title field.</description>
14+
</annotations>
15+
16+
<arguments>
17+
<argument name="title" type="string" defaultValue=""/>
18+
</arguments>
19+
<waitForElementVisible selector="{{InsertWidgetSection.title}}" stepKey="waitForField"/>
20+
<fillField selector="{{InsertWidgetSection.title}}" userInput="{{title}}" stepKey="fillTitleField"/>
21+
</actionGroup>
22+
</actionGroups>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="StorefrontAssertWidgetTitleActionGroup">
12+
<annotations>
13+
<description>Assert widget title on storefront.</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="title" type="string"/>
17+
</arguments>
18+
19+
<grabTextFrom selector="{{StorefrontWidgetsSection.widgetProductsGrid}} {{StorefrontWidgetsSection.widgetTitle}}"
20+
stepKey="grabWidgetTitle"/>
21+
<assertEquals stepKey="assertWidgetTitle">
22+
<actualResult type="string">$grabWidgetTitle</actualResult>
23+
<expectedResult type="string">{{title}}</expectedResult>
24+
</assertEquals>
25+
</actionGroup>
26+
</actionGroups>

app/code/Magento/CatalogWidget/Test/Mftf/Section/CatalogWidgetSection/InsertWidgetSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
<element name="checkElementStorefrontByPrice" type="button" selector="//*[@class='product-items widget-product-grid']//*[contains(text(),'${{arg4}}.00')]" parameterized="true"/>
2020
<element name="checkElementStorefrontByName" type="button" selector="//*[@class='product-items widget-product-grid']//*[@class='product-item'][{{productPosition}}]//a[contains(text(), '{{productName}}')]" parameterized="true"/>
2121
<element name="categoryTreeWrapper" type="text" selector=".rule-chooser .tree.x-tree"/>
22+
<element name="title" type="text" selector="input[name='parameters[title]']"/>
2223
</section>
2324
</sections>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
10+
<test name="StoreFrontWidgetTitleWithReservedCharsTest">
11+
<annotations>
12+
<features value="Cms"/>
13+
<stories value="Create a CMS Page via the Admin when widget title contains reserved chairs"/>
14+
<title value="Create CMS Page via the Admin when widget title contains reserved chairs"/>
15+
<description value="See CMS Page title on store front page if titled widget with reserved chairs added"/>
16+
<severity value="MAJOR"/>
17+
<testCaseId value="MC-37419"/>
18+
<group value="Cms"/>
19+
<group value="WYSIWYGDisabled"/>
20+
</annotations>
21+
<before>
22+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
23+
<createData entity="simpleProductWithoutCategory" stepKey="createSimpleProductWithoutCategory"/>
24+
<createData entity="_defaultCmsPage" stepKey="createCmsPage"/>
25+
</before>
26+
<after>
27+
<deleteData createDataKey="createSimpleProductWithoutCategory" stepKey="deleteProduct"/>
28+
<deleteData createDataKey="createCmsPage" stepKey="deleteCmsPage" />
29+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
30+
</after>
31+
<!--Navigate to Page in Admin-->
32+
<actionGroup ref="NavigateToCreatedCMSPageActionGroup" stepKey="navigateToCreatedCMSPage">
33+
<argument name="CMSPage" value="$createCmsPage$"/>
34+
</actionGroup>
35+
<!--Insert widget-->
36+
<actionGroup ref="AdminInsertWidgetToCmsPageContentActionGroup" stepKey="insertWidgetToCmsPageContent">
37+
<argument name="widgetType" value="Catalog Products List"/>
38+
</actionGroup>
39+
<!--Fill widget title and save-->
40+
<actionGroup ref="AdminFillCatalogProductsListWidgetTitleActionGroup" stepKey="fillWidgetTitle">
41+
<argument name="title" value="Tittle }}"/>
42+
</actionGroup>
43+
<actionGroup ref="AdminClickInsertWidgetActionGroup" stepKey="clickInsertWidgetButton"/>
44+
<actionGroup ref="SaveCmsPageActionGroup" stepKey="saveOpenedPage"/>
45+
<!--Verify data on frontend-->
46+
<actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="navigateToPageOnStorefront">
47+
<argument name="identifier" value="$createCmsPage.identifier$"/>
48+
</actionGroup>
49+
<actionGroup ref="StorefrontAssertWidgetTitleActionGroup" stepKey="verifyPageDataOnFrontend">
50+
<argument name="title" value="Tittle }}"/>
51+
</actionGroup>
52+
</test>
53+
</tests>

app/code/Magento/Widget/Model/Widget.php

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
*/
66
namespace Magento\Widget\Model;
77

8+
use Magento\Framework\App\Cache\Type\Config;
9+
use Magento\Framework\DataObject;
10+
use Magento\Framework\Escaper;
11+
use Magento\Framework\Math\Random;
12+
use Magento\Framework\View\Asset\Repository;
13+
use Magento\Framework\View\Asset\Source;
14+
use Magento\Framework\View\FileSystem;
15+
use Magento\Widget\Helper\Conditions;
16+
use Magento\Widget\Model\Config\Data;
17+
818
/**
919
* Widget model for different purposes
1020
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -15,32 +25,32 @@
1525
class Widget
1626
{
1727
/**
18-
* @var \Magento\Widget\Model\Config\Data
28+
* @var Data
1929
*/
2030
protected $dataStorage;
2131

2232
/**
23-
* @var \Magento\Framework\App\Cache\Type\Config
33+
* @var Config
2434
*/
2535
protected $configCacheType;
2636

2737
/**
28-
* @var \Magento\Framework\View\Asset\Repository
38+
* @var Repository
2939
*/
3040
protected $assetRepo;
3141

3242
/**
33-
* @var \Magento\Framework\View\Asset\Source
43+
* @var Source
3444
*/
3545
protected $assetSource;
3646

3747
/**
38-
* @var \Magento\Framework\View\FileSystem
48+
* @var FileSystem
3949
*/
4050
protected $viewFileSystem;
4151

4252
/**
43-
* @var \Magento\Framework\Escaper
53+
* @var Escaper
4454
*/
4555
protected $escaper;
4656

@@ -50,30 +60,35 @@ class Widget
5060
protected $widgetsArray = [];
5161

5262
/**
53-
* @var \Magento\Widget\Helper\Conditions
63+
* @var Conditions
5464
*/
5565
protected $conditionsHelper;
5666

5767
/**
58-
* @var \Magento\Framework\Math\Random
68+
* @var Random
5969
*/
6070
private $mathRandom;
6171

6272
/**
63-
* @param \Magento\Framework\Escaper $escaper
64-
* @param \Magento\Widget\Model\Config\Data $dataStorage
65-
* @param \Magento\Framework\View\Asset\Repository $assetRepo
66-
* @param \Magento\Framework\View\Asset\Source $assetSource
67-
* @param \Magento\Framework\View\FileSystem $viewFileSystem
68-
* @param \Magento\Widget\Helper\Conditions $conditionsHelper
73+
* @var string[]
74+
*/
75+
private $reservedChars = ['}', '{'];
76+
77+
/**
78+
* @param Escaper $escaper
79+
* @param Data $dataStorage
80+
* @param Repository $assetRepo
81+
* @param Source $assetSource
82+
* @param FileSystem $viewFileSystem
83+
* @param Conditions $conditionsHelper
6984
*/
7085
public function __construct(
71-
\Magento\Framework\Escaper $escaper,
72-
\Magento\Widget\Model\Config\Data $dataStorage,
73-
\Magento\Framework\View\Asset\Repository $assetRepo,
74-
\Magento\Framework\View\Asset\Source $assetSource,
75-
\Magento\Framework\View\FileSystem $viewFileSystem,
76-
\Magento\Widget\Helper\Conditions $conditionsHelper
86+
Escaper $escaper,
87+
Data $dataStorage,
88+
Repository $assetRepo,
89+
Source $assetSource,
90+
FileSystem $viewFileSystem,
91+
Conditions $conditionsHelper
7792
) {
7893
$this->escaper = $escaper;
7994
$this->dataStorage = $dataStorage;
@@ -110,14 +125,11 @@ public function getWidgetByClassType($type)
110125
$widgets = $this->getWidgets();
111126
/** @var array $widget */
112127
foreach ($widgets as $widget) {
113-
if (isset($widget['@'])) {
114-
if (isset($widget['@']['type'])) {
115-
if ($type === $widget['@']['type']) {
116-
return $widget;
117-
}
118-
}
128+
if (isset($widget['@']['type']) && $type === $widget['@']['type']) {
129+
return $widget;
119130
}
120131
}
132+
121133
return null;
122134
}
123135

@@ -131,6 +143,7 @@ public function getWidgetByClassType($type)
131143
*/
132144
public function getConfigAsXml($type)
133145
{
146+
// phpstan:ignore
134147
return $this->getXmlElementByType($type);
135148
}
136149

@@ -296,42 +309,70 @@ public function getWidgetsArray($filters = [])
296309
*/
297310
public function getWidgetDeclaration($type, $params = [], $asIs = true)
298311
{
299-
$directive = '{{widget type="' . $type . '"';
300312
$widget = $this->getConfigAsObject($type);
301313

314+
$params = array_filter($params, function ($value) {
315+
return $value !== null && $value !== '';
316+
});
317+
318+
$directiveParams = '';
302319
foreach ($params as $name => $value) {
303320
// Retrieve default option value if pre-configured
304-
if ($name == 'conditions') {
305-
$name = 'conditions_encoded';
306-
$value = $this->conditionsHelper->encode($value);
307-
} elseif (is_array($value)) {
308-
$value = implode(',', $value);
309-
} elseif (trim($value) == '') {
310-
$parameters = $widget->getParameters();
311-
if (isset($parameters[$name]) && is_object($parameters[$name])) {
312-
$value = $parameters[$name]->getValue();
313-
}
314-
}
315-
if (isset($value)) {
316-
$directive .= sprintf(' %s="%s"', $name, $this->escaper->escapeHtmlAttr($value, false));
317-
}
321+
$directiveParams .= $this->getDirectiveParam($widget, $name, $value);
318322
}
319323

320-
$directive .= $this->getWidgetPageVarName($params);
321-
322-
$directive .= '}}';
324+
$directive = sprintf('{{widget type="%s"%s%s}}', $type, $directiveParams, $this->getWidgetPageVarName($params));
323325

324326
if ($asIs) {
325327
return $directive;
326328
}
327329

328-
$html = sprintf(
330+
return sprintf(
329331
'<img id="%s" src="%s" title="%s">',
330332
$this->idEncode($directive),
331333
$this->getPlaceholderImageUrl($type),
332334
$this->escaper->escapeUrl($directive)
333335
);
334-
return $html;
336+
}
337+
338+
/**
339+
* Returns directive param with prepared value
340+
*
341+
* @param DataObject $widget
342+
* @param string $name
343+
* @param string|array $value
344+
* @return string
345+
*/
346+
private function getDirectiveParam(DataObject $widget, string $name, $value): string
347+
{
348+
if ($name === 'conditions') {
349+
$name = 'conditions_encoded';
350+
$value = $this->conditionsHelper->encode($value);
351+
} elseif (is_array($value)) {
352+
$value = implode(',', $value);
353+
} elseif (trim($value) === '') {
354+
$parameters = $widget->getParameters();
355+
if (isset($parameters[$name]) && is_object($parameters[$name])) {
356+
$value = $parameters[$name]->getValue();
357+
}
358+
} else {
359+
$value = $this->getPreparedValue($value);
360+
}
361+
362+
return sprintf(' %s="%s"', $name, $this->escaper->escapeHtmlAttr($value, false));
363+
}
364+
365+
/**
366+
* Returns encoded value if it contains reserved chars
367+
*
368+
* @param string $value
369+
* @return string
370+
*/
371+
private function getPreparedValue(string $value): string
372+
{
373+
$pattern = sprintf('/%s/', implode('|', $this->reservedChars));
374+
375+
return preg_match($pattern, $value) ? rawurlencode($value) : $value;
335376
}
336377

337378
/**

0 commit comments

Comments
 (0)