From dca93e6402c66a39f256f74fff044014a1cca4b9 Mon Sep 17 00:00:00 2001 From: Pierre Gauthier Date: Fri, 8 Oct 2021 18:11:21 +0200 Subject: [PATCH] [Virtual Category] Pagebuilder Compatibility #ESP-286 --- .../Product/Fulltext/Collection.php | 11 +- .../Model/Condition/ElasticsearchBuilder.php | 44 +++++++ .../Model/Rule/WidgetCondition/Combine.php | 51 ++++++++ .../Model/Rule/WidgetCondition/Product.php | 43 +++++++ .../Collection/AddVirtualCategoryQuery.php | 65 ++++++++++ .../Plugin/Widget/ProductsListPlugin.php | 117 ++++++++++++++++++ .../etc/di.xml | 52 ++++++-- .../view/adminhtml/requirejs-config.js | 7 ++ .../conditions-data-processor-mixin.js | 93 ++++++++++++++ 9 files changed, 467 insertions(+), 16 deletions(-) create mode 100644 src/module-elasticsuite-virtual-category/Model/Condition/ElasticsearchBuilder.php create mode 100644 src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Combine.php create mode 100644 src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Product.php create mode 100644 src/module-elasticsuite-virtual-category/Plugin/Collection/AddVirtualCategoryQuery.php create mode 100644 src/module-elasticsuite-virtual-category/Plugin/Widget/ProductsListPlugin.php create mode 100644 src/module-elasticsuite-virtual-category/view/adminhtml/web/js/form/provider/conditions-data-processor-mixin.js diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php index 3ed9d21cd..5da348a29 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php @@ -334,15 +334,12 @@ public function getFacetedData($field) */ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { - $categoryId = $category; - - if (is_object($category)) { - $categoryId = $category->getId(); + $categoryId = $category->getId(); + if ($categoryId) { + $this->addFieldToFilter('category_ids', $categoryId); + $this->_productLimitationFilters['category_ids'] = $categoryId; } - $this->addFieldToFilter('category_ids', $categoryId); - $this->_productLimitationFilters['category_ids'] = $categoryId; - return $this; } diff --git a/src/module-elasticsuite-virtual-category/Model/Condition/ElasticsearchBuilder.php b/src/module-elasticsuite-virtual-category/Model/Condition/ElasticsearchBuilder.php new file mode 100644 index 000000000..df5a63ded --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Model/Condition/ElasticsearchBuilder.php @@ -0,0 +1,44 @@ + + * @copyright 2021 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Model\Condition; + +use Magento\Eav\Model\Entity\Collection\AbstractCollection; +use Magento\Rule\Model\Condition\Combine; +use Magento\Rule\Model\Condition\Sql\Builder; + +/** + * Disable SQL Builder and create elasticsearch query from condition. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + */ +class ElasticsearchBuilder extends Builder +{ + /** + * Attach conditions filter to collection + * + * @param AbstractCollection $collection Product Collection. + * @param Combine $combine Conditions. + * @return void + */ + public function attachConditionToCollection( + AbstractCollection $collection, + Combine $combine + ): void { + $query = $combine->getSearchQuery(); + $collection->addQueryFilter($query); + } +} diff --git a/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Combine.php b/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Combine.php new file mode 100644 index 000000000..571af84e5 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Combine.php @@ -0,0 +1,51 @@ + + * @copyright 2020 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +/** + * Combine product search rule conditions for pagebuilder widget. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + */ +class Combine extends \Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Combine +{ + /** + * @var string + */ + protected $elementName = 'parameters'; + + /** + * @var string + */ + protected $type = 'Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition\Combine'; + + /** + * Collect validated attributes + * + * @param Collection $collection Product collection. + * @return self + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function collectValidatedAttributes(Collection $collection) + { + // Create this empty function in order to avoid undefined error + // @see magento/module-catalog-widget/Block/Product/ProductsList.php:351. + return $this; + } +} diff --git a/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Product.php b/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Product.php new file mode 100644 index 000000000..f21992b22 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Model/Rule/WidgetCondition/Product.php @@ -0,0 +1,43 @@ + + * @copyright 2021 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Product search rule condition for pagebuilder widget. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + */ +class Product extends \Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Product +{ + /** + * @var string + */ + protected $elementName = 'parameters'; + + /** + * {@inheritDoc} + */ + public function getSearchQuery($excludedCategories = [], $virtualCategoryRoot = null): ?QueryInterface + { + // Fix some js encoding error with operators. + $this->setData('operator', htmlspecialchars_decode($this->getOperator())); + + return parent::getSearchQuery($excludedCategories, $virtualCategoryRoot); + } +} diff --git a/src/module-elasticsuite-virtual-category/Plugin/Collection/AddVirtualCategoryQuery.php b/src/module-elasticsuite-virtual-category/Plugin/Collection/AddVirtualCategoryQuery.php new file mode 100644 index 000000000..dd0e9d2a5 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Plugin/Collection/AddVirtualCategoryQuery.php @@ -0,0 +1,65 @@ + + * @copyright 2021 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Plugin\Collection; + +use Magento\Catalog\Model\Category; +use Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection; +use Smile\ElasticsuiteVirtualCategory\Model\Category\Filter\Provider; +use Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Product\CollectionFactory; + +/** + * Add virtual category query in product collection filters. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + */ +class AddVirtualCategoryQuery +{ + /** + * @var Provider + */ + private $filterProvider; + + /** + * AddVirtualCategoryQuery constructor. + * + * @param Provider $filterProvider Filter prodivder. + */ + public function __construct(Provider $filterProvider) + { + $this->filterProvider = $filterProvider; + } + + /** + * Add virtual category query in collection filters. + * + * @param Collection $subject Product collection. + * @param Category $category Category. + * @return array + */ + public function beforeAddCategoryFilter(Collection $subject, Category $category) + { + if ($category && $category->getData('is_virtual_category')) { + $query = $this->filterProvider->getQueryFilter($category); + if ($query !== null) { + $subject->addQueryFilter($query); + } + $category->setId(null); + } + + return [$category]; + } +} diff --git a/src/module-elasticsuite-virtual-category/Plugin/Widget/ProductsListPlugin.php b/src/module-elasticsuite-virtual-category/Plugin/Widget/ProductsListPlugin.php new file mode 100644 index 000000000..e0c7ce3f6 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Plugin/Widget/ProductsListPlugin.php @@ -0,0 +1,117 @@ + + * @copyright 2021 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Plugin\Widget; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\CatalogWidget\Block\Product\ProductsList; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Widget\Helper\Conditions; +use Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection; +use Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Product\CollectionFactory; + +/** + * Apply category filter on widget collection. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + */ +class ProductsListPlugin +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var Conditions + */ + private $conditionsHelper; + + /** + * ProductsListPlugin constructor. + * + * @param StoreManagerInterface $storeManager Store manager. + * @param CategoryRepositoryInterface $categoryRepository Category repository. + * @param Conditions $conditionsHelper Condition helper. + */ + public function __construct( + StoreManagerInterface $storeManager, + CategoryRepositoryInterface $categoryRepository, + Conditions $conditionsHelper + ) { + $this->storeManager = $storeManager; + $this->categoryRepository = $categoryRepository; + $this->conditionsHelper = $conditionsHelper; + } + + /** + * Fix backend preview default store. + * + * @param ProductsList $subject Widget product list. + * @return array + * @throws NoSuchEntityException + */ + public function beforeCreateCollection(ProductsList $subject) + { + $storeId = $this->storeManager->getStore()->getId(); + $subject->setData('store_id', $storeId); + return []; + } + + /** + * Apply virtual category rule on widget collection. + * + * @param ProductsList $subject Widget product list. + * @param Collection $collection Product collection. + * + * @return \Magento\Catalog\Model\ResourceModel\Product\Collection + * @throws NoSuchEntityException + */ + public function afterCreateCollection(ProductsList $subject, $collection) + { + $storeId = $this->storeManager->getStore()->getId(); + + if ($subject->getData('condition_option') == 'condition' || !$subject->getData('condition_option')) { + $conditions = $subject->getData('conditions_encoded') ?: $subject->getData('conditions'); + if ($conditions) { + $conditions = $this->conditionsHelper->decode($conditions); + } + foreach ($conditions as $condition) { + if (!empty($condition['attribute'])) { + if ($condition['attribute'] == 'category_ids') { + if (array_key_exists('value', $condition)) { + $categoryId = $condition['value']; + try { + $category = $this->categoryRepository->get($categoryId, $storeId); + $collection->addCategoryFilter($category); + } catch (NoSuchEntityException $exception) { + $category = null; + } + } + } + } + } + } + + return $collection; + } +} diff --git a/src/module-elasticsuite-virtual-category/etc/di.xml b/src/module-elasticsuite-virtual-category/etc/di.xml index c513a760d..95aac7ed8 100644 --- a/src/module-elasticsuite-virtual-category/etc/di.xml +++ b/src/module-elasticsuite-virtual-category/etc/di.xml @@ -15,7 +15,7 @@ * @license Open Software License ("OSL") v. 3.0 */ --> - + @@ -29,13 +29,13 @@ Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Combine - + Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Product - + @@ -43,7 +43,7 @@ - + @@ -51,9 +51,9 @@ - + - @@ -63,18 +63,18 @@ - + Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Product\Indexer\Fulltext\Datasource\CategoryData - + - + @@ -114,6 +114,40 @@ + + + + + + + + Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory + Smile\ElasticsuiteVirtualCategory\Model\Condition\ElasticsearchBuilder + + + + + + + Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition\Combine + + + + + Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition\Product + + + + + Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition\CombineFactory + + + + + Smile\ElasticsuiteVirtualCategory\Model\Rule\WidgetCondition\ProductFactory + + + diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/requirejs-config.js b/src/module-elasticsuite-virtual-category/view/adminhtml/requirejs-config.js index 9f8196348..ea3cffa23 100644 --- a/src/module-elasticsuite-virtual-category/view/adminhtml/requirejs-config.js +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/requirejs-config.js @@ -16,5 +16,12 @@ var config = { '*': { 'Magento_Catalog/catalog/category/assign-products': 'Smile_ElasticsuiteVirtualCategory/js/component/catalog/category/form/assign-products' } + }, + config: { + mixins: { + 'Magento_PageBuilder/js/form/provider/conditions-data-processor': { + 'Smile_ElasticsuiteVirtualCategory/js/form/provider/conditions-data-processor-mixin': true + } + } } }; diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/form/provider/conditions-data-processor-mixin.js b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/form/provider/conditions-data-processor-mixin.js new file mode 100644 index 000000000..30c8b9a81 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/form/provider/conditions-data-processor-mixin.js @@ -0,0 +1,93 @@ +/** + * 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\ElasticsuiteVirtualCategory + * @author Pierre Gauthier + * @copyright 2021 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +define([ + 'mage/utils/wrapper', + 'underscore', + 'mage/utils/objects', + 'Magento_Rule/conditions-data-normalizer' +], function (wrapper, _, objectUtils, ConditionsDataNormalizer) { + 'use strict'; + + var serializer = new ConditionsDataNormalizer(); + + // Rewrite magento condition provider in order to change used condition classes. + // @see https://github.com/magento/magento2/issues/34333 + + return function (conditionsDataProcessorFunction) { + return wrapper.wrap(conditionsDataProcessorFunction, function (conditionsDataProcessor, data, attribute) { + // add extended functionality here + var pairs = {}, + conditions = ''; + + /* + * The Condition Rule Tree is not a UI component and doesn't provide good data. + * The best solution is to implement the tree as a UI component that can provide good data but + * that is outside of the scope of the feature for now. + */ + _.each(data, function (element, key) { + // parameters is hardcoded in the Magento\Rule model that creates the HTML forms. + if (key.indexOf('parameters[' + attribute + ']') === 0) { + // Remove the bad, un-normalized data. + delete data[key]; + pairs[key] = element; + } + }); + + /* + * The Combine Condition rule needs to have children, + * if does not have, we cannot parse the rule in the backend. + */ + _.each(pairs, function (element, key) { + var keyIds = key.match(/[\d?-]+/g), + combineElement = 'Smile\\ElasticsuiteVirtualCategory\\Model\\Rule\\WidgetCondition\\Combine', + nextPairsFirstKey = 'parameters[condition_source][NEXT_ITEM--1][type]', + nextPairsSecondKey = 'parameters[condition_source][NEXT_ITEM--2][type]'; + + if (keyIds !== null && element === combineElement) { + if (pairs[nextPairsFirstKey.replace('NEXT_ITEM', keyIds[0])] === undefined || + pairs[nextPairsFirstKey.replace('NEXT_ITEM', keyIds[0])] === combineElement && + pairs[nextPairsSecondKey.replace('NEXT_ITEM', keyIds[0])] === undefined) { + pairs[key] = ''; + } + } + }); + + /* + * Add pairs in case conditions source is not rules configurator + */ + if (data['condition_option'] !== 'condition') { + pairs['parameters[' + attribute + '][1--1][operator]'] = + data[data['condition_option'] + '-condition_operator'] ? + data[data['condition_option'] + '-condition_operator'] : + '=='; + pairs['parameters[' + attribute + '][1--1][type]'] = + 'Smile\\ElasticsuiteVirtualCategory\\Model\\Rule\\WidgetCondition\\Product'; + pairs['parameters[' + attribute + '][1][aggregator]'] = 'all'; + pairs['parameters[' + attribute + '][1][new_child]'] = ''; + pairs['parameters[' + attribute + '][1][type]'] = 'Smile\\ElasticsuiteVirtualCategory\\Model\\Rule\\WidgetCondition\\Combine'; + pairs['parameters[' + attribute + '][1][value]'] = '1'; + pairs['parameters[' + attribute + '][1--1][attribute]'] = data['condition_option']; + pairs['parameters[' + attribute + '][1--1][value]'] = _.isString(data[data['condition_option']]) ? + data[data['condition_option']].trim() : + ''; + } + + if (!_.isEmpty(pairs)) { + conditions = JSON.stringify(serializer.normalize(pairs).parameters[attribute]); + data['conditions_encoded'] = conditions; + objectUtils.nested(data, attribute, conditions); + } + }); + }; +});