Skip to content

Commit 9bdfce4

Browse files
authored
Merge pull request #2434 from magento-tango/MAGETWO-84209-2
MAGETWO-84209: Impossible specify Bundle option title on store view level
2 parents 199272b + bcec239 commit 9bdfce4

File tree

7 files changed

+190
-76
lines changed

7 files changed

+190
-76
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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,11 @@ protected function updateOptionSelection(
277277
* @param string $sku
278278
* @return \Magento\Catalog\Api\Data\ProductInterface
279279
* @throws \Magento\Framework\Exception\InputException
280+
* @throws \Magento\Framework\Exception\NoSuchEntityException
280281
*/
281282
private function getProduct($sku)
282283
{
283-
$product = $this->productRepository->get($sku, true);
284+
$product = $this->productRepository->get($sku, true, null, true);
284285
if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) {
285286
throw new InputException(__('Only implemented for bundle product'));
286287
}

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

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

8-
use Magento\Catalog\Api\Data\ProductInterface;
98
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
109
use Magento\Bundle\Api\ProductLinkManagementInterface;
1110
use Magento\Framework\App\ObjectManager;
@@ -53,50 +52,50 @@ public function __construct(
5352
* @param object $entity
5453
* @param array $arguments
5554
* @return \Magento\Catalog\Api\Data\ProductInterface|object
55+
* @throws \Magento\Framework\Exception\NoSuchEntityException
56+
* @throws \Magento\Framework\Exception\InputException
57+
* @throws \Magento\Framework\Exception\CouldNotSaveException
5658
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5759
*/
5860
public function execute($entity, $arguments = [])
5961
{
6062
/** @var \Magento\Bundle\Api\Data\OptionInterface[] $options */
61-
$options = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
63+
$bundleProductOptions = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
6264

63-
if ($entity->getTypeId() !== 'bundle' || empty($options)) {
65+
if ($entity->getTypeId() !== Type::TYPE_CODE || empty($bundleProductOptions)) {
6466
return $entity;
6567
}
6668

67-
if (!$entity->getCopyFromView()) {
68-
$updatedOptions = [];
69-
$oldOptions = $this->optionRepository->getList($entity->getSku());
70-
71-
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
69+
$existingBundleProductOptions = $this->optionRepository->getList($entity->getSku());
7270

73-
$productId = $entity->getData($metadata->getLinkField());
71+
$existingOptionsIds = !empty($existingBundleProductOptions)
72+
? $this->getOptionIds($existingBundleProductOptions)
73+
: [];
74+
$optionIds = !empty($bundleProductOptions)
75+
? $this->getOptionIds($bundleProductOptions)
76+
: [];
7477

75-
foreach ($options as $option) {
76-
$updatedOptions[$option->getOptionId()][$productId] = (bool)$option->getOptionId();
77-
}
78-
79-
foreach ($oldOptions as $option) {
80-
if (!isset($updatedOptions[$option->getOptionId()][$productId])) {
81-
$option->setParentId($productId);
82-
$this->removeOptionLinks($entity->getSku(), $option);
83-
$this->optionRepository->delete($option);
84-
}
85-
}
86-
}
78+
$options = $bundleProductOptions ?: [];
8779

88-
foreach ($options as $option) {
89-
$this->optionRepository->save($entity, $option);
80+
if (!$entity->getCopyFromView()) {
81+
$this->processRemovedOptions($entity->getSku(), $existingOptionsIds, $optionIds);
82+
83+
$newOptionsIds = array_diff($optionIds, $existingOptionsIds);
84+
$this->saveOptions($entity, $options, $newOptionsIds);
85+
} else {
86+
//save only labels and not selections + product links
87+
$this->saveOptions($entity, $options);
88+
$entity->setCopyFromView(false);
9089
}
9190

92-
$entity->setCopyFromView(false);
93-
9491
return $entity;
9592
}
9693

9794
/**
9895
* @param string $entitySku
9996
* @param \Magento\Bundle\Api\Data\OptionInterface $option
97+
* @throws \Magento\Framework\Exception\NoSuchEntityException
98+
* @throws \Magento\Framework\Exception\InputException
10099
* @return void
101100
*/
102101
protected function removeOptionLinks($entitySku, $option)
@@ -108,4 +107,67 @@ protected function removeOptionLinks($entitySku, $option)
108107
}
109108
}
110109
}
110+
111+
/**
112+
* Perform save for all options entities
113+
*
114+
* @param object $entity
115+
* @param array $options
116+
* @param array $newOptionsIds
117+
* @throws \Magento\Framework\Exception\CouldNotSaveException
118+
* @throws \Magento\Framework\Exception\InputException
119+
* @return void
120+
*/
121+
private function saveOptions($entity, array $options, array $newOptionsIds = [])
122+
{
123+
foreach ($options as $option) {
124+
if (in_array($option->getOptionId(), $newOptionsIds, true)) {
125+
$option->setOptionId(null);
126+
}
127+
$this->optionRepository->save($entity, $option);
128+
}
129+
}
130+
131+
/**
132+
* Get options ids from array of the options entities
133+
*
134+
* @param array $options
135+
* @return array
136+
*/
137+
private function getOptionIds(array $options)
138+
{
139+
$optionIds = [];
140+
141+
if (empty($options)) {
142+
return $optionIds;
143+
}
144+
145+
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
146+
foreach ($options as $option) {
147+
if ($option->getOptionId()) {
148+
$optionIds[] = $option->getOptionId();
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+
* @throws \Magento\Framework\Exception\NoSuchEntityException
161+
* @throws \Magento\Framework\Exception\InputException
162+
* @throws \Magento\Framework\Exception\CouldNotSaveException
163+
* @return void
164+
*/
165+
private function processRemovedOptions($entitySku, array $existingOptionsIds, array $optionIds)
166+
{
167+
foreach (array_diff($existingOptionsIds, $optionIds) as $optionId) {
168+
$option = $this->optionRepository->get($entitySku, $optionId);
169+
$this->removeOptionLinks($entitySku, $option);
170+
$this->optionRepository->delete($option);
171+
}
172+
}
111173
}

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

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -81,39 +81,28 @@ 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
];
89-
9089
$connection = $this->getConnection();
90+
$connection->delete($this->getTable('catalog_product_bundle_option_value'), $condition);
9191

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());
92+
$data = new \Magento\Framework\DataObject();
93+
$data->setOptionId($object->getId())
94+
->setStoreId($object->getStoreId())
95+
->setParentProductId($object->getParentId())
96+
->setTitle($object->getTitle());
10697

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

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-
}
100+
/**
101+
* also saving default fallback value
102+
*/
103+
if (0 !== (int)$object->getStoreId()) {
104+
$data->setStoreId(0)->setTitle($object->getDefaultTitle());
105+
$connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData());
117106
}
118107

