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

Categories Path for Product URLs not working, issue with getProductUrl on Catalog Module? #2619

Closed tigerx7 closed 8 years ago

tigerx7 commented 8 years ago

It seems that getProductUrl() isn't returning a URL with the category in the path in the catalog pages. I'm able to reproduce this with a clean install using my own data and a clean install using the sample data.

Steps: 1) Loaded Magento 2.0.0 via Composer, including sample data 2) Did web based setup wizard. Sample data loaded. 3) Admin -> Stores -> Configuration -> Catalog -> Catalog -> Search Engine Optimization -> "Category URL Suffix" set to "", "Use Categories Path for Product URLs" set to "Yes" 4) Admin -> Products -> Catalog -> SKU "24-MB01" -> Search Engine Optimization -> Verified that URL Key was set to "joust-duffle-bag" 4) Flushed all caches plus static files 5) Navigated to sample data category: http://{domain}/gear/bags 6) Link on first product "Joust Duffle Bag " is to: "http://{domain}/catalog/product/view/id/1/s/joust-duffle-bag/category/4/" instead of the expected: "http://{domain}/gear/bags/joust-duffle-bag.html"

URL Rewrite is functioning since "gear/bags/joust-duffle-bag.html" is in the url_rewrite DB table and http:///gear/bags/joust-duffle-bag.html successfully pulls up the product page. It just seems there's an issue with getProductUrl() on the catalog module inserting the category correctly; unless I have a misunderstanding on something?

sergei-sss commented 8 years ago

+ The command php bin/magento indexer:reindex changes nothing. In the table 'catalog_url_rewrite_product_category' there are no changes...

ghost commented 8 years ago

Yes having the same issue "getProductUrl() isn't returning a URL with the category in the path in the catalog pages"

vkholoshenko commented 8 years ago

Hi, @tigerx7. Thanks for reporting the issue! We've created an internal ticket MAGETWO-46927 to process it.

tigerx7 commented 8 years ago

Good to hear. I've been studying the code trying to get my head around it by following the creation of the URL path in the catalog models. I got as far as getUrl() in \Magento\Catalog\Model\Product\Url. I'm able to confirm the following values in my test setup right before an instance of \Magento\Framework\UrlFactory gets created on the last line:

$storeId = 1, $routePath = "catalog/product/view", $routeParams = ['id' => 39, 's' => 'prod-url-key', 'category' => 7, 'query' => []].

At that point it passes off to \Magento\Framework\UrlFactory and that's as far as I got. The parameters seem right, but I'm fairly new to Magento development and wouldn't consider myself too well versed just yet.

tigerx7 commented 8 years ago

I did a bit more digging. It seems that $product->hasUrlDataObject() is returning false and so is $product->getRequestPath() in function getUrl so UrlRewrite and setRequestPath never kick in.

When ignoring the false check if statement on $product->getRequestPath(), the following $filterData gets formed in my test:

[filterData] => Array
        (
            [entity_id] => 62
            [entity_type] => product
            [store_id] => 1
            [metadata] => Array
                (
                    [category_id] => 5
                )
        )

However, when that gets passed to $this->urlFinder->findOneByData($filterData); it returns NULL

The product id is correct (62) and it is in category_id 5, which is 3 levels deep.

GeromeF commented 8 years ago

I did it working this way delete FROM url_rewrite WHERE entity_type = 'product' take a product and change categories for it bin/magento indexer:reindex urls are ok for the updated product, others are still wrong Don't seems to be a bug, maybe someting went wrong during the install of the sample datas.

tigerx7 commented 8 years ago

@GeromeF , I'm experiencing the same issue without sample data loaded. The urlrewrites are correct and I'm able to to load products when typing the rewritten url directly (ie. {domain}/{category}/{subcategory}/{product_url_key}. It brings up the product successfully. However, the url's that are being generated for the products when they're being listed at the category level and use catalog path in url is set to true are failing and generating something like: http://{domain}/catalog/product/view/id/1/s/{product_url_key}/category/4/

Are you able to reproduce successful URL's with the catalog path in the product list anchor tags at the catalog level?

tigerx7 commented 8 years ago

While tracking down another issue, I think I found the problem with the Product URL paths. getProductUrl isn't taking into account categories set as anchors.

So say if a product is only assigned to the last child category: {default category}-->{level 1}->{level 2}->{level 3}. If a visitor navigates all the way to {level 3}, then the path for the product is generated correctly: {domain}/{level 1}/{level 2}/{level 3}/{product url key}.

However, if {level 2} is set as an anchor, and the visitor only navigates that far, then that's when getUrlPath() fails and generates the non-url-rewrite path for a product.

vkholoshenko commented 8 years ago

Sorry for the delay. First issue was fixed in e0f560f7d973e03a88f0f71ea932dc3add3599fa. And for issue from your last comment we've created one more internal ticket: MAGETWO-47656 Thanks for your input!

tigerx7 commented 8 years ago

@vladimir-k Awesome, I was able to confirm that applying https://github.com/magento/magento2/commit/e0f560f7d973e03a88f0f71ea932dc3add3599fa fixed the issue for category assigned products. It's one step closer, now it's just the anchor categories having the url path issue. Applying the mentioned changes didn't fix the issue in anchor categories.

rahulanand77 commented 8 years ago

Whenever we are creating product from Admin, it is doing an insert in "url_rewrite" table(magento2), after doing reindexing it is working fine on the front end. But when we are importing products from csv, then it is creating wrong url's like "catalog/product/view" etc. Even after reindexing its not working.

Also we have noticed, its not doing any insert in "url_rewrite" when doing reindexing. We have noticed that its only inserting into "url_rewrite" when doing insert from admin. Is it default feature of magento now?

mslabko commented 8 years ago

Hello @rahulanand77, Url rewrite indexer was eliminated in Magento2 and url-rewrites are generated in run-time after saving particular entity. Could you please provide more details - in what case you receive wrong url after import?

rahul-parashar commented 8 years ago

@mslabko

Same Issue & same case & same diagnosis after importing products via magento import module.

eworksmedia commented 8 years ago

We're having this issue on a brand new install, 2.0.2, and not when importing products but adding them normally. All categories are set to Is Anchor Yes.

When creating a product, if I only put it in "Sub Category 1" the url for the product when on the page for "Category 1" is

/category-1/product/view/id/1/s/product-urlkey/category/2/

But if I'm on the page for "Sub Category 1" the url is

/category-1/sub-category-1/product-urlkey.html

The URL should always be /category-1/sub-category-1/product-urlkey.html and never be /category-1/product/view/id/1/s/product-urlkey/category/2/. Has this been fixed yet?

edit: I can provide login details to the dev site if needed and access to anything else that's needed

goowai commented 8 years ago

I'm using Magento ver. 2.0.4, Still the problem persists,

If I try to change the category in the Key URL field, I get an error:

Notice: Undefined offset: 1 in /var/www/html/shop/vendor/magento/module-catalog-url-rewrite/Model/ProductUrlRewriteGenerator.php on line 166

andidhouse commented 8 years ago

same here - on import urls are messed up. Seems like this topic has a long story and no solution so far?

charleskj commented 8 years ago

I am having the same issue, after updating products attribute from a csv import, my urls got messed up in the frontend, I am using the latest version 2.0.5.

Any updates on this

oserediuk commented 8 years ago

Thanks for the report! We have created issue MAGETWO-52879

komsitr commented 8 years ago

The issue as mentioned by @eworksmedia still persists on Magento 2.0.7 Further to his feedback, I try to add a product to multiple categories. The URL is only generated correctly when it is being viewed on the assigned categories' page. Please find the following scenarios:

  1. Add product to "Sub Category 1" only
    • At Category 1 the URL is "/catalog/product/view/id/5/s/product-1/category/3/"
    • At Sub Category 1, the URL is correct "category-1/subcategory-1/product-1"
  2. Add product to both "Sub Category 1" and "Category 1"
    • At Category 1 the URL is correct "category-1/product-1"
    • At Sub Category 1, the URL is correct "category-1/subcategory-1/product-1"
ghost commented 8 years ago

Any ideas on when this will be fixed by ? This is currently holding up a site going live.

Thanks.

oserediuk commented 8 years ago

I cannot reproduce this issue on current code version if I run reindex after import. Version 2.1 will be released soon.

endergreen commented 8 years ago

I agree that this looks to be working now for newly created products on the 2.1 version but what has to happen to get the older products to have the correct URL now. Have tried saving a product with reindexing and clearing cache but that still hasn't worked. Is the only way to delete the old products and recreate?

djha108 commented 8 years ago

Hi I have also similar problem I have my old category name souvenir magneten changed to holland magneten but the product url http://dev.klompjes.com/magneten/souvenir-magneten/holland-magneten-24-stuks is not changing to http://dev.klompjes.com/magneten/holland-magneten/holland-magneten-24-stuks I mean category url key in product url is not changing from old to new can you please suggest me

Thanks

valibus commented 8 years ago

+1 same problem here

ps202 commented 8 years ago

I have imported a whole dbase with phpMyAdmin. Then I turned on SEF-Urls, and created a few new products. The new products (and all categories, even the old ones) are displayed with SEF Urls, the old products (the imported ones) are displayed without SEF Urls. I have done reindexing from the command line as well, but that did not help. Then I just deleted all data from the url_rewrite table where the entity_type='product', and now all of my products are without SEF Urls. And reindexing from the command line does not help either.

Krapulat commented 8 years ago

I have the same problem.

Using Magento 2.1

stratocentric commented 8 years ago

Has anyone gotten this working? I noticed that in some cases you can control x the sef url, uncheck the rewrite box and then add the sef url back and hit save and it will work... not sure why though.

developer-lindner commented 7 years ago

Still broken in Version 2.1.1. Will there be a fix available for such crucial stuff somewhen?!

iazel commented 7 years ago

Got this problem today with magento 2.1.2. I think the problem is when MagentoCatalog\Product\Url#getUrl try to find a $requestPath it sets the store_id to the current store_id, however in my case many urls are set only on the global store view 0 and these get ignored. One easy solution is to generate urls for every store, but this can result a little clunky when you add a new store view... The best solution would be to alter the algorithm to include the general store too or to build it on the fly if not found.

UPDATE: It seems like there's no url_rewrite for store_id === 0 and the backend just sync the store view that have Use default value flagged. However I can't understand how the default value thing works yet, any suggestion? I would like to mass update this field.

UPDATE 2: Ok, basically Use default value means it deletes/doesn't insert the attribute for that store view and Magento2 already (re)generate every link upon saving as long it has been modified, correctly handling the default store view. I fixed the missing links through a script and know all seems to work as expected.

Tristan-N commented 7 years ago

@Iazel Can you tell me something more about that script that worked for you? What script did you use, where did you put it and what did it do (in short). It would help me out a lot because I don't want to save 900+ categories one by one.

I have the same problem as mentioned by @djha108 and others. Using Magento 2.1.1 with PHP 7.0.12.

Thanks!

iazel commented 7 years ago

@Tristan-N I've uploaded the script in this repo: https://github.com/Iazel/magento2-regenurl

Please notice that this wasn't meant to be published, so it is a little rough and has been developed for my specific use case ^^" However it could be useful as a starting point.

Tristan-N commented 7 years ago

@Iazel Thanks a lot! this is great! I do have some further questions for you. Can i contact you by email for example?

iazel commented 7 years ago

@Tristan-N if it something about this issue, please write it here, so everyone can benefit from it. If it is something regarding the script, then open an issue on the repo :)

Morgon commented 7 years ago

This is definitely not fixed, @oserediuk . Our base install was 2.1.0, subsequently upgraded to 2.1.2, and the Webserver Rewrites setting is not enabling SEF URLs for products.

magnetic5355 commented 7 years ago

@Iazel - nice script :) --- here are a couple issues i found.

information: all of our sef urls are stored under store_id 0 in the catalog_product_entity_varchar table

1.calling php bin/magento iazel:regenurl is the only way I have found for the script to work.

  1. calling the script deletes and regenerates the url even if it exists correctly
  2. calling php bin/magento iazel:regenurl s4 1 - deletes the rewrite for store 4, to rebuild I have to run php bin/magento iazel:regenurl which will rebuild the url for s4
iazel commented 7 years ago

calling php bin/magento iazel:regenurl is the only way I have found for the script to work.

Yep, omitting the -s option is equal to -s0

calling the script deletes and regenerates the url even if it exists correctly

Yes, the original use case was to regenerate only a well known list of products and that's why it also deletes it.

calling php bin/magento iazel:regenurl s4 1 - deletes the rewrite for store 4, to rebuild I have to run php bin/magento iazel:regenurl which will rebuild the url for s4

This is strange. It should delete AND regen the url for the provided store and product. Please note that the correct syntax is -s4 and not s4, but I'll make some test when I have some time.

miro91 commented 7 years ago

Hi @Iazel I tried your script and I can't get URL rewrite for specific product and store. I have 4 different stores and I want to generate URL's for one product for store with id = 3 php bin/magento iazel:regenurl -s3 3082 After execution I can't find record in my url_rewrite table for entity_id= 3082 and store_id = 3 for store 1 => 8 records (which seems ok because the product is related to 8 categories) for store 2 => only 1 record which is weird and for store 3 => 0 records

I also tried to run your script for store 3 for all products and only few products get new URLs

iazel commented 7 years ago

Hi @miro91 Maybe the 3082 product has "use default" in the url rewrite? All the products that use the default must be regenerated with the default store (don't use the -s option). Give a try to:

php bin/magento iazel:regenurl 3082
Gael42 commented 7 years ago

Thanks a lot @Iazel for your command, it helped me a lot.

Using it i got problem to generate correct full SEO friendly URLS in different languages, ex :

http://domain.com/categories/written/in/french/french_url_key_for_product AND http://domain.com/categories/written/in/english/english_url_key_for_product

In case it helps, I solved this issue by setting the current store via the StoreManager before calling @Iazel code, but I did it in a custom admin block code, because I didn't manage to find a good way to get the StoreManager instance in a console command context.

Executing this for every needed store permit me to have full SEO friendly urls for my main store, and my secondary language store view.

Tristan-N commented 7 years ago

@Gael42 Thank you for this update. This sounds a great way to get full and clear URL's using regenurl (which works fine) in combination with the StoreManager.

Can you give me a bit of clarification about the steps you took and how you combined StoreManager with RegenUrl? If possible a little step by step or a summary of the steps you took?

I'm not familiar with StoreManager but my colleagues are.

Kind regards

miro91 commented 7 years ago

@Tristan-N I played a little bit with the @Iazel code and add the store manager for regenerating URL's for all stores if the Magento is having multistores. I tried it on Magento with 4 stores and works great. You don't need to add any options to the command just execute php bin/magento iazel:regenurl

Here is the code for RegenerateProductUrlCommand.php

<?php    
namespace Iazel\RegenProductUrl\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use Magento\UrlRewrite\Model\UrlPersistInterface;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;

class RegenerateProductUrlCommand extends Command
{
    /**
     * @var ProductUrlRewriteGenerator
     */
    protected $productUrlRewriteGenerator;

    /**
     * @var UrlPersistInterface
     */
    protected $urlPersist;

    /**
     * @var ProductRepositoryInterface
     */
    protected $collection;

    public function __construct(
        \Magento\Framework\App\State $state,
        Collection $collection,
        ProductUrlRewriteGenerator $productUrlRewriteGenerator,
        UrlPersistInterface $urlPersist,
        StoreManagerInterface $storeManager
    ) {
        $state->setAreaCode('adminhtml');
        $this->collection = $collection;
        $this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
        $this->urlPersist = $urlPersist;
        $this->storeManager = $storeManager;
        parent::__construct();
    }

    protected function configure()
    {
        $this->setName('iazel:regenurl')
            ->setDescription('Regenerate url for given products')
            ->addArgument(
                'pids',
                InputArgument::IS_ARRAY,
                'Products to regenerate'
            )
            ->addOption(
                'store', 's',
                InputOption::VALUE_REQUIRED,
                'Use the specific Store View',
                Store::DEFAULT_STORE_ID
            )
            ;
        return parent::configure();
    }

    public function execute(InputInterface $inp, OutputInterface $out)
    {
        if ($this->storeManager->isSingleStoreMode()) {
            $stores = [$this->storeManager->getStore(0)];
        } else {
            $stores = $this->storeManager->getStores();
        }

        foreach ($stores as $store) {
            // $store_id = $inp->getOption('store');
            $store_id = $store->getId();
            $this->collection->addStoreFilter($store_id)->setStoreId($store_id);

            $pids = $inp->getArgument('pids');
            if( !empty($pids) )
                $this->collection->addIdFilter($pids);

            $this->collection->addAttributeToSelect(['url_path', 'url_key']);
            $list = $this->collection->load();
            foreach($list as $product)
            {
                $product->setStoreId($store_id);
                $this->urlPersist->deleteByData([
                    UrlRewrite::ENTITY_ID => $product->getId(),
                    UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
                    UrlRewrite::REDIRECT_TYPE => 0,
                    UrlRewrite::STORE_ID => $store_id
                ]);
                try {
                    $this->urlPersist->replace(
                        $this->productUrlRewriteGenerator->generate($product)
                    );
                }
                catch(\Exception $e) {
                    $out->writeln('<error>Duplicated url for '. $product->getId() .' store id '. $store_id .'</error>');
                }
            }
        }
    }
}

Hope this helps

kanduvisla commented 7 years ago

Thanks for your effort @miro91

In case someone needs it, here is a CLI command to re-create all (missing) URL rewrites for all categories:

<?php

namespace Vendor\Module\Console;

use Magento\Framework\Exception\AlreadyExistsException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class RegenerateUrls.php
 */
class RegenerateUrls extends Command
{
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Magento\UrlRewrite\Model\UrlRewriteFactory
     */
    protected $urlRewriteFactory;

    /**
     * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
     */
    protected $categoryCollectionFactory;

    /**
     * RegenerateUrls constructor.
     * @param \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory
     * @param string $name
     */
    public function __construct(
        \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory,
        $name = 'regenerate_category_urls'
    )
    {
        $this->urlRewriteFactory = $urlRewriteFactory;
        $this->storeManager = $storeManager;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        parent::__construct($name);
    }

    /**
     * Configure the command
     */
    protected function configure()
    {
        $this->setName('gb:regenerate_urls');
        $this->setDescription('Regenerate Url\'s for categories');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Fetch all categories:
        $categories = $this->categoryCollectionFactory->create()->addAttributeToSelect('url_key');
        /** @var \Magento\Store\Api\Data\StoreInterface $store */
        foreach ($this->storeManager->getStores() as $store)
        {
            echo $store->getCode() . ':';
            /** @var \Magento\Catalog\Model\Category $category */
            foreach ($categories as $category) {

                $urlRewrite = $this->urlRewriteFactory->create();
                $urlRewrite->addData(
                    [
                        'entity_type' => \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite::ENTITY_TYPE_CATEGORY,
                        'entity_id' => $category->getId(),
                        'request_path' => $category->getUrlKey(),
                        'target_path' => 'catalog/category/view/id/' . $category->getId(),
                        'redirect_type' => 0,
                        'store_id' => $store->getId(),
                        'description' => null,
                        'is_autogenerated' => 1,
                        'metadata' => null
                    ]
                );
                try {
                    $urlRewrite->getResource()->save($urlRewrite);
                    echo '.';
                } catch (AlreadyExistsException $alreadyExistsException) {
                    echo '-';
                }
            }
            echo "\n";
        }
    }
}

Now if someone can combine these two in one solution to rule them all that would be great.

pravalitera commented 7 years ago

@kanduvisla

I discover this morning that there is no more indexer for url, and you post that script ^^ Thx ! But I just ran it, and i have a pb.

For example i have 1 category with 2 subcategorys:

And the URLs rewritten are http://www.site.dev/category1 http://www.site.dev/subcategory1 http://www.site.dev/subcategory2

It should be http://www.site.dev/category1/subcategory1

And maybe with the suffix too

I'm looking into it too, posting if i find how to.

Anyway, thx for posting your solution.

Edit: And when i look into the observer of the CSV import, i understand now why importing a lot of category through it is very VERY slow...

Edit2: I'm writing a solution base on the block TopMenu : Get the tree of category and then do the job. i'll try to handle the "move" too...

pravalitera commented 7 years ago
// namespace Yours :) ;

use Magento\Framework\Exception\AlreadyExistsException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class RegenerateUrls.php
 */
class RegenerateUrls extends Command
{
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Magento\UrlRewrite\Model\UrlRewriteFactory
     */
    protected $urlRewriteFactory;

    /**
     * @var \Magento\Catalog\Helper\Category
     */
    protected $_categoryHelper;

    /**
     * @var \Magento\Catalog\Model\CategoryFactory
     */
    protected $_categoryFactory;

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * [category_id] = url_key
     * @var array
     */
    protected $_categoryUrlKeys = [];

    /**
     * [store_id] = category_suffix
     * @var array
     */
    protected $_category_suffixes=[];

    /**
     * RegenerateUrls constructor.
     * @param \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory
     * @param string $name
     */
    public function __construct(
        \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Helper\Category $categoryHelper,
        \Magento\Catalog\Model\CategoryFactory $categoryFactory,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        $name = 'regenerate_category_urls'
    )
    {
        $this->urlRewriteFactory = $urlRewriteFactory;
        $this->storeManager = $storeManager;
        $this->_categoryHelper = $categoryHelper;
        $this->_categoryFactory = $categoryFactory;
        $this->scopeConfig = $scopeConfig;

        parent::__construct($name);
    }

    /**
     * Configure the command
     */
    protected function configure()
    {
        $this->setName('gb:regenerate_urls');
        $this->setDescription('Regenerate Url\'s for categories');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        foreach ($this->storeManager->getStores() as $store) {
            echo $store->getCode() . ':';
            $this->_category_suffixes[$store->getId()] = $this->scopeConfig->getValue(
                'catalog/seo/category_url_suffix',
                \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
                $store->getId()
            );
            $this->getChildrenCategories($store);
            echo "\n";
        }
    }

    protected function insertIntoRewrites($id,$url_key,$store_id) {
        $urlRewrite = $this->urlRewriteFactory->create();
        $urlRewrite->addData([
                'entity_type' => \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite::ENTITY_TYPE_CATEGORY,
                'entity_id' => $id,
                'request_path' => $url_key,
                'target_path' => 'catalog/category/view/id/' .$id,
                'redirect_type' => 0,
                'store_id' => $store_id,
                'description' => null,
                'is_autogenerated' => 1,
                'metadata' => null
            ]
        );
        try {
            $urlRewrite->getResource()->save($urlRewrite);
            echo '.';
        } catch (AlreadyExistsException $alreadyExistsException) {
            echo '-';
        }

        return $this;
    }

    public function getChildrenCategories($store,$current_category = null)
    {
        if(is_null($current_category))
            $parent_id = $store->getRootCategoryId();
        else {
            $parent_id = $current_category->getId();
            $this->_categoryUrlKeys[$current_category->getId()] = $current_category->getUrlKey();
            $path_exploded = explode('/',$current_category->getPathId());
            $complete_path = [];
            foreach($path_exploded as $path){
                if($path == 1 || $path == $store->getRootCategoryId()){
                    continue;
                    }
                    $complete_path[]= $this->_categoryUrlKeys[$path];
                }

            $complete_path = implode('/',$complete_path) . $this->_category_suffixes[$store->getId()];

            $this->insertIntoRewrites($current_category->getId(),$complete_path,$store->getId());
            }

        $category = $this->_categoryFactory->create();

        $recursionLevel = 0;
        $childrenCategories = $category->getCategories($parent_id, $recursionLevel, false, false, true);
        if(count($childrenCategories)){
            foreach($childrenCategories as $child){
                $this->getChildrenCategories($store,$child);
            }
        }
        return $this;
    }

}
ps202 commented 7 years ago

What I have noticed, is that that you should delete all the product related url-rewrites from the database table, and then make this script run!

Tjitse-E commented 7 years ago

@pravalitera your script works, thanks.

Example: www.mainstore.fr/level1/level2/ <-- level 1 has the main store url value, not the store view url value. Do you experience the same problem?

pravalitera commented 7 years ago

@Tjitse-E It's because i suck :D "_categoryUrlKeys" does not contain the store_id, you will have to

something like this:

l.144 $this->_categoryUrlKeys[$store->getId().'_'.$current_category->getId()] = $current_category->getUrlKey();

and just after in the foreach.

$this->_categoryUrlKeys[$store->getId()."_".$path]

I'm close to a very much cleaner script, that rewrites product too. But i have to handle another emergency right now :(

Tjitse-E commented 7 years ago

@pravalitera i've modified the script, according your comment. The strange thing is that the regenerated url's don't have the store view value's, but the main store value's after regeneration. I'm digging in the DB now to find out more...

pravalitera commented 7 years ago

@Tjitse-E I think it's because when the table "url_rewrite" is already generated, you fall into the "catch" of the insert (duplicate). You have "-" instead of "." when you launch it.

I will repost asap with a proper script that does everything right, i'm dealing with another things right now. I'm just astonished that i have to rewrite something to reindex urls...

pravalitera commented 7 years ago

Ok. Here is a new version that rewrite products and category.

in Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;

l.109
$productCategories = $product->getCategoryCollection()
            ->addAttributeToSelect('url_key')
            ->addAttributeToSelect('url_path')
            ->setStore($storeId) // Line to add !!!
        ;

If you don't do that, the product will not have the proper category path by stores...

My new script :


/**
 * CLI Command to Reindex URLS
 *
 *
 * @author RAVALITERA Pol <pol.ravalitera@gmail.com>
 * @author Tjitse <@github>
 * @author kanduvisla <@github>
 * @author miro91 <@github>
 * @version 0.0.1
 */

namespace Afg\Core\Console\UrlRewriting;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\UrlRewrite\Model\UrlPersistInterface;
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;

/**
 * Class RegenerateUrls.php
 */
class RegenerateUrls extends Command
{
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Magento\UrlRewrite\Model\UrlRewriteFactory
     */
    protected $urlRewriteFactory;

    /**
     * @var CategoryUrlRewriteGenerator
     */
    protected $categoryUrlRewriteGenerator;

    /**
     * @var UrlPersistInterface
     */
    protected $urlPersist;

    /**
     * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
     */
    protected $categoryCollectionFactory;

    /**
     * @var \Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler
     */
    protected $urlRewriteHandler;

    /**
     * RegenerateUrls constructor.
     * @param \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory
     * @param string $name
     */
    public function __construct(
        \Magento\UrlRewrite\Model\UrlRewriteFactory $urlRewriteFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory,
        UrlPersistInterface $urlPersist,
        \Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler $urlRewriteHandler,
        $name = 'regenerate_urls'
    )
    {
        $this->urlRewriteFactory = $urlRewriteFactory;
        $this->storeManager = $storeManager;

        $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        $this->urlPersist = $urlPersist;
        $this->urlRewriteHandler = $urlRewriteHandler;

        parent::__construct($name);
    }

    /**
     * Configure the command
     */
    protected function configure()
    {
        $this->setName('afg:rewrite_url:category2');
        $this->setDescription('Regenerate Url\'s for categories');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {

        if ($this->storeManager->isSingleStoreMode()) {
            $stores = [$this->storeManager->getStore(0)];
        } else {
            $stores = $this->storeManager->getStores();
        }

        $start_time = microtime(true);
        foreach ($stores as $store) {
            $output->writeln($store->getCode().'...');
            $categories = $this->categoryCollectionFactory->create()->setStore($store->getId())
                ->addAttributeToSelect(array('url_key', 'url_path', 'is_anchor'))
                ->addAttributeToFilter('level', 2)
                ->addAttributeToFilter('parent_id',$store->getRootCategoryId())
            ;
            foreach ($categories as $category) {
                if ($category->getUrlKey()) {
                    $category->setStoreId($store->getId());
                    $urlRewrites = array_merge(
                        $this->categoryUrlRewriteGenerator->generate($category, true)
                        , $this->urlRewriteHandler->generateProductUrlRewrites($category)
                    );
                    $this->urlPersist->replace($urlRewrites);
                }
            }
        }
        $output->writeln('[DONE] ' . round(microtime(true) - $start_time,2) .' sec');
    }

}