magento / magento2

Prior to making any Submission(s), you must sign an Adobe Contributor License Agreement, available here at: https://opensource.adobe.com/cla.html. All Submissions you make to Adobe Inc. and its affiliates, assigns and subsidiaries (collectively “Adobe”) are subject to the terms of the Adobe Contributor License Agreement.
http://www.magento.com
Open Software License 3.0
11.55k stars 9.32k forks source link

Duplicate product #15656

Closed rommelfreddy closed 3 years ago

rommelfreddy commented 6 years ago

Preconditions

  1. Magento 2.2.2
  2. Existing product with different values in different StoreViews (e.g StoreView UK, NL, DE, ...)

Steps to reproduce

  1. open the product
  2. select one StoreView (e.g. UK - not "All StoreViews"!)
  3. click on "Save product & duplicate"

Expected result

  1. the product got be duplicated with all (different) values in each storeView

Actual result

  1. the default values of the new product are not the default values from the original product. Instead, the values from the selected storeView will be used to create the new product.

Example (Productname):

Original product: StoreView Default: "My default name" StoreView UK: "My UK name" StoreView NL: "My NL name" ...

Duplicated product: (before clicking on duplicate, I switch to the storeView "UK") StoreView Default: "My UK name" StoreView UK: "My UK name" StoreView NL: "My NL name" ...

rogyar commented 6 years ago

I believe that's expected behavior. You have a possibility to choose the exact product's variant to duplicate. If you need default values - choose the global scope, otherwise choose the variant that should be duplicated.

ghost commented 6 years ago

@Fredwak, thank you for your report. We've acknowledged the issue and added to our backlog.

magento-engcom-team commented 5 years ago

Hi @shikhamis11. Thank you for working on this issue. Looks like this issue is already verified and confirmed. But if your want to validate it one more time, please, go though the following instruction:

shikhamis11 commented 5 years ago

MM19IN

m2-assistant[bot] commented 5 years ago

Hi @JeroenVanLeusden. Thank you for working on this issue. Looks like this issue is already verified and confirmed. But if you want to validate it one more time, please, go though the following instruction:


m2-assistant[bot] commented 5 years ago

Hi @deadlexus. Thank you for working on this issue. Looks like this issue is already verified and confirmed. But if you want to validate it one more time, please, go though the following instruction:


deadlexus commented 5 years ago

@magento give me 2.3-develop instance

magento-engcom-team commented 5 years ago

Hi @deadlexus. Thank you for your request. I'm working on Magento 2.3-develop instance for you

magento-engcom-team commented 5 years ago

Hi @deadlexus, here is your Magento instance. Admin access: https://i-15656-2-3-develop.instances.magento-community.engineering/admin Login: admin Password: 123123q Instance will be terminated in up to 3 hours.

rvm-pmis commented 4 years ago

I'm seeing something very similar (perhaps the same issue) when duplicating from the default storeview (currently on magento version 2.3.3-p1) In the default storeview we have everything in english. In the dutch storeview it's translated to nl_NL When duplicating a product in the default storeview, the duplicate has the default english content in the dutch storeview instead of dutch content like the original product.

We solved it by applying the attached patch: issue-15656.patch.txt

michel334 commented 4 years ago

Same issue as rvm-pmis. Magento version 2.3.3, duplicating from default store view with 2 language store views (dutch and french). It only happens when i change the URL key for a store view. Then, when i save and duplicate, the store view information (description etc) is not copied. The default store information is copied. Also the images get duplicated multiple times. This does not happen when "use default value" is checked for the URL key for each store view. Only then save and duplicate works as expected.

peterjaap commented 4 years ago

@sdzhepa any progress on this?

peterjaap commented 4 years ago

Some extra findings on my end. It might be related, might not.

I have one website with 3 storeviews. If I duplicate a product from the Default (i.e. "All") storeviews, all attributes that have Scope = Website configured, those values are copied from the Default storeview into all storeviews, even though in the original product the value is only set in the default storeview!

Example in case; country_of_manufacture. This has Scope set to Website in a vanilla M2 installation;

image

This is the value for that attribute for my original product in catalog_product_entity_varchar (entity_id 32767);

