Skip to content

Commit d008516

Browse files
committed
Merge remote-tracking branch 'origin/MAGETWO-50831' into 2.3-develop-pr22
2 parents 0ad656d + c943561 commit d008516

File tree

7 files changed

+267
-91
lines changed

7 files changed

+267
-91
lines changed

app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,13 @@ public function afterInitialize(
105105
if ($result['bundle_options'] && !$compositeReadonly) {
106106
$product->setBundleOptionsData($result['bundle_options']);
107107
}
108+
108109
$this->processBundleOptionsData($product);
109110
$this->processDynamicOptionsData($product);
111+
} elseif (!$compositeReadonly) {
112+
$extension = $product->getExtensionAttributes();
113+
$extension->setBundleProductOptions([]);
114+
$product->setExtensionAttributes($extension);
110115
}
111116

112117
$affectProductSelections = (bool)$this->request->getPost('affect_bundle_product_selections');

app/code/Magento/Bundle/Model/OptionRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ protected function updateOptionSelection(
234234
*/
235235
private function getProduct($sku)
236236
{
237-
$product = $this->productRepository->get($sku, true);
237+
$product = $this->productRepository->get($sku, true, null, true);
238238
if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) {
239239
throw new InputException(__('This is implemented for bundle products only.'));
240240
}

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

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
namespace Magento\Bundle\Model\Product;
77

8-
use Magento\Bundle\Api\Data\OptionInterface;
98
use Magento\Bundle\Model\Option\SaveAction;
109
use Magento\Catalog\Api\Data\ProductInterface;
1110
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
@@ -58,36 +57,6 @@ public function __construct(
5857
?: ObjectManager::getInstance()->get(MetadataPool::class);
5958
}
6059

61-
/**
62-
* @param ProductInterface $bundle
63-
* @param OptionInterface[] $currentOptions
64-
*
65-
* @return void
66-
*/
67-
private function removeOldOptions(
68-
ProductInterface $bundle,
69-
array $currentOptions
70-
) {
71-
$oldOptions = $this->optionRepository->getList($bundle->getSku());
72-
if ($oldOptions) {
73-
$remainingOptions = [];
74-
$metadata
75-
= $this->metadataPool->getMetadata(ProductInterface::class);
76-
$productId = $bundle->getData($metadata->getLinkField());
77-
78-
foreach ($currentOptions as $option) {
79-
$remainingOptions[] = $option->getOptionId();
80-
}
81-
foreach ($oldOptions as $option) {
82-
if (!in_array($option->getOptionId(), $remainingOptions)) {
83-
$option->setParentId($productId);
84-
$this->removeOptionLinks($bundle->getSku(), $option);
85-
$this->optionRepository->delete($option);
86-
}
87-
}
88-
}
89-
}
90-
9160
/**
9261
* @param object $entity
9362
* @param array $arguments
@@ -98,24 +67,31 @@ private function removeOldOptions(
9867
*/
9968
public function execute($entity, $arguments = [])
10069
{
101-
/** @var \Magento\Bundle\Api\Data\OptionInterface[] $options */
102-
$options = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
70+
/** @var \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions */
71+
$bundleProductOptions = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
10372
//Only processing bundle products.
104-
if ($entity->getTypeId() !== 'bundle' || empty($options)) {
73+
if ($entity->getTypeId() !== Type::TYPE_CODE || empty($bundleProductOptions)) {
10574
return $entity;
10675
}
107-
/** @var ProductInterface $entity */
108-
//Removing old options
76+
77+
$existingBundleProductOptions = $this->optionRepository->getList($entity->getSku());
78+
$existingOptionsIds = !empty($existingBundleProductOptions)
79+
? $this->getOptionIds($existingBundleProductOptions)
80+
: [];
81+
$optionIds = !empty($bundleProductOptions)
82+
? $this->getOptionIds($bundleProductOptions)
83+
: [];
84+
10985
if (!$entity->getCopyFromView()) {
110-
$this->removeOldOptions($entity, $options);
111-
}
112-
//Saving active options.
113-
foreach ($options as $option) {
114-
$this->optionSave->save($entity, $option);
86+
$this->processRemovedOptions($entity->getSku(), $existingOptionsIds, $optionIds);
87+
$newOptionsIds = array_diff($optionIds, $existingOptionsIds);
88+
$this->saveOptions($entity, $bundleProductOptions, $newOptionsIds);
89+
} else {
90+
//save only labels and not selections + product links
91+
$this->saveOptions($entity, $bundleProductOptions);
92+
$entity->setCopyFromView(false);
11593
}
11694

117-
$entity->setCopyFromView(false);
118-
11995
return $entity;
12096
}
12197

@@ -133,4 +109,62 @@ protected function removeOptionLinks($entitySku, $option)
133109
}
134110
}
135111
}
112+
113+
/**
114+
* Perform save for all options entities.
115+
*
116+
* @param object $entity
117+
* @param array $options
118+
* @param array $newOptionsIds
119+
* @return void
120+
*/
121+
private function saveOptions($entity, array $options, array $newOptionsIds = []): void
122+
{
123+
foreach ($options as $option) {
124+
if (in_array($option->getOptionId(), $newOptionsIds, true)) {
125+
$option->setOptionId(null);
126+
}
127+
128+
$this->optionSave->save($entity, $option);
129+
}
130+
}
131+
132+
/**
133+
* Get options ids from array of the options entities.
134+
*
135+
* @param array $options
136+
* @return array
137+
*/
138+
private function getOptionIds(array $options): array
139+
{
140+
$optionIds = [];
141+
142+
if (!empty($options)) {
143+
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
144+
foreach ($options as $option) {
145+
if ($option->getOptionId()) {
146+
$optionIds[] = $option->getOptionId();
147+
}
148+
}
149+
}
150+
151+
return $optionIds;
152+
}
153+
154+
/**
155+
* Removes old options that no longer exists.
156+
*
157+
* @param string $entitySku
158+
* @param array $existingOptionsIds
159+
* @param array $optionIds
160+
* @return void
161+
*/
162+
private function processRemovedOptions(string $entitySku, array $existingOptionsIds, array $optionIds): void
163+
{
164+
foreach (array_diff($existingOptionsIds, $optionIds) as $optionId) {
165+
$option = $this->optionRepository->get($entitySku, $optionId);
166+
$this->removeOptionLinks($entitySku, $option);
167+
$this->optionRepository->delete($option);
168+
}
169+
}
136170
}

