Skip to content

Commit 3c676dc

Browse files
author
Sergii Kovalenko
committed
Merge remote-tracking branch 'remotes/origin/develop' into BUGS
2 parents 60e35f5 + 0a03ae5 commit 3c676dc

File tree

18 files changed

+446
-74
lines changed

18 files changed

+446
-74
lines changed

app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
2222
*/
2323
protected $layoutFactory;
2424

25+
/**
26+
* @var array
27+
*/
28+
private $multipleAttributeList;
29+
2530
/**
2631
* Constructor
2732
*
@@ -31,22 +36,27 @@ class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
3136
* @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
3237
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
3338
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
39+
* @param array $multipleAttributeList
3440
*/
3541
public function __construct(
3642
\Magento\Backend\App\Action\Context $context,
3743
\Magento\Framework\Cache\FrontendInterface $attributeLabelCache,
3844
\Magento\Framework\Registry $coreRegistry,
3945
\Magento\Framework\View\Result\PageFactory $resultPageFactory,
4046
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
41-
\Magento\Framework\View\LayoutFactory $layoutFactory
47+
\Magento\Framework\View\LayoutFactory $layoutFactory,
48+
array $multipleAttributeList = []
4249
) {
4350
parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
4451
$this->resultJsonFactory = $resultJsonFactory;
4552
$this->layoutFactory = $layoutFactory;
53+
$this->multipleAttributeList = $multipleAttributeList;
4654
}
4755

4856
/**
4957
* @return \Magento\Framework\Controller\ResultInterface
58+
* @SuppressWarnings(PHPMD.NPathComplexity)
59+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
5060
*/
5161
public function execute()
5262
{
@@ -89,9 +99,38 @@ public function execute()
8999
$response->setHtmlMessage($layout->getMessagesBlock()->getGroupedHtml());
90100
}
91101
}
102+
103+
$multipleOption = $this->getRequest()->getParam("frontend_input");
104+
$multipleOption = null == $multipleOption ? 'select' : $multipleOption;
105+
106+
if (isset($this->multipleAttributeList[$multipleOption]) && !(null == ($multipleOption))) {
107+
$this->checkUniqueOption(
108+
$response,
109+
$this->getRequest()->getParam($this->multipleAttributeList[$multipleOption])
110+
);
111+
}
112+
92113
return $this->resultJsonFactory->create()->setJsonData($response->toJson());
93114
}
94115

116+
/**
117+
* Throws Exception if not unique values into options
118+
* @param array $optionsValues
119+
* @param array $deletedOptions
120+
* @return bool
121+
*/
122+
private function isUniqueAdminValues(array $optionsValues, array $deletedOptions)
123+
{
124+
$adminValues = [];
125+
foreach ($optionsValues as $optionKey => $values) {
126+
if (!(isset($deletedOptions[$optionKey]) and $deletedOptions[$optionKey] === '1')) {
127+
$adminValues[] = reset($values);
128+
}
129+
}
130+
$uniqueValues = array_unique($adminValues);
131+
return ($uniqueValues === $adminValues);
132+
}
133+
95134
/**
96135
* Set message to response object
97136
*
@@ -107,4 +146,18 @@ private function setMessageToResponse($response, $messages)
107146
}
108147
return $response->setData($messageKey, $messages);
109148
}
149+
150+
/**
151+
* @param DataObject $response
152+
* @param array|null $options
153+
* @return $this
154+
*/
155+
private function checkUniqueOption(DataObject $response, array $options = null)
156+
{
157+
if (is_array($options) && !$this->isUniqueAdminValues($options['value'], $options['delete'])) {
158+
$this->setMessageToResponse($response, [__("The value of Admin must be unique.")]);
159+
$response->setError(true);
160+
}
161+
return $this;
162+
}
110163
}

app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute;
77