value_id,attribute_id,store_id,entity_id,value
1805753,114,0,32767,AF

But when I duplicate that product, the rows for the new product are as follows: (duplicated product ID in this test is 46406)

value_id,attribute_id,store_id,entity_id,value
1805677,114,0,46406,AF
1805702,114,1,46406,AF
1805701,114,2,46406,AF
1805700,114,3,46406,AF

When I update the attribute to Scope 'Global' this does not happen! (duplicated product ID in this test is 46407)

value_id,attribute_id,store_id,entity_id,value
1805677,114,0,46407,AF

But it gets weirder; if I update the attribute to Scope 'Store view', it gets copied to all storeviews except to store id 1, which is my default storeview. (duplicated product ID in this test is 46408)

value_id,attribute_id,store_id,entity_id,value
1805826,114,0,46408,AF
1805872,114,2,46408,AF
1805849,114,3,46408,AF

:exploding_head:

The following attributes are Scope=Website by default;

special_from_date
special_to_date
tier_price
news_from_date
news_to_date
status
country_of_manufacture
msrp_display_actual_price_type
tax_class_id

Edit; when I Xdebug and Duplicate the product, I see duplicate lines show up when this save() is called; https://github.com/magento/magento2/blob/2.3/app/code/Magento/Catalog/Model/Product/Copier.php#L152

I see there have been 3 commits to this file in 2.4-develop;

peterjaap commented 4 years ago

Ha! It appears this commit from this PR by @JeroenVanLeusden not only fixes the original problem in this issue, but also my specific issue! :tada:

peterjaap commented 4 years ago

Here's my patch based on the abovementioned PR for Magento 2.3.4;

--- a/app/code/Magento/Catalog/Model/Product/Copier.php 2020-01-10 07:20:38.000000000 +0100
+++ b/app/code/Magento/Catalog/Model/Product/Copier.php 2020-05-08 15:58:47.320664853 +0200
@@ -8,7 +8,11 @@
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
 use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Option\Repository as OptionRepository;
 use Magento\Catalog\Model\ProductFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;

 /**
  * Catalog product copier.
@@ -35,9 +39,10 @@
     protected $productFactory;

     /**
-     * @var \Magento\Framework\EntityManager\MetadataPool
+     * @var MetadataPool
      */
     protected $metadataPool;
+
     /**
      * @var ScopeOverriddenValue
      */
@@ -47,30 +52,38 @@
      * @param CopyConstructorInterface $copyConstructor
      * @param ProductFactory $productFactory
      * @param ScopeOverriddenValue $scopeOverriddenValue
+     * @param OptionRepository|null $optionRepository
+     * @param MetadataPool|null $metadataPool
      */
     public function __construct(
         CopyConstructorInterface $copyConstructor,
         ProductFactory $productFactory,
-        ScopeOverriddenValue $scopeOverriddenValue
+        ScopeOverriddenValue $scopeOverriddenValue,
+        OptionRepository $optionRepository,
+        MetadataPool $metadataPool
     ) {
         $this->productFactory = $productFactory;
         $this->copyConstructor = $copyConstructor;
         $this->scopeOverriddenValue = $scopeOverriddenValue;
+        $this->optionRepository = $optionRepository;
+        $this->metadataPool = $metadataPool;
     }

     /**
      * Create product duplicate
      *
      * @param \Magento\Catalog\Model\Product $product
+     *
      * @return \Magento\Catalog\Model\Product
+     *
+     * @throws \Exception
      */
     public function copy(Product $product)
     {
         $product->getWebsiteIds();
         $product->getCategoryIds();

-        /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
-        $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
+        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);

         /** @var \Magento\Catalog\Model\Product $duplicate */
         $duplicate = $this->productFactory->create();
@@ -88,7 +101,7 @@
         $this->copyConstructor->build($product, $duplicate);
         $this->setDefaultUrl($product, $duplicate);
         $this->setStoresUrl($product, $duplicate);
