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.48k stars 9.29k forks source link

Update category issue #4430

Closed krisdass closed 7 years ago

krisdass commented 8 years ago

I have created a new category and its not visible in navigation menu as of now. I am adding products to it via code and its working.

Now i am editing the category to show in navigation menu, it thorws error as "URL key for specified store already exists".

I tried to

Please let me know.

maksek commented 8 years ago

Hi @krisdass, what you mean by "let me know"? The error means you have similar category with the same URL key. And you trying to do another one. We don't know how you added products via code, and if it was correct. Please provide exact steps to reproduce with examples and issue you are experience, then we will be able to try reproduce. At the current state the issue is not possible to confirm.

krisdass commented 8 years ago

Hi @maksek, The code for adding product programmatically is working fine. But the issue noted was, After adding product when you are trying to update the category I am getting the error as "URL key for specified store already exists".

There is no duplication of URL. Even this error occurs when you edit product as well.

If you select any store from Store View dropdown, there is no error !

I am using Magento 2.0.4 version.

FYI : I am using following code to create products programmatically,

$product = $this->_objectManager->create('\Magento\Catalog\Model\Product');

// Set product's attribute values
$product->setStoreId($this->_helper->getStoreDetails()->getId()); // Set store id for the product
$product->setSku('prefix_'.$uniqId); // Set your sku 
$product->setName('Test Product'); // Name of Product
$product->setAttributeSetId($attributeSetId); // Attribute set id
$product->setStatus(0); // Status on product enabled/ disabled 1/0
$product->setWeight(0); // Weight of product
$product->setVisibility(4); // Visibilty of product (catalog / search / catalog, search / Not visible individually)
$product->setTaxClassId(0); // Tax class id
$product->setTypeId('simple'); // Type of product (simple/virtual/downloadable/configurable)
$product->setPrice(0); // Price of product
$product->setCategoryIds([3,5]); // Category ids
$product->setStockData(array(
    'use_config_manage_stock'   => 0,
    'manage_stock'          => 1,
    'is_in_stock'               => 1,
    'qty'                       => 1 // All products to have min stock for Product listing page
)); // stock datas
$product->setUrlKey($urlKey); // Dynamic Url key
$product->setWebsiteIds(array(1)); // Product under websites                            

// Adding Image to product
$product->addImageToMediaGallery($hasUploadedFile, array('image', 'small_image', 'thumbnail'), false, false);
$product->save();

Steps to Reproduce :

  1. Create a category
  2. Assign some product
  3. Go to some other pages in admin and come back to categories page, select your category and then try to update any detail of the category.
gamort commented 8 years ago

I've run into a similar issue with magento 2.0.7 [and a number of prior versions].

Attempting to change a category url key throws a duplicate url error.