app/code/Magento/Bundle/Model/ResourceModel/Option.php

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -81,39 +81,29 @@ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object)
8181
{
8282
parent::_afterSave($object);
8383

84-
$conditions = [
84+
$condition = [
8585
'option_id = ?' => $object->getId(),
8686
'store_id = ? OR store_id = 0' => $object->getStoreId(),
8787
'parent_product_id = ?' => $object->getParentId()
8888
];
8989

9090
$connection = $this->getConnection();
91+
$connection->delete($this->getTable('catalog_product_bundle_option_value'), $condition);
9192

92-
if ($this->isOptionPresent($conditions)) {
93-
$connection->update(
94-
$this->getTable('catalog_product_bundle_option_value'),
95-
[
96-
'title' => $object->getTitle()
97-
],
98-
$conditions
99-
);
100-
} else {
101-
$data = new \Magento\Framework\DataObject();
102-
$data->setOptionId($object->getId())
103-
->setStoreId($object->getStoreId())
104-
->setParentProductId($object->getParentId())
105-
->setTitle($object->getTitle());
93+
$data = new \Magento\Framework\DataObject();
94+
$data->setOptionId($object->getId())
95+
->setStoreId($object->getStoreId())
96+
->setParentProductId($object->getParentId())
97+
->setTitle($object->getTitle());
10698

107-
$connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData());
99+
$connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData());
108100

109-
/**
110-
* also saving default value if this store view scope
111-
*/
112-
if ($object->getStoreId()) {
113-
$data->setStoreId(0);
114-
$data->setTitle($object->getDefaultTitle());
115-
$connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData());
116-
}
101+
/**
102+
* also saving default fallback value
103+
*/
104+
if (0 !== (int)$object->getStoreId()) {
105+
$data->setStoreId(0)->setTitle($object->getDefaultTitle());
106+
$connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData());
117107
}
118108

119109
return $this;
@@ -218,26 +208,4 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
218208

219209
return $this;
220210
}
221-
222-
/**
223-
* Is Bundle option present in the database
224-
*
225-
* @param array $conditions
226-
*
227-
* @return bool
228-
*/
229-
private function isOptionPresent($conditions)
230-
{
231-
$connection = $this->getConnection();
232-
233-
$select = $connection->select()->from($this->getTable('catalog_product_bundle_option_value'));
234-
foreach ($conditions as $condition => $conditionValue) {
235-
$select->where($condition, $conditionValue);
236-
}
237-
$select->limit(1);
238-
239-
$rowSelect = $connection->fetchRow($select);
240-
241-
return (is_array($rowSelect) && !empty($rowSelect));
242-
}
243211
}

