Skip to content

Commit 04b2487

Browse files
author
Joan He
committed
Merge remote-tracking branch 'honey/MAGETWO-92931-Html-entity-breadcrumb' into MAGETWO-90927-product-save-warning
2 parents 73c9a4f + 65b4bbe commit 04b2487

File tree

6 files changed

+172
-21
lines changed

6 files changed

+172
-21
lines changed

app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,32 @@
219219
<requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity>
220220
<requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity>
221221
</entity>
222+
<entity name="productWithHTMLEntityOne" type="product">
223+
<data key="sku" unique="suffix">SimpleOne&#8482;Product</data>
224+
<data key="type_id">simple</data>
225+
<data key="attribute_set_id">4</data>
226+
<data key="visibility">4</data>
227+
<data key="name" unique="suffix">SimpleOne&#8482;Product</data>
228+
<data key="price">50.00</data>
229+
<data key="urlKey" unique="suffix">testurlkey</data>
230+
<data key="status">1</data>
231+
<data key="quantity">100</data>
232+
<requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity>
233+
<requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity>
234+
</entity>
235+
<entity name="productWithHTMLEntityTwo" type="product">
236+
<data key="sku" unique="suffix">SimpleTwo&#38657;&#20135;&#21697;&lt;カネボウPro</data>
237+
<data key="type_id">simple</data>
238+
<data key="attribute_set_id">4</data>
239+
<data key="visibility">4</data>
240+
<data key="name" unique="suffix">SimpleTwo&#38657;&#20135;&#21697;&lt;カネボウPro</data>
241+
<data key="price">50.00</data>
242+
<data key="urlKey" unique="suffix">testurlkey</data>
243+
<data key="status">1</data>
244+
<data key="quantity">100</data>
245+
<requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity>
246+
<requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity>
247+
</entity>
222248
<entity name="defaultVirtualProduct" type="product">
223249
<data key="sku" unique="suffix">virtualProduct</data>
224250
<data key="type_id">virtual</data>

app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,62 @@
6060
<argument name="product" value="SimpleProductNameWithDoubleQuote"/>
6161
</actionGroup>
6262
</test>
63+
64+
<test name="StorefrontProductNameWithHTMLEntities">
65+
<annotations>
66+
<features value="Catalog"/>
67+
<title value=":Proudct with html special characters in name"/>
68+
<description value="Product with html entities in the name should appear correctly on the PDP breadcrumbs on storefront"/>
69+
<severity value="CRITICAL"/>
70+
<group value="product"/>
71+
<testCaseId value="MAGETWO-93794"/>
72+
</annotations>
73+
<before>
74+
<createData entity="_defaultCategory" stepKey="createCategoryOne"/>
75+
<createData entity="productWithHTMLEntityOne" stepKey="productOne">
76+
<requiredEntity createDataKey="createCategoryOne"/>
77+
</createData>
78+
<createData entity="productWithHTMLEntityTwo" stepKey="productTwo">
79+
<requiredEntity createDataKey="createCategoryOne"/>
80+
</createData>
81+
</before>
82+
<after>
83+
<deleteData createDataKey="productOne" stepKey="deleteProductOne"/>
84+
<deleteData createDataKey="productTwo" stepKey="deleteProductTwo"/>
85+
<deleteData createDataKey="createCategoryOne" stepKey="deleteCategory"/>
86+
</after>
87+
88+
<!--Check product in category listing-->
89+
<amOnPage url="{{StorefrontCategoryPage.url($$createCategoryOne.name$$)}}" stepKey="navigateToCategoryPage"/>
90+
<waitForPageLoad stepKey="waitforCategoryPageToLoad"/>
91+
<see selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" userInput="{{productWithHTMLEntityOne.name}}" stepKey="seeCorrectNameProd1CategoryPage"/>
92+
<see selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityTwo.name)}}" userInput="{{productWithHTMLEntityTwo.name}}" stepKey="seeCorrectNameProd2CategoryPage"/>
93+
94+
<!--Open product display page-->
95+
<click selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" stepKey="clickProductToGoProductPage"/>
96+
<waitForPageLoad stepKey="waitForProductDisplayPageLoad2"/>
97+
98+
<see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{productWithHTMLEntityOne.name}}" stepKey="seeCorrectName"/>
99+
<see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{productWithHTMLEntityOne.sku}}" stepKey="seeCorrectSku"/>
100+
<see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{productWithHTMLEntityOne.price}}" stepKey="seeCorrectPrice"/>
101+
102+
<!--Veriy the breadcrumbs on Product Display page-->
103+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="Home" stepKey="seeHomePageInBreadcrumbs1"/>
104+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$createCategoryOne.name$$" stepKey="seeCorrectBreadCrumbCategory"/>
105+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$productOne.name$$" stepKey="seeCorrectBreadCrumbProduct"/>
106+
107+
<click selector="{{StorefrontNavigationSection.topCategory($$createCategoryOne.name$$)}}" stepKey="goBackToCategoryPage"/>
108+
<waitForPageLoad stepKey="waitforCategoryPageToLoad2"/>
109+
110+
<!--Open product display page-->
111+
<click selector="{{StorefrontCategoryProductSection.ProductTitleByNumber('1')}}" stepKey="goToProduct2DisplayPage"/>
112+
<!--<click selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" stepKey="clickProductToGoProductPage"/>-->
113+
<waitForPageLoad stepKey="waitForProductDisplayPageLoad3"/>
114+
115+
<!--Veriy the breadcrumbs on Product Display page-->
116+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="Home" stepKey="seeHomePageInBreadcrumbs2"/>
117+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$createCategoryOne.name$$" stepKey="seeCorrectBreadCrumbCategory2"/>
118+
<see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$productTwo.name$$" stepKey="seeCorrectBreadCrumbProduct2"/>
119+
120+
</test>
63121
</tests>

