Skip to content

Commit 28e20d9

Browse files
author
Michal Derlatka
committed
#26110: Bundle products options input validation
1 parent c6f5b12 commit 28e20d9

File tree

4 files changed

+358
-7
lines changed

4 files changed

+358
-7
lines changed

app/code/Magento/Bundle/Model/Product/Type.php

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
namespace Magento\Bundle\Model\Product;
88

9+
use Magento\Bundle\Model\Option;
10+
use Magento\Bundle\Model\ResourceModel\Option\Collection;
911
use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections;
1012
use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier;
1113
use Magento\Catalog\Api\ProductRepositoryInterface;
@@ -459,12 +461,12 @@ public function getOptionsIds($product)
459461
* Retrieve bundle option collection
460462
*
461463
* @param \Magento\Catalog\Model\Product $product
462-
* @return \Magento\Bundle\Model\ResourceModel\Option\Collection
464+
* @return Collection
463465
*/
464466
public function getOptionsCollection($product)
465467
{
466468
if (!$product->hasData($this->_keyOptionsCollection)) {
467-
/** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection */
469+
/** @var Collection $optionsCollection */
468470
$optionsCollection = $this->_bundleOption->create()
469471
->getResourceCollection();
470472
$optionsCollection->setProductIdFilter($product->getEntityId());
@@ -682,6 +684,11 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p
682684
$options
683685
);
684686

687+
$this->validateRadioAndSelectOptions(
688+
$optionsCollection,
689+
$options
690+
);
691+
685692
$selectionIds = array_values($this->arrayUtility->flatten($options));
686693
// If product has not been configured yet then $selections array should be empty
687694
if (!empty($selectionIds)) {
@@ -887,7 +894,7 @@ public function getSelectionsByIds($selectionIds, $product)
887894
*
888895
* @param array $optionIds
889896
* @param \Magento\Catalog\Model\Product $product
890-
* @return \Magento\Bundle\Model\ResourceModel\Option\Collection
897+
* @return Collection
891898
*/
892899
public function getOptionsByIds($optionIds, $product)
893900
{
@@ -1254,7 +1261,7 @@ protected function getBeforeQty($product, $selection)
12541261
*
12551262
* @param \Magento\Catalog\Model\Product $product
12561263
* @param bool $isStrictProcessMode
1257-
* @param \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection
1264+
* @param Collection $optionsCollection
12581265
* @param int[] $options
12591266
* @return void
12601267
* @throws \Magento\Framework\Exception\LocalizedException
@@ -1272,12 +1279,59 @@ protected function checkIsAllRequiredOptions($product, $isStrictProcessMode, $op
12721279
}
12731280
}
12741281

1282+
/**
1283+
* Validate Options for Radio and Select input types
1284+
*
1285+
* @param Collection $optionsCollection
1286+
* @param int[] $options
1287+
* @return void
1288+
* @throws \Magento\Framework\Exception\LocalizedException
1289+
*/
1290+
private function validateRadioAndSelectOptions($optionsCollection, $options): void
1291+
{
1292+
$errorTypes = [];
1293+
1294+
if (is_array($optionsCollection->getItems())) {
1295+
foreach ($optionsCollection->getItems() as $option) {
1296+
if ($this->isSelectedOptionValid($option, $options)) {
1297+
$errorTypes[] = $option->getType();
1298+
}
1299+
}
1300+
}
1301+
1302+
if (!empty($errorTypes)) {
1303+
throw new \Magento\Framework\Exception\LocalizedException(
1304+
__(
1305+
'Option type (%types) should have only one element.',
1306+
['types' => implode(", ", $errorTypes)]
1307+
)
1308+
);
1309+
}
1310+
}
1311+
1312+
/**
1313+
* Check if selected option is valid
1314+
*
1315+
* @param Option $option
1316+
* @param array $options
1317+
* @return bool
1318+
*/
1319+
private function isSelectedOptionValid($option, $options): bool
1320+
{
1321+
return (
1322+
($option->getType() == 'radio' || $option->getType() == 'select') &&
1323+
isset($options[$option->getOptionId()]) &&
1324+
is_array($options[$option->getOptionId()]) &&
1325+
count($options[$option->getOptionId()]) > 1
1326+
);
1327+
}
1328+
12751329
/**
12761330
* Check if selection is salable
12771331
*
12781332
* @param \Magento\Bundle\Model\ResourceModel\Selection\Collection $selections
12791333
* @param bool $skipSaleableCheck
1280-
* @param \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection
1334+
* @param Collection $optionsCollection
12811335
* @param int[] $options
12821336
* @return void
12831337
* @throws \Magento\Framework\Exception\LocalizedException

dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/AddBundleProductToCartTest.php

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function testAddBundleProductToCart()
8080
$maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
8181

8282
$query = <<<QUERY
83-
mutation {
83+
mutation {
8484
addBundleProductsToCart(input:{
8585
cart_id:"{$maskedQuoteId}"
8686
cart_items:[
@@ -223,7 +223,7 @@ public function testAddBundleToCartWithoutOptions()
223223
$maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
224224

225225
$query = <<<QUERY
226-
mutation {
226+
mutation {
227227
addBundleProductsToCart(input:{
228228
cart_id:"{$maskedQuoteId}"
229229
cart_items:[
@@ -268,6 +268,106 @@ public function testAddBundleToCartWithoutOptions()
268268
}
269269
}
270270
}
271+
QUERY;
272+
273+
$this->graphQlMutation($query);
274+
}
275+
276+
/**
277+
* @magentoApiDataFixture Magento/Bundle/_files/product_with_multiple_options_radio_select.php
278+
* @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
279+
*/
280+
public function testAddBundleToCartWithRadioAndSelectErr()
281+
{
282+
$this->expectException(\Exception::class);
283+
$this->expectExceptionMessage('Option type (select, radio) should have only one element.');
284+
285+
$sku = 'bundle-product';
286+
287+
$this->quoteResource->load(
288+
$this->quote,
289+
'test_order_1',
290+
'reserved_order_id'
291+
);
292+
293+
$product = $this->productRepository->get($sku);
294+
295+
/** @var $typeInstance \Magento\Bundle\Model\Product\Type */
296+
$typeInstance = $product->getTypeInstance();
297+
$typeInstance->setStoreFilter($product->getStoreId(), $product);
298+
/** @var $option \Magento\Bundle\Model\Option */
299+
$options = $typeInstance->getOptionsCollection($product);
300+
301+
$selectionIds = [];
302+
foreach ($options as $option) {
303+
$type = $option->getType();
304+
305+
/** @var \Magento\Catalog\Model\Product $selection */
306+
$selections = $typeInstance->getSelectionsCollection([$option->getId()], $product);
307+
$optionIds[$type] = $option->getId();
308+
309+
foreach ($selections->getItems() as $selection) {
310+
$selectionIds[$type][] = $selection->getSelectionId();
311+
}
312+
}
313+
314+
$maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
315+
316+
$query = <<<QUERY
317+
mutation {
318+
addBundleProductsToCart(input:{
319+
cart_id:"{$maskedQuoteId}"
320+
cart_items:[
321+
{
322+
data:{
323+
sku:"{$sku}"
324+
quantity:1
325+
}
326+
bundle_options:[
327+
{
328+
id:{$optionIds['select']}
329+
quantity:1
330+
value:[
331+
"{$selectionIds['select'][0]}"
332+
"{$selectionIds['select'][1]}"
333+
]
334+
},
335+
{
336+
id:{$optionIds['radio']}
337+
quantity:1
338+
value:[
339+
"{$selectionIds['radio'][0]}"
340+
"{$selectionIds['radio'][1]}"
341+
]
342+
}
343+
]
344+
}
345+
]
346+
}) {
347+
cart {
348+
items {
349+
id
350+
quantity
351+
product {
352+
sku
353+
}
354+
... on BundleCartItem {
355+
bundle_options {
356+
id
357+
label
358+
type
359+
values {
360+
id
361+
label
362+
price
363+
quantity
364+
}
365+
}
366+
}
367+
}
368+
}
369+
}
370+
}
271371
QUERY;
272372

273373
$this->graphQlMutation($query);

0 commit comments

Comments
 (0)