Skip to content

Commit 5939e8c

Browse files
committed
Merge remote-tracking branch 'origin/imported-magento-magento2-32716' into 2.4-develop-pr143
2 parents 9676ca6 + cd803c5 commit 5939e8c

File tree

2 files changed

+107
-31
lines changed

2 files changed

+107
-31
lines changed

app/code/Magento/ConfigurableProduct/Plugin/Model/Attribute/Backend/AttributeValidation.php

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\ConfigurableProduct\Plugin\Model\Attribute\Backend;
79

810
use Magento\Catalog\Api\Data\ProductInterface;
911
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
12+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
13+
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
14+
use Magento\Framework\DataObject;
1015

1116
/**
1217
* Skip validate attributes used for create configurable product
@@ -19,7 +24,6 @@ class AttributeValidation
1924
private $configurableProductType;
2025

2126
/**
22-
* AttributeValidation constructor.
2327
* @param Configurable $configurableProductType
2428
*/
2529
public function __construct(
@@ -29,27 +33,42 @@ public function __construct(
2933
}
3034

3135
/**
32-
* @param \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $subject
36+
* Verify is attribute used for configurable product creation and should not be validated.
37+
*
38+
* @param AbstractBackend $subject
3339
* @param \Closure $proceed
34-
* @param \Magento\Framework\DataObject $entity
40+
* @param DataObject $entity
3541
* @return bool
3642
*/
3743
public function aroundValidate(
38-
\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $subject,
44+
AbstractBackend $subject,
3945
\Closure $proceed,
40-
\Magento\Framework\DataObject $entity
46+
DataObject $entity
4147
) {
4248
$attribute = $subject->getAttribute();
43-
if ($entity instanceof ProductInterface
44-
&& $entity->getTypeId() == Configurable::TYPE_CODE
45-
&& in_array(
46-
$attribute->getAttributeId(),
47-
$this->configurableProductType->getUsedProductAttributeIds($entity),
48-
true
49-
)
50-
) {
49+
if ($this->isAttributeShouldNotBeValidated($entity, $attribute)) {
5150
return true;
5251
}
5352
return $proceed($entity);
5453
}
54+
55+
/**
56+
* Verify if attribute is a part of configurable product and should not be validated.
57+
*
58+
* @param DataObject $entity
59+
* @param AbstractAttribute $attribute
60+
* @return bool
61+
*/
62+
private function isAttributeShouldNotBeValidated(DataObject $entity, AbstractAttribute $attribute): bool
63+
{
64+
if (!($entity instanceof ProductInterface && $entity->getTypeId() === Configurable::TYPE_CODE)) {
65+
return false;
66+
}
67+
$attributeId = $attribute->getAttributeId();
68+
$options = $entity->getConfigurableProductOptions() ?: [];
69+
$configurableAttributeIds = array_column($options, 'attribute_id');
70+
71+
return in_array($attributeId, $configurableAttributeIds)
72+
|| in_array($attributeId, $this->configurableProductType->getUsedProductAttributeIds($entity), true);
73+
}
5574
}

dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ProductRepositoryTest.php

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
67
namespace Magento\ConfigurableProduct\Api;
78

89
use Magento\Catalog\Api\Data\ProductInterface;
910
use Magento\Catalog\Api\ProductRepositoryInterface;
1011
use Magento\Catalog\Model\Entity\Attribute;
12+
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
1113
use Magento\Eav\Model\Config;
1214
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection;
1315
use Magento\Framework\Api\ExtensibleDataInterface;
1416
use Magento\Framework\ObjectManagerInterface;
1517
use Magento\Framework\Webapi\Rest\Request;
1618
use Magento\TestFramework\Helper\Bootstrap;
17-
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
1819
use Magento\TestFramework\TestCase\WebapiAbstract;
1920

2021
/**
@@ -102,18 +103,18 @@ protected function createConfigurableProduct()
102103

103104
$configurableProductOptions = [
104105
[
105-
"attribute_id" => $this->configurableAttribute->getId(),
106+
"attribute_id" => $this->configurableAttribute->getId(),
106107
"label" => $label,
107108
"position" => 0,
108109
"values" => [
109110
[
110-
"value_index" => $options[0]['option_id'],
111+
"value_index" => $options[0]['option_id'],
111112
],
112113
[
113-
"value_index" => $options[1]['option_id'],
114-
]
114+
"value_index" => $options[1]['option_id'],
115+
],
115116
],
116-
]
117+
],
117118
];
118119

119120
$product = [
@@ -173,6 +174,20 @@ public function testCreateConfigurableProduct()
173174
$this->assertEquals([$productId1, $productId2], $resultConfigurableProductLinks);
174175
}
175176

177+
/**
178+
* Verify configurable product creation passes validation with required attribute not specified in product itself.
179+
*
180+
* @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
181+
*/
182+
public function testCreateConfigurableProductWithRequiredAttribute(): void
183+
{
184+
$configurableAttribute = $this->eavConfig->getAttribute('catalog_product', 'test_configurable');
185+
$configurableAttribute->setIsRequired(true);
186+
$configurableAttribute->save();
187+
$response = $this->createConfigurableProductWithRequiredAttribute();
188+
$this->assertEquals(self::CONFIGURABLE_PRODUCT_SKU, $response[ProductInterface::SKU]);
189+
}
190+
176191
/**
177192
* Create configurable with simple which has zero attribute value
178193
*
@@ -340,10 +355,10 @@ public function testUpdateConfigurableProductLinks()
340355
= [$productId1, $productId2];
341356
//set the value for required attribute
342357
$response["custom_attributes"][] =
343-
[
344-
"attribute_code" => $this->configurableAttribute->getAttributeCode(),
345-
"value" => $resultConfigurableProductOptions[0]['values'][0]['value_index'],
346-
];
358+
[
359+
"attribute_code" => $this->configurableAttribute->getAttributeCode(),
360+
"value" => $resultConfigurableProductOptions[0]['values'][0]['value_index'],
361+
];
347362

348363
$response = $this->saveProduct($response);
349364

@@ -364,7 +379,8 @@ public function testUpdateConfigurableProductLinksWithNonExistingProduct()
364379
//leave existing option untouched
365380
unset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_options']);
366381
$response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_links'] = [
367-
$productId1, $nonExistingId
382+
$productId1,
383+
$nonExistingId,
368384
];
369385

370386
$expectedMessage = 'The product that was requested doesn\'t exist. Verify the product and try again.';
@@ -400,14 +416,15 @@ public function testUpdateConfigurableProductLinksWithDuplicateAttributes()
400416
[
401417
'attribute_code' => 'test_configurable',
402418
'value' => $optionValue1,
403-
]
419+
],
404420
];
405421
$this->saveProduct($product2);
406422

407423
//leave existing option untouched
408424
unset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_options']);
409425
$response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_links'] = [
410-
$productId1, $productId2
426+
$productId1,
427+
$productId2,
411428
];
412429

413430
$expectedMessage = 'Products "%1" and "%2" have the same set of attribute values.';
@@ -440,7 +457,8 @@ public function testUpdateConfigurableProductLinksWithWithoutVariationAttributes
440457
/** delete all variation attribute */
441458
$response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_options'] = [];
442459
$response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_links'] = [
443-
$productId1, $productId2
460+
$productId1,
461+
$productId2,
444462
];
445463