-        $this->getOptionRepository()->duplicate($product, $duplicate);
+        $this->optionRepository->duplicate($product, $duplicate);
         $product->getResource()->duplicate(
             $product->getData($metadata->getLinkField()),
             $duplicate->getData($metadata->getLinkField())
@@ -123,13 +136,16 @@
      *
      * @param Product $product
      * @param Product $duplicate
+     *
      * @return void
+     * @throws UrlAlreadyExistsException
      */
     private function setStoresUrl(Product $product, Product $duplicate) : void
     {
         $storeIds = $duplicate->getStoreIds();
         $productId = $product->getId();
         $productResource = $product->getResource();
+        $attribute = $productResource->getAttribute('url_key');
         $duplicate->setData('save_rewrites_history', false);
         foreach ($storeIds as $storeId) {
             $useDefault = !$this->scopeOverriddenValue->containsValue(
@@ -141,20 +157,23 @@
             if ($useDefault) {
                 continue;
             }
-            $isDuplicateSaved = false;
+
             $duplicate->setStoreId($storeId);
             $urlKey = $productResource->getAttributeRawValue($productId, 'url_key', $storeId);
+            $iteration = 0;
+
             do {
+                if ($iteration === 10) {
+                    throw new UrlAlreadyExistsException();
+                }
+
                 $urlKey = $this->modifyUrl($urlKey);
                 $duplicate->setUrlKey($urlKey);
-                $duplicate->setData('url_path', null);
-                try {
-                    $duplicate->save();
-                    $isDuplicateSaved = true;
-                    // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
-                } catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
-                }
-            } while (!$isDuplicateSaved);
+                $iteration++;
+            } while (!$attribute->getEntity()->checkAttributeUniqueValue($attribute, $duplicate));
+            $duplicate->setData('url_path', null);
+            $productResource->saveAttribute($duplicate, 'url_path');
+            $productResource->saveAttribute($duplicate, 'url_key');
         }
         $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
     }
@@ -168,38 +187,8 @@
     private function modifyUrl(string $urlKey) : string
     {
         return preg_match('/(.*)-(\d+)$/', $urlKey, $matches)
-                    ? $matches[1] . '-' . ($matches[2] + 1)
-                    : $urlKey . '-1';
-    }
-
-    /**
-     * Returns product option repository.
-     *
-     * @return Option\Repository
-     * @deprecated 101.0.0
-     */
-    private function getOptionRepository()
-    {
-        if (null === $this->optionRepository) {
-            $this->optionRepository = \Magento\Framework\App\ObjectManager::getInstance()
-                ->get(\Magento\Catalog\Model\Product\Option\Repository::class);
-        }
-        return $this->optionRepository;
-    }
-
-    /**
-     * Returns metadata pool.
-     *
-     * @return \Magento\Framework\EntityManager\MetadataPool
-     * @deprecated 101.0.0
-     */
-    private function getMetadataPool()
-    {
-        if (null === $this->metadataPool) {
-            $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
-                ->get(\Magento\Framework\EntityManager\MetadataPool::class);
-        }
-        return $this->metadataPool;
+            ? $matches[1] . '-' . ($matches[2] + 1)
+            : $urlKey . '-1';
     }

     /**
PsmIndia commented 4 years ago

Need Urgent Help, magento setup version is 2.3.4[upgraded m1], we have "29542 records found" products, i am facing duplicate product issue, try to fix using available patch but its not fix.

Today i remove all products images in test environment, there no image for a single product, now i add product image for a single product, i save it, working cool, but when i try to do duplicate this product, its again went into infinite loop, within few mints it create huge[4.3GB] garbage of that image that recently add. now i am helpless.

timbaltissen commented 3 years ago

Does anyone have a solution for 2,4,1? Still facing this problem, but logic seems to have changed a lot and patches won't work anymore.

pprignesh commented 3 years ago

Hello;

We also discover the same issue in magento2.4.1 as @rvm-pmis facing.

Please let us know if you have any news on it.

Thanks

leonhardtjo commented 3 years ago

Hi, same problem here - but on version magento2.4.2. Any news or patches on this topic? Thanks.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 14 days if no further activity occurs. Is this issue still relevant? If so, what is blocking it? Is there anything you can do to help move it forward? Thank you for your contributions!