88
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate;
9-
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
10-
use Magento\Framework\Controller\Result\JsonFactory as ResultJsonFactory;
11-
use Magento\Framework\Controller\Result\Json as ResultJson;
12-
use Magento\Framework\View\LayoutFactory;
13-
use Magento\Framework\ObjectManagerInterface;
149
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
10+
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
1511
use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet;
12+
use Magento\Framework\Controller\Result\Json as ResultJson;
13+
use Magento\Framework\Controller\Result\JsonFactory as ResultJsonFactory;
1614
use Magento\Framework\Escaper;
15+
use Magento\Framework\ObjectManagerInterface;
16+
use Magento\Framework\View\LayoutFactory;
1717
use Magento\Framework\View\LayoutInterface;
1818

1919
/**
@@ -104,6 +104,7 @@ protected function getModel()
104104
'resultPageFactory' => $this->resultPageFactoryMock,
105105
'resultJsonFactory' => $this->resultJsonFactoryMock,
106106
'layoutFactory' => $this->layoutFactoryMock,
107+
'multipleAttributeList' => ['select' => 'option']
107108
]);
108109
}
109110

@@ -147,4 +148,98 @@ public function testExecute()
147148

148149
$this->assertInstanceOf(ResultJson::class, $this->getModel()->execute());
149150
}
151+
152+
/**
153+
* @dataProvider provideUniqueData
154+
* @param array $options
155+
* @param boolean $isError
156+
* @throws \Magento\Framework\Exception\NotFoundException
157+
*/
158+
public function testUniqueValidation(array $options, $isError)
159+
{
160+
$countFunctionCalls = ($isError) ? 6 : 5;
161+
$this->requestMock->expects($this->exactly($countFunctionCalls))
162+
->method('getParam')
163+
->willReturnMap([
164+
['frontend_label', null, null],
165+
['attribute_code', null, "test_attribute_code"],
166+
['new_attribute_set_name', null, 'test_attribute_set_name'],
167+
['option', null, $options],
168+
['message_key', null, Validate::DEFAULT_MESSAGE_KEY]
169+
]);
170+
171+
$this->objectManagerMock->expects($this->once())
172+
->method('create')
173+
->willReturn($this->attributeMock);
174+
175+
$this->attributeMock->expects($this->once())
176+
->method('loadByCode')
177+
->willReturnSelf();
178+
179+
$this->requestMock->expects($this->once())
180+
->method('has')
181+
->with('new_attribute_set_name')
182+
->willReturn(false);
183+
184+
$this->resultJsonFactoryMock->expects($this->once())
185+
->method('create')
186+
->willReturn($this->resultJson);
187+
188+
$this->resultJson->expects($this->once())
189+
->method('setJsonData')
190+
->willReturnSelf();
191+
192+
$this->assertInstanceOf(ResultJson::class, $this->getModel()->execute());
193+
}
194+
195+
public function provideUniqueData()
196+
{
197+
return [
198+
// valid options
199+
[
200+
[
201+
'value' => [
202+
"option_0" => [1, 0],
203+
"option_1" => [2, 0],
204+
"option_2" => [3, 0],
205+
],
206+
'delete' => [
207+
"option_0" => "",
208+
"option_1" => "",
209+
"option_2" => "",
210+
]
211+
], false
212+
],
213+
//with duplicate
214+
[
215+
[
216+
'value' => [
217+
"option_0" => [1, 0],
218+
"option_1" => [1, 0],
219+
"option_2" => [3, 0],
220+
],
221+
'delete' => [
222+
"option_0" => "",
223+
"option_1" => "",
224+
"option_2" => "",
225+
]
226+
], true
227+
],
228+
//with duplicate but deleted
229+
[
230+
[
231+
'value' => [
232+
"option_0" => [1, 0],
233+
"option_1" => [1, 0],
234+
"option_2" => [3, 0],
235+
],
236+
'delete' => [
237+
"option_0" => "",
238+
"option_1" => "1",
239+
"option_2" => "",
240+
]
241+
], false
242+
],
243+
];
244+
}
150245
}

app/code/Magento/Catalog/etc/adminhtml/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@
4747
</argument>
4848
</arguments>
4949
</type>
50+
<type name="Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate">
51+
<arguments>
52+
<argument name="multipleAttributeList" xsi:type="array">
53+
<item name="select" xsi:type="string">option</item>
54+
</argument>
55+
</arguments>
56+
</type>
5057
<type name="Magento\Catalog\Model\Product\Copier">
5158
<arguments>
5259
<argument name="copyConstructor" xsi:type="object">Magento\Catalog\Model\Product\CopyConstructor\Composite</argument>

