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 52addeb6d6280..6aca274680999 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -74,6 +74,11 @@ class Helper * @var \Magento\Framework\Stdlib\DateTime\Filter\DateTime */ private $dateTimeFilter; + + /** + * @var \Magento\Catalog\Model\Product\LinkTypeProvider + */ + private $linkTypeProvider; /** * Helper constructor. @@ -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); } /** @@ -247,11 +256,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 a3a08ceb77fd0..8bc397b301df5 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,7 +6,7 @@ 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\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; @@ -19,6 +19,9 @@ use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; use Magento\Catalog\Api\Data\ProductCustomOptionInterface; 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 @@ -100,12 +103,17 @@ class HelperTest extends \PHPUnit_Framework_TestCase protected $customOptionMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Model\Product\Link\Resolver|\PHPUnit_Framework_MockObject_MockObject */ protected $linkResolverMock; /** - * @var ProductLinks + * @var \Magento\Catalog\Model\Product\LinkTypeProvider|\PHPUnit_Framework_MockObject_MockObject + */ + protected $linkTypeProviderMock; + + /** + * @var ProductLinks|\PHPUnit_Framework_MockObject_MockObject */ protected $productLinksMock; @@ -113,6 +121,7 @@ protected function setUp() { $this->objectManager = new ObjectManager($this); $this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class) + ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); $this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class) @@ -150,7 +159,6 @@ protected function setUp() '__sleep', '__wakeup', 'getSku', - 'getProductLinks', 'getWebsiteIds' ]) ->disableOriginalConstructor() @@ -165,10 +173,12 @@ protected function setUp() $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); - $this->productLinksMock->expects($this->any()) ->method('initializeLinks') ->willReturn($this->productMock); + $this->linkTypeProviderMock = $this->getMockBuilder(LinkTypeProvider::class) + ->disableOriginalConstructor() + ->getMock(); $this->helper = $this->objectManager->getObject(Helper::class, [ 'request' => $this->requestMock, @@ -179,6 +189,7 @@ protected function setUp() 'customOptionFactory' => $this->customOptionFactoryMock, 'productLinkFactory' => $this->productLinkFactoryMock, 'productRepository' => $this->productRepositoryMock, + 'linkTypeProvider' => $this->linkTypeProviderMock, ]); $this->linkResolverMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Link\Resolver::class) @@ -191,10 +202,10 @@ protected function setUp() } /** - * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param array $links */ - public function testInitialize() + private function assembleProductMock($links = []) { $this->customOptionMock->expects($this->once()) ->method('setProductSku'); @@ -230,9 +241,6 @@ public function testInitialize() $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'); @@ -255,7 +263,7 @@ public function testInitialize() ->method('getPost') ->with('use_default') ->willReturn($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']) @@ -267,9 +275,6 @@ public function testInitialize() $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'); @@ -284,7 +289,7 @@ public function testInitialize() $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()) @@ -298,10 +303,193 @@ public function testInitialize() $this->productMock->expects($this->once()) ->method('setOptions') ->with([$this->customOptionMock]); + } + + /** + * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize + */ + public function testInitialize() + { + $this->assembleProductMock(); + $this->linkTypeProviderMock->expects($this->once()) + ->method('getItems') + ->willReturn($this->assembleLinkTypes(['related', 'upsell', 'crosssell'])); $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); } + /** + * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize + * @dataProvider initializeWithLinksDataProvider + */ + public function testInitializeWithLinks($links, $linkTypes, $expectedLinks) + { + $this->productLinkFactoryMock->expects($this->any()) + ->method('create') + ->willReturnCallback(function () { + return $this->getMockBuilder(ProductLink::class) + ->setMethods(null) + ->disableOriginalConstructor() + ->getMock(); + }); + + $this->linkTypeProviderMock->expects($this->once()) + ->method('getItems') + ->willReturn($this->assembleLinkTypes($linkTypes)); + + $this->assembleProductRepositoryMock($links); + $this->assembleProductMock($links); + + $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); + + $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 initializeWithLinksDataProvider() + { + return [ + // No links + [ + 'links' => [], + 'linkTypes' => ['related', 'upsell', 'crosssell'], + 'expected_links' => [], + ], + + // Related links + [ + '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 + [ + '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 + [ + '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 + [ + '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'], + ], + ], + ]; + } + /** * Data provider for testMergeProductOptions * @@ -391,4 +579,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)); + } }