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 d6c3037d5bf62..3efe939c51901 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -20,8 +20,10 @@ use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; use Magento\Catalog\Model\Product\Link\Resolver as LinkResolver; use Magento\Catalog\Model\Product\LinkTypeProvider; +use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\RequestInterface; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Locale\FormatInterface; use Magento\Framework\Stdlib\DateTime\Filter\Date; use Magento\Store\Model\StoreManagerInterface; @@ -278,6 +280,7 @@ public function initialize(Product $product) * @param Product $product * @return Product * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws NoSuchEntityException * @since 101.0.0 */ protected function setProductLinks(Product $product) @@ -301,20 +304,18 @@ protected function setProductLinks(Product $product) } foreach ($linkTypes as $linkType => $readonly) { - if (isset($links[$linkType]) && !$readonly) { - foreach ((array) $links[$linkType] as $linkData) { - if (empty($linkData['id'])) { - continue; - } - - $linkProduct = $this->productRepository->getById($linkData['id']); - $link = $this->productLinkFactory->create(); - $link->setSku($product->getSku()) - ->setLinkedProductSku($linkProduct->getSku()) - ->setLinkType($linkType) - ->setPosition(isset($linkData['position']) ? (int) $linkData['position'] : 0); - $productLinks[] = $link; - } + $isReadOnlyLinks = $readonly && in_array($linkType, ['upsell', 'related']); + if ($isReadOnlyLinks) { + $productLinks = null; + break; + } else { + $productLinks = $this->setProductLinksForNotReadOnlyItems( + $productLinks, + $product, + $links, + $linkType, + $readonly + ); } } @@ -401,6 +402,9 @@ private function overwriteValue($optionId, $option, $overwriteOptions) $option['is_delete_store_title'] = 1; } } + if (CustomOptions::FIELD_TITLE_NAME === $fieldName) { + $option[CustomOptions::FIELD_IS_USE_DEFAULT] = $overwrite; + } } } @@ -523,4 +527,39 @@ private function setCategoryLinks(Product $product): void $extensionAttributes->setCategoryLinks(!empty($newCategoryLinks) ? $newCategoryLinks : null); $product->setExtensionAttributes($extensionAttributes); } + + /** + * Set product links when readonly is false + * + * @param array $productLinks + * @param Product $product + * @param array $links + * @param string $linkType + * @param mixed $readonly + * @return array + * @throws NoSuchEntityException + */ + private function setProductLinksForNotReadOnlyItems( + array $productLinks, + Product $product, + array $links, + string $linkType, + mixed $readonly + ): array { + if (isset($links[$linkType]) && !$readonly) { + foreach ((array)$links[$linkType] as $linkData) { + if (empty($linkData['id'])) { + continue; + } + $linkProduct = $this->productRepository->getById($linkData['id']); + $link = $this->productLinkFactory->create(); + $link->setSku($product->getSku()) + ->setLinkedProductSku($linkProduct->getSku()) + ->setLinkType($linkType) + ->setPosition(isset($linkData['position']) ? (int)$linkData['position'] : 0); + $productLinks[] = $link; + } + } + return $productLinks; + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php index 2238ad91550e4..80dd719794c18 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php @@ -14,7 +14,6 @@ /** * Catalog product custom option resource model * - * @author Magento Core Team * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb @@ -25,15 +24,11 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $metadataPool; /** - * Store manager - * * @var \Magento\Store\Model\StoreManagerInterface */ protected $_storeManager; /** - * Currency factory - * * @var \Magento\Directory\Model\CurrencyFactory */ protected $_currencyFactory; @@ -259,12 +254,13 @@ protected function _saveValueTitles(AbstractModel $object) } } else { // we should insert record into not default store only of if it does not exist in default store - if (($storeId == Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + if (((int)$storeId === Store::DEFAULT_STORE_ID && !$existInDefaultStore) || ( - $storeId != Store::DEFAULT_STORE_ID && - !$existInCurrentStore && - !$isDeleteStoreTitle - ) + (int)$storeId !== Store::DEFAULT_STORE_ID && + !$isDeleteStoreTitle && + ($object->getDefaultTitle() !== null && $object->getTitle() !== $object->getDefaultTitle()) + ) || + ($object->getIsUseDefault() !== null && !(int)$object->getIsUseDefault()) ) { $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 494dbac02d792..89a20c6277339 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -27,15 +27,11 @@ class Value extends AbstractDb { /** - * Store manager - * * @var StoreManagerInterface */ protected $_storeManager; /** - * Currency factory - * * @var CurrencyFactory */ protected $_currencyFactory; @@ -288,8 +284,12 @@ protected function _saveValueTitles(AbstractModel $object) Store::DEFAULT_STORE_ID ); // we should insert record into not default store only of if it does not exist in default store - if (($storeId == Store::DEFAULT_STORE_ID && !$existInDefaultStore) - || ($storeId != Store::DEFAULT_STORE_ID && !$existInCurrentStore) + if (((int)$storeId === Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + (int)$storeId !== Store::DEFAULT_STORE_ID && + ($object->getDefaultTitle() !== null && $object->getTitle() !== $object->getDefaultTitle()) + ) || + ($object->getIsUseDefault() !== null && !(int)$object->getIsUseDefault()) ) { $bind = [ 'option_type_id' => (int)$object->getId(), @@ -456,6 +456,7 @@ public function duplicate(OptionValue $object, $oldOptionId, $newOptionId) * * @return FormatInterface * @deprecated 101.0.8 + * @see Avoid direct use of ObjectManager */ private function getLocaleFormatter() { 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 a437ee49b398f..ada2661ee1b76 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 @@ -161,9 +161,7 @@ protected function setUp(): void ->getMock(); $this->productLinksMock = $this->createMock(ProductLinks::class); $this->linkTypeProviderMock = $this->createMock(LinkTypeProvider::class); - $this->productLinksMock->expects($this->any()) - ->method('initializeLinks') - ->willReturn($this->productMock); + $this->attributeFilterMock = $this->createMock(AttributeFilter::class); $this->localeFormatMock = $this->createMock(Format::class); @@ -221,6 +219,7 @@ protected function setUp(): void * @param array|null $tierPrice * @dataProvider initializeDataProvider * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function testInitialize( $isSingleStore, @@ -229,8 +228,17 @@ public function testInitialize( $links, $linkTypes, $expectedLinks, - $tierPrice = null + $tierPrice = null, + $isReadOnlyRelatedItems = null, + $isReadOnlyUpSellItems = null, + $ignoreLinksFlag = false ) { + $this->productMock->setData('related_readonly', $isReadOnlyRelatedItems); + $this->productMock->setData('upsell_readonly', $isReadOnlyUpSellItems); + $this->productLinksMock->expects($this->any()) + ->method('initializeLinks') + ->willReturn($this->productMock); + $this->linkTypeProviderMock->expects($this->once()) ->method('getItems') ->willReturn($this->assembleLinkTypes($linkTypes)); @@ -346,6 +354,11 @@ function () { $productLinks = $this->productMock->getProductLinks(); $this->assertCount(count($expectedLinks), $productLinks); + if ($ignoreLinksFlag) { + $this->assertTrue($this->productMock->getDataByKey('ignore_links_flag')); + } else { + $this->assertFalse($this->productMock->getDataByKey('ignore_links_flag')); + } $resultLinks = []; $this->assertEquals($tierPrice ?: [], $this->productMock->getData('tier_price')); @@ -559,6 +572,34 @@ public static function initializeDataProvider() ['type' => 'related', 'linked_product_sku' => 'Test'], ], ], + + // readonly links + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 2], + 'expected_website_ids' => ['1' => 1, '2' => 2], + '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' => [], + 'tierPrice' => [], + true, + true, + true + ], ]; } @@ -660,6 +701,7 @@ public static function mergeProductOptionsDataProvider() 'default_key2' => 'val22', ], ], + 'is_use_default' => 1, ], ], ], @@ -702,6 +744,7 @@ public static function mergeProductOptionsDataProvider() 'default_key1' => 'val11', 'default_title' => 'val22', 'is_delete_store_title' => 1, + 'is_use_default' => 1, ], ], ], diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml index 9d0e78d6ad14c..55694a6d4909a 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml @@ -20,7 +20,7 @@ $attributes = $block->getProductAttributes(); getData('config/dataScope'); $nameStep = /* @noEscape */ $block->getData('config/nameStepWizard'); $scriptString = <<