src/module-elasticsuite-catalog/Plugin/Search/RequestMapperPlugin.php
<?php
/**
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
* versions in the future.
*
* @category Smile
* @package Smile\ElasticsuiteCatalog
* @author Aurelien FOUCRET <aurelien.foucret@smile.fr>
* @copyright 2020 Smile
* @license Open Software License ("OSL") v. 3.0
*/
namespace Smile\ElasticsuiteCatalog\Plugin\Search;
use Magento\Framework\Api\Search\SearchCriteriaInterface;
use Smile\ElasticsuiteCatalog\Api\LayeredNavAttributeInterface;
use Smile\ElasticsuiteCatalog\Model\Attribute\LayeredNavAttributesProvider;
use Smile\ElasticsuiteCatalog\Model\Search\Request\Field\Mapper as RequestFieldMapper;
use Smile\ElasticsuiteCore\Api\Search\Request\ContainerConfigurationInterface;
use Smile\ElasticsuiteCore\Model\Search\RequestMapper;
use Smile\ElasticsuiteCore\Search\Request\SortOrderInterface;
/**
* Apply catalog product settings to the search API request mapper.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*
* @category Smile
* @package Smile\ElasticsuiteCatalog
* @author Aurelien FOUCRET <aurelien.foucret@smile.fr>
*/
class RequestMapperPlugin
{
/**
* @var array
*/
private $productSearchContainers = [
'quick_search_container',
'catalog_view_container',
];
/**
* @var \Magento\Customer\Model\Session
*/
private $customerSession;
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var \Smile\ElasticsuiteCore\Helper\Mapping
*/
private $mappingHelper;
/**
* @var \Smile\ElasticsuiteCore\Api\Search\ContextInterface
*/
private $searchContext;
/**
* @var \Magento\Catalog\Api\CategoryRepositoryInterface
*/
private $categoryRepository;
/**
* @var RequestFieldMapper
*/
private $requestFieldMapper;
/**
* @var LayeredNavAttributesProvider
*/
protected $layeredNavAttributesProvider;
/**
* Constructor.
*
* @param \Magento\Customer\Model\Session $customerSession Customer session.
* @param \Magento\Store\Model\StoreManagerInterface $storeManager Store manager.
* @param \Smile\ElasticsuiteCore\Helper\Mapping $mappingHelper Mapping helper.
* @param \Smile\ElasticsuiteCore\Api\Search\ContextInterface $searchContext Search context.
* @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository Category Repository.
* @param RequestFieldMapper $requestFieldMapper Search request field mapper.
* @param LayeredNavAttributesProvider $layeredNavAttributesProvider Layered navigation Attributes Provider.
* @param array $productSearchContainers Product Search containers.
*/
public function __construct(
\Magento\Customer\Model\Session $customerSession,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Smile\ElasticsuiteCore\Helper\Mapping $mappingHelper,
\Smile\ElasticsuiteCore\Api\Search\ContextInterface $searchContext,
\Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
RequestFieldMapper $requestFieldMapper,
LayeredNavAttributesProvider $layeredNavAttributesProvider,
$productSearchContainers = []
) {
$this->customerSession = $customerSession;
$this->storeManager = $storeManager;
$this->mappingHelper = $mappingHelper;
$this->searchContext = $searchContext;
$this->categoryRepository = $categoryRepository;
$this->requestFieldMapper = $requestFieldMapper;
$this->layeredNavAttributesProvider = $layeredNavAttributesProvider;
if (is_array($productSearchContainers) && !empty($productSearchContainers)) {
$this->productSearchContainers = array_merge($productSearchContainers, $this->productSearchContainers);
}
}
/**
* Post process catalog sort orders.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param RequestMapper $subject Request mapper.
* @param array $result Original sort orders.
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
* @param SearchCriteriaInterface $searchCriteria Search criteria.
*
* @return array[]
*/
public function afterGetSortOrders(
RequestMapper $subject,
$result,
ContainerConfigurationInterface $containerConfiguration,
SearchCriteriaInterface $searchCriteria
) {
if ($this->isEnabled($containerConfiguration)) {
$result = $this->addDefaultSortOrders($result, $containerConfiguration);
$sortOrders = [];
foreach ($result as $sortField => $sortParams) {
if ($sortField == 'price') {
$sortParams['nestedFilter'] = ['price.customer_group_id' => $this->customerSession->getCustomerGroupId()];
} elseif ($sortField == 'position') {
$categoryId = $this->getCurrentCategoryId($containerConfiguration, $searchCriteria);
$sortParams['nestedFilter'] = ['category.category_id' => $categoryId];
}
$sortOrders[$this->getMappingField($containerConfiguration, $sortField)] = $sortParams;
}
$result = $sortOrders;
}
return $result;
}
/**
* Post process catalog filters.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param RequestMapper $subject Request mapper.
* @param array $result Original filters.
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
* @param SearchCriteriaInterface $searchCriteria Search criteria.
*
* @return array[]
*
* @SuppressWarnings(PHPMD.ElseExpression)
*/
public function afterGetFilters(
RequestMapper $subject,
$result,
ContainerConfigurationInterface $containerConfiguration,
SearchCriteriaInterface $searchCriteria
) {
if ($this->isEnabled($containerConfiguration)) {
$filters = [];
foreach ($result as $fieldName => $filterValue) {
$layeredNavAttribute = $this->layeredNavAttributesProvider->getLayeredNavAttribute($fieldName);
if ($layeredNavAttribute instanceof LayeredNavAttributeInterface) {
$fieldName = $layeredNavAttribute->getFilterField();
// Use reset to remove graphql operator.
$filters[$fieldName] = $layeredNavAttribute->getFilterQuery(reset($filterValue));
} else {
$fieldName = $this->getMappingField($containerConfiguration, $fieldName);
$filters[$fieldName] = $this->getFieldValue($containerConfiguration, $fieldName, $filterValue);
}
}
$result = $filters;
if ($containerConfiguration->getName() === 'catalog_view_container') {
$this->updateSearchContext(
$containerConfiguration->getStoreId(),
$this->getCurrentCategoryId($containerConfiguration, $searchCriteria)
);
}
}
return $result;
}
/**
* Add default sort orders according to context.
*
* @param array $sortOrders Original sort orders.
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
*
* @return array
*/
private function addDefaultSortOrders($sortOrders, $containerConfiguration)
{
if ($containerConfiguration->getName() == "catalog_view_container" && empty($sortOrders)) {
$sortOrders['position'] = ['direction' => SortOrderInterface::SORT_ASC];
}
if ($containerConfiguration->getName() == "quick_search_container" && empty($sortOrders)) {
if ($searchQuery = $this->searchContext->getCurrentSearchQuery()) {
if ($searchQuery->getId()) {
$sortOrders['search_query.position'] = [
'direction' => SortOrderInterface::SORT_ASC,
'nestedFilter' => ['search_query.query_id' => $searchQuery->getId()],
];
}
}
}
return $sortOrders;
}
/**
* Name of the field in the search engine mapping.
*
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
* @param string $fieldName Request field name.
*
* @return string
*/
private function getMappingField(ContainerConfigurationInterface $containerConfiguration, $fieldName)
{
$fieldName = $this->requestFieldMapper->getMappedFieldName($fieldName);
try {
$field = $containerConfiguration->getMapping()->getField($fieldName);
} catch (\Exception $e) {
$field = null;
}
try {
if ($field === null || $field->getType() != 'boolean') {
$optionTextFieldName = $this->mappingHelper->getOptionTextFieldName($fieldName);
$containerConfiguration->getMapping()->getField($optionTextFieldName);
$fieldName = $optionTextFieldName;
}
} catch (\Exception $e) {
;
}
return $fieldName;
}
/**
* Get field value in the proper type.
*
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
* @param string $fieldName Field name.
* @param mixed $fieldValue Field value.
*
* @return mixed
*/
private function getFieldValue(ContainerConfigurationInterface $containerConfiguration, string $fieldName, $fieldValue)
{
try {
$field = $containerConfiguration->getMapping()->getField($fieldName);
if ($field->getType() === 'boolean' && is_array($fieldValue)) {
foreach ($fieldValue as &$value) {
$value = (bool) $value;
}
}
} catch (\Exception $e) {
;
}
return $fieldValue;
}
/**
* Return current category id for the search request.
*
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
* @param SearchCriteriaInterface $searchCriteria Search criteria.
*
* @return integer
*/
private function getCurrentCategoryId(ContainerConfigurationInterface $containerConfiguration, SearchCriteriaInterface $searchCriteria)
{
if ($this->searchContext->getCurrentCategory() && $this->searchContext->getCurrentCategory()->getId()) {
return $this->searchContext->getCurrentCategory()->getId();
}
$store = $this->storeManager->getStore($containerConfiguration->getStoreId());
$categoryId = $this->storeManager->getGroup($store->getStoreGroupId())->getRootCategoryId();
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
foreach ($filterGroup->getFilters() as $filter) {
if ($filter->getField() == "category_ids") {
$categoryId = $filter->getValue();
}
}
}
return $categoryId;
}
/**
* Update the search context using current store id and category Id.
*
* @param integer $storeId Store id.
* @param integer $categoryId Category Id.
*
* @return void
*/
private function updateSearchContext($storeId, $categoryId)
{
$this->searchContext->setStoreId($storeId);
$category = $this->categoryRepository->get($categoryId, $storeId);
if ($category->getId()) {
$this->searchContext->setCurrentCategory($category);
}
}
/**
* Indicates if the plugin should be used or not.
*
* @param ContainerConfigurationInterface $containerConfiguration Container configuration.
*
* @return boolean
*/
private function isEnabled(ContainerConfigurationInterface $containerConfiguration)
{
return in_array($containerConfiguration->getName(), $this->productSearchContainers);
}
}