app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class BreadcrumbsTest extends \PHPUnit\Framework\TestCase
4141
/**
4242
* @inheritdoc
4343
*/
44-
protected function setUp()
44+
protected function setUp() : void
4545
{
4646
$this->catalogHelper = $this->getMockBuilder(CatalogHelper::class)
4747
->setMethods(['getProduct'])
@@ -53,19 +53,22 @@ protected function setUp()
5353
->disableOriginalConstructor()
5454
->getMockForAbstractClass();
5555

56+
$escaper = $this->getObjectManager()->getObject(\Magento\Framework\Escaper::class);
57+
5658
$this->viewModel = $this->getObjectManager()->getObject(
5759
Breadcrumbs::class,
5860
[
5961
'catalogData' => $this->catalogHelper,
6062
'scopeConfig' => $this->scopeConfig,
63+
'escaper' => $escaper
6164
]
6265
);
6366
}
6467

6568
/**
6669
* @return void
6770
*/
68-
public function testGetCategoryUrlSuffix()
71+
public function testGetCategoryUrlSuffix() : void
6972
{
7073
$this->scopeConfig->expects($this->once())
7174
->method('getValue')
@@ -78,7 +81,7 @@ public function testGetCategoryUrlSuffix()
7881
/**
7982
* @return void
8083
*/
81-
public function testIsCategoryUsedInProductUrl()
84+
public function testIsCategoryUsedInProductUrl() : void
8285
{
8386
$this->scopeConfig->expects($this->once())
8487
->method('isSetFlag')
@@ -95,7 +98,7 @@ public function testIsCategoryUsedInProductUrl()
9598
* @param string $expectedName
9699
* @return void
97100
*/
98-
public function testGetProductName($product, string $expectedName)
101+
public function testGetProductName($product, string $expectedName) : void
99102
{
100103
$this->catalogHelper->expects($this->atLeastOnce())
101104
->method('getProduct')
@@ -107,18 +110,71 @@ public function testGetProductName($product, string $expectedName)
107110
/**
108111
* @return array
109112
*/
110-
public function productDataProvider()
113+
public function productDataProvider() : array
111114
{
112115
return [
113116
[$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test']]), 'Test'],
114117
[null, ''],
115118
];
116119
}
117120

121+
/**
122+
* @dataProvider productJsonEncodeDataProvider
123+
*
124+
* @param Product|null $product
125+
* @param string $expectedJson
126+
* @return void
127+
*/
128+
public function testGetJsonConfiguration($product, string $expectedJson) : void
129+
{
130+
$this->catalogHelper->expects($this->atLeastOnce())
131+
->method('getProduct')
132+
->willReturn($product);
133+
134+
$this->scopeConfig->expects($this->any())
135+
->method('isSetFlag')
136+
->with('catalog/seo/product_use_categories', \Magento\Store\Model\ScopeInterface::SCOPE_STORE)
137+
->willReturn(false);
138+
139+
$this->scopeConfig->expects($this->any())
140+
->method('getValue')
141+
->with('catalog/seo/category_url_suffix', \Magento\Store\Model\ScopeInterface::SCOPE_STORE)
142+
->willReturn('."html');
143+
144+
$this->assertEquals($expectedJson, $this->viewModel->getJsonConfiguration());
145+
}
146+
147+
/**
148+
* @return array
149+
*/
150+
public function productJsonEncodeDataProvider() : array
151+
{
152+
return [
153+
[
154+
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test ™']]),
155+
'{"breadcrumbs":{"categoryUrlSuffix":".&quot;html","userCategoryPathInUrl":0,"product":"Test \u2122"}}',
156+
],
157+
[
158+
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test "']]),
159+
'{"breadcrumbs":{"categoryUrlSuffix":".&quot;html","userCategoryPathInUrl":0,"product":"Test &quot;"}}',
160+
],
161+
[
162+
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test <b>x</b>']]),
163+
'{"breadcrumbs":{"categoryUrlSuffix":".&quot;html","userCategoryPathInUrl":0,"product":'
164+
. '"Test &lt;b&gt;x&lt;\/b&gt;"}}',
165+
],
166+
[
167+
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test \'abc\'']]),
168+
'{"breadcrumbs":'
169+
. '{"categoryUrlSuffix":".&quot;html","userCategoryPathInUrl":0,"product":"Test &#039;abc&#039;"}}'
170+
],
171+
];
172+
}
173+
118174
/**
119175
* @return ObjectManager
120176
*/
121-
private function getObjectManager()
177+
private function getObjectManager() : ObjectManager
122178
{
123179
if (null === $this->objectManager) {
124180
$this->objectManager = new ObjectManager($this);

app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php

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

87
namespace Magento\Catalog\ViewModel\Product;
98

@@ -32,11 +31,6 @@ class Breadcrumbs extends DataObject implements ArgumentInterface
3231
*/
3332
private $scopeConfig;
3433

35-
/**
36-
* @var Json
37-
*/
38-
private $json;
39-
4034
/**
4135
* @var Escaper
4236
*/
@@ -47,6 +41,7 @@ class Breadcrumbs extends DataObject implements ArgumentInterface
4741
* @param ScopeConfigInterface $scopeConfig
4842
* @param Json|null $json
4943
* @param Escaper|null $escaper
44+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5045
*/
5146
public function __construct(
5247
Data $catalogData,
@@ -58,7 +53,6 @@ public function __construct(
5853

5954
$this->catalogData = $catalogData;
6055
$this->scopeConfig = $scopeConfig;
61-
$this->json = $json ?: ObjectManager::getInstance()->get(Json::class);
6256
$this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class);
6357
}
6458

@@ -101,20 +95,32 @@ public function getProductName(): string
10195
}
10296

10397
/**
104-
* Returns breadcrumb json.
98+
* Returns breadcrumb json with html escaped names
10599
*
106100
* @return string
107101
*/
108-
public function getJsonConfiguration()
102+
public function getJsonConfigurationHtmlEscaped() : string
109103
{
110-
return $this->json->serialize(
104+
return json_encode(
111105
[
112106
'breadcrumbs' => [
113107
'categoryUrlSuffix' => $this->escaper->escapeHtml($this->getCategoryUrlSuffix()),
114108
'userCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(),
115-
'product' => $this->escaper->escapeHtml($this->escaper->escapeJs($this->getProductName()))
109+
'product' => $this->escaper->escapeHtml($this->getProductName())
116110
]
117-
]
111+
],
112+
JSON_HEX_TAG
118113
);
119114
}
115+
116+
/**
117+
* Returns breadcrumb json.
118+
*
119+
* @return string
120+
* @deprecated in favor of new method with name {suffix}Html{postfix}()
121+
*/
122+
public function getJsonConfiguration()
123+
{
124+
return $this->getJsonConfigurationHtmlEscaped();
125+
}
120126
}

app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@
77
/** @var \Magento\Catalog\ViewModel\Product\Breadcrumbs $viewModel */
88
$viewModel = $block->getData('viewModel');
99
?>
10-
<div class="breadcrumbs" data-mage-init='<?= /* @escapeNotVerified */ $viewModel->getJsonConfiguration() ?>'></div>
10+
<div class="breadcrumbs"></div>
11+
<script type="text/x-magento-init">
12+
{
13+
".breadcrumbs": <?= $viewModel->getJsonConfigurationHtmlEscaped() ?>
14+
}
15+
</script>

app/code/Magento/Theme/view/frontend/web/templates/breadcrumbs.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
<% if (crumb.link) { %>
1111
<a href="<%= crumb.link %>" title="<%- crumb.title %>"><%- crumb.label %></a>
1212
<% } else if (crumb.last) { %>
13-
<strong><%- crumb.label %></strong>
13+
<strong><%= crumb.label %></strong>
1414
<% } else { %>
15-
<%- crumb.label %>
15+
<%= crumb.label %>
1616
<% } %>
1717
</li>
1818
<% }); %>

0 commit comments

Comments
 (0)