app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,27 @@ public function testAfterInitializeIfBundleSelectionsAndCustomOptionsExist()
163163
$this->productMock->expects($this->once())->method('setCanSaveBundleSelections')->with(false);
164164
$this->model->afterInitialize($this->subjectMock, $this->productMock);
165165
}
166+
167+
/**
168+
* @return void
169+
*/
170+
public function testAfterInitializeIfBundleOptionsNotExist(): void
171+
{
172+
$valueMap = [
173+
['bundle_options', null, null],
174+
['affect_bundle_product_selections', null, false],
175+
];
176+
$this->requestMock->expects($this->any())->method('getPost')->will($this->returnValueMap($valueMap));
177+
$extentionAttribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtensionInterface::class)
178+
->disableOriginalConstructor()
179+
->setMethods(['setBundleProductOptions'])
180+
->getMockForAbstractClass();
181+
$extentionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]);
182+
$this->productMock->expects($this->any())->method('getCompositeReadonly')->will($this->returnValue(false));
183+
$this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extentionAttribute);
184+
$this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extentionAttribute);
185+
$this->productMock->expects($this->once())->method('setCanSaveBundleSelections')->with(false);
186+
187+
$this->model->afterInitialize($this->subjectMock, $this->productMock);
188+
}
166189
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Bundle\Model\Product;
9+
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
12+
/**
13+
* Test class for \Magento\Bundle\Model\Product\SaveHandler
14+
* The tested class used indirectly
15+
*
16+
* @magentoDataFixture Magento/Bundle/_files/product.php
17+
* @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php
18+
* @magentoDbIsolation disabled
19+
* @magentoAppIsolation enabled
20+
*/
21+
class SaveHandlerTest extends \PHPUnit\Framework\TestCase
22+
{
23+
/**
24+
* @var \Magento\Framework\ObjectManagerInterface
25+
*/
26+
private $objectManager;
27+
28+
/**
29+
* @var \Magento\Store\Model\Store
30+
*/
31+
private $store;
32+
33+
/**
34+
* @var ProductRepositoryInterface
35+
*/
36+
private $productRepository;
37+
38+
/**
39+
* @inheritdoc
40+
*/
41+
protected function setUp()
42+
{
43+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
44+
$this->store = $this->objectManager->create(\Magento\Store\Model\Store::class);
45+
/** @var ProductRepositoryInterface $productRepository */
46+
$this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
47+
}
48+
49+
/**
50+
* @return void
51+
*/
52+
public function testOptionTitlesOnDifferentStores(): void
53+
{
54+
/**
55+
* @var \Magento\Bundle\Model\Product\OptionList $optionList
56+
*/
57+
$optionList = $this->objectManager->create(\Magento\Bundle\Model\Product\OptionList::class);
58+
59+
$secondStoreId = $this->store->load('fixture_second_store')->getId();
60+
$thirdStoreId = $this->store->load('fixture_third_store')->getId();
61+
62+
$product = $this->productRepository->get('bundle-product', true, $secondStoreId, true);
63+
$options = $optionList->getItems($product);
64+
$title = $options[0]->getTitle();
65+
$newTitle = $title . ' ' . $this->store->load('fixture_second_store')->getCode();
66+
$options[0]->setTitle($newTitle);
67+
$extension = $product->getExtensionAttributes();
68+
$extension->setBundleProductOptions($options);
69+
$product->setExtensionAttributes($extension);
70+
$product->save();
71+
72+
$product = $this->productRepository->get('bundle-product', true, $thirdStoreId, true);
73+
$options = $optionList->getItems($product);
74+
$newTitle = $title . ' ' . $this->store->load('fixture_third_store')->getCode();
75+
$options[0]->setTitle($newTitle);
76+
$extension = $product->getExtensionAttributes();
77+
$extension->setBundleProductOptions($options);
78+
$product->setExtensionAttributes($extension);
79+
$product->save();
80+
81+
$product = $this->productRepository->get('bundle-product', false, $secondStoreId, true);
82+
$options = $optionList->getItems($product);
83+
$this->assertEquals(1, count($options));
84+
$this->assertEquals(
85+
$title . ' ' . $this->store->load('fixture_second_store')->getCode(),
86+
$options[0]->getTitle()
87+
);
88+
}
89+
}

0 commit comments

Comments
 (0)