app/code/Magento/Catalog/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,3 +715,4 @@ Disable,Disable
715715
none,none
716716
Overview,Overview
717717
Details,Details
718+
"The value of Admin must be unique.", "The value of Admin must be unique."

app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@
1111
$stores = $block->getStoresSortedBySortOrder();
1212
?>
1313
<fieldset class="fieldset">
14-
<legend class="legend"><span><?php /* @escapeNotVerified */ echo __('Manage Options (Values of Your Attribute)') ?></span></legend>
14+
<legend class="legend">
15+
<span><?php echo $block->escapeHtml(__('Manage Options (Values of Your Attribute)')); ?></span>
16+
</legend>
1517
<div id="manage-options-panel" data-index="attribute_options_select_container">
1618
<table class="admin__control-table" data-index="attribute_options_select">
1719
<thead>
1820
<tr id="attribute-options-table">
1921
<th class="col-draggable"></th>
20-
<th class="col-default control-table-actions-th"><span><?php /* @escapeNotVerified */ echo __('Is Default') ?></span></th>
22+
<th class="col-default control-table-actions-th">
23+
<span><?php echo $block->escapeHtml(__('Is Default')); ?></span>
24+
</th>
2125
<?php
2226
foreach ($stores as $_store): ?>
2327
<th<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> class="_required"<?php endif; ?>>
24-
<span><?php /* @escapeNotVerified */ echo __($_store->getName()) ?></span>
28+
<span><?php echo $block->escapeHtml(__($_store->getName())) ?></span>
2529
</th>
2630
<?php endforeach;
2731
$storetotal = count($stores) + 3;
@@ -32,16 +36,18 @@ $stores = $block->getStoresSortedBySortOrder();
3236
<tbody data-role="options-container" class="ignore-validate"></tbody>
3337
<tfoot>
3438
<tr>
35-
<th colspan="<?php /* @escapeNotVerified */ echo $storetotal; ?>" class="validation">
39+
<th colspan="<?php echo (int)$storetotal; ?>" class="validation">
3640
<input type="hidden" class="required-dropdown-attribute-entry" name="dropdown_attribute_validation"/>
41+
<input type="hidden" class="required-dropdown-attribute-unique" name="dropdown_attribute_validation_unique"/>
3742
</th>
3843
</tr>
3944
<tr>
40-
<th colspan="<?php /* @escapeNotVerified */ echo $storetotal; ?>" class="col-actions-add">
45+
<th colspan="<?php echo (int) $storetotal; ?>" class="col-actions-add">
4146
<?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?>
42-
<button id="add_new_option_button" data-action="add_new_row" title="<?php /* @escapeNotVerified */ echo __('Add Option'); ?>"
43-
type="button" class="action- scalable add">
44-
<span><?php /* @escapeNotVerified */ echo __('Add Option'); ?></span>
47+
<button id="add_new_option_button" data-action="add_new_row"
48+
title="<?php echo $block->escapeHtml(__('Add Option')); ?>"
49+
type="button" class="action- scalable add">
50+
<span><?php echo $block->escapeHtml(__('Add Option')); ?></span>
4551
</button>
4652
<?php endif; ?>
4753
</th>
@@ -54,23 +60,25 @@ $stores = $block->getStoresSortedBySortOrder();
5460
<tr>
5561
<td class="col-draggable">
5662
<?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()): ?>
57-
<div data-role="draggable-handle" class="draggable-handle" title="<?php /* @escapeNotVerified */ echo __('Sort Option'); ?>"></div>
63+
<div data-role="draggable-handle" class="draggable-handle"
64+
title="<?php echo $block->escapeHtml(__('Sort Option')); ?>">
65+
</div>
5866
<?php endif; ?>
5967
<input data-role="order" type="hidden" name="option[order][<%- data.id %>]" value="<%- data.sort_order %>" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()): ?> disabled="disabled"<?php endif; ?>/>
6068
</td>
6169
<td class="col-default control-table-actions-cell">
6270
<input class="input-radio" type="<%- data.intype %>" name="default[]" value="<%- data.id %>" <%- data.checked %><?php if ($block->getReadOnly()):?>disabled="disabled"<?php endif;?>/>
6371
</td>
6472
<?php foreach ($stores as $_store): ?>
65-
<td class="col-<%- data.id %>"><input name="option[value][<%- data.id %>][<?php /* @escapeNotVerified */ echo $_store->getId() ?>]" value="<%- data.store<?php /* @escapeNotVerified */ echo $_store->getId() ?> %>" class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option<?php endif; ?>" type="text" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()):?> disabled="disabled"<?php endif;?>/></td>
73+
<td class="col-<%- data.id %>"><input name="option[value][<%- data.id %>][<?php echo (int) $_store->getId() ?>]" value="<%- data.store<?php /* @noEscape */ echo (int) $_store->getId() ?> %>" class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option required-unique<?php endif; ?>" type="text" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()):?> disabled="disabled"<?php endif;?>/></td>
6674
<?php endforeach; ?>
6775
<td id="delete_button_container_<%- data.id %>" class="col-delete">
6876
<input type="hidden" class="delete-flag" name="option[delete][<%- data.id %>]" value="" />
6977
<?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?>
70-
<button id="delete_button_<%- data.id %>" title="<?php /* @escapeNotVerified */ echo __('Delete') ?>" type="button"
78+
<button id="delete_button_<%- data.id %>" title="<?php echo $block->escapeHtml(__('Delete'));?>" type="button"
7179
class="action- scalable delete delete-option"
7280
>
73-
<span><?php /* @escapeNotVerified */ echo __('Delete') ?></span>
81+
<span><?php echo $block->escapeHtml(__('Delete')) ?></span>
7482
</button>
7583
<?php endif;?>
7684
</td>
@@ -90,6 +98,10 @@ $stores = $block->getStoresSortedBySortOrder();
9098
"attributesData": <?php /* @noEscape */ echo json_encode($values, JSON_HEX_QUOT); ?>,
9199
"isSortable": <?php echo (int)(!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) ?>,
92100
"isReadOnly": <?php echo (int)$block->getReadOnly(); ?>
101+
},
102+
"Magento_Catalog/catalog/product/attribute/unique-validate": {
103+
"element": "required-dropdown-attribute-unique",
104+
"message": "<?php echo $block->escapeHtml(__("The value of Admin must be unique.")); ?>"
93105
}
94106
}
95107
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright © 2016 Magento. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
define([
7+
'jquery',
8+
'mage/backend/validation'
9+
], function (jQuery) {
10+
'use strict';
11+
12+
return function (config) {
13+
var _config = jQuery.extend({
14+
element: null,
15+
message: '',
16+
uniqueClass: 'required-unique'
17+
}, config);
18+
19+
if (typeof _config.element === 'string') {
20+
jQuery.validator.addMethod(
21+
_config.element,
22+
23+
function (value, element) {
24+
var inputs = jQuery(element)
25+
.closest('table')
26+
.find('.' + _config.uniqueClass + ':visible'),
27+
valuesHash = {},
28+
isValid = true;
29+
30+
inputs.each(function (el) {
31+
var inputValue = inputs[el].value;
32+
33+
if (typeof valuesHash[inputValue] !== 'undefined') {
34+
isValid = false;
35+
}
36+
valuesHash[inputValue] = el;
37+
});
38+
39+
return isValid;
40+
},
41+
42+
_config.message
43+
);
44+
}
45+
};
46+
});

app/code/Magento/Swatches/Model/Plugin/EavAttribute.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,11 @@ protected function processTextualSwatch(Attribute $attribute)
277277
//option was deleted by button with basket
278278
continue;
279279
}
280+
$defaultSwatchValue = reset($storeValues);
280281
foreach ($storeValues as $storeId => $value) {
282+
if (!$value) {
283+
$value = $defaultSwatchValue;
284+
}
281285
$swatch = $this->loadSwatchIfExists($optionId, $storeId);
282286
$swatch->isDeleted($isOptionForDelete);
283287
$this->saveSwatchData(

0 commit comments

Comments
 (0)