Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature virtual categories subtree #235

16 changes: 14 additions & 2 deletions src/module-elasticsuite-catalog/Model/Layer/Filter/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Category extends \Magento\CatalogSearch\Model\Layer\Filter\Category implem
/**
* @var \Magento\Catalog\Model\ResourceModel\Category\Collection|\Magento\Catalog\Model\Category[]
*/
private $childrenCategories;
protected $childrenCategories;

/**
* Constructor.
Expand Down Expand Up @@ -145,7 +145,7 @@ protected function _getItemsData()
'label' => $this->escaper->escapeHtml($category->getName()),
'value' => $category->getId(),
'count' => $optionsFacetedData[$category->getId()]['count'],
'url' => $category->getUrl(),
'url' => $this->getCategoryFilterUrl($category),
];

$items[] = $item;
Expand Down Expand Up @@ -237,4 +237,16 @@ protected function getDataProvider()
{
return $this->dataProvider;
}

/**
* Retrieve Category Url to build filter
*
* @param \Magento\Catalog\Api\Data\CategoryInterface $category Category.
*
* @return string
*/
protected function getCategoryFilterUrl($category)
{
return $category->getUrl();
}
}
156 changes: 156 additions & 0 deletions src/module-elasticsuite-virtual-category/Controller/Router.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php
/**
* DISCLAIMER
* Do not edit or add to this file if you wish to upgrade Smile Elastic Suite to newer
* versions in the future.
*
* @category Smile
* @package Smile\ElasticsuiteVirtualCategory
* @author Romain Ruaud <romain.ruaud@smile.fr>
* @copyright 2016 Smile
* @license Open Software License ("OSL") v. 3.0
*/
namespace Smile\ElasticsuiteVirtualCategory\Controller;

use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManagerInterface;
use Smile\ElasticsuiteVirtualCategory\Model\Url;
use Smile\ElasticsuiteVirtualCategory\Model\VirtualCategory\Root as VirtualCategoryRoot;

/**
* Router used when accessing a product via an url containing a virtual category request path.
*
* @category Smile
* @package Smile\ElasticsuiteVirtualCategory
* @author Romain Ruaud <romain.ruaud@smile.fr>
*/
class Router implements \Magento\Framework\App\RouterInterface
{
/**
* @var ActionFactory
*/
private $actionFactory;

/**
* @var StoreManagerInterface
*/
private $storeManager;

/**
* @var Url
*/
private $urlModel;

/**
* @var VirtualCategoryRoot
*/
private $virtualCategoryRoot;

/**
* Router Constructor
*
* @param ActionFactory $actionFactory Action Factory
* @param StoreManagerInterface $storeManager Store Manager
* @param Url $urlModel Url Model
* @param VirtualCategoryRoot $virtualCategoryRoot Virtual Category Root
*/
public function __construct(
ActionFactory $actionFactory,
StoreManagerInterface $storeManager,
Url $urlModel,
VirtualCategoryRoot $virtualCategoryRoot
) {
$this->actionFactory = $actionFactory;
$this->storeManager = $storeManager;
$this->urlModel = $urlModel;
$this->virtualCategoryRoot = $virtualCategoryRoot;
}

/**
* Validate and match Product or Category Page under virtual category navigation and modify request
*
* @param \Magento\Framework\App\RequestInterface $request The Request
*
* @return bool
*/
public function match(\Magento\Framework\App\RequestInterface $request)
{
$action = null;

$identifier = trim($request->getPathInfo(), '/');

$appliedRoot = $this->getAppliedVirtualCategoryRoot($identifier);

if ($appliedRoot && $appliedRoot->getId()) {
$this->virtualCategoryRoot->setAppliedRootCategory($appliedRoot);
}

$productRewrite = $this->getProductRewrite($identifier);
if ($productRewrite) {
$request->setAlias(UrlInterface::REWRITE_REQUEST_PATH_ALIAS, $productRewrite->getRequestPath());
$request->setPathInfo('/' . $productRewrite->getTargetPath());

return $this->actionFactory->create('Magento\Framework\App\Action\Forward');
}

$categoryRewrite = $this->getCategoryRewrite($identifier);
if ($categoryRewrite) {
$request->setAlias(UrlInterface::REWRITE_REQUEST_PATH_ALIAS, $identifier);
$request->setPathInfo('/' . $categoryRewrite->getTargetPath());

return $this->actionFactory->create('Magento\Framework\App\Action\Forward');
}

return $action;
}

/**
* Check if the current request could match a product.
*
* @param string $identifier Current identifier
*
* @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null
*/
private function getProductRewrite($identifier)
{
$chunks = explode('/', $identifier);
$productPath = array_pop($chunks);
$categoryPath = implode('/', $chunks);
$storeId = $this->storeManager->getStore()->getId();

return $this->urlModel->getProductRewrite($productPath, $categoryPath, $storeId);
}

/**
* Check if the current request could match a category under a virtual category subtree.
*
* @param string $identifier Current identifier
*
* @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null
*/
private function getCategoryRewrite($identifier)
{
$chunks = explode('/', $identifier);
$categoryPath = array_pop($chunks);
$storeId = $this->storeManager->getStore()->getId();

return $this->urlModel->getCategoryRewrite($categoryPath, $storeId);
}

/**
* Retrieve the current applied virtual category root.
*
* @param string $identifier Current identifier
*
* @return CategoryInterface
*/
private function getAppliedVirtualCategoryRoot($identifier)
{
$urlKeys = explode('/', $identifier);
array_pop($urlKeys);

return $this->virtualCategoryRoot->getByUrlKeys($urlKeys);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

namespace Smile\ElasticsuiteVirtualCategory\Model\Layer\Filter;

use Magento\Catalog\Api\Data\CategoryInterface;
use Smile\ElasticsuiteCore\Search\Request\BucketInterface;

/**
Expand All @@ -32,6 +33,11 @@ class Category extends \Smile\ElasticsuiteCatalog\Model\Layer\Filter\Category
*/
private $cache;

/**
* @var \Smile\ElasticsuiteVirtualCategory\Model\Url
*/
private $urlModel;

/**
* Constructor.
*
Expand All @@ -44,6 +50,7 @@ class Category extends \Smile\ElasticsuiteCatalog\Model\Layer\Filter\Category
* @param \Magento\Framework\Escaper $escaper HTML escaper.
* @param \Magento\Catalog\Model\Layer\Filter\DataProvider\CategoryFactory $dataProviderFactory Data provider.
* @param \Magento\Framework\App\CacheInterface $cache Cache.
* @param \Smile\ElasticsuiteVirtualCategory\Model\Url $urlModel Virtual Categories URL Model
* @param boolean $useUrlRewrites Uses URLs rewrite for rendering.
* @param array $data Custom data.
*/
Expand All @@ -55,6 +62,7 @@ public function __construct(
\Magento\Framework\Escaper $escaper,
\Magento\Catalog\Model\Layer\Filter\DataProvider\CategoryFactory $dataProviderFactory,
\Magento\Framework\App\CacheInterface $cache,
\Smile\ElasticsuiteVirtualCategory\Model\Url $urlModel,
$useUrlRewrites = false,
array $data = []
) {
Expand All @@ -68,6 +76,7 @@ public function __construct(
$useUrlRewrites,
$data
);
$this->urlModel = $urlModel;
$this->cache = $cache;
}

Expand Down Expand Up @@ -110,14 +119,96 @@ protected function applyCategoryFilterToCollection(\Magento\Catalog\Api\Data\Cat
return $this;
}

/**
* Retrieve currently selected category children categories.
*
* @return \Magento\Catalog\Model\ResourceModel\Category\Collection|\Magento\Catalog\Model\Category[]
*/
protected function getChildrenCategories()
{
if ($this->childrenCategories === null) {
$currentCategory = $this->getDataProvider()->getCategory();
$this->childrenCategories = $currentCategory->getChildrenCategories();
// Use the root category to retrieve children if needed.
if ($this->useVirtualRootCategorySubtree($currentCategory)) {
$this->childrenCategories = $this->getVirtualRootCategory($currentCategory)->getChildrenCategories();
$this->childrenCategories->clear()->addFieldToFilter('entity_id', ['neq' => $currentCategory->getId()]);
}
}

return $this->childrenCategories;
}

/**
* Retrieve Category Url to build filter
*
* @param \Magento\Catalog\Api\Data\CategoryInterface $childCategory Category.
*
* @return string
*/
protected function getCategoryFilterUrl($childCategory)
{
$url = parent::getCategoryFilterUrl($childCategory);

$currentCategory = $this->getDataProvider()->getCategory();

$appliedRootCategory = $this->getDataProvider()->getAppliedRootCategory();

// Use the root category to retrieve children categories Url if needed.
if ($this->useVirtualRootCategorySubtree($currentCategory)) {
$url = $this->urlModel->getVirtualCategorySubtreeUrl($currentCategory, $childCategory);
} elseif ($appliedRootCategory) {
// Occurs when navigating through the subtree of a virtual root category.
$url = $this->urlModel->getVirtualCategorySubtreeUrl($appliedRootCategory, $childCategory);
}

return $url;
}

/**
* Retrieve the Virtual Root Category of a category.
*
* @param CategoryInterface $category The category
*
* @return CategoryInterface
*/
private function getVirtualRootCategory($category)
{
$virtualRule = $category->getVirtualRule();
$rootCategory = $virtualRule->getVirtualRootCategory($category);

return $rootCategory;
}

/**
* Check if a category is configured to use its "virtual root category" to display facets
*
* @param CategoryInterface $category The category
*
* @return bool
*/
private function useVirtualRootCategorySubtree($category)
{
$rootCategory = $this->getVirtualRootCategory($category);

return ($rootCategory && $rootCategory->getId() && (bool) $category->getGenerateRootCategorySubtree());
}

/**
* List of subcategories queries by category id.
*
* @return \Smile\ElasticsuiteCore\Search\Request\QueryInterface[]
*/
private function getFacetQueries()
{
return $this->loadUsingCache('getSearchQueriesByChildren');
$category = $this->getDataProvider()->getCategory();

// Use the root category to display facets if configured this way.
if ($this->useVirtualRootCategorySubtree($category) && $this->useUrlRewrites()) {
$category = $this->getVirtualRootCategory($category);
}

return $this->loadUsingCache($category, 'getSearchQueriesByChildren');
}

/**
Expand All @@ -127,19 +218,27 @@ private function getFacetQueries()
*/
private function getFilterQuery()
{
return $this->loadUsingCache('getCategorySearchQuery');
$category = $this->getDataProvider()->getCategory();
$appliedRootCategory = $this->getDataProvider()->getAppliedRootCategory();
$categoryFilter = $this->loadUsingCache($category, 'getCategorySearchQuery');

if ($appliedRootCategory && $appliedRootCategory->getId()) {
$categoryFilter = $category->getVirtualRule()->mergeCategoryQueries([$category, $appliedRootCategory]);
}

return $categoryFilter;
}

/**
* Load data from the cache if exits. Use a callback on the current category virtual root if not yet present into the cache.
*
* @param string $callback name of the virtual rule method to be used for actual loading.
* @param CategoryInterface $category The category
* @param string $callback name of the virtual rule method to be used for actual loading.
*
* @return mixed
*/
private function loadUsingCache($callback)
private function loadUsingCache($category, $callback)
{
$category = $this->getDataProvider()->getCategory();
$cacheKey = implode('|', [$callback, $category->getStoreId(), $category->getId()]);

$data = $this->cache->load($cacheKey);
Expand Down
Loading