By turning on database query logging, I tracked down the query that through the error and discovered the issue was with product url's not category url's. When the category url key is updated it looks like: url_rewrite is updated with the new category url[successful] magento then builds an array of all the products in the category and what their new url paths should be[ie new category url/product-key

For some reason for every product request-path the array contains TWO copies of each new path per store. This means the attempt to update the table fails due to the duplicate url's trying to be set. The transaction is then rolled back and I'm left with a url_rewrite table that looks correct and should have no problems[because the problem was in the new urls being set, not the old ones!]

As a complete hack I changed Magento\UrlRewrite\Model\Storage\DbStorage on line 94: https://github.com/magento/magento2/blob/6ea7d2d85cded3fa0fbcf4e7aa0dcd4edbf568a6/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php#L94

Replace the foreach loop with the following: foreach ($urls as $url) { $urlArray = $url->toArray(); $urlPath = $urlArray['request_path']; $storeId = $urlArray['store_id']; $dataKey = $storeId.'..'.$urlPath; $data[$dataKey] = $urlArray; }

This de-dupes the list of urls[specifically the list of urls for each store].

A better hack would be to subclass DbStorage and use dependency injection to use the subclass.

Obviously the REAL fix is to backtrack the code to figure out how/why these duplicates are being created. My guess would be whatever query is used to build this list either needs a DISTINCT added or to convert some of the filters from "OR" clauses to "AND" clauses.

With site launch one week away, I'm using the hack for now and as time permits will create a real fix and submit a pull request.

krisdass commented 8 years ago

Thank you @gamort . I will check your solution

LoganGS commented 8 years ago

I installed Magento via composer, so my core modules are in the "/vendor" directory.

I tracked down where the info was being inserted in the database. First breadcrumb was in this file:

vendor/magento/module-catalog-import-export/Model/Import/Product.php

At the bottom of the method _saveProducts(), we are calling _saveProductAttributes(), passing through the attributes (including the url_key). The _saveProductAttributes() method is calling the insertOnDuplicate() method from the Magento\Framework\DB\Adapter\Pdo\Mysql class:

vendor/magento/framework/DB/Adapter/Pdo/Mysql.php

An insert query is built towards the bottom of the insertOnDuplicate() method, defaulting to just updating the attribute value in the case there is a duplicate key.

I haven't yet found where the infamous exception / error message is being pulled in relative to this. I just see calls to the translator function containing the message here:

vendor/module-url-rewrite/Model/Storage/AbstractStorage.php
vendor/module-url-rewrite/Model/Storage/DbStorage.php

In my case, I was seeing the error while trying to import products (via System > Data Transfer > Import).

afoucret commented 8 years ago

I was able to reproduce this one while I was testing 2.1.0-rc2.

It seems the error handling is wrong into Magento\UrlRewrite\Model\Storage\AbstractStorage::replace

    public function replace(array $urls)
    {
        if (!$urls) {
            return;
        }

        try {
            $this->doReplace($urls);
        } catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
            throw new \Magento\Framework\Exception\AlreadyExistsException(
                __('URL key for specified store already exists.')
            );
        }
    }

The code here is catching an exception raised by the doReplace method and raising the same type of exception (with the same message).

Everything goes find again when I replace the the code bellow by :

/**
    public function replace(array $urls)
    {
        if (!$urls) {
            return;
        }

        $this->doReplace($urls);
    }

The doReplace method (implemented into the DBStorage) does the job nicely and the duplicated URL rewrite feature is still operational (tested on my dataset).

I will provide a PR for this one.

AVoskoboinikov commented 8 years ago

Hi guys, @afoucret and @krisdass can anyone of you provide full steps to reproduce issue? Something like:

  1. install magento;
  2. create view A;
  3. create view B;
  4. etc. Because it is still not obvious how to reproduce it in new magento installation with sample data. I've tried several ideas but without success. It looks like it is problem somewhere in url rewrite engine or in misunderstanding of some functionality. So it would be great if you can clarify steps.

Thanks!

jvreeken commented 8 years ago

I also had this issue and the only fix was @gamort 's hack.

With this website needing to be launched in two days it will have to do for now. hopefully there aren't any long term consequences, but for now it works.

In my situation it was a shop that was migrated from 1.9.x to 2.0 then subsequently 2.0.7 then 2.1.0 then 2.1.1

The old database had a HUGE url re-write table with Total 89548 records found for url rewrite management... there are only about 1000 products... I think the problem comes after moving categories around with migrated data.

AVoskoboinikov commented 8 years ago

Hi guys,

Can anyone help me with detailed steps to reproduce this issue? I still have no success in it in my clean version of 2.1.1 with sample data.

Right now I assume that you already have such url when trying to save new one - that is why you get this error. If this is not your case - tell me how can I reproduce it on my side.

Thanks for contributing.

southerncomputer commented 8 years ago

Create a category - load 1000's of sku's into that category

disable category from menu

Click on "SAVE"

This error shows up!

Afourcet's patch works fine!

`

cgaubuchon commented 7 years ago

We are having an issue that seems directly related to what others are experiencing here. Problem happens on composer installed CE 2.1.2

Our data set is 1500 products. If there is a category such as Bedroom/Storage then a URL rewrite is created within the url_rewrite table. If I then import a product with a category of Bathroom/Storage the importer throws an error about the key already existing.

If I go and manually create the category "Storage" within the "Bathroom" category then the importer will import as expected.

There is also the same issue with the character case within categories. Such that storage and Storage categories will throw a duplicate error.

I am still investigating further to see how this issue effects the rewrites since /storage could now essentially redirect to a few places rather than a single category.

cgaubuchon commented 7 years ago

It seems as though @gamort hack works for us. Is there is temporary way to override/inherit the DbStorage.php file so that, when installed via composer, this file is not overridden by a system update? We have tried to override as we have previously with Block functionality but it seems a though there are issues with other dependencies when doing so with this file specifically. Anyone have a solution?

cowmix88 commented 7 years ago

I'm having the same problem in CE 2.1.2 and 2.1.3, but @gamort's hack does fix it

rey8a commented 7 years ago

I also was experiencing this issue thanks @gamort Your hack resolved my issue.

cgaubuchon commented 7 years ago

@cowmix88 @rey8a @gamort Are you installed using Composer or another method? Reason I ask is that without modifying the vendor folder directly, we cannot seem to override the protected doReplace function. Can you provide some more detail how you implemented this hack? I have a ticket open with the Enterprise team who is looking into it but not really getting anywhere.

rey8a commented 7 years ago

Hi @cgaubuchon, I edited the /vendor/magento/module-url-rewrite/Model/Storage/DbStorage.php

Probably not the best practice editing M2 core files but this seemed to do the trick.

cgaubuchon commented 7 years ago

@rey8a Yup, editing the file directly works for us as well in development but because of our release process, we cannot edit core files when moving to production. Just part of the process of keeping our code base and developers working toward a maintainable system. Unfortunately, without editing the file directly, we cannot seem to override the functionality in any way. I am working with Magento Enterprise Support to see how we can better fix this issue.

petterjv commented 7 years ago

@cgaubuchon You can override almost anything using di / preference for, even classes in core. Try that as a temporary solution.

cgaubuchon commented 7 years ago

@petol880 We have tried overriding the /module-url-rewrite/Model/Storage/DbStorage.php file a number of ways yet it never seems to be overridden when using di / preference. We also of course are concerned about any residual effects this may have on the store moving forward.

Risk aside, can anyone identify the proper way of overriding the DbStorage class to temporarily resolve the issue?

TommyKolkman commented 7 years ago

@gamort's fix works for me too! Give the man a medal. Thanks!

cgaubuchon commented 7 years ago

Just as a bit of an update since there has not seemingly been progress on a final solution/cause other than @gamort response. We have two stores experiencing this issue to the point that both of them are completely on hold until a official solution is found.

  1. Enterprise 2.1.3 store with ~300 products, of which nearly 1/4 experience this error and cannot be modified in any way.
  2. Community 2.1.3 store with ~ 2000 products, of which nearly 1/2 of products cannot be edited.

Progress

Given the number of issues we have seen with both CE and EE it really seems as though M2 is not ready for any production environment regardless of what Magento may admit.

janeblonde commented 7 years ago

Also looking for a solution

nathanjhastings commented 7 years ago

Same issue after import from 1.9 to 2.1.2, @gamort's fix worked for me too but it would be good to get a fix from the Magento team.

gediminaskv commented 7 years ago

I can't edit a category with ~7000 items (http://prnt.sc/f664xv) on Magento 2.1.6. Always throws "Something went wrong while saving the category." Exception.log:

[2017-05-10 10:38:33] main.CRITICAL: Exception: Warning: Error while sending QUERY packet. PID=24847 in /home/[my-domain]/public_html/vendor/magento/zendframework1/library/Zend/Db/Adapter/Pdo/Abstract.php on line 324 in /home/[my-domain]/public_html/vendor/magento/framework/App/ErrorHandler.php:61
Stack trace:
#0 [internal function]: Magento\Framework\App\ErrorHandler->handler(2, 'Error while sen...', '/home/[my-domain]...', 324, Array)
#1 /home/[my-domain]/public_html/vendor/magento/zendframework1/library/Zend/Db/Adapter/Pdo/Abstract.php(324): PDO->rollBack()
#2 /home/[my-domain]/public_html/vendor/magento/zendframework1/library/Zend/Db/Adapter/Abstract.php(524): Zend_Db_Adapter_Pdo_Abstract->_rollBack()
#3 /home/[my-domain]/public_html/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php(274): Zend_Db_Adapter_Abstract->rollBack()
#4 /home/[my-domain]/public_html/vendor/magento/framework/Model/ResourceModel/AbstractResource.php(99): Magento\Framework\DB\Adapter\Pdo\Mysql->rollBack()
#5 /home/[my-domain]/public_html/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Category.php(52): Magento\Framework\Model\ResourceModel\AbstractResource->rollBack()
#6 /home/[my-domain]/public_html/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Category.php(29): Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Category->addCommitCallback(Object(Magento\Catalog\Model\ResourceModel\Category\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Category\Interceptor))
#7 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Chain/Chain.php(67): Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Category->aroundSave(Object(Magento\Catalog\Model\ResourceModel\Category\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Category\Interceptor))
#8 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\\Catalog...', 'save', Object(Magento\Catalog\Model\ResourceModel\Category\Interceptor), Array, 'clean_cache')
#9 /home/[my-domain]/public_html/vendor/magento/framework/App/Cache/FlushCacheByTags.php(71): Magento\Catalog\Model\ResourceModel\Category\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Catalog\Model\Category\Interceptor))
#10 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(142): Magento\Framework\App\Cache\FlushCacheByTags->aroundSave(Object(Magento\Catalog\Model\ResourceModel\Category\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Category\Interceptor))
#11 /home/[my-domain]/public_html/var/generation/Magento/Catalog/Model/ResourceModel/Category/Interceptor.php(52): Magento\Catalog\Model\ResourceModel\Category\Interceptor->___callPlugins('save', Array, Array)
#12 /home/[my-domain]/public_html/vendor/magento/framework/Model/AbstractModel.php(631): Magento\Catalog\Model\ResourceModel\Category\Interceptor->save(Object(Magento\Catalog\Model\Category\Interceptor))
#13 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(146): Magento\Framework\Model\AbstractModel->save()
#14 /home/[my-domain]/public_html/var/generation/Magento/Catalog/Model/Category/Interceptor.php(26): Magento\Catalog\Model\Category\Interceptor->___callPlugins('save', Array, Array)
#15 /home/[my-domain]/public_html/vendor/magento/module-catalog/Controller/Adminhtml/Category/Save.php(203): Magento\Catalog\Model\Category\Interceptor->save()
#16 /home/[my-domain]/public_html/vendor/magento/framework/App/Action/Action.php(102): Magento\Catalog\Controller\Adminhtml\Category\Save->execute()
#17 /home/[my-domain]/public_html/vendor/magento/module-backend/App/AbstractAction.php(226): Magento\Framework\App\Action\Action->dispatch(Object(Magento\Framework\App\Request\Http))
#18 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(74): Magento\Backend\App\AbstractAction->dispatch(Object(Magento\Framework\App\Request\Http))
#19 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Chain/Chain.php(70): Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor->___callParent('dispatch', Array)
#20 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Chain/Chain.php(63): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\\Catalog...', 'dispatch', Object(Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor), Array, 'adminAuthentica...')
#21 /home/[my-domain]/public_html/vendor/magento/module-backend/App/Action/Plugin/Authentication.php(143): Magento\Framework\Interception\Chain\Chain->Magento\Framework\Interception\Chain\{closure}(Object(Magento\Framework\App\Request\Http))
#22 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Chain/Chain.php(67): Magento\Backend\App\Action\Plugin\Authentication->aroundDispatch(Object(Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor), Object(Closure), Object(Magento\Framework\App\Request\Http))
#23 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\\Catalog...', 'dispatch', Object(Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor), Array, 'adminMassaction...')
#24 /home/[my-domain]/public_html/vendor/magento/module-backend/App/Action/Plugin/MassactionKey.php(33): Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#25 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(142): Magento\Backend\App\Action\Plugin\MassactionKey->aroundDispatch(Object(Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor), Object(Closure), Object(Magento\Framework\App\Request\Http))
#26 /home/[my-domain]/public_html/var/generation/Magento/Catalog/Controller/Adminhtml/Category/Save/Interceptor.php(26): Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor->___callPlugins('dispatch', Array, Array)
#27 /home/[my-domain]/public_html/vendor/magento/framework/App/FrontController.php(55): Magento\Catalog\Controller\Adminhtml\Category\Save\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#28 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(74): Magento\Framework\App\FrontController->dispatch(Object(Magento\Framework\App\Request\Http))
#29 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Chain/Chain.php(70): Magento\Framework\App\FrontController\Interceptor->___callParent('dispatch', Array)
#30 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\\Framewo...', 'dispatch', Object(Magento\Framework\App\FrontController\Interceptor), Array, 'install')
#31 /home/[my-domain]/public_html/vendor/magento/framework/Module/Plugin/DbStatusValidator.php(69): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#32 /home/[my-domain]/public_html/vendor/magento/framework/Interception/Interceptor.php(142): Magento\Framework\Module\Plugin\DbStatusValidator->aroundDispatch(Object(Magento\Framework\App\FrontController\Interceptor), Object(Closure), Object(Magento\Framework\App\Request\Http))
#33 /home/[my-domain]/public_html/var/generation/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\Framework\App\FrontController\Interceptor->___callPlugins('dispatch', Array, Array)
#34 /home/[my-domain]/public_html/vendor/magento/framework/App/Http.php(135): Magento\Framework\App\FrontController\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#35 /home/[my-domain]/public_html/vendor/magento/framework/App/Bootstrap.php(258): Magento\Framework\App\Http->launch()
#36 /home/[my-domain]/public_html/index.php(39): Magento\Framework\App\Bootstrap->run(Object(Magento\Framework\App\Http))
#37 {main} [] []
Tjitse-E commented 7 years ago

@gamort hack solved my problem on a Magento 2.1.6 site

ashwinshahi commented 7 years ago

Hi @afoucret , @gamort ,

Initially my issue was the same @afoucret mentioned after applying the fix I encountered the issue was on DbStorage.php on insertMultiple($data) function here for some categories the variable $data is array and so I am getting the following exception :

Fatal error: Uncaught Magento\Framework\Exception\AlreadyExistsException: URL key for specified store already exists. in C:\xampp\htdocs\fao\html\vendor\magento\module-url-rewrite\Model\Storage\DbStorage.php:116 Stack trace: #0 C:\xampp\htdocs\fao\html\vendor\magento\module-url-rewrite\Model\Storage\DbStorage.php(97): Magento\UrlRewrite\Model\Storage\DbStorage->insertMultiple(Array) #1 C:\xampp\htdocs\fao\html\vendor\magento\module-url-rewrite\Model\Storage\AbstractStorage.php(82): Magento\UrlRewrite\Model\Storage\DbStorage->doReplace(Array) #2 C:\xampp\htdocs\fao\html\vendor\magento\framework\Interception\Interceptor.php(74): Magento\UrlRewrite\Model\Storage\AbstractStorage->replace(Array) #3 C:\xampp\htdocs\fao\html\vendor\magento\framework\Interception\Chain\Chain.php(70): Magento\UrlRewrite\Model\Storage\DbStorage\Interceptor->___callParent('replace', Array) #4 C:\xampp\htdocs\fao\html\vendor\magento\framework\Interception\Interceptor.php(138): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\UrlRewr.. in C:\xampp\htdocs\fao\html\vendor\magento\module-url-rewrite\Model\Storage\DbStorage.php on line 116

Can you please help.

Thanks,

luyentrv commented 7 years ago

Hi @cgaubuchon I encoutered same issue of @afoucret , @gamort . I migrated data from version 1.6.2 to version 2.1.6. When i saved category in admin i got an error "URL key for specified store already exists.". I see in magento 1.6.2 the parameter "id" alway passed in save url but in magento 2 the parameter id did'nt pass in url then when _initCategory the category not loaded and category alway re-generate url_rewrite.

Can you please help.

Thanks.

orlangur commented 7 years ago

System behaves normally in current implementation if your data is valid in the first place. If https://github.com/magento/data-migration-tool produces invalid data in some cases, it should be reported.

Error message was improved on Magento 2.2.0 branch to give more information to merchant what exactly needs to be changed.