diff --git a/src/module-elasticsuite-catalog/Model/CategoryPermissions/Filter/Provider.php b/src/module-elasticsuite-catalog/Model/CategoryPermissions/Filter/Provider.php new file mode 100644 index 000000000..238b2f286 --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/CategoryPermissions/Filter/Provider.php @@ -0,0 +1,80 @@ + + * @copyright 2025 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Model\CategoryPermissions\Filter; + +use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Query Provider for Catalog permissions + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class Provider +{ + /** + * @var \Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory + */ + private $queryFactory; + + /** + * @param \Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory $queryFactory Query Factory + */ + public function __construct(QueryFactory $queryFactory) + { + $this->queryFactory = $queryFactory; + } + + /** + * Build a clause to filter on products which are not available for the current customer group id. + * By default, the clause is a "NOT equal to -2" in legacy Magento.s + * + * @param int $customerGroupId Customer Group Id + * @param int $value Permission value + * @param string $operator Operator to use (default is mustNot because the legacy query is "is not denied") + * + * @return \Smile\ElasticsuiteCore\Search\Request\QueryInterface|null + */ + public function getQueryFilter(int $customerGroupId, int $value, string $operator = 'mustNot') : ?QueryInterface + { + $query = $this->queryFactory->create( + QueryInterface::TYPE_NESTED, + [ + 'path' => 'category_permissions', + 'query' => $this->queryFactory->create( + QueryInterface::TYPE_BOOL, + [ + 'must' => [ + $this->queryFactory->create( + QueryInterface::TYPE_TERM, + ['field' => 'category_permissions.customer_group_id', 'value' => $customerGroupId] + ), + $this->queryFactory->create( + QueryInterface::TYPE_TERM, + ['field' => 'category_permissions.permission', 'value' => $value] + ), + ], + ] + ), + ] + ); + + if ('mustNot' === $operator) { + $query = $this->queryFactory->create(QueryInterface::TYPE_NOT, ['query' => $query]); + } + + return $query; + } +} diff --git a/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryPermissions.php b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryPermissions.php new file mode 100644 index 000000000..ae6e406bf --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryPermissions.php @@ -0,0 +1,115 @@ + + * @copyright 2025 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteCatalog\Model\Product\Indexer\Fulltext\Datasource; + +use Magento\Framework\ObjectManagerInterface; +use Smile\ElasticsuiteCore\Api\Index\DatasourceInterface; + +/** + * Category Permissions data source. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class CategoryPermissions implements DatasourceInterface +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\CatalogPermissions\Model\ResourceModel\Permission\Index|null|false + */ + private $categoryPermissionsIndex; + + /** + * @var null|boolean + */ + private $isEnabled = null; + + /** + * @param \Magento\Framework\ObjectManagerInterface $objectManager Object Manager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * {@inheritDoc} + */ + public function addData($storeId, array $indexData) + { + if ($this->isEnabled() && $this->getPermissionsIndex() !== false) { + $permissionData = $this->getPermissionsIndex()->getIndexForProduct(array_keys($indexData), null, $storeId); + + foreach ($permissionData as $permission) { + $indexData[(int) $permission['product_id']]['category_permissions'][] = [ + 'customer_group_id' => (int) $permission['customer_group_id'], + 'permission' => (int) $permission['grant_catalog_category_view'], + ]; + } + } + + return $indexData ?? []; + } + + /** + * Fetch CategoryPermissions resource model, if the class exist. + * + * @return false|\Magento\CatalogPermissions\Model\ResourceModel\Permission\Index + */ + private function getPermissionsIndex() + { + if (null === $this->categoryPermissionsIndex) { + $this->categoryPermissionsIndex = false; + try { + // Class will be missing if not using Adobe Commerce. + $this->categoryPermissionsIndex = $this->objectManager->get( + \Magento\CatalogPermissions\Model\ResourceModel\Permission\Index::class + ); + } catch (\Exception $exception) { + ; // Nothing to do, it's already kinda hacky to allow this to happen. + } + } + + return $this->categoryPermissionsIndex; + } + + /** + * Check if category permissions feature is enabled. + * + * @return bool + */ + private function isEnabled() + { + if (null === $this->isEnabled) { + $this->isEnabled = false; + try { + // Class will be missing if not using Adobe Commerce. + $config = $this->objectManager->get(\Magento\CatalogPermissions\App\ConfigInterface::class); + $this->isEnabled = $config->isEnabled(); + } catch (\Exception $exception) { + ; // Nothing to do, it's already kinda hacky to allow this to happen. + } + } + + return $this->isEnabled; + } +} diff --git a/src/module-elasticsuite-catalog/Model/Product/Search/Request/Container/Filter/CategoryPermissions.php b/src/module-elasticsuite-catalog/Model/Product/Search/Request/Container/Filter/CategoryPermissions.php new file mode 100644 index 000000000..efa3e8c4b --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/Product/Search/Request/Container/Filter/CategoryPermissions.php @@ -0,0 +1,109 @@ + + * @copyright 2025 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter; + +use Smile\ElasticsuiteCore\Api\Search\Request\Container\FilterInterface; + +/** + * Category Permissions filter. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class CategoryPermissions implements FilterInterface +{ + /** + * @var \Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory + */ + private $queryFactory; + + /** + * @var \Magento\Customer\Model\Session + */ + private $customerSession; + + /** + * @var \Smile\ElasticsuiteCatalog\Model\CategoryPermissions\Filter\Provider + */ + private $categoryPermissionsFilter; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var null|boolean + */ + private $isEnabled = null; + + /** + * Search Blacklist filter constructor. + * + * @param \Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory $queryFactory Query Factory + * @param \Smile\ElasticsuiteCatalog\Model\CategoryPermissions\Filter\Provider $categoryPermissionsFilter Query Filter for Permissions + * @param \Magento\Customer\Model\Session $customerSession Customer Session + * @param \Magento\Framework\ObjectManagerInterface $objectManager Object Manager + */ + public function __construct( + \Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory $queryFactory, + \Smile\ElasticsuiteCatalog\Model\CategoryPermissions\Filter\Provider $categoryPermissionsFilter, + \Magento\Customer\Model\Session $customerSession, + \Magento\Framework\ObjectManagerInterface $objectManager + ) { + $this->queryFactory = $queryFactory; + $this->categoryPermissionsFilter = $categoryPermissionsFilter; + $this->customerSession = $customerSession; + $this->objectManager = $objectManager; + } + + /** + * {@inheritDoc} + */ + public function getFilterQuery() + { + $query = null; + + if ($this->isEnabled()) { + $query = $this->categoryPermissionsFilter->getQueryFilter( + $this->customerSession->getCustomerGroupId(), + -2 // Cannot use \Magento\CatalogPermissions\Model::PERMISSION_DENY because the class can be missing. + ); + } + + return $query; + } + + /** + * Check if category permissions feature is enabled. + * + * @return bool + */ + private function isEnabled() + { + if (null === $this->isEnabled) { + $this->isEnabled = false; + try { + // Class will be missing if not using Adobe Commerce. + $config = $this->objectManager->get(\Magento\CatalogPermissions\App\ConfigInterface::class); + $this->isEnabled = $config->isEnabled(); + } catch (\Exception $exception) { + ; // Nothing to do, it's already kinda hacky to allow this to happen. + } + } + + return $this->isEnabled; + } +} diff --git a/src/module-elasticsuite-catalog/Plugin/Search/RequestMapperPlugin.php b/src/module-elasticsuite-catalog/Plugin/Search/RequestMapperPlugin.php index 6e7b3c012..07d29e2e1 100644 --- a/src/module-elasticsuite-catalog/Plugin/Search/RequestMapperPlugin.php +++ b/src/module-elasticsuite-catalog/Plugin/Search/RequestMapperPlugin.php @@ -193,6 +193,10 @@ public function afterGetFilters( } } + // Discard Category Permissions fields in all cases. They don't exist in the mapping and cannot be handled like this. + unset($result['category_permissions_field']); + unset($result['category_permissions_value']); + return $result; } diff --git a/src/module-elasticsuite-catalog/etc/di.xml b/src/module-elasticsuite-catalog/etc/di.xml index 5c7698e8e..a0149ea2f 100644 --- a/src/module-elasticsuite-catalog/etc/di.xml +++ b/src/module-elasticsuite-catalog/etc/di.xml @@ -347,6 +347,7 @@ Smile\ElasticsuiteCatalog\Model\Product\Indexer\Fulltext\Datasource\InventoryData Smile\ElasticsuiteCatalog\Model\Product\Indexer\Fulltext\Datasource\SearchPositionData Smile\ElasticsuiteCatalog\Model\Product\Indexer\Fulltext\Datasource\IgnoreOutOfStockPositionsData + Smile\ElasticsuiteCatalog\Model\Product\Indexer\Fulltext\Datasource\CategoryPermissions Smile\ElasticsuiteCatalog\Model\Category\Indexer\Fulltext\Datasource\AttributeData @@ -402,6 +403,11 @@ Magento\Customer\Model\Session\Proxy + + + Magento\Customer\Model\Session\Proxy + + diff --git a/src/module-elasticsuite-catalog/etc/elasticsuite_indices.xml b/src/module-elasticsuite-catalog/etc/elasticsuite_indices.xml index 375b43b7e..ffb7b7949 100644 --- a/src/module-elasticsuite-catalog/etc/elasticsuite_indices.xml +++ b/src/module-elasticsuite-catalog/etc/elasticsuite_indices.xml @@ -71,6 +71,9 @@ + + + diff --git a/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml b/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml index 07742adfe..c22eb84d1 100644 --- a/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml +++ b/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml @@ -23,6 +23,7 @@ Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\Stock Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\VisibleInSearch Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\SearchBlacklist + Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\CategoryPermissions searchFilterableAttributesProvider @@ -36,6 +37,7 @@ Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\Stock Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\VisibleInSearch Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\SearchBlacklist + Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\CategoryPermissions autocompleteFilterableAttributesProvider @@ -47,6 +49,7 @@ Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\Stock Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\VisibleInCatalog Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\CurrentCategory + Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\CategoryPermissions categoryFilterableAttributesProvider @@ -61,6 +64,7 @@ Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\Stock Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\VisibleInCatalog + Smile\ElasticsuiteCatalog\Model\Product\Search\Request\Container\Filter\CategoryPermissions