diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index d402c81becf1ce21e636b6d36082a47ddcc2f155..d59492c4065e72706f8c1a168e4a33b1c2c9e825 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -199,6 +199,9 @@ class Helper $customOptions = []; foreach ($options as $customOptionData) { if (empty($customOptionData['is_delete'])) { + if (empty($customOptionData['option_id'])) { + $customOptionData['option_id'] = null; + } if (isset($customOptionData['values'])) { $customOptionData['values'] = array_filter($customOptionData['values'], function ($valueData) { return empty($valueData['is_delete']); @@ -206,7 +209,6 @@ class Helper } $customOption = $this->getCustomOptionFactory()->create(['data' => $customOptionData]); $customOption->setProductSku($product->getSku()); - $customOption->setOptionId(null); $customOptions[] = $customOption; } } @@ -255,7 +257,7 @@ class Helper foreach ($linkTypes as $linkType => $readonly) { if (isset($links[$linkType]) && !$readonly) { - foreach ((array) $links[$linkType] as $linkData) { + foreach ((array)$links[$linkType] as $linkData) { if (empty($linkData['id'])) { continue; } @@ -321,9 +323,11 @@ class Helper if (isset($option['values']) && isset($overwriteOptions[$optionId]['values'])) { foreach ($option['values'] as $valueIndex => $value) { - $valueId = $value['option_type_id']; - $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); - $option['values'][$valueIndex] = $value; + if (isset($value['option_type_id'])) { + $valueId = $value['option_type_id']; + $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); + $option['values'][$valueIndex] = $value; + } } } @@ -347,6 +351,9 @@ class Helper foreach ($overwriteOptions[$optionId] as $fieldName => $overwrite) { if ($overwrite && isset($option[$fieldName]) && isset($option['default_' . $fieldName])) { $option[$fieldName] = $option['default_' . $fieldName]; + if ('title' == $fieldName) { + $option['is_delete_store_title'] = 1; + } } } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php index d48bed8e3ccd08718e02c57d764e89c147ebf161..946bebd93c94029907b0ac7ed13dc6c1610b376c 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php @@ -137,10 +137,33 @@ class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryIn if (!$productSku) { throw new CouldNotSaveException(__('ProductSku should be specified')); } + /** @var \Magento\Catalog\Model\Product $product */ $product = $this->productRepository->get($productSku); $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $option->setData('product_id', $product->getData($metadata->getLinkField())); - $option->setOptionId(null); + $option->setData('store_id', $product->getStoreId()); + + if ($option->getOptionId()) { + $options = $product->getOptions(); + if (!$options) { + $options = $this->getProductOptions($product); + } + + $persistedOption = array_filter($options, function ($iOption) use ($option) { + return $option->getOptionId() == $iOption->getOptionId(); + }); + $persistedOption = reset($persistedOption); + + if (!$persistedOption) { + throw new NoSuchEntityException(); + } + $originalValues = $persistedOption->getValues(); + $newValues = $option->getData('values'); + if ($newValues) { + $newValues = $this->markRemovedValues($newValues, $originalValues); + $option->setData('values', $newValues); + } + } $option->save(); return $option; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index 7c32232b6591aeb9a99a9b63f0b40300cd11dfe4..1c8c5b018ddf3b19642242431205756795fce45f 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -20,7 +20,6 @@ class SaveHandler implements ExtensionInterface /** * @param OptionRepository $optionRepository - * @param MetadataPool $metadataPool */ public function __construct( OptionRepository $optionRepository @@ -36,15 +35,28 @@ class SaveHandler implements ExtensionInterface */ public function execute($entity, $arguments = []) { + $options = $entity->getOptions(); + $optionIds = []; + + if ($options) { + $optionIds = array_map(function ($option) { + /** @var \Magento\Catalog\Model\Product\Option $option */ + return $option->getOptionId(); + }, $options); + } + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity */ foreach ($this->optionRepository->getProductOptions($entity) as $option) { - $this->optionRepository->delete($option); + if (!in_array($option->getOptionId(), $optionIds)) { + $this->optionRepository->delete($option); + } } - if ($entity->getOptions()) { - foreach ($entity->getOptions() as $option) { + if ($options) { + foreach ($options as $option) { $this->optionRepository->save($option); } } + return $entity; } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php index ba7103a5eada1acac077ae3ce432e0187e1fbe22..d1a317be7c7e0c2d09cd0b5bbddf6ab3844c96d6 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php @@ -51,6 +51,9 @@ class Select extends DefaultValidator $storeId = $option->getProduct()->getStoreId(); } foreach ($values as $value) { + if (isset($value['is_delete']) && (bool)$value['is_delete']) { + continue; + } $type = isset($value['price_type']) ? $value['price_type'] : null; $price = isset($value['price']) ? $value['price'] : null; $title = isset($value['title']) ? $value['title'] : null; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index 55a14e26333547543a14e22b7dcbc0342442596d..55c7c74e9a8e62bf14233f8c8b9b90309adfee66 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -200,7 +200,7 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu 'store_id', $this->getOption()->getStoreId() ); - $this->unsetData('option_type_id'); + if ($this->getData('is_delete') == '1') { if ($this->getId()) { $this->deleteValues($this->getId()); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php index 7b23e5979c8b841d9468f4c93596d4138c24d692..8dcdf441d7b85f014a5b6aa0398aa21592b81932 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php @@ -247,14 +247,21 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb $titleTableName = $this->getTable('catalog_product_option_title'); foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); - $existInDefaultStore = $this->getColFromOptionTable( - $titleTableName, - (int)$object->getId(), - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ); + $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInCurrentStore : + $this->getColFromOptionTable( + $titleTableName, + (int)$object->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + if ($object->getTitle()) { + $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_title'); if ($existInCurrentStore) { - if ($object->getStoreId() == $storeId) { + if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + $connection->delete($titleTableName, ['option_title_id = ?' => $existInCurrentStore]); + + } elseif ($object->getStoreId() == $storeId) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject(['title' => $object->getTitle()]), $titleTableName @@ -270,8 +277,13 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb } } else { // we should insert record into not default store only of if it does not exist in default store - if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) - || ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInCurrentStore) + if ( + ($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + !$existInCurrentStore && + !$isDeleteStoreTitle + ) ) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 39a67f9722e185894640178329d008de10cb574e..0fc5e0cbc955f704d7662c260c336c5bcbebd214 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -231,6 +231,11 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb ); $optionTypeId = $this->getConnection()->fetchOne($select); $existInCurrentStore = $this->getOptionIdFromOptionTable($titleTable, (int)$object->getId(), (int)$storeId); + + if ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && $object->getData('is_delete_store_title')) { + $object->unsetData('title'); + } + if ($object->getTitle()) { if ($existInCurrentStore) { if ($storeId == $object->getStoreId()) { diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php index 389599f510f08961211d7cc212b757db9620547a..edd654a7393126c61b877d35b36de9077316ec4d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php @@ -6,10 +6,11 @@ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization; use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; -use Magento\Catalog\Api\ProductRepositoryInterface\Proxy as ProductRepository; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\RequestInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; @@ -17,7 +18,6 @@ use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Stdlib\DateTime\Filter\Date as DateFilter; use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; -use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; /** @@ -95,7 +95,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase protected $customOptionFactoryMock; /** - * @var ProductCustomOptionInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Option|\PHPUnit_Framework_MockObject_MockObject */ protected $customOptionMock; @@ -136,8 +136,6 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->getMock(); $this->productMock = $this->getMockBuilder(Product::class) ->setMethods([ - 'setData', - 'addData', 'getId', 'setWebsiteIds', 'isLockedAttribute', @@ -145,7 +143,6 @@ class HelperTest extends \PHPUnit_Framework_TestCase 'getAttributes', 'unlockAttribute', 'getOptionsReadOnly', - 'setOptions', 'setCanSaveCustomOptions', '__sleep', '__wakeup', @@ -159,9 +156,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->customOptionMock = $this->getMockBuilder(ProductCustomOptionInterface::class) + $this->customOptionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() - ->getMockForAbstractClass(); + ->setMethods(null) + ->getMock(); $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); @@ -196,14 +194,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ public function testInitialize() { - $this->customOptionMock->expects($this->once()) - ->method('setProductSku'); - $this->customOptionMock->expects($this->once()) - ->method('setOptionId'); - $optionsData = [ - 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1'], - 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1'], + 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1', 'option_id' => ''], + 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '13'], + 'option3' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '14'] ]; $productData = [ 'stock_data' => ['stock_data'], @@ -277,14 +271,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->method('getAttributes') ->willReturn($attributesArray); - $productData['category_ids'] = []; - $productData['website_ids'] = []; - unset($productData['options']); - - $this->productMock->expects($this->once()) - ->method('addData') - ->with($productData); - $this->productMock->expects($this->once()) + $this->productMock->expects($this->any()) ->method('getSku') ->willReturn('sku'); $this->productMock->expects($this->any()) @@ -293,13 +280,25 @@ class HelperTest extends \PHPUnit_Framework_TestCase $this->customOptionFactoryMock->expects($this->any()) ->method('create') - ->with(['data' => $optionsData['option2']]) - ->willReturn($this->customOptionMock); - $this->productMock->expects($this->once()) - ->method('setOptions') - ->with([$this->customOptionMock]); + ->willReturnMap([ + [ + ['data' => $optionsData['option2']], + (clone $this->customOptionMock)->setData($optionsData['option2']) + ], [ + ['data' => $optionsData['option3']], + (clone $this->customOptionMock)->setData($optionsData['option3']) + ] + ]); $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); + + $productOptions = $this->productMock->getOptions(); + $this->assertTrue(2 == count($productOptions)); + list($option2, $option3) = $productOptions; + $this->assertTrue($option2->getOptionId() == $optionsData['option2']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); + $this->assertTrue($option3->getOptionId() == $optionsData['option3']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); } /** @@ -362,9 +361,9 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val2', + 'title' => 'val2', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', 'values' => [ [ 'option_type_id' => '2', @@ -379,7 +378,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 5 => [ 'key1' => '0', - 'key2' => '1', + 'title' => '1', 'values' => [2 => ['key1' => 1]] ] ], @@ -387,9 +386,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val4', + 'title' => 'val4', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', + 'is_delete_store_title' => 1, 'values' => [ [ 'option_type_id' => '2', @@ -413,8 +413,9 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_type_id' => '2', 'key1' => 'val1', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'title' => 'val2', + 'default_key1' => 'val11', + 'default_title' => 'val22' ] ] ] @@ -423,7 +424,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase 7 => [ 'key1' => '1', 'key2' => '1', - 'values' => [2 => ['key1' => 1, 'key2' => 1]] + 'values' => [2 => ['key1' => 0, 'title' => 1]] ] ], [ @@ -435,9 +436,11 @@ class HelperTest extends \PHPUnit_Framework_TestCase 'values' => [ [ 'option_type_id' => '2', - 'key1' => 'val11', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'key1' => 'val1', + 'title' => 'val22', + 'default_key1' => 'val11', + 'default_title' => 'val22', + 'is_delete_store_title' => 1 ] ] ] diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php index c461e7a616d81c3e9a72a3031d979b9f44817de1..894596342722d392fd79b51a330341c0b14660f5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php @@ -8,6 +8,8 @@ namespace Magento\Catalog\Test\Unit\Model\Product\Option; use \Magento\Catalog\Model\Product\Option\Repository; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -30,10 +32,15 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase protected $optionResourceMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductCustomOptionInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $optionMock; + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionCollectionFactory; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -71,13 +78,10 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase '', false ); - $optionCollectionFactory = $this->getMock( - \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory::class, - ['create'], - [], - '', - false - ); + $this->optionCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); $metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) ->disableOriginalConstructor() ->getMock(); @@ -96,7 +100,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $this->optionRepository, [ 'optionFactory' => $optionFactory, - 'optionCollectionFactory' => $optionCollectionFactory, + 'collectionFactory' => $this->optionCollectionFactory, 'metadataPool' => $metadataPool ] ); @@ -240,4 +244,75 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase } } } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage ProductSku should be specified + */ + public function testSaveCouldNotSaveException() + { + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn(null); + $this->optionRepository->save($this->optionMock); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSaveNoSuchEntityException() + { + $productSku = 'simple_product'; + $optionId = 1; + $productOptionId = 2; + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $productOption = clone $this->optionMock; + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $productOption->expects($this->any())->method('getOptionId')->willReturn($productOptionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([$productOption]); + $this->optionRepository->save($this->optionMock); + } + + public function testSave() + { + $productSku = 'simple_product'; + $optionId = 1; + $originalValue1 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) + ->disableOriginalConstructor() + ->getMock(); + $originalValue2 = clone $originalValue1; + $originalValue3 = clone $originalValue1; + + $originalValue1->expects($this->at(0))->method('getData')->with('option_type_id')->willReturn(10); + $originalValue1->expects($this->once())->method('setData')->with('is_delete', 1); + $originalValue2->expects($this->once())->method('getData')->with('option_type_id')->willReturn(4); + $originalValue3->expects($this->once())->method('getData')->with('option_type_id')->willReturn(5); + + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([]); + $this->optionMock->expects($this->once())->method('getData')->with('values')->willReturn([ + ['option_type_id' => 4], + ['option_type_id' => 5] + ]); + $optionCollection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Option\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $optionCollection->expects($this->once())->method('getProductOptions')->willReturn([$this->optionMock]); + $this->optionCollectionFactory->expects($this->once())->method('create')->willReturn($optionCollection); + $this->optionMock->expects($this->once())->method('getValues')->willReturn([ + $originalValue1, + $originalValue2, + $originalValue3 + ]); + $this->assertEquals($this->optionMock, $this->optionRepository->save($this->optionMock)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..222abadd9fe4b9ff2e6c82a9bd7efc24bf5166de --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\Product\Option; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Option; +use \Magento\Catalog\Model\Product\Option\Repository; +use \Magento\Catalog\Model\Product\Option\SaveHandler; + +class SaveHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var SaveHandler|\PHPUnit_Framework_MockObject_MockObject + */ + protected $model; + + /** + * @var Product|\PHPUnit_Framework_MockObject_MockObject + */ + protected $entity; + + /** + * @var Option|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionMock; + + /** + * @var Repository|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionRepository; + + public function setUp() + { + $this->entity = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionRepository = $this->getMockBuilder(Repository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new SaveHandler($this->optionRepository); + } + + public function testExecute() + { + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn(5); + $this->entity->expects($this->once())->method('getOptions')->willReturn([$this->optionMock]); + + $secondOptionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $secondOptionMock->expects($this->once())->method('getOptionId')->willReturn(6); + + $this->optionRepository + ->expects($this->once()) + ->method('getProductOptions') + ->with($this->entity) + ->willReturn([$this->optionMock, $secondOptionMock]); + + $this->optionRepository->expects($this->once())->method('delete'); + $this->optionRepository->expects($this->once())->method('save')->with($this->optionMock); + + $this->assertEquals($this->entity, $this->model->execute($this->entity)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php index c2b3de0c24cb0736b778efb75d9e722d0fcb47b8..75a2b7c27b4d263d6603438ae9d7c8ab13578e91 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php @@ -87,35 +87,47 @@ class CustomOptionsTest extends AbstractModifierTest $originalData = [ $productId => [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::DATA_SOURCE_DEFAULT => [ 'title' => 'original' ] ] ]; $options = [ - $this->getProductOptionMock(['title' => 'option1']), + $this->getProductOptionMock(['title' => 'option1', 'store_title' => 'Option Store Title']), $this->getProductOptionMock( - ['title' => 'option2'], + ['title' => 'option2', 'store_title' => null], [ - $this->getProductOptionMock(['title' => 'value1']), - $this->getProductOptionMock(['title' => 'value2']) + $this->getProductOptionMock(['title' => 'value1', 'store_title' => 'Option Value Store Title']), + $this->getProductOptionMock(['title' => 'value2', 'store_title' => null]) ] ) ]; $resultData = [ $productId => [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ - 'title' => 'original', - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::FIELD_ENABLE => 1, - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::GRID_OPTIONS_NAME => [ - ['title' => 'option1'], + CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::FIELD_TITLE_NAME => 'original', + CustomOptions::FIELD_ENABLE => 1, + CustomOptions::GRID_OPTIONS_NAME => [ [ - 'title' => 'option2', + CustomOptions::FIELD_TITLE_NAME => 'option1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'option2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true, CustomOptions::GRID_TYPE_SELECT_NAME => [ - ['title' => 'value1'], - ['title' => 'value2'] + [ + CustomOptions::FIELD_TITLE_NAME => 'value1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Value Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'value2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true + ] ] ] ] @@ -154,13 +166,13 @@ class CustomOptionsTest extends AbstractModifierTest */ protected function getProductOptionMock(array $data, array $values = []) { + /** @var ProductOption|\PHPUnit_Framework_MockObject_MockObject $productOptionMock */ $productOptionMock = $this->getMockBuilder(ProductOption::class) ->disableOriginalConstructor() + ->setMethods(['getValues']) ->getMock(); - $productOptionMock->expects($this->any()) - ->method('getData') - ->willReturn($data); + $productOptionMock->setData($data); $productOptionMock->expects($this->any()) ->method('getValues') ->willReturn($values); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 5e6c88a1ba93d43f3ab05236769761aa17fcfb12..780cdbbb00e0a3ac06208e6c70a01708376f071d 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -68,6 +68,7 @@ class CustomOptions extends AbstractModifier const FIELD_ENABLE = 'affect_product_custom_options'; const FIELD_OPTION_ID = 'option_id'; const FIELD_TITLE_NAME = 'title'; + const FIELD_STORE_TITLE_NAME = 'store_title'; const FIELD_TYPE_NAME = 'type'; const FIELD_IS_REQUIRE_NAME = 'is_require'; const FIELD_SORT_ORDER_NAME = 'sort_order'; @@ -79,6 +80,7 @@ class CustomOptions extends AbstractModifier const FIELD_IMAGE_SIZE_X_NAME = 'image_size_x'; const FIELD_IMAGE_SIZE_Y_NAME = 'image_size_y'; const FIELD_IS_DELETE = 'is_delete'; + const FIELD_IS_USE_DEFAULT = 'is_use_default'; /**#@-*/ /**#@+ @@ -112,7 +114,7 @@ class CustomOptions extends AbstractModifier * @var UrlInterface */ protected $urlBuilder; - + /** * @var ArrayManager */ @@ -162,9 +164,14 @@ class CustomOptions extends AbstractModifier /** @var \Magento\Catalog\Model\Product\Option $option */ foreach ($productOptions as $index => $option) { - $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $option->getData()); + $optionData = $option->getData(); + $optionData[static::FIELD_IS_USE_DEFAULT] = !$option->getData(static::FIELD_STORE_TITLE_NAME); + $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $optionData); $values = $option->getValues() ?: []; + foreach ($values as $value) { + $value->setData(static::FIELD_IS_USE_DEFAULT, !$value->getData(static::FIELD_STORE_TITLE_NAME)); + } /** @var \Magento\Catalog\Model\Product\Option $value */ foreach ($values as $value) { $options[$index][static::GRID_TYPE_SELECT_NAME][] = $this->formatPriceByPath( @@ -529,7 +536,8 @@ class CustomOptions extends AbstractModifier 'component' => 'Magento_Catalog/component/static-type-input', 'valueUpdate' => 'input', 'imports' => [ - 'optionId' => '${ $.provider }:${ $.parentScope }.option_id' + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ] ], ], @@ -604,6 +612,7 @@ class CustomOptions extends AbstractModifier 'imports' => [ 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', 'optionTypeId' => '${ $.provider }:${ $.parentScope }.option_type_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ], 'service' => [ 'template' => 'Magento_Catalog/form/element/helper/custom-option-type-service', @@ -1109,7 +1118,7 @@ class CustomOptions extends AbstractModifier } return $this->localeCurrency; } - + /** * Format price according to the locale of the currency * diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html index a3f7970e7420ad52ceb96ed23943f8166d1e2c4a..442d96b5ae4bac2c2954f377bba818448d1284b9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html @@ -12,6 +12,6 @@ name: 'options_use_default[' + $data.optionId + '][values][' + $data.optionTypeId + '][' + $data.index + ']', 'data-form-part': $data.ns " - ko-checked="isUseDefault"> + ko-checked="$data.isUseDefault"> <label translate="'Use Default Value'" attr="for: $data.uid + '_default'" class="admin__field-label"></label> </div> diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php index aadf1e74a883ce823d574178cba29ca617f5abf0..3125cc2a690ba9a0788079f37be339e5e6b5c28f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php @@ -160,14 +160,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -183,14 +183,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php index 18c70968039f8f6df0056daa98da1dbef068e055..d933cd00656d3c8ffde5bd37526aa31aaacba681 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php @@ -98,14 +98,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -121,14 +121,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php index a3bf1686b5fdb2181f28939a1bc10d36170532ea..ffee568b0622fa8ef3f5af185e24805367ee9203 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php @@ -18,16 +18,30 @@ $productModel = $objectManager->create(\Magento\Catalog\Model\Product::class); $customOptions = [ [ - 'id' => 'test_option_code_1', - 'option_id' => '0', + 'option_id' => null, 'sort_order' => '0', 'title' => 'Option 1', 'type' => 'drop_down', 'is_require' => 1, 'values' => [ - 1 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 1"', 'price' => '1.00', 'price_type' => 'fixed'], - 2 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 2"', 'price' => '2.00', 'price_type' => 'fixed'], - 3 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 3"', 'price' => '3.00', 'price_type' => 'fixed'] + 1 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 1"', + 'price' => '1.00', + 'price_type' => 'fixed' + ], + 2 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 2"', + 'price' => '2.00', + 'price_type' => 'fixed' + ], + 3 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 3"', + 'price' => '3.00', + 'price_type' => 'fixed' + ] ] ], [ diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php index 053e33da09fe9f812aa7a1d4164cf274467a3a6c..6322a60cef8a5fda1b50e220b10de7c5385956d4 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php @@ -90,8 +90,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase $options = $this->prepareOptions( [ [ - 'id' => 1, - 'option_id' => 0, + 'option_id' => null, 'previous_group' => 'text', 'title' => 'Test Field', 'type' => 'field', diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php index b35d87d48063a39011a92e5184090402ea64b4c0..ff08056e4b18def3f520d2a5d9f2090b168d5ec8 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php @@ -104,14 +104,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -127,14 +127,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed',