I hope someone comes up with a better solution, because I'm pretty sure mine if far from being perfect, sub-optimal, and VERY hacky. Anyway, when I need the most specific category related to a product, I do something like this:
$categoryIds = $product->getCategoryIds();
$collection = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect('name')
->addAttributeToFilter('entity_id', $categoryIds)
->addAttributeToFilter('is_active', 1);
$collection->getSelect()->order('level DESC')->limit(1);
$name = $collection->getFirstItem()->getName();
By sorting the categories by their level, you will get the most specific one by calling getFirstItem()
. Hopefully there's a better/easier way to do it which I'm not aware of, but this one works for sure.
I should note that, in case you have 2+ categories which are on the same level, you will get only one of them, specifically the first one which appears in the collection. The result is not so easy to predict, but it was usually good enough for what I needed to do.
If you look at the class Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator
which is responsible for the category URL rewrite generation, you will see that it uses a constant value to determine if the parent path should be added:
protected function isNeedToGenerateUrlPathForParent($category)
{
return $category->isObjectNew() || $category->getLevel() >= self::MINIMAL_CATEGORY_LEVEL_FOR_PROCESSING;
}
The value is
const MINIMAL_CATEGORY_LEVEL_FOR_PROCESSING = 3;
which means the path is generated for every category that is not a top category (level 1 is the root category) and you cannot configure this value or turn off parent path generation.
And since it is a protected method we cannot modify the return value with a plugin. Instead you will have to write a plugin for the public method getUrlPath()
that uses isNeedToGenerateUrlPathForParent()
.
I would use an around plugin which does not call the original method, essentially replacing the method:
class RemoveParentCategoryPathPlugin
{
public function aroundGetUrlPath($subject, $proceed, $category)
{
if (in_array($category->getParentId(), [Category::ROOT_CATEGORY_ID, Category::TREE_ROOT_ID])) {
return '';
}
$path = $category->getUrlPath();
if ($path !== null && !$category->dataHasChangedFor('url_key') && !$category->dataHasChangedFor('parent_id')) {
return $path;
}
$path = $category->getUrlKey();
if ($path === false) {
return $category->getUrlPath();
}
return $path;
}
}
(this is the original method, but without the parent path generation)
Plugin documentation: http://devdocs.magento.com/guides/v2.1/extension-dev-guide/plugins.html
Update existing categories
This will apply for new categories. Unfortunately the category URLs are not generated by an indexer anymore, so reindexing does not change them and the only way to regenerate them is to save each category. It could be done with a one-off script that loads a collection of all categories and calls save()
on it:
$allCategories = $categoryCollectionFactory->create()->load();
foreach ($allCategories as $category) {
$category->setDataChanges(true);
$category->save();
}
Note that this is not optimized for performance at all and will load all categories at once. So if you have thousands of categories you might want to process them in batches.
Best Answer
If you want to do this then there will be lots of issues.
The category main table is
catalog_category_entity
. You need to change the category id in its primary key fieldentity_id.
There are multiple tables that reference this key:
catalog_category_product
the EAV value tables:
catalog_category_entity_datetime
catalog_category_entity_decimal
catalog_category_entity_int
catalog_category_entity_text
catalog_category_entity_varchar
You can try it, on your risk. But please take a backup first!