446464
$expectedMessage = 'The product that was requested doesn\'t exist. Verify the product and try again.';
@@ -496,7 +514,7 @@ protected function createProduct($product)
496514
$serviceInfo = [
497515
'rest' => [
498516
'resourcePath' => self::RESOURCE_PATH,
499-
'httpMethod' => Request::HTTP_METHOD_POST
517+
'httpMethod' => Request::HTTP_METHOD_POST,
500518
],
501519
'soap' => [
502520
'service' => self::SERVICE_NAME,
@@ -521,7 +539,7 @@ protected function deleteProductBySku($productSku)
521539
$serviceInfo = [
522540
'rest' => [
523541
'resourcePath' => $resourcePath,
524-
'httpMethod' => Request::HTTP_METHOD_DELETE
542+
'httpMethod' => Request::HTTP_METHOD_DELETE,
525543
],
526544
'soap' => [
527545
'service' => self::SERVICE_NAME,
@@ -544,7 +562,7 @@ protected function saveProduct($product)
544562
{
545563
if (isset($product['custom_attributes'])) {
546564
$count = count($product['custom_attributes']);
547-
for ($i=0; $i < $count; $i++) {
565+
for ($i = 0; $i < $count; $i++) {
548566
if ($product['custom_attributes'][$i]['attribute_code'] == 'category_ids'
549567
&& !is_array($product['custom_attributes'][$i]['value'])
550568
) {
@@ -556,7 +574,7 @@ protected function saveProduct($product)
556574
$serviceInfo = [
557575
'rest' => [
558576
'resourcePath' => $resourcePath,
559-
'httpMethod' => Request::HTTP_METHOD_PUT
577+
'httpMethod' => Request::HTTP_METHOD_PUT,
560578
],
561579
'soap' => [
562580
'service' => self::SERVICE_NAME,
@@ -568,4 +586,43 @@ protected function saveProduct($product)
568586
$response = $this->_webApiCall($serviceInfo, $requestData);
569587
return $response;
570588
}
589+
590+
/**
591+
* Create configurable product with required attribute by web api.
592+
*
593+
* @return array
594+
*/
595+
private function createConfigurableProductWithRequiredAttribute(): array
596+
{
597+
$this->configurableAttribute = $this->eavConfig->getAttribute('catalog_product', 'test_configurable');
598+
$options = $this->getConfigurableAttributeOptions();
599+
$configurableProductOptions = [
600+
[
601+
"attribute_id" => $this->configurableAttribute->getId(),
602+
"label" => 'color',
603+
"position" => 0,
604+
"values" => [
605+
[
606+
"value_index" => $options[0]['option_id'],
607+
],
608+
[
609+
"value_index" => $options[1]['option_id'],
610+
],
611+
],
612+
],
613+
];
614+
$product = [
615+
"sku" => self::CONFIGURABLE_PRODUCT_SKU,
616+
"name" => self::CONFIGURABLE_PRODUCT_SKU,
617+
"type_id" => "configurable",
618+
"price" => 50,
619+
'attribute_set_id' => 4,
620+
"extension_attributes" => [
621+
"configurable_product_options" => $configurableProductOptions,
622+
"configurable_product_links" => [10, 20],
623+
],
624+
];
625+
626+
return $this->createProduct($product);
627+
}
571628
}

0 commit comments

Comments
 (0)