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 2744a22b3b2b1..08d0a114cc979 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -75,6 +75,11 @@ class Helper */ private $dateTimeFilter; + /** + * @var \Magento\Catalog\Model\Product\LinkTypeProvider + */ + private $linkTypeProvider; + /** * Helper constructor. * @param \Magento\Framework\App\RequestInterface $request @@ -83,6 +88,7 @@ class Helper * @param ProductLinks $productLinks * @param \Magento\Backend\Helper\Js $jsHelper * @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter + * @param \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider */ public function __construct( \Magento\Framework\App\RequestInterface $request, @@ -90,7 +96,8 @@ public function __construct( StockDataFilter $stockFilter, \Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks $productLinks, \Magento\Backend\Helper\Js $jsHelper, - \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter + \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter, + \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider = null ) { $this->request = $request; $this->storeManager = $storeManager; @@ -98,6 +105,8 @@ public function __construct( $this->productLinks = $productLinks; $this->jsHelper = $jsHelper; $this->dateFilter = $dateFilter; + $this->linkTypeProvider = $linkTypeProvider ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\Model\Product\LinkTypeProvider::class); } /** @@ -244,11 +253,17 @@ protected function setProductLinks(\Magento\Catalog\Model\Product $product) $product = $this->productLinks->initializeLinks($product, $links); $productLinks = $product->getProductLinks(); - $linkTypes = [ - 'related' => $product->getRelatedReadonly(), - 'upsell' => $product->getUpsellReadonly(), - 'crosssell' => $product->getCrosssellReadonly() - ]; + $linkTypes = []; + + /** @var \Magento\Catalog\Api\Data\ProductLinkTypeInterface $linkTypeObject */ + foreach ($this->linkTypeProvider->getItems() as $linkTypeObject) { + $linkTypes[$linkTypeObject->getName()] = $product->getData($linkTypeObject->getName() . '_readonly'); + } + + // skip linkTypes that were already processed on initializeLinks plugins + foreach ($productLinks as $productLink) { + unset($linkTypes[$productLink->getLinkType()]); + } foreach ($linkTypes as $linkType => $readonly) { if (isset($links[$linkType]) && !$readonly) { 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 eed7d0b8e0530..1250470814ec9 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 @@ -5,6 +5,7 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization; +use Magento\Catalog\Api\ProductRepositoryInterface as ProductRepository; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; use Magento\Catalog\Model\Product; @@ -15,6 +16,9 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; +use Magento\Catalog\Model\Product\LinkTypeProvider; +use Magento\Catalog\Api\Data\ProductLinkTypeInterface; +use Magento\Catalog\Model\ProductLink\Link as ProductLink; /** * Class HelperTest @@ -35,6 +39,11 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ protected $helper; + /** + * @var ProductLinkInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productLinkFactoryMock; + /** * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -55,6 +64,11 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ protected $productMock; + /** + * @var ProductRepository|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productRepositoryMock; + /** * @var ProductCustomOptionInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ @@ -65,6 +79,11 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ protected $linkResolverMock; + /** + * @var \Magento\Catalog\Model\Product\LinkTypeProvider|\PHPUnit_Framework_MockObject_MockObject + */ + protected $linkTypeProviderMock; + /** * @var ProductLinks|\PHPUnit_Framework_MockObject_MockObject */ @@ -73,6 +92,13 @@ class HelperTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->objectManager = new ObjectManager($this); + $this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class) + ->disableOriginalConstructor() + ->getMock(); $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->setMethods(['getPost']) ->getMockForAbstractClass(); @@ -91,7 +117,6 @@ protected function setUp() 'unlockAttribute', 'getOptionsReadOnly', 'getSku', - 'getProductLinks', ] ) ->disableOriginalConstructor() @@ -103,6 +128,9 @@ protected function setUp() $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); + $this->linkTypeProviderMock = $this->getMockBuilder(LinkTypeProvider::class) + ->disableOriginalConstructor() + ->getMock(); $this->productLinksMock->expects($this->any()) ->method('initializeLinks') ->willReturn($this->productMock); @@ -113,6 +141,9 @@ protected function setUp() 'stockFilter' => $this->stockFilterMock, 'productLinks' => $this->productLinksMock, 'customOptionFactory' => $this->customOptionFactoryMock, + 'productLinkFactory' => $this->productLinkFactoryMock, + 'productRepository' => $this->productRepositoryMock, + 'linkTypeProvider' => $this->linkTypeProviderMock, ]); $this->linkResolverMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Link\Resolver::class) @@ -126,14 +157,22 @@ protected function setUp() /** * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @param bool $isSingleStore * @param array $websiteIds * @param array $expWebsiteIds + * @param array $links + * @param array $linkTypes + * @param array $expectedLinks * * @dataProvider initializeDataProvider */ - public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) + public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds, $links, $linkTypes, $expectedLinks) { + $this->linkTypeProviderMock->expects($this->once()) + ->method('getItems') + ->willReturn($this->assembleLinkTypes($linkTypes)); + $optionsData = [ 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1', 'option_id' => ''], 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '13'], @@ -161,7 +200,6 @@ public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) $attributeNonDate->expects($this->any())->method('getBackend')->willReturn($attributeNonDateBackEnd); $attributeDate->expects($this->any())->method('getBackend')->willReturn($attributeDateBackEnd); - $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); $attributeNonDateBackEnd->expects($this->any())->method('getType')->willReturn('non-datetime'); $attributeDateBackEnd->expects($this->any())->method('getType')->willReturn('datetime'); @@ -173,12 +211,11 @@ public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) ['use_default', null, $useDefaults] ] ); - $this->linkResolverMock->expects($this->once())->method('getLinks')->willReturn([]); + $this->linkResolverMock->expects($this->once())->method('getLinks')->willReturn($links); $this->stockFilterMock->expects($this->once())->method('filter')->with(['stock_data']) ->willReturn(['stock_data']); $this->productMock->expects($this->once())->method('isLockedAttribute')->with('media')->willReturn(true); $this->productMock->expects($this->once())->method('unlockAttribute')->with('media'); - $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); $this->productMock->expects($this->once())->method('lockAttribute')->with('media'); $this->productMock->expects($this->once())->method('getAttributes') ->willReturn([$attributeNonDate, $attributeDate]); @@ -209,6 +246,17 @@ public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) $this->storeManagerMock->expects($this->once())->method('isSingleStoreMode')->willReturn($isSingleStore); $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($website); + $this->assembleProductRepositoryMock($links); + + $this->productLinkFactoryMock->expects($this->any()) + ->method('create') + ->willReturnCallback(function () { + return $this->getMockBuilder(ProductLink::class) + ->setMethods(null) + ->disableOriginalConstructor() + ->getMock(); + }); + $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); $this->assertEquals($expWebsiteIds, $this->productMock->getDataByKey('website_ids')); @@ -219,10 +267,24 @@ public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) $this->assertTrue('sku' == $option2->getData('product_sku')); $this->assertTrue($option3->getOptionId() == $optionsData['option3']['option_id']); $this->assertTrue('sku' == $option2->getData('product_sku')); + + $productLinks = $this->productMock->getProductLinks(); + + $this->assertCount(count($expectedLinks), $productLinks); + $resultLinks = []; + + foreach ($productLinks as $link) { + $this->assertInstanceOf(ProductLink::class, $link); + $this->assertEquals('sku', $link->getSku()); + $resultLinks[] = ['type' => $link->getLinkType(), 'linked_product_sku' => $link->getLinkedProductSku()]; + } + + $this->assertEquals($expectedLinks, $resultLinks); } /** * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function initializeDataProvider() { @@ -230,22 +292,165 @@ public function initializeDataProvider() [ 'single_store' => false, 'website_ids' => ['1' => 1, '2' => 1], - 'expected_website_ids' => ['1' => 1, '2' => 1] + 'expected_website_ids' => ['1' => 1, '2' => 1], + 'links' => [], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [], ], [ 'single_store' => false, 'website_ids' => ['1' => 1, '2' => 0], - 'expected_website_ids' => ['1' => 1] + 'expected_website_ids' => ['1' => 1], + 'links' => [], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [], ], [ 'single_store' => false, 'website_ids' => ['1' => 0, '2' => 0], - 'expected_website_ids' => [] + 'expected_website_ids' => [], + 'links' => [], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [], ], [ 'single_store' => true, 'website_ids' => [], - 'expected_website_ids' => ['1' => 1] + 'expected_website_ids' => ['1' => 1], + 'links' => [], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [], + ], + + // Related links + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1], + 'links' => [ + 'related' => [ + 0 => [ + 'id' => 1, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Test', + 'price' => 1.00, + 'position' => 1, + 'record_id' => 1, + ] + ] + ], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [ + ['type' => 'related', 'linked_product_sku' => 'Test'], + ], + ], + + // Custom link + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1], + 'links' => [ + 'customlink' => [ + 0 => [ + 'id' => 4, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test Custom', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Testcustom', + 'price' => 1.00, + 'position' => 1, + 'record_id' => 1, + ], + ], + ], + 'linkTypes' => ['related', 'upsell', 'crosssell', 'customlink'], + 'expected_links' => [ + ['type' => 'customlink', 'linked_product_sku' => 'Testcustom'], + ], + ], + + // Both links + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1], + 'links' => [ + 'related' => [ + 0 => [ + 'id' => 1, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Test', + 'price' => 1.00, + 'position' => 1, + 'record_id' => 1, + ], + ], + 'customlink' => [ + 0 => [ + 'id' => 4, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test Custom', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Testcustom', + 'price' => 2.00, + 'position' => 2, + 'record_id' => 1, + ], + ], + ], + 'linkTypes' => ['related', 'upsell', 'crosssell', 'customlink'], + 'expected_links' => [ + ['type' => 'related', 'linked_product_sku' => 'Test'], + ['type' => 'customlink', 'linked_product_sku' => 'Testcustom'], + ], + ], + + // Undefined link type + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1], + 'links' => [ + 'related' => [ + 0 => [ + 'id' => 1, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Test', + 'price' => 1.00, + 'position' => 1, + 'record_id' => 1, + ], + ], + 'customlink' => [ + 0 => [ + 'id' => 4, + 'thumbnail' => 'http://magento.dev/media/no-image.jpg', + 'name' => 'Test Custom', + 'status' => 'Enabled', + 'attribute_set' => 'Default', + 'sku' => 'Testcustom', + 'price' => 2.00, + 'position' => 2, + 'record_id' => 1, + ], + ], + ], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [ + ['type' => 'related', 'linked_product_sku' => 'Test'], + ], ], ]; } @@ -409,4 +614,55 @@ public function testMergeProductOptions($productOptions, $defaultOptions, $expec $result = $this->helper->mergeProductOptions($productOptions, $defaultOptions); $this->assertEquals($expectedResults, $result); } + + /** + * @param array $types + * @return array + */ + private function assembleLinkTypes($types) + { + $linkTypes = []; + $linkTypeCode = 1; + + foreach ($types as $typeName) { + $linkType = $this->getMock(ProductLinkTypeInterface::class); + $linkType->method('getCode')->willReturn($linkTypeCode++); + $linkType->method('getName')->willReturn($typeName); + + $linkTypes[] = $linkType; + } + + return $linkTypes; + } + + /** + * @param array $links + */ + private function assembleProductRepositoryMock($links) + { + $repositoryReturnMap = []; + + foreach ($links as $linkType) { + foreach ($linkType as $link) { + $mockLinkedProduct = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + + $mockLinkedProduct->expects($this->any()) + ->method('getId') + ->willReturn($link['id']); + + $mockLinkedProduct->expects($this->any()) + ->method('getSku') + ->willReturn($link['sku']); + + // Even optional arguments need to be provided for returnMapValue + $repositoryReturnMap[] = [$link['id'], false, null, false, $mockLinkedProduct]; + } + } + + $this->productRepositoryMock->expects($this->any()) + ->method('getById') + ->will($this->returnValueMap($repositoryReturnMap)); + } }