119108
return $this;
@@ -218,26 +207,4 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
218207

219208
return $this;
220209
}
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-
}
243210
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Model\Product;
8+
9+
/**
10+
* Test class for \Magento\Bundle\Model\Product\SaveHandler
11+
* The tested class used indirectly
12+
*
13+
* @magentoDataFixture Magento/Bundle/_files/product.php
14+
* @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php
15+
* @magentoDbIsolation disabled
16+
* @magentoAppIsolation enabled
17+
*/
18+
class SaveHandlerTest extends \PHPUnit\Framework\TestCase
19+
{
20+
/**
21+
* @var \Magento\Framework\ObjectManagerInterface
22+
*/
23+
private $objectManager;
24+
25+
/**
26+
* @var \Magento\Store\Model\Store
27+
*/
28+
private $store;
29+
30+
/**
31+
* @var \Magento\Catalog\Api\ProductRepositoryInterface
32+
*/
33+
private $productRepository;
34+
35+
protected function setUp()
36+
{
37+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
38+
$this->store = $this->objectManager->create(\Magento\Store\Model\Store::class);
39+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
40+
$this->productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
41+
}
42+
43+
public function testOptionTitlesOnDifferentStores()
44+
{
45+
/**
46+
* @var \Magento\Bundle\Model\Product\OptionList $optionList
47+
*/
48+
$optionList = $this->objectManager->create(\Magento\Bundle\Model\Product\OptionList::class);
49+
50+
$secondStoreId = $this->store->load('fixture_second_store')->getId();
51+
$thirdStoreId = $this->store->load('fixture_third_store')->getId();
52+
53+
$product = $this->productRepository->get('bundle-product', true, $secondStoreId, true);
54+
$options = $optionList->getItems($product);
55+
$title = $options[0]->getTitle();
56+
$newTitle = $title . ' ' . $this->store->load('fixture_second_store')->getCode();
57+
$options[0]->setTitle($newTitle);
58+
$extension = $product->getExtensionAttributes();
59+
$extension->setBundleProductOptions($options);
60+
$product->setExtensionAttributes($extension);
61+
$product->save();
62+
63+
$product = $this->productRepository->get('bundle-product', true, $thirdStoreId, true);
64+
$options = $optionList->getItems($product);
65+
$newTitle = $title . ' ' . $this->store->load('fixture_third_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', false, $secondStoreId, true);
73+
$options = $optionList->getItems($product);
74+
$this->assertEquals(1, count($options));
75+
$this->assertEquals(
76+
$title . ' ' . $this->store->load('fixture_second_store')->getCode(),
77+
$options[0]->getTitle()
78+
);
79+
}
80+
}

dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ public function testPrepareData()
6464
*/
6565
public function testPrepareDataWithDifferentStoreValues()
6666
{
67-
$this->markTestSkipped('Test is blocked by MAGETWO-84209.');
6867
$storeCode = 'default';
6968
$expectedNames = [
7069
'name' => 'Bundle Product Items',

dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ function (Product $product) {
7373
array_values($resultCollection->getItems())
7474
);
7575

76-
asort($expectedSkuList);
77-
asort($resultSkuList);
76+
sort($expectedSkuList);
77+
sort($resultSkuList);
7878

7979
$this->assertEquals($expectedSkuList, $resultSkuList, sprintf('%s failed', $variationName));
8080
}

0 commit comments

Comments
 (0)