From 56cffc9bce9d1f57c4a3c6049a37f98f28f2a65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 7 Jun 2016 15:06:55 +0200 Subject: [PATCH 01/48] Fix LESS files for Magento 2.1-rc1 --- .../view/frontend/web/css/source/_module.less | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/module-elasticsuite-core/view/frontend/web/css/source/_module.less b/src/module-elasticsuite-core/view/frontend/web/css/source/_module.less index b1237571d..47a109f17 100644 --- a/src/module-elasticsuite-core/view/frontend/web/css/source/_module.less +++ b/src/module-elasticsuite-core/view/frontend/web/css/source/_module.less @@ -22,12 +22,12 @@ .lib-list-reset-styles(); dt { &:not(:empty) { - .lib-css(background, @autocomplete-background); - .lib-css(border, @autocomplete-border); + .lib-css(background, @autocomplete__background-color); + .lib-css(border, @autocomplete__border); border-top: 0; border-bottom: 0; } - .lib-css(border-top, @autocomplete-item-border); + .lib-css(border-top, @autocomplete-item__border); cursor: default; margin: 0; padding: @indent__xs @indent__xl @indent__xs @indent__s; @@ -39,16 +39,16 @@ } &:hover, &.selected { - .lib-css(background, @autocomplete-item-hover); + .lib-css(background, @autocomplete-item__hover__color); } } dd { &:not(:empty) { - .lib-css(background, @autocomplete-background); - .lib-css(border, @autocomplete-border); + .lib-css(background, @autocomplete__background-color); + .lib-css(border, @autocomplete__border); border-top: 0; } - .lib-css(border-top, @autocomplete-item-border); + .lib-css(border-top, @autocomplete-item__border); cursor: pointer; margin: 0; padding: @indent__xs @indent__s; @@ -60,10 +60,10 @@ } &:hover, &.selected { - .lib-css(background, @autocomplete-item-hover); + .lib-css(background, @autocomplete-item__hover__color); } .amount { - .lib-css(color, @autocomplete-item-amount-color); + .lib-css(color, @autocomplete-item-amount__color); position: absolute; right: 7px; top: @indent__xs; From 0a0d9c6c0b64bf4c7757ace02ddcb049692a601e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 7 Jun 2016 15:13:56 +0200 Subject: [PATCH 02/48] Better support for attribute sorting in ES product collection. --- .../Product/Fulltext/Collection.php | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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 a0e624cab..be575e17b 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php @@ -185,7 +185,7 @@ public function getSize() /** * {@inheritDoc} */ - public function setOrder($attribute, $dir = Select::SQL_DESC) + public function setOrder($attribute, $dir = self::SORT_ORDER_DESC) { $this->_orders[$attribute] = $dir; @@ -203,6 +203,14 @@ public function addFieldToFilter($field, $condition = null) return $this; } + /** + * {@inheritDoc} + */ + public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) + { + return $this->setOrder($attribute, $dir); + } + /** * Append a prebuilt (QueryInterface) query filter to the collection. * @@ -348,12 +356,20 @@ public function addIsInStockFilter() */ public function addSortFilterParameters($sortName, $sortField, $nestedPath = null, $nestedFilter = null) { - $this->_productLimitationFilters['sortParams'][$sortName] = [ + $sortParams = []; + + if (isset($this->_productLimitationFilters['sortParams'])) { + $sortParams = $this->_productLimitationFilters['sortParams']; + } + + $sortParams[$sortName] = [ 'sortField' => $sortField, 'nestedPath' => $nestedPath, 'nestedFilter' => $nestedFilter, ]; + $this->_productLimitationFilters['sortParams'] = $sortParams; + return $this; } From 3526ada05faa46d138ddb31d38f93393bb74e97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 7 Jun 2016 15:14:36 +0200 Subject: [PATCH 03/48] Remove object manager usage on factories (bad pattern since compile is not able to detect them). --- .../Index/Indices/Config.php | 44 ++++++++----------- .../Aggregation/AggregationFactory.php | 21 +++------ .../Search/Request/Query/QueryFactory.php | 28 +++--------- src/module-elasticsuite-core/etc/di.xml | 27 ++++++++++++ 4 files changed, 55 insertions(+), 65 deletions(-) diff --git a/src/module-elasticsuite-core/Index/Indices/Config.php b/src/module-elasticsuite-core/Index/Indices/Config.php index fda87ad32..9d0b23c8f 100644 --- a/src/module-elasticsuite-core/Index/Indices/Config.php +++ b/src/module-elasticsuite-core/Index/Indices/Config.php @@ -18,6 +18,9 @@ use Magento\Framework\Config\CacheInterface; use Magento\Framework\ObjectManagerInterface; use Smile\ElasticsuiteCore\Api\Index\Mapping\DynamicFieldProviderInterface; +use Smile\ElasticsuiteCore\Api\Index\TypeInterfaceFactory as TypeFactory; +use Smile\ElasticsuiteCore\Api\Index\MappingInterfaceFactory as MappingFactory; +use Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterfaceFactory as MappingFieldFactory; /** * ElasticSuite indices configuration; @@ -68,19 +71,28 @@ class Config extends \Magento\Framework\Config\Data /** * Instanciate config. * - * @param Reader $reader Config file reader. - * @param CacheInterface $cache Cache instance. - * @param ObjectManagerInterface $objectManager Object manager (used to instanciate several factories) - * @param string $cacheId Default config cache id. + * @param Reader $reader Config file reader. + * @param CacheInterface $cache Cache instance. + * @param ObjectManagerInterface $objectManager Object manager (used to instanciate several factories) + * @param TypeFactory $typeFactory Index type factory. + * @param MappingFactory $mappingFactory Index mapping factory. + * @param MappingFieldFactory $mappingFieldFactory Index mapping field factory. + * @param string $cacheId Default config cache id. */ public function __construct( Reader $reader, CacheInterface $cache, ObjectManagerInterface $objectManager, + TypeFactory $typeFactory, + MappingFactory $mappingFactory, + MappingFieldFactory $mappingFieldFactory, $cacheId = self::CACHE_ID ) { - $this->objectManager = $objectManager; - $this->initFactories(); + $this->typeFactory = $typeFactory; + $this->mappingFactory = $mappingFactory; + $this->mappingFieldFactory = $mappingFieldFactory; + $this->objectManager = $objectManager; + parent::__construct($reader, $cache, $cacheId); } @@ -95,26 +107,6 @@ protected function initData() $this->_data = array_map([$this, 'initIndexConfig'], $this->_data); } - /** - * Init factories used by the configuration to build types, mappings and fields objects. - * - * @return void - */ - private function initFactories() - { - $this->typeFactory = $this->objectManager->get( - 'Smile\ElasticsuiteCore\Api\Index\TypeInterfaceFactory' - ); - - $this->mappingFactory = $this->objectManager->get( - 'Smile\ElasticsuiteCore\Api\Index\MappingInterfaceFactory' - ); - - $this->mappingFieldFactory = $this->objectManager->get( - 'Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterfaceFactory' - ); - } - /** * Init type, mapping, and fields from a index configuration array. * diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationFactory.php b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationFactory.php index 9cf23d591..aeaffc413 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationFactory.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationFactory.php @@ -29,25 +29,16 @@ class AggregationFactory /** * @var array */ - private $factories = [ - BucketInterface::TYPE_TERM => 'Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\TermFactory', - BucketInterface::TYPE_HISTOGRAM => 'Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\HistogramFactory', - BucketInterface::TYPE_QUERY_GROUP => 'Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\QueryGroupFactory', - ]; - - /** - * @var ObjectManagerInterface - */ - private $objectManager; + private $factories; /** * Constructor. * - * @param ObjectManagerInterface $objectManager Object manager instance. + * @param array $factories Aggregation factories by type. */ - public function __construct(ObjectManagerInterface $objectManager) + public function __construct($factories = []) { - $this->objectManager = $objectManager; + $this->factories = $factories; } /** @@ -64,8 +55,6 @@ public function create($bucketType, $bucketParams) throw new \LogicException("No factory found for query of type {$bucketType}"); } - $factory = $this->objectManager->get($this->factories[$bucketType]); - - return $factory->create($bucketParams); + return $this->factories[$bucketType]->create($bucketParams); } } diff --git a/src/module-elasticsuite-core/Search/Request/Query/QueryFactory.php b/src/module-elasticsuite-core/Search/Request/Query/QueryFactory.php index dbc5fe9aa..58aac63ba 100644 --- a/src/module-elasticsuite-core/Search/Request/Query/QueryFactory.php +++ b/src/module-elasticsuite-core/Search/Request/Query/QueryFactory.php @@ -28,32 +28,16 @@ class QueryFactory /** * @var array */ - private $factories = [ - QueryInterface::TYPE_BOOL => 'Smile\ElasticsuiteCore\Search\Request\Query\BooleanFactory', - QueryInterface::TYPE_FILTER => 'Smile\ElasticsuiteCore\Search\Request\Query\FilteredFactory', - QueryInterface::TYPE_NESTED => 'Smile\ElasticsuiteCore\Search\Request\Query\NestedFactory', - QueryInterface::TYPE_NOT => 'Smile\ElasticsuiteCore\Search\Request\Query\NotFactory', - QueryInterface::TYPE_TERM => 'Smile\ElasticsuiteCore\Search\Request\Query\TermFactory', - QueryInterface::TYPE_TERMS => 'Smile\ElasticsuiteCore\Search\Request\Query\TermsFactory', - QueryInterface::TYPE_RANGE => 'Smile\ElasticsuiteCore\Search\Request\Query\RangeFactory', - QueryInterface::TYPE_MATCH => 'Smile\ElasticsuiteCore\Search\Request\Query\MatchFactory', - QueryInterface::TYPE_COMMON => 'Smile\ElasticsuiteCore\Search\Request\Query\CommonFactory', - QueryInterface::TYPE_MULTIMATCH => 'Smile\ElasticsuiteCore\Search\Request\Query\MultiMatchFactory', - ]; - - /** - * @var ObjectManagerInterface - */ - private $objectManager; + private $factories; /** * Constructor. * - * @param ObjectManagerInterface $objectManager Object manager. + * @param array $factories Query factories by type. */ - public function __construct(ObjectManagerInterface $objectManager) + public function __construct($factories = []) { - $this->objectManager = $objectManager; + $this->factories = $factories; } /** @@ -70,8 +54,6 @@ public function create($queryType, $queryParams) throw new \LogicException("No factory found for query of type {$queryType}"); } - $factory = $this->objectManager->get($this->factories[$queryType]); - - return $factory->create($queryParams); + return $this->factories[$queryType]->create($queryParams); } } diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index ee7a4266e..58cd5d3b6 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -56,6 +56,33 @@ + + + + Smile\ElasticsuiteCore\Search\Request\Query\BooleanFactory + Smile\ElasticsuiteCore\Search\Request\Query\FilteredFactory + Smile\ElasticsuiteCore\Search\Request\Query\NestedFactory + Smile\ElasticsuiteCore\Search\Request\Query\NotFactory + Smile\ElasticsuiteCore\Search\Request\Query\TermFactory + Smile\ElasticsuiteCore\Search\Request\Query\TermsFactory + Smile\ElasticsuiteCore\Search\Request\Query\RangeFactory + Smile\ElasticsuiteCore\Search\Request\Query\MatchFactory + Smile\ElasticsuiteCore\Search\Request\Query\CommonFactory + Smile\ElasticsuiteCore\Search\Request\Query\MultiMatchFactory + + + + + + + + Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\TermFactory + Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\HistogramFactory + Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket\QueryGroupFactory + + + + From ac48f81c85ff2bbc8a962b97db068e995c0908ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 7 Jun 2016 16:06:13 +0200 Subject: [PATCH 04/48] Fix swatches behavior in Magento 2.1-rc1 --- .../Helper/Swatches.php | 44 +++++++++++++------ .../Model/Plugin/ProductImage.php | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/module-elasticsuite-swatches/Helper/Swatches.php b/src/module-elasticsuite-swatches/Helper/Swatches.php index e33dccf62..e8a09ff29 100644 --- a/src/module-elasticsuite-swatches/Helper/Swatches.php +++ b/src/module-elasticsuite-swatches/Helper/Swatches.php @@ -12,6 +12,7 @@ */ namespace Smile\ElasticsuiteSwatches\Helper; +use Magento\Catalog\Api\Data\ProductInterface as Product; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; /** @@ -26,28 +27,45 @@ class Swatches extends \Magento\Swatches\Helper\Data { /** + * @SuppressWarnings(PHPMD.ElseExpression) * {@inheritDoc} */ - public function loadVariationByFallback($parentProduct, array $attributes) + public function loadVariationByFallback(Product $parentProduct, array $attributes) { - $parentProduct = $this->createSwatchProduct($parentProduct); - if (! $parentProduct) { - return false; - } + $variation = false; + + if ($this->isProductHasSwatch($parentProduct) && $parentProduct->getDocumentSource() !== null) { + $documentSource = $parentProduct->getDocumentSource(); + $childrenIds = isset($documentSource['children_ids']) ? $documentSource['children_ids'] : []; + + if (!empty($childrenIds)) { + $productCollection = $this->productCollectionFactory->create(); + $productCollection->addIdFilter($childrenIds); - $productCollection = $this->productCollectionFactory->create(); - $this->addFilterByParent($productCollection, $parentProduct->getId()); + $configurableAttributes = $this->getAttributesFromConfigurable($parentProduct); + $allAttributesArray = []; - $resultAttributesToFilter = $attributes; + foreach ($configurableAttributes as $attribute) { + foreach ($attribute->getOptions() as $option) { + $allAttributesArray[$attribute['attribute_code']][] = $option->getValue(); + } + } - $this->addFilterByAttributes($productCollection, $resultAttributesToFilter); + $resultAttributesToFilter = array_merge($attributes, array_diff_key($allAttributesArray, $attributes)); - $variationProduct = $productCollection->getFirstItem(); - if ($variationProduct && $variationProduct->getId()) { - return $this->productRepository->getById($variationProduct->getId()); + $this->addFilterByAttributes($productCollection, $resultAttributesToFilter); + + $variationProduct = $productCollection->getFirstItem(); + + if ($variationProduct && $variationProduct->getId()) { + $variation = $this->productRepository->getById($variationProduct->getId()); + } + } + } else { + $variation = parent::loadVariationByFallback($parentProduct, $attributes); } - return false; + return $variation; } /** diff --git a/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php b/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php index 8dedcb57c..84738f08e 100644 --- a/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php +++ b/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php @@ -52,7 +52,7 @@ protected function getFilterArray(array $request) if (in_array($code, $attributeCodes)) { $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $code); if ($attribute->getId() && $this->canReplaceImageWithSwatch($attribute)) { - $filterArray[$code] = $this->getOptionIds($attribute, $value); + $filterArray[$code][] = $this->getOptionIds($attribute, $value); } } } From 4e8c182a8ccda3c77473b19f8e09c3e4d4144f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 14 Jun 2016 05:51:17 +0200 Subject: [PATCH 05/48] Swatches optimization. --- src/module-elasticsuite-swatches/Helper/Swatches.php | 7 ++++++- .../Model/Plugin/ProductImage.php | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-swatches/Helper/Swatches.php b/src/module-elasticsuite-swatches/Helper/Swatches.php index e8a09ff29..e61dc6d09 100644 --- a/src/module-elasticsuite-swatches/Helper/Swatches.php +++ b/src/module-elasticsuite-swatches/Helper/Swatches.php @@ -39,6 +39,11 @@ public function loadVariationByFallback(Product $parentProduct, array $attribute $childrenIds = isset($documentSource['children_ids']) ? $documentSource['children_ids'] : []; if (!empty($childrenIds)) { + $childrenIds = array_map( + function($id) { return (int) $id; }, + $childrenIds + ); + $productCollection = $this->productCollectionFactory->create(); $productCollection->addIdFilter($childrenIds); @@ -47,7 +52,7 @@ public function loadVariationByFallback(Product $parentProduct, array $attribute foreach ($configurableAttributes as $attribute) { foreach ($attribute->getOptions() as $option) { - $allAttributesArray[$attribute['attribute_code']][] = $option->getValue(); + $allAttributesArray[$attribute['attribute_code']][] = (int) $option->getValue(); } } diff --git a/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php b/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php index 84738f08e..44474c763 100644 --- a/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php +++ b/src/module-elasticsuite-swatches/Model/Plugin/ProductImage.php @@ -51,6 +51,11 @@ protected function getFilterArray(array $request) foreach ($request as $code => $value) { if (in_array($code, $attributeCodes)) { $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $code); + + if (isset($filterArray[$code]) && !is_array($filterArray[$code])) { + $filterArray[$code] = [$filterArray[$code]]; + } + if ($attribute->getId() && $this->canReplaceImageWithSwatch($attribute)) { $filterArray[$code][] = $this->getOptionIds($attribute, $value); } @@ -81,7 +86,7 @@ private function getOptionIds(Attribute $attribute, $labels) foreach ($labels as $label) { foreach ($options as $option) { if ($option['label'] == $label) { - $optionIds[] = $option['value']; + $optionIds[] = (int) $option['value']; } } } From 4481a02f991a705d10c9556d111a8e4e4404ffe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 14 Jun 2016 13:20:30 +0200 Subject: [PATCH 06/48] Do not apply is_anchor to category with id 1. --- src/module-elasticsuite-catalog/Setup/InstallData.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-catalog/Setup/InstallData.php b/src/module-elasticsuite-catalog/Setup/InstallData.php index 29bdc6d8b..a54c9a96c 100644 --- a/src/module-elasticsuite-catalog/Setup/InstallData.php +++ b/src/module-elasticsuite-catalog/Setup/InstallData.php @@ -115,7 +115,7 @@ private function updateCategoryIsAnchorAttribute() { $this->eavSetup->updateAttribute(Category::ENTITY, 'is_anchor', 'frontend_input', 'hidden'); $this->eavSetup->updateAttribute(Category::ENTITY, 'is_anchor', 'source_model', null); - $this->updateAttributeDefaultValue(Category::ENTITY, 'is_anchor', 1); + $this->updateAttributeDefaultValue(Category::ENTITY, 'is_anchor', 1, [\Magento\Catalog\Model\Category::TREE_ROOT_ID]); } /** @@ -128,7 +128,7 @@ private function updateCategoryIsAnchorAttribute() * * @return void */ - private function updateAttributeDefaultValue($entityTypeId, $attributeId, $value) + private function updateAttributeDefaultValue($entityTypeId, $attributeId, $value, $excludedIds = []) { $entityTable = $this->eavSetup->getEntityType($entityTypeId, 'entity_table'); $attributeTable = $this->eavSetup->getAttributeTable($entityTypeId, $attributeId); @@ -143,6 +143,10 @@ private function updateAttributeDefaultValue($entityTypeId, $attributeId, $value [new \Zend_Db_Expr("{$attributeId} as attribute_id"), 'entity_id', new \Zend_Db_Expr("{$value} as value")] ); + if (!empty($excludedIds)) { + $entitySelect->where('entity_id NOT IN(?)', $excludedIds); + } + $insertQuery = $this->getConnection()->insertFromSelect( $entitySelect, $attributeTable, From bfda8546f4627f28921429b85567e680788c4476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 14 Jun 2016 14:34:40 +0200 Subject: [PATCH 07/48] Fix stock indexing in Magento 2.1.x --- .../Fulltext/Datasource/InventoryData.php | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php index 75f20dbee..e84ff800e 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php @@ -13,6 +13,7 @@ namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Datasource; use Magento\CatalogInventory\Api\StockRegistryInterface; +use \Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\Framework\App\ResourceConnection; use Magento\Store\Model\StoreManagerInterface; use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer; @@ -31,6 +32,12 @@ class InventoryData extends Indexer */ private $stockRegistry; + + /** + * @var \Magento\CatalogInventory\Api\StockConfigurationInterface + */ + private $stockConfiguration; + /** * @var int[] */ @@ -39,16 +46,20 @@ class InventoryData extends Indexer /** * InventoryData constructor. * - * @param ResourceConnection $resource Database adapter. - * @param StoreManagerInterface $storeManager Store manager. - * @param StockRegistryInterface $stockRegistry Stock registry. + * @param ResourceConnection $resource Database adapter. + * @param StoreManagerInterface $storeManager Store manager. + * @param StockRegistryInterface $stockRegistry Stock registry. + * @param StockConfigurationInterface $stockConfiguration Stock configuration. */ public function __construct( ResourceConnection $resource, StoreManagerInterface $storeManager, - StockRegistryInterface $stockRegistry + StockRegistryInterface $stockRegistry, + StockConfigurationInterface $stockConfiguration ) { - $this->stockRegistry = $stockRegistry; + $this->stockRegistry = $stockRegistry; + $this->stockConfiguration = $stockConfiguration; + parent::__construct($resource, $storeManager); } @@ -68,7 +79,7 @@ public function loadInventoryData($storeId, $productIds) $select = $this->getConnection()->select() ->from(['ciss' => $this->getTable('cataloginventory_stock_status')], ['product_id', 'stock_status', 'qty']) ->where('ciss.stock_id = ?', $stockId) - ->where('ciss.website_id = ?', $websiteId) + ->where('ciss.website_id = ?', $this->stockConfiguration->getDefaultScopeId()) ->where('ciss.product_id IN(?)', $productIds); return $this->getConnection()->fetchAll($select); From 5c616816a8d7c0fc605dd6396907c77edd020a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 06:28:20 +0200 Subject: [PATCH 08/48] Restore price filter in catalog product search rules. --- .../Model/Rule/Condition/Product/AttributeList.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/AttributeList.php b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/AttributeList.php index 0f4dd1dad..69c6d3d0c 100644 --- a/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/AttributeList.php +++ b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/AttributeList.php @@ -105,10 +105,17 @@ public function getAttributeCollection() if ($this->attributeCollection === null) { $this->attributeCollection = $this->attributeCollectionFactory->create(); - $mapping = $this->getMapping(); + $mapping = $this->getMapping(); + $attributeNameMapping = array_flip($this->fieldNameMapping); - $arrayNameCb = function (FieldInterface $field) { - return $field->getName(); + $arrayNameCb = function (FieldInterface $field) use ($attributeNameMapping) { + $attributeName = $field->getName(); + + if (isset($attributeNameMapping[$attributeName])) { + $attributeName = $attributeNameMapping[$attributeName]; + } + + return $attributeName; }; $attributeFilterCb = function (FieldInterface $field) use ($mapping) { From 47cd242645293d25bbeca1e70472877c69a48f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 07:15:29 +0200 Subject: [PATCH 09/48] Refactoring of the category admin form (Using new Magento 2.1 form model). --- .../templates/product/conditions.phtml | 24 +- .../js/component/catalog/product/form/rule.js | 96 ++++++ .../Catalog/Product/Form/Renderer/Sort.php | 96 ------ .../Setup/InstallData.php | 1 + .../view/adminhtml/requirejs-config.js | 21 -- .../catalog/product/form/renderer/sort.phtml | 31 -- .../adminhtml/web/css/source/_module.less | 65 +++- .../js/catalog/product/form/renderer/sort.js | 261 ---------------- .../web/js/form/element/product-sorter.js | 198 +++++++++++++ .../js/form/element/product-sorter/item.js | 70 +++++ .../catalog/product/form/renderer/sort.html | 35 --- .../template/form/element/product-sorter.html | 44 +++ .../view/base/requirejs-config.js | 21 -- .../view/base/web/js/form-listener.js | 85 ------ .../Helper/Swatches.php | 5 +- .../Category/Edit/Tab/Merchandising.php | 279 ------------------ .../Catalog/Category/VirtualRule.php | 97 ++++++ .../Adminhtml/Category/Virtual/Preview.php | 25 +- .../Attribute/Backend/VirtualRule.php | 20 +- .../Model/Preview.php | 49 +-- .../Observer/CategoryProductFormObserver.php | 64 ---- .../Catalog/Category/DataProviderPlugin.php | 126 ++++++++ .../etc/adminhtml/events.xml | 24 -- .../etc/di.xml | 5 + .../adminhtml/ui_component/category_form.xml | 141 +++++++++ .../adminhtml/web/css/source/_module.less | 69 +---- .../category/form/assigned_products.js | 77 +++++ 27 files changed, 992 insertions(+), 1037 deletions(-) create mode 100644 src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js delete mode 100644 src/module-elasticsuite-catalog/Block/Adminhtml/Catalog/Product/Form/Renderer/Sort.php delete mode 100644 src/module-elasticsuite-catalog/view/adminhtml/requirejs-config.js delete mode 100644 src/module-elasticsuite-catalog/view/adminhtml/templates/catalog/product/form/renderer/sort.phtml delete mode 100644 src/module-elasticsuite-catalog/view/adminhtml/web/js/catalog/product/form/renderer/sort.js create mode 100644 src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js create mode 100644 src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter/item.js delete mode 100644 src/module-elasticsuite-catalog/view/adminhtml/web/template/catalog/product/form/renderer/sort.html create mode 100644 src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html delete mode 100644 src/module-elasticsuite-core/view/base/requirejs-config.js delete mode 100644 src/module-elasticsuite-core/view/base/web/js/form-listener.js delete mode 100644 src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/Edit/Tab/Merchandising.php create mode 100644 src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php delete mode 100644 src/module-elasticsuite-virtual-category/Observer/CategoryProductFormObserver.php create mode 100644 src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/DataProviderPlugin.php delete mode 100644 src/module-elasticsuite-virtual-category/etc/adminhtml/events.xml create mode 100644 src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml create mode 100644 src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js diff --git a/src/module-elasticsuite-catalog-rule/view/adminhtml/templates/product/conditions.phtml b/src/module-elasticsuite-catalog-rule/view/adminhtml/templates/product/conditions.phtml index acfd54936..9fd275ad7 100644 --- a/src/module-elasticsuite-catalog-rule/view/adminhtml/templates/product/conditions.phtml +++ b/src/module-elasticsuite-catalog-rule/view/adminhtml/templates/product/conditions.phtml @@ -1,4 +1,25 @@ + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + * + * Product search rule admin form rendering. + */ +?> + +getElement(); $fieldId = ($element->getHtmlContainerId()) ? ' id="' . $element->getHtmlContainerId() . '"' : ''; $fieldClass = "field admin__field field-{$element->getId()} {$element->getCssClass()}"; @@ -17,12 +38,11 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' . $block->getUiId( - diff --git a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js new file mode 100644 index 000000000..7c145035b --- /dev/null +++ b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js @@ -0,0 +1,96 @@ +/** + * 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\ElasticsuiteCatalogRule + * @author Aurelien FOUCRET + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +define([ + 'Magento_Ui/js/form/components/html', + 'jquery', + 'MutationObserver', +], function (Component, $) { + 'use strict'; + + return Component.extend({ + defaults: { + value: {}, + links: { + value: '${ $.provider }:${ $.dataScope }' + }, + additionalClasses: "admin__fieldset" + }, + initialize: function () { + this._super(); + this.initRuleListener(); + }, + + initObservable: function () { + this._super(); + this.ruleObject = {}; + this.observe('ruleObject value'); + + return this; + }, + + initRuleListener: function () { + var observer = new MutationObserver(function () { + var rootNode = document.getElementById(this.index); + if (rootNode !== null) { + this.rootNode = document.getElementById(this.index); + observer.disconnect(); + var ruleObserver = new MutationObserver(this.updateRule.bind(this)); + var ruleObserverConfig = {childList:true, subtree: true, attributes: true}; + ruleObserver.observe(rootNode, ruleObserverConfig); + this.updateRule(); + } + }.bind(this)); + var observerConfig = {childList: true, subtree: true}; + observer.observe(document, observerConfig) + }, + + updateRule: function () { + var ruleObject = {}; + var hashValues = []; + + $(this.rootNode).find("[name*=" + this.index + "]").each(function () { + hashValues.push(this.name + this.value.toString()); + var currentRuleObject = ruleObject; + + var path = this.name.match(/\[([^[\[\]]+)\]/g) + .map(function (pathItem) { return pathItem.substr(1, pathItem.length-2); }); + + while (path.length > 1) { + var currentKey = path.shift(); + + if (currentRuleObject[currentKey] === undefined) { + currentRuleObject[currentKey] = {}; + } + + currentRuleObject = currentRuleObject[currentKey]; + } + + var currentKey = path.shift(); + currentRuleObject[currentKey] = $(this).val(); + }); + + var newHashValue = hashValues.sort().join(''); + + if (newHashValue !== this.currentHashValue) { + if (this.currentHashValue !== undefined) { + this.bubble('update', true); + } + this.currentHashValue = newHashValue; + this.ruleObject(ruleObject); + this.value(ruleObject); + } + } + }) +}); diff --git a/src/module-elasticsuite-catalog/Block/Adminhtml/Catalog/Product/Form/Renderer/Sort.php b/src/module-elasticsuite-catalog/Block/Adminhtml/Catalog/Product/Form/Renderer/Sort.php deleted file mode 100644 index 19a1e00f9..000000000 --- a/src/module-elasticsuite-catalog/Block/Adminhtml/Catalog/Product/Form/Renderer/Sort.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -namespace Smile\ElasticsuiteCatalog\Block\Adminhtml\Catalog\Product\Form\Renderer; - -use Magento\Backend\Block\Template; -use Magento\Framework\Locale\FormatInterface; -use Magento\Framework\Data\Form\Element\AbstractElement; -use Magento\Framework\Data\Form\Element\Renderer\RendererInterface; - -/** - * A generic block to allow admin having a nice product sorter with preview and drag and drop feature. - * - * @SuppressWarnings(PHPMD.CamelCasePropertyName) - * - * @category Smile - * @package Smile\ElasticsuiteCatalog - * @author Aurelien FOUCRET - */ -class Sort extends Template implements RendererInterface -{ - /** - * @var string - */ - const JS_COMPONENT = 'Smile_ElasticsuiteCatalog/js/catalog/product/form/renderer/sort'; - - /** - * @var string - */ - const JS_TEMPLATE = 'Smile_ElasticsuiteCatalog/catalog/product/form/renderer/sort'; - - /** - * @var string - */ - protected $_template = 'catalog/product/form/renderer/sort.phtml'; - - /** - * @var \Magento\Framework\Locale\FormatInterface - */ - private $localeFormat; - - /** - * Constructor. - * - * @param \Magento\Backend\Block\Template\Context $context Template context. - * @param \Magento\Framework\Locale\FormatInterface $localeFormat Locale format. - * @param array $data Additional data. - */ - public function __construct(\Magento\Backend\Block\Template\Context $context, FormatInterface $localeFormat, array $data = []) - { - parent::__construct($context, $data); - $this->localeFormat = $localeFormat; - } - - /** - * {@inheritDoc} - */ - public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) - { - $this->setElement($element); - - return $this->toHtml(); - } - - /** - * {@inheritDoc} - */ - public function getJsLayout() - { - $layoutJsComponents = []; - $layoutJsComponents['adminProductSort']['component'] = self::JS_COMPONENT; - $layoutJsComponents['adminProductSort']['config'] = [ - 'template' => self::JS_TEMPLATE, - 'loadUrl' => $this->getElement()->getLoadUrl(), - 'targetElementName' => $this->getElement()->getName(), - 'formId' => $this->getElement()->getFormId(), - 'refreshElements' => $this->getElement()->getRefreshElements(), - 'savedPositions' => $this->getElement()->getSavedPositions(), - 'pageSize' => $this->getElement()->getPageSize(), - 'priceFormat' => $this->localeFormat->getPriceFormat(), - ]; - - return json_encode(['components' => $layoutJsComponents]); - } -} diff --git a/src/module-elasticsuite-catalog/Setup/InstallData.php b/src/module-elasticsuite-catalog/Setup/InstallData.php index a54c9a96c..a7ced3815 100644 --- a/src/module-elasticsuite-catalog/Setup/InstallData.php +++ b/src/module-elasticsuite-catalog/Setup/InstallData.php @@ -125,6 +125,7 @@ private function updateCategoryIsAnchorAttribute() * @param integer|string $entityTypeId Target entity id. * @param integer|string $attributeId Target attribute id. * @param mixed $value Value to be set. + * @param array $excludedIds List of categories that should not be updated during the process. * * @return void */ diff --git a/src/module-elasticsuite-catalog/view/adminhtml/requirejs-config.js b/src/module-elasticsuite-catalog/view/adminhtml/requirejs-config.js deleted file mode 100644 index 1e6ece968..000000000 --- a/src/module-elasticsuite-catalog/view/adminhtml/requirejs-config.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 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\ElasticsuiteCatalog - * @author Aurelien FOUCRET - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -var config = { - map: { - '*': { - adminProductSorter: 'Smile_ElasticsuiteCatalog/js/catalog/product/form/renderer/sort' - } - } -}; diff --git a/src/module-elasticsuite-catalog/view/adminhtml/templates/catalog/product/form/renderer/sort.phtml b/src/module-elasticsuite-catalog/view/adminhtml/templates/catalog/product/form/renderer/sort.phtml deleted file mode 100644 index d9ea2a8d9..000000000 --- a/src/module-elasticsuite-catalog/view/adminhtml/templates/catalog/product/form/renderer/sort.phtml +++ /dev/null @@ -1,31 +0,0 @@ - - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ -?> - - - -
-
- -
-
- - diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less index 8ad695027..c76e879f4 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less @@ -1,6 +1,45 @@ +// /** +// * 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\ElasticsuiteCatalog +// * @author Aurelien FOUCRET +// * @copyright 2016 Smile +// * @license Open Software License ("OSL") v. 3.0 +// */ + + .elasticsuite-admin-product-sorter { + margin: 40px 0; + + span.title { + font-size: 1.7rem; + font-weight: 600; + letter-spacing: .025em; + } + + .bottom-links { + width: 40%; + margin: 10px auto; + border: 1px solid #cccccc; + text-align: center; + + a.show-more-link { + padding: 15px 10px; + width: 100%; + display: block; + } + } + .product-list { + + margin: 20px 40px 0; + li.product-list-item, li.product-list-item-placeholder { display: inline-block; @@ -13,7 +52,7 @@ h1 { font-size: 1.2em; font-weight: 600; - line-height: 2.05em; + line-height: 1.9em; margin: 0 0 2px; span { @@ -28,7 +67,7 @@ .info { position: relative; background: rgba(44,44,44, 0.75); - width: 192px; + width: 100%; margin-top: -31px; color: #FFF; padding: 4px; @@ -45,13 +84,10 @@ } .draggable-handle { - display: block; + &:extend(.abs-draggable-handle all); + padding: 0; float: left; - margin: 10px; - cursor: move; - float: left; - margin: 10px 9px 0px 5px; - transform: scale(1.2); + margin: 3px 0 0 0; } @@ -67,9 +103,16 @@ } } - li.product-list-item.manual-sorting { - .draggable-handle { - background: none; + .admin__actions-switch { + margin: 10px; + font-weight: bold; + color: #666666; + } + + li.product-list-item.automatic-sorting { + .draggable-handle::before { + opacity: 0; + cursor: default; } } } diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/catalog/product/form/renderer/sort.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/catalog/product/form/renderer/sort.js deleted file mode 100644 index 9eecf7e8e..000000000 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/js/catalog/product/form/renderer/sort.js +++ /dev/null @@ -1,261 +0,0 @@ -/** - * 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\ElasticsuiteCatalog - * @author Aurelien FOUCRET - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -/*jshint browser:true jquery:true*/ -/*global console*/ - -define([ - 'uiComponent', - 'jquery', - 'Smile.ES.FormListener', - 'Magento_Catalog/js/price-utils', - 'mage/translate' -], function (Component, $, FormListener, priceUtil) { - - 'use strict'; - - var Product = Component.extend({ - initialize : function () { - this._super(); - this.observe(['position']); - this.setPosition(this.data.position); - }, - - setPosition : function (position) { - if (position) { - position = parseInt(position, 10); - } - - this.position(position); - }, - - compareTo : function(product) { - var result = 0; - result = this.hasPosition() && product.hasPosition() ? this.getPosition() - product.getPosition() : 0; - result = result === 0 && this.hasPosition() ? -1 : result; - result = result === 0 && product.hasPosition() ? 1 : result; - result = result === 0 ? product.getScore() - this.getScore() : result; - result = result === 0 ? product.getId() - this.getId(): result; - - return result; - }, - - getPosition : function () { return this.position(); }, - - hasPosition : function () { return this.getPosition() !== undefined && this.getPosition() !== null; }, - - getFormattedPrice : function () { return priceUtil.formatPrice(this.data.price, this.data.priceFormat); }, - - getId : function () { return parseInt(this.data.id, 10); }, - - getScore : function () { return parseFloat(this.data.score); }, - - getImageUrl : function () { return this.data.image; }, - - getName : function () { return this.data.name; }, - - getIsInStock : function () { return Boolean(this.data['is_in_stock']) }, - - getStockLabel : function () { return this.getIsInStock() === true ? $.mage.__('In Stock') : $.mage.__('Out Of Stock'); } - }); - - var productSorterComponent = Component.extend({ - - initialize : function () { - this._super(); - - this.products = []; - this.countTotalProducts = 0; - this.currentSize = this.pageSize; - - this.addListners(); - this.observe(['products', 'countTotalProducts', 'currentSize']); - this.loadProducts(); - }, - - addListners : function () { - // Reload the product list when something change into the form. - var formListenerChangeEvent = 'formListener:' + this.targetElementName; - this.formListener = new FormListener(this.formId, formListenerChangeEvent, this.refreshElements); - $(document).bind(formListenerChangeEvent, this.loadProducts.bind(this)); - }, - - loadProducts : function () { - if (this.loadXhr) { - this.loadXhr.abort(); - } - this.loadXhr = $.post(this.loadUrl, this.getLoadParams(), this.onProductLoad.bind(this)); - }, - - getLoadParams : function() { - var formData = this.formListener.serializeArray(); - - if (Array.isArray(this.savedPositions)) { - this.savedPositions = {}; - } - - var positionedProducts = this.isLoaded ? this.getEditPositions() : this.savedPositions; - - Object.keys(positionedProducts).each(function(productId) { - formData.push({name: 'product_position[' + productId + ']', value: positionedProducts[productId]}); - }); - - formData.push({name: 'page_size', value: this.currentSize()}); - - return formData; - }, - - onProductLoad : function (loadedData) { - this.isLoaded = true; - this.products(loadedData.products.map(this.createProduct.bind(this))); - this.countTotalProducts(parseInt(loadedData.size, 10)); - this.currentSize(Math.max(this.currentSize(), this.products().length)); - this.formListener.startListener(); - - }, - - createProduct : function(productData) { - productData.priceFormat = this.priceFormat; - - if (this.products() !== undefined && this.getEditPositions()[productData.id]) { - productData.position = this.getEditPositions()[productData.id]; - } else if (this.savedPositions[productData.id]) { - productData.position = this.savedPositions[productData.id]; - } - - return new Product({data : productData}); - }, - - getSortedProducts : function () { - var products = this.products(); - products.sort(function (product1, product2) { return product1.compareTo(product2); }); - return products; - }, - - getSerializedSortOrder: function () { - return JSON.stringify(this.getEditPositions()); - }, - - getEditPositions : function() { - var serializedProductPosition = {}; - - this.products() - .filter(function (product) { - return product.hasPosition(); - }) - .each(function (product) { - serializedProductPosition[product.getId()] = product.getPosition(); - }); - return serializedProductPosition; - }, - - hasProducts: function() { - return this.products().length > 0; - }, - - hasMoreProducts: function() { - return this.products().length < this.countTotalProducts(); - }, - - showMoreProducts: function() - { - this.currentSize(this.currentSize() + this.pageSize); - this.loadProducts(); - }, - - getProductById : function (productId) { - var product = null; - productId = parseInt(productId, 10); - this.products().each(function(currentProduct) { - if (currentProduct.getId() === productId) { - product = currentProduct; - } - }); - return product; - }, - - enableSortableList: function (element, component) { - $(element).sortable({ - items : "li:not('.manual-sorting')", - helper : 'clone', - handle : '.draggable-handle', - placeholder : 'product-list-item-placeholder', - update : component.onSortUpdate.bind(component) - }); - $(element).disableSelection(); - }, - - onSortUpdate : function(event, ui) - { - var productId = ui.item.attr('data-product-id'); - var position = 1; - - var previousProductId = ui.item.prev('li.product-list-item').attr('data-product-id'); - if (previousProductId !== undefined) { - var previousProduct = this.getProductById(previousProductId); - position = parseInt(previousProduct.getPosition(), 10) + 1; - } - - this.getProductById(productId).setPosition(position); - - ui.item.nextAll('li.product-list-item').each(function (index, element) { - var currentProduct = this.getProductById(element.getAttribute('data-product-id')); - if(currentProduct.getPosition()) { - position = position + 1; - currentProduct.setPosition(position); - } - }.bind(this)) - }, - - toggleSortType: function(product) { - if (product.getPosition() !== undefined) { - var lastProduct = this.getSortedProducts()[this.products().length -1]; - if (lastProduct.hasPosition() || lastProduct.getScore() >= product.getScore()) { - this.loadProducts(); - } - product.setPosition(undefined); - if (this.savedPositions[product.getId()]) { - delete this.savedPositions[product.getId()] - } - } else { - var allPositions = this.products() - .filter(function (product) { return product.hasPosition(); }) - .map(function (product) { return product.getPosition(); }) - .concat([0]); - - var maxPosition = Math.max.apply(null, allPositions); - - product.setPosition(maxPosition + 1); - } - }, - - getAutomaticSortLabel : function () { - return $.mage.__('Automatic Sort'); - }, - - getManualSortLabel : function () { - return $.mage.__('Manual Sort'); - }, - - getShowMoreLabel : function () { - return $.mage.__('Show more'); - }, - - getEmptyListMessage : function() { - return $.mage.__('Your product selection is empty.'); - } - }); - - return productSorterComponent; -}); \ No newline at end of file diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js new file mode 100644 index 000000000..cc0c51c82 --- /dev/null +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js @@ -0,0 +1,198 @@ +/** + * 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\ElasticsuiteCatalog + * @author Aurelien FOUCRET + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +define([ + 'Magento_Ui/js/form/element/abstract', + 'jquery', + 'Smile_ElasticsuiteCatalog/js/form/element/product-sorter/item', + 'MutationObserver', +], function (Component, $, Product) { + 'use strict'; + + return Component.extend({ + defaults: { + template: "Smile_ElasticsuiteCatalog/form/element/product-sorter", + refreshFields: {}, + maxRefreshInterval: 500, + imports: { + formData: "${ $.provider }:data" + }, + messages : { + blockTitle : $.mage.__('Product Preview and Sorting'), + emptyText : $.mage.__('Your product selection is empty.'), + automaticSort : $.mage.__('Automatic Sort'), + manualSort : $.mage.__('Manual Sort'), + showMore : $.mage.__('Show more') + } + }, + + initialize: function () + { + this.updateImports(arguments[0]); + this._super(); + + this.editPositions = JSON.parse(this.value()); + this.products = []; + this.countTotalProducts = 0; + this.pageSize = parseInt(this.pageSize, 10); + this.currentSize = this.pageSize; + + this.observe(['products', 'countTotalProducts', 'currentSize', 'editPositions']); + + this.editPositions.subscribe(function () { this.value(JSON.stringify(this.editPositions())); }.bind(this)); + }, + + updateImports: function (config) { + Object.keys(config.refreshFields).each (function (fieldName) { + fieldName = '${ $.provider }:data.' + fieldName; + + if (config.listens === undefined) { + config.listens = {} + } + config.listens[fieldName] = "refreshProductList"; + }); + }, + + refreshProductList: function () { + if (this.refreshRateLimiter !== undefined) { + clearTimeout(); + } + + this.refreshRateLimiter = setTimeout(function () { + console.log('Refresh list'); + var formData = this.formData; + + Object.keys(this.editPositions()).each(function (productId) { + formData['product_position[' + productId + ']'] = this.editPositions()[productId]; + }.bind(this)); + + formData['page_size'] = this.currentSize(); + + this.loadXhr = $.post(this.loadUrl, this.formData, this.onProductListLoad.bind(this)); + }.bind(this), this.maxRefreshInterval); + }, + + onProductListLoad: function (loadedData) { + var products = this.sortProduct(loadedData.products.map(this.createProduct.bind(this))); + this.products(products); + this.countTotalProducts(parseInt(loadedData.size, 10)); + this.currentSize(Math.max(this.currentSize(), this.products().length)); + }, + + createProduct: function (productData) { + productData.priceFormat = this.priceFormat; + if (this.editPositions()[productData.id]) { + productData.position = this.editPositions()[productData.id]; + } + return new Product({data : productData}); + }, + + hasProducts: function () { + return this.products().length > 0; + }, + + hasMoreProducts: function () { + return this.products().length < this.countTotalProducts(); + }, + + showMoreProducts: function () { + this.currentSize(this.currentSize() + this.pageSize); + this.refreshProductList(); + }, + + sortProduct : function (products) { + products.sort(function (product1, product2) { return product1.compareTo(product2); }); + return products; + }, + + getProductById : function (productId) { + var product = null; + productId = parseInt(productId, 10); + this.products().each(function (currentProduct) { + if (currentProduct.getId() === productId) { + product = currentProduct; + } + }); + return product; + }, + + enableSortableList: function (element, component) { + $(element).sortable({ + items : "li:not(.automatic-sorting)", + helper : 'clone', + handle : '.draggable-handle', + placeholder : 'product-list-item-placeholder', + update : component.onSortUpdate.bind(component) + }); + $(element).disableSelection(); + }, + + onSortUpdate : function (event, ui) + { + var productId = ui.item.attr('data-product-id'); + var position = 1; + var products = this.products(); + var editPositions = this.editPositions(); + + var previousProductId = ui.item.prev('li.product-list-item').attr('data-product-id'); + if (previousProductId !== undefined) { + var previousProduct = this.getProductById(previousProductId); + position = parseInt(previousProduct.getPosition(), 10) + 1; + } + + this.getProductById(productId).setPosition(position); + editPositions[productId] = position; + + ui.item.nextAll('li.product-list-item').each(function (index, element) { + var currentProduct = this.getProductById(element.getAttribute('data-product-id')); + if(currentProduct.getPosition()) { + position = position + 1; + currentProduct.setPosition(position); + editPositions[currentProduct.getId()] = position; + } + }.bind(this)) + + this.products(this.sortProduct(products)); + this.editPositions(editPositions); + }, + + toggleSortType: function (product) { + var products = this.products(); + var editPositions = this.editPositions(); + + if (product.getPosition() !== undefined) { + var lastProduct = products[products.length -1]; + if (lastProduct.hasPosition() || lastProduct.getScore() >= product.getScore()) { + this.refreshProductList(); + } + product.setPosition(undefined); + if (editPositions[product.getId()]) { + delete editPositions[product.getId()]; + } + } else { + var allPositions = products + .filter(function (product) { return product.hasPosition(); }) + .map(function (product) { return product.getPosition(); }) + .concat([0]); + + var maxPosition = Math.max.apply(null, allPositions); + editPositions[product.getId()] = maxPosition + 1; + product.setPosition(maxPosition + 1); + } + + this.products(this.sortProduct(products)); + this.editPositions(editPositions); + }, + }); +}); diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter/item.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter/item.js new file mode 100644 index 000000000..ae9633855 --- /dev/null +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter/item.js @@ -0,0 +1,70 @@ +/** + * 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\ElasticsuiteCatalog + * @author Aurelien FOUCRET + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +define([ + 'uiComponent', + 'jquery', + 'Magento_Catalog/js/price-utils', + 'mage/translate' +], function (Component, $, priceUtil) { + + 'use strict'; + + + return Component.extend({ + initialize : function () { + this._super(); + this.observe(['position']); + this.setPosition(this.data.position); + }, + + setPosition : function (position) { + if (position) { + position = parseInt(position, 10); + } + + this.position(position); + }, + + compareTo : function(product) { + var result = 0; + result = this.hasPosition() && product.hasPosition() ? this.getPosition() - product.getPosition() : 0; + result = result === 0 && this.hasPosition() ? -1 : result; + result = result === 0 && product.hasPosition() ? 1 : result; + result = result === 0 ? product.getScore() - this.getScore() : result; + result = result === 0 ? product.getId() - this.getId(): result; + + return result; + }, + + getPosition : function () { return this.position(); }, + + hasPosition : function () { return this.getPosition() !== undefined && this.getPosition() !== null; }, + + getFormattedPrice : function () { return priceUtil.formatPrice(this.data.price, this.data.priceFormat); }, + + getId : function () { return parseInt(this.data.id, 10); }, + + getScore : function () { return parseFloat(this.data.score); }, + + getImageUrl : function () { return this.data.image; }, + + getName : function () { return this.data.name; }, + + getIsInStock : function () { return Boolean(this.data['is_in_stock']) }, + + getStockLabel : function () { return this.getIsInStock() === true ? $.mage.__('In Stock') : $.mage.__('Out Of Stock'); } + }); + +}); diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/template/catalog/product/form/renderer/sort.html b/src/module-elasticsuite-catalog/view/adminhtml/web/template/catalog/product/form/renderer/sort.html deleted file mode 100644 index af4c869fb..000000000 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/template/catalog/product/form/renderer/sort.html +++ /dev/null @@ -1,35 +0,0 @@ - - -
-

-
- -
-
    -
  • - -
    - -

    - -
    - -
    -

    -

    -
    - -
    - - -
    - -
  • -
- -

- -

-
\ No newline at end of file diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html new file mode 100644 index 000000000..7a5806054 --- /dev/null +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html @@ -0,0 +1,44 @@ +
+ + + + + +
+

+
+ +
+ +
    +
  • +
    + +

    + +
    + +
    +

    +

    +
    + +
    + + +
    +
  • +
+ + +
+
diff --git a/src/module-elasticsuite-core/view/base/requirejs-config.js b/src/module-elasticsuite-core/view/base/requirejs-config.js deleted file mode 100644 index c7e83c853..000000000 --- a/src/module-elasticsuite-core/view/base/requirejs-config.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 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\ElasticsuiteCatalog - * @author Aurelien FOUCRET - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -var config = { - map: { - '*': { - 'Smile.ES.FormListener': 'Smile_ElasticsuiteCore/js/form-listener' - } - } -}; diff --git a/src/module-elasticsuite-core/view/base/web/js/form-listener.js b/src/module-elasticsuite-core/view/base/web/js/form-listener.js deleted file mode 100644 index 2ee77a134..000000000 --- a/src/module-elasticsuite-core/view/base/web/js/form-listener.js +++ /dev/null @@ -1,85 +0,0 @@ -define(["jquery"], function ($) { - - var FormListener = function (formId, formChangedEvent, listenFormElements) { - this.form = $('#' + formId); - this.formChangedEvent = formChangedEvent; - this.listenFormElements = listenFormElements; - }; - - FormListener.prototype.startListener = function () { - if (this.timer === undefined || this.timer === null) { - this.hash = this.getFormHash(); - this.timer = setInterval(this.detectChanges.bind(this), 1000); - } - }; - - FormListener.prototype.stopListener = function () { - if (this.timer === undefined || this.timer === null) { - clearInterval(this.timer); - delete this.timer; - } - }; - - FormListener.prototype.getFormHash = function () { - var serializedElements = this.form.serializeArray(); - - var filterElementFunction = this.getFilterFunction(); - if (filterElementFunction) { - serializedElements = serializedElements.filter(filterElementFunction); - } - - return serializedElements.map(function (formElement) { return formElement.name + formElement.value; }).join('|'); - } - - FormListener.prototype.getFilterFunction = function () { - var filterFunction = null; - - var isElementTargeted = function (elementName, targetName) { - var isElementTargeted = elementName === targetName; - if (targetName.match(/.*\[.*\]$/)) { - isElementTargeted = elementName.startsWith(targetName); - } - return isElementTargeted; - } - - if (Object.prototype.toString.call(this.listenFormElements) === '[object Array]') { - filterFunction = function(element) { - var addElement = false; - for (var i = 0; i < this.listenFormElements.length; i++) { - addElement = addElement || isElementTargeted(element.name, this.listenFormElements[i]); - } - return addElement; - }; - } else if (typeof this.listenFormElements === 'string') { - filterFunction = function (element) { - return isElementTargeted(element.name, this.listenFormElements); - }; - } else if (typeof this.listenFormElements === 'object') { - filterFunction = function (element) { - return this.listenFormElements.name === element.name; - }; - } - - return filterFunction.bind(this); - }; - - FormListener.prototype.detectChanges = function () { - var currentHash = this.getFormHash(); - - if (currentHash !== this.hash) { - $(document).trigger(this.formChangedEvent, [this.form]); - } - - this.hash = currentHash; - } - - FormListener.prototype.serialize = function () { - return this.form.serialize(); - } - - FormListener.prototype.serializeArray = function () { - return this.form.serializeArray(); - } - - return FormListener; -}); \ No newline at end of file diff --git a/src/module-elasticsuite-swatches/Helper/Swatches.php b/src/module-elasticsuite-swatches/Helper/Swatches.php index e61dc6d09..9d1afd3b6 100644 --- a/src/module-elasticsuite-swatches/Helper/Swatches.php +++ b/src/module-elasticsuite-swatches/Helper/Swatches.php @@ -39,10 +39,7 @@ public function loadVariationByFallback(Product $parentProduct, array $attribute $childrenIds = isset($documentSource['children_ids']) ? $documentSource['children_ids'] : []; if (!empty($childrenIds)) { - $childrenIds = array_map( - function($id) { return (int) $id; }, - $childrenIds - ); + $childrenIds = array_map('intval', $childrenIds); $productCollection = $this->productCollectionFactory->create(); $productCollection->addIdFilter($childrenIds); diff --git a/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/Edit/Tab/Merchandising.php b/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/Edit/Tab/Merchandising.php deleted file mode 100644 index 9994f93fa..000000000 --- a/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/Edit/Tab/Merchandising.php +++ /dev/null @@ -1,279 +0,0 @@ - - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ -namespace Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category\Edit\Tab; - -use Magento\Catalog\Model\Category; -use Magento\Backend\Block\Template; -use Magento\Config\Model\Config\Source\Yesno; - -/** - * Category edit merchandising tab form implementation. - * - * @category Smile - * @package Smile\ElasticsuiteVirtualCategory - * @author Aurelien FOUCRET - */ -class Merchandising extends \Magento\Catalog\Block\Adminhtml\Form -{ - /** - * @var integer - */ - const DEFAULT_PREVIEW_SIZE = 20; - - /** - * @var Category|null - */ - private $category; - - /** - * @var \Magento\Config\Model\Config\Source\Yesno - */ - private $booleanSource; - - /** - * @var \Magento\CatalogRule\Model\RuleFactory - */ - private $ruleFactory; - - /** - * @var integer - */ - private $previewSize; - - /** - * @var \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position - */ - private $productPositionResource; - - /** - * Constructor. - * - * @param \Magento\Backend\Block\Template\Context $context Template context. - * @param \Magento\Framework\Registry $registry Registry (used to read current category) - * @param \Magento\Framework\Data\FormFactory $formFactory Form factory. - * @param \Magento\Config\Model\Config\Source\Yesno $booleanSource Data source for boolean fields. - * @param \Smile\ElasticsuiteCatalogRule\Model\RuleFactory $ruleFactory Catalog product rule factory. - * @param \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position $productPositionResource Product position loading resource. - * @param integer $previewSize Preview size. - * @param array $data Additional data. - */ - public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Framework\Registry $registry, - \Magento\Framework\Data\FormFactory $formFactory, - \Magento\Config\Model\Config\Source\Yesno $booleanSource, - \Smile\ElasticsuiteCatalogRule\Model\RuleFactory $ruleFactory, - \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position $productPositionResource, - $previewSize = self::DEFAULT_PREVIEW_SIZE, - array $data = [] - ) { - parent::__construct($context, $registry, $formFactory, $data); - - $this->booleanSource = $booleanSource; - $this->ruleFactory = $ruleFactory; - $this->productPositionResource = $productPositionResource; - $this->previewSize = $previewSize; - } - - /** - * Return currently edited category - * - * @return \Magento\Catalog\Model\Category - */ - public function getCategory() - { - if (!$this->category) { - $this->category = $this->_coreRegistry->registry('category'); - } - - return $this->category; - } - - /** - * @SuppressWarnings(PHPMD.CamelCaseMethodName) - * {@inheritDoc} - */ - protected function _prepareLayout() - { - - /** @var \Magento\Framework\Data\Form $form */ - $form = $this->_formFactory->create(); - $form->setDataObject($this->getCategory()); - - $this->addCategoryMode($form) - ->addVirtualCategorySettings($form) - ->addProductSorter($form) - ->addDependenceManager(); - - $form->addValues($this->getCategory()->getData()); - $form->setFieldNameSuffix('general'); - - $this->addFieldRenderers($form); - - $this->setForm($form); - - return parent::_prepareLayout(); - } - - /** - * Append the category mode selector. - * - * @param \Magento\Framework\Data\Form $form Current form. - * - * @return $this - */ - private function addCategoryMode(\Magento\Framework\Data\Form $form) - { - $fieldset = $form->addFieldset('merchandising_category_mode_fieldset', ['legend' => __('Category mode')]); - - $booleanSelectValues = $this->booleanSource->toOptionArray(); - $categoryModeFieldOptions = ['name' => 'is_virtual_category', 'label' => __('Virtual category'), 'values' => $booleanSelectValues]; - $fieldset->addField('is_virtual_category', 'select', $categoryModeFieldOptions); - - return $this; - } - - /** - * Append settings related to a virtual category (category root and rule applied). - * - * @param \Magento\Framework\Data\Form $form Current form. - * - * @return $this - */ - private function addVirtualCategorySettings(\Magento\Framework\Data\Form $form) - { - $fieldset = $form->addFieldset('merchandising_virtual_settings_fieldset', ['legend' => __('Virtual category settings')]); - - // This field is added to manage fieldset dependence to the "is_virtual_category" field. - // @see self::addDependenceManager for more additional information. - $fieldset->addField('virtual_rule_fieldset_visibility_switcher', 'hidden', ['name' => 'virtual_rule_fieldset_visibility_switcher']); - - // Append the virtual rule conditions field. - $fieldset->addField('virtual_rule', 'text', ['name' => 'virtual_rule', 'label' => __('Virtual rule')]); - - // Create the virtual category root selector field. - $categoryChooserFieldOptions = ['name' => 'virtual_category_root', 'label' => __('Virtual category root')]; - $fieldset->addField('virtual_category_root', 'label', $categoryChooserFieldOptions); - - return $this; - } - - /** - * - * @param \Magento\Framework\Data\Form $form Current form. - * - * @return $this - */ - private function addProductSorter(\Magento\Framework\Data\Form $form) - { - $fieldset = $form->addFieldset('merchandising_product_sort_fieldset', ['legend' => __('Preview and sorting')]); - - $fieldset->addField('sorted_products', 'text', ['name' => 'sorted_products']); - - return $this; - } - - /** - * Append renderers to the form. - * - * Note : This is called AFTER calling $form->addValues since the category chooser field renderer is not a - * real renderer and is not applied when the form is rendered but at build time => we need the values are set. - * - * @param \Magento\Framework\Data\Form $form Current form. - * - * @return $this - */ - private function addFieldRenderers(\Magento\Framework\Data\Form $form) - { - // Append the virtual conditions rule renderer. - $virtualRuleField = $form->getElement('virtual_rule'); - $virtualRuleRenderer = $this->getLayout()->createBlock('Smile\ElasticsuiteCatalogRule\Block\Product\Conditions'); - $virtualRuleField->setRenderer($virtualRuleRenderer); - - // Append the virtual category root chooser. - $categoryChooserField = $form->getElement('virtual_category_root'); - $categoryChooserRenderer = $this->getLayout()->createBlock('Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser'); - $categoryChooserRenderer->setFieldsetId($form->getElement('merchandising_virtual_settings_fieldset')->getId()) - ->setConfig(['buttons' => ['open' => __('Select category ...')]]); - $categoryChooserRenderer->prepareElementHtml($categoryChooserField); - - $productSortField = $form->getElement('sorted_products'); - $productSortField->setLoadUrl($this->getPreviewUrl()) - ->setFormId('category_edit_form') - ->setRefreshElements([ - 'category_products', - $form->getElement('is_virtual_category')->getName(), - $form->getElement('virtual_rule')->getName(), - $form->getElement('virtual_category_root')->getName(), - ]) - ->setSavedPositions($this->getProductSavedPositions()) - ->setPageSize($this->previewSize); - - $productSortRenderer = $this->getLayout()->createBlock('Smile\ElasticsuiteCatalog\Block\Adminhtml\Catalog\Product\Form\Renderer\Sort'); - $productSortField->setRenderer($productSortRenderer); - - return $this; - } - - /** - * Apply depedence manegemnt on the form. - * - * Due to the difficulty to manage dependencies between the multiple fieldset we hacked the mechanisms by using an - * arbitary chosen dummy field with a predictable id container to get things working. - * - * @return $this - */ - private function addDependenceManager() - { - $dependenceManagerBlock = $this->getLayout()->createBlock('Magento\Backend\Block\Widget\Form\Element\Dependence'); - - $dependenceManagerBlock->addConfigOptions(['levels_up' => 0]) - ->addFieldMap('is_virtual_category', 'is_virtual_category') - ->addFieldMap('virtual_rule_fieldset_visibility_switcher', 'virtual_rule_fieldset_visibility_switcher') - ->addFieldDependence('virtual_rule_fieldset_visibility_switcher', 'is_virtual_category', 1); - - $this->setChild('form_after', $dependenceManagerBlock); - - return $this; - } - - /** - * Return the product list preview URL. - * - * @return string - */ - private function getPreviewUrl() - { - $storeId = $this->getCategory()->getStoreId(); - - if ($storeId === 0) { - $storeId = current(array_filter($this->getCategory()->getStoreIds())); - } - - $urlParams = ['ajax' => true, 'store' => $storeId]; - - return $this->getUrl('virtualcategory/category_virtual/preview', $urlParams); - } - - /** - * Load product saved positions for the current category. - * - * @return array - */ - private function getProductSavedPositions() - { - return $this->productPositionResource->getProductPositionsByCategory($this->getCategory()); - } -} diff --git a/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php b/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php new file mode 100644 index 000000000..be3681fe7 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php @@ -0,0 +1,97 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category; + +/** + * Create the virtual rule edit field in the category edit form. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Aurelien FOUCRET + */ +class VirtualRule extends \Magento\Backend\Block\AbstractBlock +{ + /** + * @var \Magento\Catalog\Model\Category\DataProvider + */ + private $dataProvider; + + /** + * @var \Magento\Framework\Registry + */ + private $registry; + + /** + * Constructor. + * + * @param \Magento\Backend\Block\Context $context Block context. + * @param \Magento\Framework\Data\FormFactory $formFactory Form factory. + * @param \Magento\Framework\Registry $registry Registry. + * @param array $data Additional data. + */ + public function __construct( + \Magento\Backend\Block\Context $context, + \Magento\Framework\Data\FormFactory $formFactory, + \Magento\Framework\Registry $registry, + array $data = [] + ) { + $this->formFactory = $formFactory; + $this->registry = $registry; + parent::__construct($context, $data); + } + + /** + * @SuppressWarnings(PHPMD.CamelCaseMethodName) + * {@inheritDoc} + */ + protected function _toHtml() + { + return $this->escapeJsQuote($this->getForm()->toHtml()); + } + + /** + * Returns the currently edited category. + * + * @return \Magento\Catalog\Model\Category + */ + private function getCategory() + { + return $this->registry->registry('category'); + } + + /** + * Create the form containing the virtual rule field. + * + * @return \Magento\Framework\Data\Form + */ + private function getForm() + { + $form = $this->formFactory->create(); + $form->setHtmlId('virtual_rule'); + + $virtualRuleField = $form->addField( + 'virtual_rule', + 'text', + ['name' => 'virtual_rule', 'label' => __('Virtual rule'), 'container_id' => 'virtual_rule'] + ); + + $virtualRuleField->setValue($this->getCategory()->getVirtualRule()); + $virtualRuleRenderer = $this->getLayout()->createBlock('Smile\ElasticsuiteCatalogRule\Block\Product\Conditions'); + $virtualRuleField->setRenderer($virtualRuleRenderer); + + return $form; + } +} diff --git a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php index e6ecee106..7e524d7e2 100644 --- a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php +++ b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php @@ -38,27 +38,27 @@ class Preview extends Action private $jsonHelper; /** - * @var \Magento\Catalog\Api\CategoryRepositoryInterface + * @var \Magento\Catalog\Model\CategoryFactory */ - private $categoryRepository; + private $categoryFactory; /** * Constructor. * * @param \Magento\Backend\App\Action\Context $context Controller context. * @param \Smile\ElasticsuiteVirtualCategory\Model\PreviewFactory $previewModelFactory Preview model factory. - * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository Category repository. + * @param \Magento\Catalog\Model\CategoryFactory $categoryFactory Category factory. * @param \Magento\Framework\Json\Helper\Data $jsonHelper JSON Helper. */ public function __construct( \Magento\Backend\App\Action\Context $context, \Smile\ElasticsuiteVirtualCategory\Model\PreviewFactory $previewModelFactory, - \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository, + \Magento\Catalog\Model\CategoryFactory $categoryFactory, \Magento\Framework\Json\Helper\Data $jsonHelper ) { parent::__construct($context); - $this->categoryRepository = $categoryRepository; + $this->categoryFactory = $categoryFactory; $this->previewModelFactory = $previewModelFactory; $this->jsonHelper = $jsonHelper; } @@ -103,18 +103,21 @@ private function getPreviewObject() */ private function getCategory() { - $storeId = $this->getRequest()->getParam('store'); - $category = $this->categoryRepository->get($this->getRequest()->getParam('id'), $storeId); + $storeId = $this->getRequest()->getParam('store'); + $categoryId = $this->getRequest()->getParam('entity_id'); - $categoryProductIds = $this->jsonHelper->jsonDecode($this->getRequest()->getParam('category_products')); - $category->setProductIds(array_keys($categoryProductIds)); + $category = $this->categoryFactory->create()->setStoreId($storeId)->load($categoryId); - $categoryPostData = $this->getRequest()->getParam('general', []); + $selectedProducts = $this->getRequest()->getParam('selected_products', []); + $category->setAddedProductIds(isset($selectedProducts['added_products']) ? $selectedProducts['added_products'] : []); + $category->setDeletedProductIds(isset($selectedProducts['deleted_products']) ? $selectedProducts['deleted_products'] : []); + + $categoryPostData = $this->getRequest()->getParams(); $isVirtualCategory = isset($categoryPostData['is_virtual_category']) ? (bool) $categoryPostData['is_virtual_category'] : false; + $category->setIsVirtualCategory($isVirtualCategory); if ($isVirtualCategory) { - $category->setIsVirtualCategory($isVirtualCategory); $category->getVirtualRule()->loadPost($categoryPostData['virtual_rule']); $category->setVirtualCategoryRoot($categoryPostData['virtual_category_root']); } diff --git a/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php b/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php index 2b3645d47..8bdec266b 100644 --- a/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php +++ b/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php @@ -68,16 +68,22 @@ public function afterLoad($object) $attributeCode = $this->getAttributeCode(); $attributeData = $object->getData($attributeCode); - $rule = $this->ruleFactory->create(); - $rule->setStoreId($object->getStoreId()); + if (!is_object($attributeData)) { + $rule = $this->ruleFactory->create(); + $rule->setStoreId($object->getStoreId()); - if ($attributeData !== null && is_string($attributeData)) { - $attributeData = unserialize($attributeData); - } + if ($attributeData !== null && is_string($attributeData)) { + $attributeData = unserialize($attributeData); + + } - $rule->getConditions()->loadArray($attributeData); + if ($attributeData !== null && is_array($attributeData)) { + $rule->getConditions()->loadArray($attributeData); + } + + $object->setData($attributeCode, $rule); + } - $object->setData($attributeCode, $rule); return $this; } diff --git a/src/module-elasticsuite-virtual-category/Model/Preview.php b/src/module-elasticsuite-virtual-category/Model/Preview.php index 4be96f97f..cae6e10d4 100644 --- a/src/module-elasticsuite-virtual-category/Model/Preview.php +++ b/src/module-elasticsuite-virtual-category/Model/Preview.php @@ -106,7 +106,7 @@ private function getAutomaticSortProductCollection() $productCollection ->setStoreId($this->category->getStoreId()) - ->addQueryFilter($this->getFilterQuery()) + ->addQueryFilter($this->getQueryFilter()) ->addAttributeToSelect(['name', 'small_image']); return $productCollection; @@ -166,28 +166,43 @@ private function loadItems($products = []) * * @return QueryInterface */ - private function getFilterQuery() + private function getQueryFilter() { - $queryClauses = []; + $queryParams = []; + $this->category->setIsActive(true); - if ($this->category->getIsVirtualCategory()) { - $queryClauses['must'][] = $this->category->getVirtualRule()->getCategorySearchQuery($this->category); - } else { - if (empty($this->category->getProductIds())) { - $this->category->setProductIds([0]); - } + if ($this->category->getIsVirtualCategory() || $this->category->getId()) { + $queryParams['must'][] = $this->category->getVirtualRule()->getCategorySearchQuery($this->category); + } elseif (!$this->category->getId()) { + $queryParams['must'][] = $this->getEntityIdFilterQuery([0]); + } + + if ((bool) $this->category->getIsVirtualCategory() === false) { + $addedProductIds = $this->category->getAddedProductIds(); + $deletedProductIds = $this->category->getDeletedProductIds(); - $queryClauses['should'][] = $this->queryFactory->create( - QueryInterface::TYPE_TERMS, - ['values' => $this->category->getProductIds(), 'field' => 'entity_id'] - ); + if ($addedProductIds && !empty($addedProductIds)) { + $queryParams = ['should' => $queryParams['must']]; + $queryParams['should'][] = $this->getEntityIdFilterQuery($addedProductIds); + } - $childrenQueries = $this->category->getVirtualRule()->getSearchQueriesByChildren($this->category); - foreach ($childrenQueries as $childrenQuery) { - $queryClauses['should'][] = $childrenQuery; + if ($deletedProductIds && !empty($deletedProductIds)) { + $queryParams['mustNot'][] = $this->getEntityIdFilterQuery($deletedProductIds); } } - return $this->queryFactory->create(QueryInterface::TYPE_BOOL, $queryClauses); + return $this->queryFactory->create(QueryInterface::TYPE_BOOL, $queryParams); + } + + /** + * Create a product id filter query. + * + * @param array $ids Id to be filtered. + * + * @return QueryInterface + */ + private function getEntityIdFilterQuery($ids) + { + return $this->queryFactory->create(QueryInterface::TYPE_TERMS, ['field' => 'entity_id', 'values' => $ids]); } } diff --git a/src/module-elasticsuite-virtual-category/Observer/CategoryProductFormObserver.php b/src/module-elasticsuite-virtual-category/Observer/CategoryProductFormObserver.php deleted file mode 100644 index e665317a3..000000000 --- a/src/module-elasticsuite-virtual-category/Observer/CategoryProductFormObserver.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright 2016 Smile - * @license Open Software License ("OSL") v. 3.0 - */ -namespace Smile\ElasticsuiteVirtualCategory\Observer; - -use Magento\Catalog\Block\Adminhtml\Category\Tabs; -use Magento\Framework\Event\ObserverInterface; -use Magento\Catalog\Api\Data\CategoryInterface; - -/** - * Handles additional tab for merchandising in the catagory edit page. - * - * @category Smile - * @package Smile\ElasticsuiteVirtualCategory - * @author Aurelien FOUCRET - */ -class CategoryProductFormObserver implements ObserverInterface -{ - /** - * {@inheritDoc} - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - /** - * @var Tabs $tabs - */ - $tabs = $observer->getEvent()->getTabs(); - - $virtualCategoryFormBlock = $tabs->getLayout()->createBlock( - 'Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category\Edit\Tab\Merchandising', - 'category.merchandising.form' - ); - - if ($this->canDisplayTab($virtualCategoryFormBlock->getCategory())) { - $tabs->addTab( - 'category.merchandising', - ['label' => __('Merchandising'), 'content' => $virtualCategoryFormBlock->toHtml()] - ); - } - } - - /** - * Indicates if the merchandising tab can be displayed on the current category. - * - * @param CategoryInterface $category Current category. - * - * @return boolean - */ - private function canDisplayTab(CategoryInterface $category) - { - return $category->getId() !== null && $category->getLevel() > 1; - } -} diff --git a/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/DataProviderPlugin.php b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/DataProviderPlugin.php new file mode 100644 index 000000000..30825e449 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/DataProviderPlugin.php @@ -0,0 +1,126 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteVirtualCategory\Plugin\Catalog\Category; + +use Magento\Catalog\Model\Category\DataProvider as CategoryDataProvider; +use \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position as ProductPositionResource; +use Magento\Catalog\Model\Category; + +/** + * Extenstion of the category form UI data provider. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Aurelien FOUCRET + */ +class DataProviderPlugin +{ + /** + * + * @var \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position + */ + private $productPositionResource; + + /** + * @var \Magento\Backend\Model\UrlInterface + */ + private $urlBuilder; + + /** + * @var \Magento\Framework\Locale\FormatInterface + */ + private $localeFormat; + + /** + * Constructor. + * + * @param ProductPositionResource $productPositionResource Product position resource model. + * @param \Magento\Framework\Locale\FormatInterface $localeFormat Locale formater. + * @param \Magento\Backend\Model\UrlInterface $urlBuilder Admin URL Builder. + */ + public function __construct( + ProductPositionResource $productPositionResource, + \Magento\Framework\Locale\FormatInterface $localeFormat, + \Magento\Backend\Model\UrlInterface $urlBuilder + ) { + $this->productPositionResource = $productPositionResource; + $this->localeFormat = $localeFormat; + $this->urlBuilder = $urlBuilder; + } + + /** + * Append virtual rule and sorting product data. + * + * @param CategoryDataProvider $dataProvider Data provider. + * @param \Closure $proceed Original method. + * + * @return array + */ + public function aroundGetData(CategoryDataProvider $dataProvider, \Closure $proceed) + { + $data = $proceed(); + + $currentCategory = $dataProvider->getCurrentCategory(); + + if ($currentCategory->getId() === null || $currentCategory->getLevel() < 2) { + $data[$currentCategory->getId()]['use_default']['is_virtual_category'] = true; + } + + $data[$currentCategory->getId()]['sorted_products'] = $this->getProductSavedPositions($currentCategory); + $data[$currentCategory->getId()]['product_sorter_load_url'] = $this->getProductSorterLoadUrl($currentCategory); + $data[$currentCategory->getId()]['price_format'] = $this->localeFormat->getPriceFormat(); + + return $data; + } + + /** + * Retrieve the category product sorter load URL. + * + * @param Category $category Category. + * + * @return string + */ + private function getProductSorterLoadUrl(Category $category) + { + $storeId = $category->getStoreId(); + + if ($storeId === 0) { + $storeId = current(array_filter($category->getStoreIds())); + } + + $urlParams = ['ajax' => true, 'store' => $storeId]; + + return $this->urlBuilder->getUrl('virtualcategory/category_virtual/preview', $urlParams); + } + + /** + * Load product saved positions for the current category. + * + * @param Category $category Category. + * + * @return array + */ + private function getProductSavedPositions(Category $category) + { + $productPositions = []; + + if ($category->getId()) { + $productPositions = $this->productPositionResource->getProductPositionsByCategory($category); + } + + return json_encode($productPositions, JSON_FORCE_OBJECT); + } +} diff --git a/src/module-elasticsuite-virtual-category/etc/adminhtml/events.xml b/src/module-elasticsuite-virtual-category/etc/adminhtml/events.xml deleted file mode 100644 index 442fad001..000000000 --- a/src/module-elasticsuite-virtual-category/etc/adminhtml/events.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/src/module-elasticsuite-virtual-category/etc/di.xml b/src/module-elasticsuite-virtual-category/etc/di.xml index 81741542d..fab50e1a4 100644 --- a/src/module-elasticsuite-virtual-category/etc/di.xml +++ b/src/module-elasticsuite-virtual-category/etc/di.xml @@ -56,4 +56,9 @@
+ + + + diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml b/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml new file mode 100644 index 000000000..3bf74205d --- /dev/null +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml @@ -0,0 +1,141 @@ + + + + +
+ +
+ + + true + + + + + + + 10 + boolean + checkbox + category + toggle + Virtual Category + + 1 + 0 + + + false + + 0 + + !${ $.provider }:data.use_default.is_virtual_category + + + + + + + + + 20 + + + + + + Smile_ElasticsuiteVirtualCategory/js/component/catalog/category/form/assigned_products + selected_products + + !category_form.category_form.assign_products.is_virtual_category:checked + + + + + + + + + + 20 + + + + Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category\VirtualRule + + + 10 + Smile_ElasticsuiteCatalogRule/js/component/catalog/product/form/rule + virtual_rule + + category_form.category_form.assign_products.is_virtual_category:checked + + + + + + + + + 20 + string + input + Virtual Category Root + + category_form.category_form.assign_products.is_virtual_category:checked + + + + + + + + + + 30 + + + + + + 10 + text + text + category + Smile_ElasticsuiteCatalog/js/form/element/product-sorter + Products List Preview and Sorting + sorted_products + 20 + + is_virtual_category + virtual_rule + selected_products.added_products + selected_products.deleted_products + + + ${ $.provider }:data.product_sorter_load_url + ${ $.provider }:data.price_format + + + + + +
+
diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-virtual-category/view/adminhtml/web/css/source/_module.less index 47749ed22..33fe2cebf 100644 --- a/src/module-elasticsuite-virtual-category/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/web/css/source/_module.less @@ -13,73 +13,6 @@ // */ // Display fieldsets legend into catalog category merchandising tab. -#category_info_tabs_category\.merchandising_content { - .fieldset { - padding: 10px 0 15px; - .legend { - display: block; - } - } -} - -// Hide the position edit column into catalog category products tab -#catalog_category_products { - - .data-grid td:nth-last-child(2) { - border-right-style: solid; - } - - .col-position { - display: none; - } -} - -#merchandising_virtual_settings_fieldset { - - label.label { - width: 100%; - float: none; - text-align: left; - padding: 0px 0px 0 10px; - margin: 0px; - background: #41362f; - color: #FFFFFF; - } - - div.control { - width: 100%; - padding: 0 10px; - } - - .field-virtual_rule { - width: 61%; - float: left; - border: 1px solid #DADADA; - margin: 0 2% 0 0; - } - - .field-virtual_category_root { - float: left; - width: 25%; - border: 1px solid #DADADA; - border-right: none; - - div.control { - label { - padding: 10px; - font-weight: bold; - } - } - } - - .field-chooservirtual_category_root { - float: left; - width: 12%; - border: 1px solid #DADADA; - border-left: none; +.catalog-category-edit { - button { - margin: 6px 0 0; - } - } } diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js new file mode 100644 index 000000000..e23950914 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js @@ -0,0 +1,77 @@ +/** + * 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 Aurelien FOUCRET + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +define([ + 'Magento_Ui/js/form/components/html', + 'underscore', + 'MutationObserver', +], function (Component, _) { + 'use strict'; + + return Component.extend({ + defaults: { + formField: "in_category_products", + links: { + addedProducts: '${ $.provider }:${ $.dataScope }.added_products', + deletedProducts: '${ $.provider }:${ $.dataScope }.deleted_products' + }, + }, + initialize: function () { + this._super(); + this.initAssignedProductsListener(); + }, + + initObservable: function () { + this._super(); + this.addedProducts = {}; + this.deletedProducts = {}; + this.observe('addedProducts'); + this.observe('deletedProducts'); + + return this; + }, + + initAssignedProductsListener: function () { + var observer = new MutationObserver(function () { + var selectedProductsField = document.getElementById(this.formField); + if (selectedProductsField) { + observer.disconnect(); + observer = new MutationObserver(this.onProductIdsUpdated.bind(this)); + observerConfig = {attributes: true, attributeFilter: ['value']}; + observer.observe(selectedProductsField, observerConfig); + } + }.bind(this)); + + var observerConfig = {childList: true, subtree: true}; + observer.observe(document, observerConfig); + }, + + onProductIdsUpdated: function (mutations) { + while (mutations.length > 0) { + var currentMutation = mutations.shift(); + var productIds = Object.keys(JSON.parse(currentMutation.target.value)); + this.updateProductIds(productIds); + } + }, + + updateProductIds: function (productIds) { + if (this.initialProductIds === undefined) { + this.initialProductIds = productIds; + } else { + this.addedProducts(_.difference(productIds, this.initialProductIds)); + this.deletedProducts(_.difference(this.initialProductIds, productIds)); + } + } + }) +}); From f55b2592361a22a1a26965384c75a55404c04a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 11:32:24 +0200 Subject: [PATCH 10/48] Virtual category root field admin UI. --- .../Adminhtml/Category/Virtual/Preview.php | 6 +-- .../Model/Rule.php | 2 +- .../adminhtml/ui_component/category_form.xml | 39 ++++++++++++++----- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php index 7e524d7e2..8aad7d8fe 100644 --- a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php +++ b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php @@ -114,12 +114,12 @@ private function getCategory() $categoryPostData = $this->getRequest()->getParams(); - $isVirtualCategory = isset($categoryPostData['is_virtual_category']) ? (bool) $categoryPostData['is_virtual_category'] : false; + $isVirtualCategory = (bool) $categoryPostData['is_virtual_category']; $category->setIsVirtualCategory($isVirtualCategory); if ($isVirtualCategory) { - $category->getVirtualRule()->loadPost($categoryPostData['virtual_rule']); - $category->setVirtualCategoryRoot($categoryPostData['virtual_category_root']); + $category->getVirtualRule()->loadPost($this->getRequest()->getParam('virtual_rule', [])); + $category->setVirtualCategoryRoot($this->getRequest()->getParam('virtual_category_root', null)); } $productPositions = $this->getRequest()->getParam('product_position', []); diff --git a/src/module-elasticsuite-virtual-category/Model/Rule.php b/src/module-elasticsuite-virtual-category/Model/Rule.php index 39b57204a..b5559056d 100644 --- a/src/module-elasticsuite-virtual-category/Model/Rule.php +++ b/src/module-elasticsuite-virtual-category/Model/Rule.php @@ -172,7 +172,7 @@ private function getVirtualRootCategory(CategoryInterface $category) $rootCategory = $this->categoryFactory->create()->setStoreId($storeId); if ($category->getVirtualCategoryRoot() !== null && !empty($category->getVirtualCategoryRoot())) { - $rootCategoryId = explode('/', $category->getVirtualCategoryRoot())[1]; + $rootCategoryId = $category->getVirtualCategoryRoot(); $rootCategory->load($rootCategoryId); } diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml b/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml index 3bf74205d..6666ecb55 100644 --- a/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/ui_component/category_form.xml @@ -77,33 +77,51 @@ 20 - - Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category\VirtualRule + + + Magento\Catalog\Ui\Component\Product\Form\Categories\Options + Virtual Category Root + field + select + Magento_Catalog/js/components/new-category + ui/grid/filters/elements/ui-select + virtual_category_root + true + false + true + false + 1 10 - Smile_ElasticsuiteCatalogRule/js/component/catalog/product/form/rule - virtual_rule + false + + false + category_form.category_form.assign_products.is_virtual_category:checked + + setParsed + - + - + + Smile\ElasticsuiteVirtualCategory\Block\Adminhtml\Catalog\Category\VirtualRule 20 - string - input - Virtual Category Root + Smile_ElasticsuiteCatalogRule/js/component/catalog/product/form/rule + virtual_rule category_form.category_form.assign_products.is_virtual_category:checked - + + @@ -126,6 +144,7 @@ is_virtual_category virtual_rule + virtual_category_root selected_products.added_products selected_products.deleted_products From e9a966ba8ddd3f76538ad8ae1fb176dbc4c755ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 12:04:55 +0200 Subject: [PATCH 11/48] Cosmetic changes --- .../view/adminhtml/web/css/source/_module.less | 18 ++++++++++++++++++ .../js/component/catalog/product/form/rule.js | 2 +- .../view/adminhtml/web/css/source/_module.less | 4 ++++ .../template/form/element/product-sorter.html | 4 +++- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/module-elasticsuite-catalog-rule/view/adminhtml/web/css/source/_module.less diff --git a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/css/source/_module.less new file mode 100644 index 000000000..77842731b --- /dev/null +++ b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/css/source/_module.less @@ -0,0 +1,18 @@ +// /** +// * 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\ElasticsuiteCatalogRule +// * @author Aurelien FOUCRET +// * @copyright 2016 Smile +// * @license Open Software License ("OSL") v. 3.0 +// */ + +// Display fieldsets legend into catalog category merchandising tab. +.admin__fieldset.virtual-rule-fieldset { + padding: 0 +} diff --git a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js index 7c145035b..591d84357 100644 --- a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js +++ b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js @@ -25,7 +25,7 @@ define([ links: { value: '${ $.provider }:${ $.dataScope }' }, - additionalClasses: "admin__fieldset" + additionalClasses: "admin__fieldset virtual-rule-fieldset" }, initialize: function () { this._super(); diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less index c76e879f4..e7f5d95ba 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less @@ -36,6 +36,10 @@ } } + .elasticsuite-admin-product-sorter-empty { + margin: 20px 100px 20px 100px; + } + .product-list { margin: 20px 40px 0; diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html index 7a5806054..15fc54000 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html @@ -5,7 +5,9 @@
-

+
+
+
From 26fd8415da899d89668c76d15fe387eabb8eaebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 12:09:38 +0200 Subject: [PATCH 12/48] Remove debug instruction into JS --- .../view/adminhtml/web/js/form/element/product-sorter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js index cc0c51c82..de886ba6e 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js @@ -24,7 +24,7 @@ define([ defaults: { template: "Smile_ElasticsuiteCatalog/form/element/product-sorter", refreshFields: {}, - maxRefreshInterval: 500, + maxRefreshInterval: 1000, imports: { formData: "${ $.provider }:data" }, @@ -70,9 +70,7 @@ define([ } this.refreshRateLimiter = setTimeout(function () { - console.log('Refresh list'); var formData = this.formData; - Object.keys(this.editPositions()).each(function (productId) { formData['product_position[' + productId + ']'] = this.editPositions()[productId]; }.bind(this)); From 95475c121a5633cf75a9c59dd4103263c746d1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 12:28:15 +0200 Subject: [PATCH 13/48] JS code cleanup --- .../adminhtml/web/js/component/catalog/product/form/rule.js | 4 ++-- .../view/adminhtml/web/js/form/element/product-sorter.js | 6 +++--- .../js/component/catalog/category/form/assigned_products.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js index 591d84357..775b4417f 100644 --- a/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js +++ b/src/module-elasticsuite-catalog-rule/view/adminhtml/web/js/component/catalog/product/form/rule.js @@ -15,7 +15,7 @@ define([ 'Magento_Ui/js/form/components/html', 'jquery', - 'MutationObserver', + 'MutationObserver' ], function (Component, $) { 'use strict'; @@ -77,7 +77,7 @@ define([ currentRuleObject = currentRuleObject[currentKey]; } - var currentKey = path.shift(); + currentKey = path.shift(); currentRuleObject[currentKey] = $(this).val(); }); diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js index de886ba6e..ddc347e06 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js @@ -16,7 +16,7 @@ define([ 'Magento_Ui/js/form/element/abstract', 'jquery', 'Smile_ElasticsuiteCatalog/js/form/element/product-sorter/item', - 'MutationObserver', + 'MutationObserver' ], function (Component, $, Product) { 'use strict'; @@ -69,7 +69,7 @@ define([ clearTimeout(); } - this.refreshRateLimiter = setTimeout(function () { + this.refreshRateLimiter = setTimeout(function () { var formData = this.formData; Object.keys(this.editPositions()).each(function (productId) { formData['product_position[' + productId + ']'] = this.editPositions()[productId]; @@ -191,6 +191,6 @@ define([ this.products(this.sortProduct(products)); this.editPositions(editPositions); - }, + } }); }); diff --git a/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js index e23950914..437aa40e0 100644 --- a/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js +++ b/src/module-elasticsuite-virtual-category/view/adminhtml/web/js/component/catalog/category/form/assigned_products.js @@ -15,7 +15,7 @@ define([ 'Magento_Ui/js/form/components/html', 'underscore', - 'MutationObserver', + 'MutationObserver' ], function (Component, _) { 'use strict'; @@ -25,7 +25,7 @@ define([ links: { addedProducts: '${ $.provider }:${ $.dataScope }.added_products', deletedProducts: '${ $.provider }:${ $.dataScope }.deleted_products' - }, + } }, initialize: function () { this._super(); From e05816cd869e077b56694945415cba78dc34faf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 14:40:49 +0200 Subject: [PATCH 14/48] Code cleaning --- .../Catalog/Category/VirtualRule.php | 4 +- .../Adminhtml/Category/Virtual/Preview.php | 71 ++++++++++++++++--- .../Attribute/Backend/VirtualRule.php | 1 - 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php b/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php index be3681fe7..186004aac 100644 --- a/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php +++ b/src/module-elasticsuite-virtual-category/Block/Adminhtml/Catalog/Category/VirtualRule.php @@ -25,9 +25,9 @@ class VirtualRule extends \Magento\Backend\Block\AbstractBlock { /** - * @var \Magento\Catalog\Model\Category\DataProvider + * @var \Magento\Framework\Data\FormFactory */ - private $dataProvider; + private $formFactory; /** * @var \Magento\Framework\Registry diff --git a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php index 8aad7d8fe..33478422b 100644 --- a/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php +++ b/src/module-elasticsuite-virtual-category/Controller/Adminhtml/Category/Virtual/Preview.php @@ -103,18 +103,41 @@ private function getPreviewObject() */ private function getCategory() { + $category = $this->loadCategory(); + + $this->addVirtualCategoryData($category) + ->addSelectedProducts($category) + ->setSortedProducts($category); + + return $category; + } + + /** + * Load current category using the request params. + * + * @return CategoryInterface + */ + private function loadCategory() + { + $category = $this->categoryFactory->create(); $storeId = $this->getRequest()->getParam('store'); $categoryId = $this->getRequest()->getParam('entity_id'); - $category = $this->categoryFactory->create()->setStoreId($storeId)->load($categoryId); + $category->setStoreId($storeId)->load($categoryId); - $selectedProducts = $this->getRequest()->getParam('selected_products', []); - $category->setAddedProductIds(isset($selectedProducts['added_products']) ? $selectedProducts['added_products'] : []); - $category->setDeletedProductIds(isset($selectedProducts['deleted_products']) ? $selectedProducts['deleted_products'] : []); - - $categoryPostData = $this->getRequest()->getParams(); + return $category; + } - $isVirtualCategory = (bool) $categoryPostData['is_virtual_category']; + /** + * Append virtual rule params to the category. + * + * @param CategoryInterface $category Category. + * + * @return $this + */ + private function addVirtualCategoryData(CategoryInterface $category) + { + $isVirtualCategory = (bool) $this->getRequest()->getParam('is_virtual_category'); $category->setIsVirtualCategory($isVirtualCategory); if ($isVirtualCategory) { @@ -122,10 +145,42 @@ private function getCategory() $category->setVirtualCategoryRoot($this->getRequest()->getParam('virtual_category_root', null)); } + return $this; + } + + /** + * Add user selected products. + * + * @param CategoryInterface $category Category. + * + * @return $this + */ + private function addSelectedProducts(CategoryInterface $category) + { + $selectedProducts = $this->getRequest()->getParam('selected_products', []); + + $addedProducts = isset($selectedProducts['added_products']) ? $selectedProducts['added_products'] : []; + $category->setAddedProductIds($addedProducts); + + $deletedProducts = isset($selectedProducts['deleted_products']) ? $selectedProducts['deleted_products'] : []; + $category->setDeletedProductIds($deletedProducts); + + return $this; + } + + /** + * Append products sorted by the user to the category. + * + * @param CategoryInterface $category Category. + * + * @return $this + */ + private function setSortedProducts(CategoryInterface $category) + { $productPositions = $this->getRequest()->getParam('product_position', []); $category->setSortedProductIds(array_keys($productPositions)); - return $category; + return $this; } /** diff --git a/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php b/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php index 8bdec266b..55ac59367 100644 --- a/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php +++ b/src/module-elasticsuite-virtual-category/Model/Category/Attribute/Backend/VirtualRule.php @@ -74,7 +74,6 @@ public function afterLoad($object) if ($attributeData !== null && is_string($attributeData)) { $attributeData = unserialize($attributeData); - } if ($attributeData !== null && is_array($attributeData)) { From 7740e907810f426fdb9d8509476f6051b17e21d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 15:34:08 +0200 Subject: [PATCH 15/48] Hide is_anchor field into category edit forms. --- .../adminhtml/ui_component/category_form.xml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml diff --git a/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml b/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml new file mode 100644 index 000000000..cb77002c8 --- /dev/null +++ b/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml @@ -0,0 +1,35 @@ + + + + +
+ +
+ + + + hidden + 1 + + + +
+ +
From 4d656a860c2103805544863c489f4eb684cd4514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 16:52:53 +0200 Subject: [PATCH 16/48] Virtual category data migration. --- .../Setup/InstallData.php | 2 +- .../Setup/UpgradeData.php | 114 ++++++++++++++++++ .../etc/module.xml | 2 +- 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/module-elasticsuite-virtual-category/Setup/UpgradeData.php diff --git a/src/module-elasticsuite-virtual-category/Setup/InstallData.php b/src/module-elasticsuite-virtual-category/Setup/InstallData.php index dab0f8d08..61c0729ea 100644 --- a/src/module-elasticsuite-virtual-category/Setup/InstallData.php +++ b/src/module-elasticsuite-virtual-category/Setup/InstallData.php @@ -82,7 +82,7 @@ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface Category::ENTITY, 'virtual_category_root', [ - 'type' => 'varchar', + 'type' => 'int', 'label' => 'Virtual category root', 'input' => null, 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, diff --git a/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php b/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php new file mode 100644 index 000000000..e0d4099c1 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php @@ -0,0 +1,114 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteVirtualCategory\Setup; + +use Magento\Catalog\Model\Category; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Catalog data upgrade. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Aurelien FOUCRET + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * EAV setup factory + * + * @var EavSetupFactory + */ + private $eavSetupFactory; + + /** + * Class Constructor + * + * @param EavSetupFactory $eavSetupFactory Eav setup factory. + */ + public function __construct(EavSetupFactory $eavSetupFactory) + { + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * Upgrade the module data. + * + * @param ModuleDataSetupInterface $setup The setup interface + * @param ModuleContextInterface $context The module Context + * + * @return void + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '1.1.0', '<')) { + $this->updateVirtualCategoryRootTypeToInt($setup); + } + + $setup->endSetup(); + } + + /** + * Migration from 1.0.0 to 1.1.0 : + * - Updating the attribute virtual_category_root from type varchar to type int + * - Updating the value of the attribute from 'category/13' to '13. + * + * @param ModuleDataSetupInterface $setup Setup. + * + * @return $this + */ + private function updateVirtualCategoryRootTypeToInt(ModuleDataSetupInterface $setup) + { + /** + * @var \Magento\Eav\Setup\EavSetup $eavSetup + */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); + + // Fix the attribute type. + $eavSetup->updateAttribute(Category::ENTITY, 'virtual_category_root', 'backend_type', 'int'); + + // Retrieve information about the attribute and storage config. + $virtualRootAttributeId = $eavSetup->getAttribute(Category::ENTITY, 'virtual_category_root', 'attribute_id'); + + $originalTable = $setup->getTable('catalog_category_entity_varchar'); + $targetTable = $setup->getTable('catalog_category_entity_int'); + + $baseFields = array_slice(array_keys($setup->getConnection()->describeTable($originalTable)), 1, -1); + + // Select old value. + $valueSelect = $setup->getConnection()->select(); + $valueSelect->from('catalog_category_entity_varchar', $baseFields) + ->where('attribute_id = ?', $virtualRootAttributeId) + ->columns(['value' => new \Zend_Db_Expr('REPLACE(value, "category/", "")')]); + + // Insert old values into the new table. + $query = $setup->getConnection()->insertFromSelect( + $valueSelect, + $targetTable, + array_merge($baseFields, ['value']), + AdapterInterface::INSERT_IGNORE + ); + $setup->getConnection()->query($query); + + // Delete old value. + $setup->getConnection()->delete($originalTable, "attribute_id = {$virtualRootAttributeId}"); + + return $this; + } +} diff --git a/src/module-elasticsuite-virtual-category/etc/module.xml b/src/module-elasticsuite-virtual-category/etc/module.xml index e22a1f841..7baa3e54d 100644 --- a/src/module-elasticsuite-virtual-category/etc/module.xml +++ b/src/module-elasticsuite-virtual-category/etc/module.xml @@ -17,7 +17,7 @@ */ --> - + From e181f3c38b11d5eb8640296757330a8879880455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 16 Jun 2016 21:35:36 +0200 Subject: [PATCH 17/48] Fix bug in parent category when switching a child from normal to virtual category. --- .../Product/NestedFilterInterface.php | 32 ++++++++++ .../Rule/Condition/Product/QueryBuilder.php | 29 +++++++-- .../Fulltext/Datasource/CategoryData.php | 19 +++--- .../web/js/form/element/product-sorter.js | 11 ++++ .../Model/Preview.php | 3 +- .../Fulltext/Datasource/CategoryData.php | 44 +------------ .../Model/Rule.php | 3 +- .../Product/CategoryNestedFilter.php | 62 +++++++++++++++++++ .../Category/SaveProductsPositions.php | 4 ++ .../etc/di.xml | 10 ++- .../etc/elasticsuite_indices.xml | 30 +++++++++ 11 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/NestedFilterInterface.php create mode 100644 src/module-elasticsuite-virtual-category/Model/Rule/Condition/Product/CategoryNestedFilter.php create mode 100644 src/module-elasticsuite-virtual-category/etc/elasticsuite_indices.xml diff --git a/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/NestedFilterInterface.php b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/NestedFilterInterface.php new file mode 100644 index 000000000..b85b9ab0e --- /dev/null +++ b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/NestedFilterInterface.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalogRule\Model\Rule\Condition\Product; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Allow to apply automatic filters on nested field during rule query conversion. + * + * @category Smile + * @package Smile\ElasticsuiteCatalogRule + * @author Aurelien FOUCRET + */ +interface NestedFilterInterface +{ + /** + * @return \Smile\ElasticsuiteCore\Search\Request\QueryInterface + */ + public function getFilter(); +} diff --git a/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/QueryBuilder.php b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/QueryBuilder.php index 1e304bebd..8139cdd2f 100644 --- a/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/QueryBuilder.php +++ b/src/module-elasticsuite-catalog-rule/Model/Rule/Condition/Product/QueryBuilder.php @@ -38,16 +38,23 @@ class QueryBuilder */ private $attributeList; + /** + * @var NestedFilterInterface[] + */ + private $nestedFilters; + /** * Constructor. * - * @param AttributeList $attributeList Search rule product attributes list - * @param QueryFactory $queryFactory Search query factory. + * @param AttributeList $attributeList Search rule product attributes list + * @param QueryFactory $queryFactory Search query factory. + * @param NestedFilterInterface[] $nestedFilters Filters applied to nested fields during query building. */ - public function __construct(AttributeList $attributeList, QueryFactory $queryFactory) + public function __construct(AttributeList $attributeList, QueryFactory $queryFactory, $nestedFilters = []) { $this->queryFactory = $queryFactory; $this->attributeList = $attributeList; + $this->nestedFilters = $nestedFilters; } /** @@ -59,7 +66,7 @@ public function __construct(AttributeList $attributeList, QueryFactory $queryFac */ public function getSearchQuery(ProductCondition $productCondition) { - $query = null; + $query = null; $this->prepareFieldValue($productCondition); @@ -84,7 +91,19 @@ public function getSearchQuery(ProductCondition $productCondition) $field = $this->getSearchField($productCondition); if ($field->isNested()) { - $nestedQueryParams = ['query' => $query, 'path' => $field->getNestedPath()]; + $nestedPath = $field->getNestedPath(); + $nestedQueryParams = ['query' => $query, 'path' => $nestedPath]; + + if (isset($this->nestedFilters[$nestedPath])) { + $nestedFilterClauses = []; + $nestedFilterClauses['must'][] = $this->nestedFilters[$nestedPath]->getFilter(); + $nestedFilterClauses['must'][] = $nestedQueryParams['query']; + + $nestedFilter = $this->queryFactory->create(QueryInterface::TYPE_BOOL, $nestedFilterClauses); + + $nestedQueryParams['query'] = $nestedFilter; + } + $query = $this->queryFactory->create(QueryInterface::TYPE_NESTED, $nestedQueryParams); } } diff --git a/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryData.php b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryData.php index 20d230017..a1b897536 100644 --- a/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryData.php +++ b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/CategoryData.php @@ -52,17 +52,22 @@ public function addData($storeId, array $indexData) foreach ($categoryData as $categoryDataRow) { $productId = (int) $categoryDataRow['product_id']; - $currentCategoryData = [ - 'category_id' => (int) $categoryDataRow['category_id'], - 'is_parent' => (bool) $categoryDataRow['is_parent'], - 'name' => (string) $categoryDataRow['name'], - ]; + unset($categoryDataRow['product_id']); + + $categoryDataRow = array_merge( + $categoryDataRow, + [ + 'category_id' => (int) $categoryDataRow['category_id'], + 'is_parent' => (bool) $categoryDataRow['is_parent'], + 'name' => (string) $categoryDataRow['name'], + ] + ); if (isset($categoryDataRow['position']) && $categoryDataRow['position'] !== null) { - $currentCategoryData['position'] = (int) $categoryDataRow['position']; + $categoryDataRow['position'] = (int) $categoryDataRow['position']; } - $indexData[$productId]['category'][] = $currentCategoryData; + $indexData[$productId]['category'][] = array_filter($categoryDataRow); } return $indexData; diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js index ddc347e06..06cf54593 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js @@ -86,6 +86,17 @@ define([ this.products(products); this.countTotalProducts(parseInt(loadedData.size, 10)); this.currentSize(Math.max(this.currentSize(), this.products().length)); + + var productIds = products.map(function (product) { return product.getId() }); + var editPositions = this.editPositions(); + + for (var productId in editPositions) { + if ($.inArray(parseInt(productId), productIds) < 0) { + delete editPositions[productId]; + } + } + + this.editPositions(editPositions); }, createProduct: function (productData) { diff --git a/src/module-elasticsuite-virtual-category/Model/Preview.php b/src/module-elasticsuite-virtual-category/Model/Preview.php index cae6e10d4..2320a5482 100644 --- a/src/module-elasticsuite-virtual-category/Model/Preview.php +++ b/src/module-elasticsuite-virtual-category/Model/Preview.php @@ -122,11 +122,12 @@ private function getManualSortProductCollection() $productIds = $this->getSortedProductIds(); $productCollection = $this->getAutomaticSortProductCollection(); - $productCollection->setPageSize(count($productIds)); $idFilter = $this->queryFactory->create(QueryInterface::TYPE_TERMS, ['values' => $productIds, 'field' => 'entity_id']); $productCollection->addQueryFilter($idFilter); + $productCollection->setPageSize(count($productIds)); + return $productCollection; } diff --git a/src/module-elasticsuite-virtual-category/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php b/src/module-elasticsuite-virtual-category/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php index c3febddd8..c893303f3 100644 --- a/src/module-elasticsuite-virtual-category/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php +++ b/src/module-elasticsuite-virtual-category/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php @@ -26,11 +26,6 @@ */ class CategoryData extends \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Datasource\CategoryData { - /** - * @var array - */ - private $virtualCategoriesIds; - /** * {@inheritDoc} */ @@ -69,7 +64,8 @@ private function getBaseSelect($productIds, $storeId) 'category_id' => 'cpi.category_id', 'product_id' => 'cpi.product_id', 'is_parent' => 'cpi.is_parent', - 'position' => 'p.position', + 'is_virtual' => new \Zend_Db_Expr(0), + 'position' => 'p.position', ]); return $select; @@ -84,53 +80,19 @@ private function getBaseSelect($productIds, $storeId) */ private function getVirtualSelect($productIds) { - $virtualCategoriesIds = $this->getVirtualCategoriesIds(); - $select = $this->getConnection()->select() ->from(['cpi' => $this->getTable(ProductPositionResourceModel::TABLE_NAME)], []) - ->where('cpi.category_id IN (?)', $virtualCategoriesIds) ->where('cpi.product_id IN(?)', $productIds) ->columns( [ 'category_id' => 'cpi.category_id', 'product_id' => 'cpi.product_id', 'is_parent' => new \Zend_Db_Expr('0'), + 'is_virtual' => new \Zend_Db_Expr('1'), 'position' => 'cpi.position', ] ); return $select; } - - /** - * List of the ids of the virtual categories of the site. - * - * @return array - */ - private function getVirtualCategoriesIds() - { - if ($this->virtualCategoriesIds === null) { - $isVirtualAttribute = $this->getIsVirtualCategoryAttribute(); - - $select = $this->getConnection()->select(); - $select->from($isVirtualAttribute->getBackendTable(), ['entity_id']) - ->where('attribute_id = ?', (int) $isVirtualAttribute->getAttributeId()) - ->where('value = ?', true) - ->group('entity_id'); - - $this->virtualCategoriesIds = $this->getConnection()->fetchCol($select); - } - - return $this->virtualCategoriesIds; - } - - /** - * Retrieve the 'is_virtual_category' attribute model. - * - * @return \Magento\Eav\Model\Entity\Attribute\AbstractAttribute - */ - private function getIsVirtualCategoryAttribute() - { - return $this->getEavConfig()->getAttribute(\Magento\Catalog\Model\Category::ENTITY, 'is_virtual_category'); - } } diff --git a/src/module-elasticsuite-virtual-category/Model/Rule.php b/src/module-elasticsuite-virtual-category/Model/Rule.php index b5559056d..91ffb4582 100644 --- a/src/module-elasticsuite-virtual-category/Model/Rule.php +++ b/src/module-elasticsuite-virtual-category/Model/Rule.php @@ -110,11 +110,10 @@ public function getCategorySearchQuery($category, $excludedCategories = []) $queryParams = []; if ((bool) $category->getIsVirtualCategory() && $category->getIsActive()) { - $parentCategory = $this->getVirtualRootCategory($category); $excludedCategories[] = $category->getId(); - $queryParams['must'][] = $this->getVirtualCategoryQuery($category, $excludedCategories); + $parentCategory = $this->getVirtualRootCategory($category); if ($parentCategory && $parentCategory->getId()) { $queryParams['must'][] = $this->getCategorySearchQuery($parentCategory, $excludedCategories); } diff --git a/src/module-elasticsuite-virtual-category/Model/Rule/Condition/Product/CategoryNestedFilter.php b/src/module-elasticsuite-virtual-category/Model/Rule/Condition/Product/CategoryNestedFilter.php new file mode 100644 index 000000000..7c842c328 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/Model/Rule/Condition/Product/CategoryNestedFilter.php @@ -0,0 +1,62 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Product; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; + +/** + * Category product nested filter. + * + * @category Smile + * @package Smile\ElasticsuiteVirtualCategory + * @author Aurelien FOUCRET + */ +class CategoryNestedFilter implements \Smile\ElasticsuiteCatalogRule\Model\Rule\Condition\Product\NestedFilterInterface +{ + /** + * @var string + */ + const FILTER_FIELD = 'category.is_virtual'; + + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * Constructor. + * + * @param QueryFactory $queryFactory Query factory. + */ + public function __construct(QueryFactory $queryFactory) + { + $this->queryFactory = $queryFactory; + } + + /** + * {@inheritDoc} + */ + public function getFilter() + { + $filterParams = ['field' => self::FILTER_FIELD, 'value' => true]; + $filterQuery = $this->queryFactory->create( + QueryInterface::TYPE_NOT, + ['query' => $this->queryFactory->create(QueryInterface::TYPE_TERM, $filterParams)] + ); + + return $filterQuery; + } +} diff --git a/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php index 49dc9bc18..bb09727c6 100644 --- a/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php +++ b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php @@ -75,6 +75,10 @@ public function aroundSave( if ($category->getId() && $category->getSortedProducts()) { $this->unserializeProductPositions($category); + if ($category->getIsVirtualCategory()) { + $category->setPostedProducts([]); + } + $categoryResource->addCommitCallback( function () use ($category) { $affectedProductIds = $this->getAffectedProductIds($category); diff --git a/src/module-elasticsuite-virtual-category/etc/di.xml b/src/module-elasticsuite-virtual-category/etc/di.xml index fab50e1a4..82389b0c2 100644 --- a/src/module-elasticsuite-virtual-category/etc/di.xml +++ b/src/module-elasticsuite-virtual-category/etc/di.xml @@ -35,7 +35,15 @@ Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Product - + + + + + + Smile\ElasticsuiteVirtualCategory\Model\Rule\Condition\Product\CategoryNestedFilter + + + diff --git a/src/module-elasticsuite-virtual-category/etc/elasticsuite_indices.xml b/src/module-elasticsuite-virtual-category/etc/elasticsuite_indices.xml new file mode 100644 index 000000000..90a723719 --- /dev/null +++ b/src/module-elasticsuite-virtual-category/etc/elasticsuite_indices.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + From bd33687088a26d0cf92f74f36768de391fbb0575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 17 Jun 2016 05:36:37 +0200 Subject: [PATCH 18/48] Fix non backaward compatible inheritance in Magento 2.1.0-rc3. --- .../Search/Adapter/Elasticsuite/Response/QueryResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/QueryResponse.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/QueryResponse.php index 086bdfc5c..a52bc5e1f 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/QueryResponse.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/QueryResponse.php @@ -24,7 +24,7 @@ * @package Smile\ElasticsuiteCore * @author Aurelien FOUCRET */ -class QueryResponse implements ResponseInterface, \IteratorAggregate, \Countable +class QueryResponse implements ResponseInterface { /** * Document Collection From d5530ee940a672cc7a6ca3c606b654ad8f5e8963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 17 Jun 2016 06:00:55 +0200 Subject: [PATCH 19/48] Repair broken autocomplete - Knockout JS libs path have changed in Magento 2.1-rc* --- src/module-elasticsuite-core/view/frontend/web/js/form-mini.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js index 91b071e41..e687161d4 100644 --- a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js +++ b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js @@ -5,7 +5,7 @@ define([ 'underscore', 'mage/template', 'Magento_Catalog/js/price-utils', - 'Magento_Ui/js/lib/ko/template/loader', + 'Magento_Ui/js/lib/knockout/template/loader', 'jquery/ui', 'mage/translate', 'mageQuickSearch' From 8394dd687ed12532b882a035d48b8e6f204445ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 17 Jun 2016 09:20:17 +0200 Subject: [PATCH 20/48] Better user experience when using the product sorter (loading feedback). --- .../view/adminhtml/web/css/source/_module.less | 4 ++++ .../adminhtml/web/js/form/element/product-sorter.js | 11 +++++++---- .../web/template/form/element/product-sorter.html | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less index e7f5d95ba..90266916c 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/css/source/_module.less @@ -36,6 +36,10 @@ } } + .admin__data-grid-loading-mask { + position: fixed; + } + .elasticsuite-admin-product-sorter-empty { margin: 20px 100px 20px 100px; } diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js index 06cf54593..0b185dc08 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/js/form/element/product-sorter.js @@ -22,6 +22,7 @@ define([ return Component.extend({ defaults: { + showSpinner: true, template: "Smile_ElasticsuiteCatalog/form/element/product-sorter", refreshFields: {}, maxRefreshInterval: 1000, @@ -48,7 +49,7 @@ define([ this.pageSize = parseInt(this.pageSize, 10); this.currentSize = this.pageSize; - this.observe(['products', 'countTotalProducts', 'currentSize', 'editPositions']); + this.observe(['products', 'countTotalProducts', 'currentSize', 'editPositions', 'loading', 'showSpinner']); this.editPositions.subscribe(function () { this.value(JSON.stringify(this.editPositions())); }.bind(this)); }, @@ -68,7 +69,9 @@ define([ if (this.refreshRateLimiter !== undefined) { clearTimeout(); } - + + this.loading(true); + this.refreshRateLimiter = setTimeout(function () { var formData = this.formData; Object.keys(this.editPositions()).each(function (productId) { @@ -76,7 +79,6 @@ define([ }.bind(this)); formData['page_size'] = this.currentSize(); - this.loadXhr = $.post(this.loadUrl, this.formData, this.onProductListLoad.bind(this)); }.bind(this), this.maxRefreshInterval); }, @@ -91,12 +93,13 @@ define([ var editPositions = this.editPositions(); for (var productId in editPositions) { - if ($.inArray(parseInt(productId), productIds) < 0) { + if ($.inArray(parseInt(productId, 10), productIds) < 0) { delete editPositions[productId]; } } this.editPositions(editPositions); + this.loading(false); }, createProduct: function (productData) { diff --git a/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html index 15fc54000..69e52659d 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html +++ b/src/module-elasticsuite-catalog/view/adminhtml/web/template/form/element/product-sorter.html @@ -1,5 +1,11 @@ + +
+
+
+
+ From b724e7b8d793dc065a14f6d60ad14898029483dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Thu, 23 Jun 2016 05:49:06 +0200 Subject: [PATCH 21/48] Fix ES module version to comply with Magento EE minimum required version. --- composer.json | 2 +- src/module-elasticsuite-core/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 036367629..b71333bf6 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "magento/module-catalog": "*", "magento/module-catalog-search": "*", "magento/magento-composer-installer": "*", - "elasticsearch/elasticsearch": "2.1.*" + "elasticsearch/elasticsearch": "^2.2" }, "replace": { "smile/module-elasticsuite-core": "self.version", diff --git a/src/module-elasticsuite-core/composer.json b/src/module-elasticsuite-core/composer.json index 5871efa03..c34692633 100644 --- a/src/module-elasticsuite-core/composer.json +++ b/src/module-elasticsuite-core/composer.json @@ -27,7 +27,7 @@ "magento/module-catalog": "*", "magento/module-catalog-search": "*", "magento/magento-composer-installer": "*", - "elasticsearch/elasticsearch": "2.1.*" + "elasticsearch/elasticsearch": "^2.2" }, "version": "2.1.0", "autoload": { From 75af0eaea4c9020e4388f09bbe99a00d77d5e819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 5 Jul 2016 16:26:24 +0200 Subject: [PATCH 22/48] Fixing #72 - Undefined index in sort params limitation. --- .../Model/ResourceModel/Product/Fulltext/Collection.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 be575e17b..3c586312c 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Fulltext/Collection.php @@ -498,10 +498,12 @@ private function prepareSortOrders() { $sortOrders = []; + $useProductuctLimitation = isset($this->_productLimitationFilters['sortParams']); + foreach ($this->_orders as $attribute => $direction) { $sortParams = ['direction' => $direction]; - if (isset($this->_productLimitationFilters['sortParams'][$attribute])) { + if ($useProductuctLimitation && isset($this->_productLimitationFilters['sortParams'][$attribute])) { $sortField = $this->_productLimitationFilters['sortParams'][$attribute]['sortField']; $sortParams = array_merge($sortParams, $this->_productLimitationFilters['sortParams'][$attribute]); } elseif ($attribute == 'price') { From 9862a8aae8c4dcdf500242133b785270233d9bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 12 Jul 2016 06:15:48 +0200 Subject: [PATCH 23/48] #81 : Flatten array structure during indexing. --- src/module-elasticsuite-catalog/Helper/Attribute.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-catalog/Helper/Attribute.php b/src/module-elasticsuite-catalog/Helper/Attribute.php index 99323c0fa..4d658bd11 100644 --- a/src/module-elasticsuite-catalog/Helper/Attribute.php +++ b/src/module-elasticsuite-catalog/Helper/Attribute.php @@ -171,11 +171,17 @@ public function prepareIndexValue(AttributeInterface $attribute, $storeId, $valu $value = [$value]; } - $values[$attributeCode] = $value = array_filter(array_map($simpleValueMapper, $value)); + $value = array_map($simpleValueMapper, $value); + $value = array_filter($value); + $value = array_values($value); + $values[$attributeCode] = $value; if ($attribute->usesSource()) { $optionTextFieldName = $this->getOptionTextFieldName($attributeCode); - $values[$optionTextFieldName] = array_filter($this->getIndexOptionsText($attribute, $storeId, $value)); + $optionTextValues = $this->getIndexOptionsText($attribute, $storeId, $value); + $optionTextValues = array_filter($optionTextValues); + $optionTextValues = array_values($optionTextValues); + $values[$optionTextFieldName] = $optionTextValues; } return array_filter($values); From b88a60cf5ed6c6bafebca470c53de86f6f0b48ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 12 Jul 2016 06:47:25 +0200 Subject: [PATCH 24/48] Fix #74 - Better admin for fuzziness config. --- .../Model/Config/Source/FuzzinessValue.php | 37 +++++++++++++++++++ .../etc/adminhtml/elasticsuite_relevance.xml | 5 ++- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/module-elasticsuite-core/Model/Config/Source/FuzzinessValue.php diff --git a/src/module-elasticsuite-core/Model/Config/Source/FuzzinessValue.php b/src/module-elasticsuite-core/Model/Config/Source/FuzzinessValue.php new file mode 100644 index 000000000..ef68003d0 --- /dev/null +++ b/src/module-elasticsuite-core/Model/Config/Source/FuzzinessValue.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\Config\Source; + +/** + * Fuzziness value config source model. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class FuzzinessValue implements \Magento\Framework\Option\ArrayInterface +{ + /** + * {@inheritDoc} + */ + public function toOptionArray() + { + return [ + 'AUTO' => __('Automatic'), + '1' => 1, + '2' => 2, + ]; + } +} diff --git a/src/module-elasticsuite-core/etc/adminhtml/elasticsuite_relevance.xml b/src/module-elasticsuite-core/etc/adminhtml/elasticsuite_relevance.xml index fa6c44c23..b66066e5f 100644 --- a/src/module-elasticsuite-core/etc/adminhtml/elasticsuite_relevance.xml +++ b/src/module-elasticsuite-core/etc/adminhtml/elasticsuite_relevance.xml @@ -71,13 +71,14 @@ Magento\Config\Model\Config\Source\Yesno - + + Smile\ElasticsuiteCore\Model\Config\Source\FuzzinessValue 1 - here for more information.]]> + here for more information.]]> From 183b2952c84e19032ebb1a9a2cf1576a99644403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 12 Jul 2016 06:48:51 +0200 Subject: [PATCH 25/48] Wording into the code of the fulltext query builder. --- .../Search/Request/Query/Fulltext/QueryBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-core/Search/Request/Query/Fulltext/QueryBuilder.php b/src/module-elasticsuite-core/Search/Request/Query/Fulltext/QueryBuilder.php index 37c74a481..430c0159d 100644 --- a/src/module-elasticsuite-core/Search/Request/Query/Fulltext/QueryBuilder.php +++ b/src/module-elasticsuite-core/Search/Request/Query/Fulltext/QueryBuilder.php @@ -62,7 +62,7 @@ public function create(ContainerConfigurationInterface $containerConfig, $queryT $fuzzySpellingTypes = [SpellcheckerInterface::SPELLING_TYPE_FUZZY, SpellcheckerInterface::SPELLING_TYPE_MOST_FUZZY]; if ($spellingType == SpellcheckerInterface::SPELLING_TYPE_PURE_STOPWORDS) { - $query = $this->getPurewordsQuery($containerConfig, $queryText, $boost); + $query = $this->getPureStopwordsQuery($containerConfig, $queryText, $boost); } elseif (in_array($spellingType, $fuzzySpellingTypes)) { $query = $this->getSpellcheckedQuery($containerConfig, $queryText, $spellingType, $boost); } @@ -147,7 +147,7 @@ private function getWeightedSearchQuery(ContainerConfigurationInterface $contain * * @return QueryInterface */ - private function getPurewordsQuery(ContainerConfigurationInterface $containerConfig, $queryText, $boost) + private function getPureStopwordsQuery(ContainerConfigurationInterface $containerConfig, $queryText, $boost) { $relevanceConfig = $containerConfig->getRelevanceConfig(); From 709a2264e298156c84b76605e033e2e4f7e5b750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 12 Jul 2016 10:16:49 +0200 Subject: [PATCH 26/48] Fix composer.json elasticsearch deps --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b71333bf6..3a1c13f14 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "magento/module-catalog": "*", "magento/module-catalog-search": "*", "magento/magento-composer-installer": "*", - "elasticsearch/elasticsearch": "^2.2" + "elasticsearch/elasticsearch": "^2.1" }, "replace": { "smile/module-elasticsuite-core": "self.version", From 51db995ee4dda9f7b30cbc9d60e76fa846a0648d Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Mon, 11 Jul 2016 11:40:26 +0200 Subject: [PATCH 27/48] Fixup table name retrieval when using table prefixes. --- src/module-elasticsuite-catalog/Setup/InstallData.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/module-elasticsuite-catalog/Setup/InstallData.php b/src/module-elasticsuite-catalog/Setup/InstallData.php index a7ced3815..fcfa4d199 100644 --- a/src/module-elasticsuite-catalog/Setup/InstallData.php +++ b/src/module-elasticsuite-catalog/Setup/InstallData.php @@ -131,7 +131,8 @@ private function updateCategoryIsAnchorAttribute() */ private function updateAttributeDefaultValue($entityTypeId, $attributeId, $value, $excludedIds = []) { - $entityTable = $this->eavSetup->getEntityType($entityTypeId, 'entity_table'); + $setup = $this->eavSetup->getSetup(); + $entityTable = $setup->getTable($this->eavSetup->getEntityType($entityTypeId, 'entity_table')); $attributeTable = $this->eavSetup->getAttributeTable($entityTypeId, $attributeId); if (!is_int($attributeId)) { From f4309dd2e4d8d797c0d70e742c8f8c4a4b6f651c Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Tue, 12 Jul 2016 11:10:48 +0200 Subject: [PATCH 28/48] Added missing getTable on virtual category upgrade file. --- src/module-elasticsuite-virtual-category/Setup/UpgradeData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php b/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php index e0d4099c1..61f754517 100644 --- a/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php +++ b/src/module-elasticsuite-virtual-category/Setup/UpgradeData.php @@ -93,7 +93,7 @@ private function updateVirtualCategoryRootTypeToInt(ModuleDataSetupInterface $se // Select old value. $valueSelect = $setup->getConnection()->select(); - $valueSelect->from('catalog_category_entity_varchar', $baseFields) + $valueSelect->from($setup->getTable('catalog_category_entity_varchar'), $baseFields) ->where('attribute_id = ?', $virtualRootAttributeId) ->columns(['value' => new \Zend_Db_Expr('REPLACE(value, "category/", "")')]); From b9beb45103b15f797fb1709c9b1163ca71f3f1bc Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Tue, 12 Jul 2016 11:11:13 +0200 Subject: [PATCH 29/48] Added missing getTable into Thesaurus indexer and Resource Model. --- .../Model/ResourceModel/Indexer/Thesaurus.php | 6 +++--- .../Model/ResourceModel/Thesaurus.php | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php index 380907f44..647eabac3 100644 --- a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php +++ b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php @@ -56,7 +56,7 @@ public function getExpansions($storeId) $select = $this->getBaseSelect($storeId, ThesaurusInterface::TYPE_EXPANSION); $select->join( - ['expanded_terms' => ThesaurusInterface::REFERENCE_TABLE_NAME], + ['expanded_terms' => $this->getTable(ThesaurusInterface::REFERENCE_TABLE_NAME)], 'expanded_terms.term_id = terms.term_id AND expanded_terms.thesaurus_id = terms.thesaurus_id', [] ); @@ -80,8 +80,8 @@ private function getBaseSelect($storeId, $type) $select = $connection->select(); $select->from(['thesaurus' => $this->getMainTable()], []) - ->join(['terms' => ThesaurusInterface::EXPANSION_TABLE_NAME], 'thesaurus.thesaurus_id = terms.thesaurus_id', []) - ->join(['store' => ThesaurusInterface::STORE_TABLE_NAME], 'store.thesaurus_id = thesaurus.thesaurus_id', []) + ->join(['terms' => $this->getTable(ThesaurusInterface::EXPANSION_TABLE_NAME)], 'thesaurus.thesaurus_id = terms.thesaurus_id', []) + ->join(['store' => $this->getTable(ThesaurusInterface::STORE_TABLE_NAME)], 'store.thesaurus_id = thesaurus.thesaurus_id', []) ->group(['thesaurus.thesaurus_id', 'terms.term_id']) ->where("thesaurus.type = ?", $type) ->where('store.store_id IN (?)', [0, $storeId]); diff --git a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Thesaurus.php b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Thesaurus.php index 3b0ce3fd3..82ede6781 100644 --- a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Thesaurus.php +++ b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Thesaurus.php @@ -159,8 +159,12 @@ private function saveStoreRelation(\Magento\Framework\Model\AbstractModel $objec $deleteCondition[ThesaurusInterface::STORE_ID . " NOT IN (?)"] = array_keys($storeIds); - $this->getConnection()->delete(ThesaurusInterface::STORE_TABLE_NAME, $deleteCondition); - $this->getConnection()->insertOnDuplicate(ThesaurusInterface::STORE_TABLE_NAME, $storeLinks, array_keys(current($storeLinks))); + $this->getConnection()->delete($this->getTable(ThesaurusInterface::STORE_TABLE_NAME), $deleteCondition); + $this->getConnection()->insertOnDuplicate( + $this->getTable(ThesaurusInterface::STORE_TABLE_NAME), + $storeLinks, + array_keys(current($storeLinks)) + ); } } @@ -206,7 +210,7 @@ private function saveTermsRelation(\Magento\Framework\Model\AbstractModel $objec // Saves expansion terms for a thesaurus. Expansion terms are used by expansion AND synonym thesauri. $this->getConnection()->insertOnDuplicate( - ThesaurusInterface::EXPANSION_TABLE_NAME, + $this->getTable(ThesaurusInterface::EXPANSION_TABLE_NAME), $expansionTermLinks, array_keys(current($expansionTermLinks)) ); @@ -227,7 +231,7 @@ private function deleteThesaurusRelations(\Magento\Framework\Model\AbstractModel $deleteCondition = [ThesaurusInterface::THESAURUS_ID . " = ?" => $object->getThesaurusId()]; $this->getConnection()->delete( - ThesaurusInterface::EXPANSION_TABLE_NAME, + $this->getTable(ThesaurusInterface::EXPANSION_TABLE_NAME), $deleteCondition ); @@ -246,7 +250,7 @@ private function saveReferenceTerms(\Magento\Framework\Model\AbstractModel $obje { if ($object->getType() === ThesaurusInterface::TYPE_EXPANSION) { $this->getConnection()->insertOnDuplicate( - ThesaurusInterface::REFERENCE_TABLE_NAME, + $this->getTable(ThesaurusInterface::REFERENCE_TABLE_NAME), $referenceTerms, array_keys(current($referenceTerms)) ); From 0bfd963f77c25f1f426dccc36c342811da4f25b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 29 Jul 2016 09:01:11 +0200 Subject: [PATCH 30/48] Append missing query type into the query builder. --- .../Elasticsuite/Request/Query/Builder.php | 1 + .../Request/Query/Builder/Missing.php | 36 ++++++++ .../Search/Request/Query/Missing.php | 87 +++++++++++++++++++ .../Search/Request/QueryInterface.php | 1 + src/module-elasticsuite-core/etc/di.xml | 1 + 5 files changed, 126 insertions(+) create mode 100644 src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Missing.php create mode 100644 src/module-elasticsuite-core/Search/Request/Query/Missing.php diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder.php index 32fe5016a..0a557328c 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder.php @@ -40,6 +40,7 @@ class Builder implements BuilderInterface QueryInterface::TYPE_MATCH => 'Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Match', QueryInterface::TYPE_COMMON => 'Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Common', QueryInterface::TYPE_MULTIMATCH => 'Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\MultiMatch', + QueryInterface::TYPE_MISSING => 'Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Missing', ]; /** diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Missing.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Missing.php new file mode 100644 index 000000000..3045a3ff2 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Missing.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\BuilderInterface; + +/** + * Build an ES missing field query. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class Missing implements BuilderInterface +{ + /** + * {@inheritDoc} + */ + public function buildQuery(QueryInterface $query) + { + return ['missing' => ['field' => $query->getField()]]; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/Query/Missing.php b/src/module-elasticsuite-core/Search/Request/Query/Missing.php new file mode 100644 index 000000000..51c484a85 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/Query/Missing.php @@ -0,0 +1,87 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Request\Query; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Missing field definition implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class Missing implements QueryInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $field; + + /** + * Constructor. + * @param \Magento\Framework\Search\Request\QueryInterface $query Negated query. + * @param string $name Query name. + * @param integer $boost Query boost. + */ + public function __construct( + $field, + $name = null, + $boost = QueryInterface::DEFAULT_BOOST_VALUE + ) { + $this->name = $name; + $this->boost = $boost; + $this->field = $field; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritDoc} + */ + public function getBoost() + { + return $this->boost; + } + + /** + * {@inheritDoc} + */ + public function getType() + { + return QueryInterface::TYPE_MISSING; + } + + /** + * Negated query. + * + * @return string + */ + public function getField() + { + return $this->field; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/QueryInterface.php b/src/module-elasticsuite-core/Search/Request/QueryInterface.php index 67c497be6..b25708b3f 100644 --- a/src/module-elasticsuite-core/Search/Request/QueryInterface.php +++ b/src/module-elasticsuite-core/Search/Request/QueryInterface.php @@ -32,4 +32,5 @@ interface QueryInterface extends \Magento\Framework\Search\Request\QueryInterfac const TYPE_NOT = 'notQuery'; const TYPE_MULTIMATCH = 'multiMatchQuery'; const TYPE_COMMON = 'commonQuery'; + const TYPE_MISSING = 'missingQuery'; } diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index 58cd5d3b6..17c2f103d 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -63,6 +63,7 @@ Smile\ElasticsuiteCore\Search\Request\Query\FilteredFactory Smile\ElasticsuiteCore\Search\Request\Query\NestedFactory Smile\ElasticsuiteCore\Search\Request\Query\NotFactory + Smile\ElasticsuiteCore\Search\Request\Query\MissingFactory Smile\ElasticsuiteCore\Search\Request\Query\TermFactory Smile\ElasticsuiteCore\Search\Request\Query\TermsFactory Smile\ElasticsuiteCore\Search\Request\Query\RangeFactory From f7cf1a74d1d82e6e36c9fcdcec327a3d6c28790a Mon Sep 17 00:00:00 2001 From: vo1 Date: Wed, 31 Aug 2016 14:36:23 +0200 Subject: [PATCH 31/48] Conf product may have conf. attribute not used in layered nav When configurable attribute is not used in LayeredNav, L155 fails indexation due to attribute is not in array. E.g. you have clothing with color and size, where color is always some complex naming "crimson/blood red/peach" and color_group for this is "red". Color is not used in layered while color_group is. Such solution results in Notice: Undefined offset: which is fixable by isset proposed here. --- .../Product/Indexer/Fulltext/Datasource/AttributeData.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/AttributeData.php b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/AttributeData.php index 269180b91..2f04ac58f 100644 --- a/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/AttributeData.php +++ b/src/module-elasticsuite-catalog/Model/Product/Indexer/Fulltext/Datasource/AttributeData.php @@ -152,7 +152,9 @@ private function addRelationData(&$parentData, $childAttributes, $relation) $configurableAttributesCodes = array_map( function ($attributeId) { - return $this->attributesById[(int) $attributeId]->getAttributeCode(); + if (isset($this->attributesById[(int) $attributeId])) { + return $this->attributesById[(int) $attributeId]->getAttributeCode(); + } }, $relation['configurable_attributes'] ); From 3a74b9a626d3fdbe5b193215b44736348bff45c0 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Tue, 13 Sep 2016 11:26:47 +0200 Subject: [PATCH 32/48] Code cleaning : Fixing missing query model. --- .../Search/Request/Query/Missing.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/module-elasticsuite-core/Search/Request/Query/Missing.php b/src/module-elasticsuite-core/Search/Request/Query/Missing.php index 51c484a85..8aad2f384 100644 --- a/src/module-elasticsuite-core/Search/Request/Query/Missing.php +++ b/src/module-elasticsuite-core/Search/Request/Query/Missing.php @@ -35,11 +35,17 @@ class Missing implements QueryInterface */ private $field; + /** + * @var integer + */ + private $boost; + /** * Constructor. - * @param \Magento\Framework\Search\Request\QueryInterface $query Negated query. - * @param string $name Query name. - * @param integer $boost Query boost. + * + * @param string $field Query field. + * @param string $name Query name. + * @param integer $boost Query boost. */ public function __construct( $field, From 8074cb5eeceebf7f3ced3406d3a1591eb8d63f92 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Tue, 13 Sep 2016 18:55:34 +0200 Subject: [PATCH 33/48] Adding the ability to use the Manual mode for price navigation step, with a default price navigation step defined in configuration. --- .../Block/Navigation/Renderer/PriceSlider.php | 79 +++++++++++++++++++ .../Block/Navigation/Renderer/Slider.php | 40 ++++++---- .../Model/Layer/Filter/Price.php | 7 ++ .../frontend/web/js/range-slider-widget.js | 3 +- 4 files changed, 114 insertions(+), 15 deletions(-) diff --git a/src/module-elasticsuite-catalog/Block/Navigation/Renderer/PriceSlider.php b/src/module-elasticsuite-catalog/Block/Navigation/Renderer/PriceSlider.php index f77b45d08..5d8ddb023 100644 --- a/src/module-elasticsuite-catalog/Block/Navigation/Renderer/PriceSlider.php +++ b/src/module-elasticsuite-catalog/Block/Navigation/Renderer/PriceSlider.php @@ -12,7 +12,9 @@ */ namespace Smile\ElasticsuiteCatalog\Block\Navigation\Renderer; +use Magento\Store\Model\ScopeInterface; use Smile\ElasticsuiteCatalog\Model\Layer\Filter\Price; +use Magento\Catalog\Model\Layer\Filter\DataProvider\Price as PriceDataProvider; /** * This block handle price slider rendering. @@ -45,4 +47,81 @@ protected function getFieldFormat() { return $this->localeFormat->getPriceFormat(); } + + /** + * {@inheritDoc} + */ + protected function getConfig() + { + $config = parent::getConfig(); + + if ($this->isManualCalculation() && ($this->getStepValue() > 0)) { + $config['step'] = $this->getStepValue(); + } + + return $config; + } + + /** + * Returns min value of the slider. + * + * @return int + */ + protected function getMinValue() + { + $minValue = $this->getFilter()->getMinValue(); + + if ($this->isManualCalculation() && ($this->getStepValue() > 0)) { + $stepValue = $this->getStepValue(); + $minValue = floor($minValue / $stepValue) * $stepValue; + } + + return $minValue; + } + + /** + * Returns max value of the slider. + * + * @return int + */ + protected function getMaxValue() + { + $maxValue = $this->getFilter()->getMaxValue() + 1; + + if ($this->isManualCalculation() && ($this->getStepValue() > 0)) { + $stepValue = $this->getStepValue(); + $maxValue = ceil($maxValue / $stepValue) * $stepValue; + } + + return $maxValue; + } + + /** + * Check if price interval is manually set in the configuration + * + * @return bool + */ + private function isManualCalculation() + { + $result = false; + $calculation = $this->_scopeConfig->getValue(PriceDataProvider::XML_PATH_RANGE_CALCULATION, ScopeInterface::SCOPE_STORE); + + if ($calculation === PriceDataProvider::RANGE_CALCULATION_MANUAL) { + $result = true; + } + + return $result; + } + + /** + * Retrieve the value for "Default Price Navigation Step". + * + * @return int + */ + private function getStepValue() + { + $value = $this->_scopeConfig->getValue(PriceDataProvider::XML_PATH_RANGE_STEP, ScopeInterface::SCOPE_STORE); + + return (int) $value; + } } diff --git a/src/module-elasticsuite-catalog/Block/Navigation/Renderer/Slider.php b/src/module-elasticsuite-catalog/Block/Navigation/Renderer/Slider.php index 6bc150e43..e6f449920 100644 --- a/src/module-elasticsuite-catalog/Block/Navigation/Renderer/Slider.php +++ b/src/module-elasticsuite-catalog/Block/Navigation/Renderer/Slider.php @@ -73,18 +73,7 @@ public function __construct( */ public function getJsonConfig() { - $config = [ - 'minValue' => $this->getMinValue(), - 'maxValue' => $this->getMaxValue(), - 'currentValue' => $this->getCurrentValue(), - 'fieldFormat' => $this->getFieldFormat(), - 'intervals' => $this->getIntervals(), - 'urlTemplate' => $this->getUrlTemplate(), - 'messageTemplates' => [ - 'displayCount' => __('<%- count %> products'), - 'displayEmpty' => __('No products in the selected range.'), - ], - ]; + $config = $this->getConfig(); return $this->jsonEncoder->encode($config); } @@ -128,12 +117,35 @@ protected function getFieldFormat() return $format; } + /** + * Retrieve configuration + * + * @return array + */ + protected function getConfig() + { + $config = [ + 'minValue' => $this->getMinValue(), + 'maxValue' => $this->getMaxValue(), + 'currentValue' => $this->getCurrentValue(), + 'fieldFormat' => $this->getFieldFormat(), + 'intervals' => $this->getIntervals(), + 'urlTemplate' => $this->getUrlTemplate(), + 'messageTemplates' => [ + 'displayCount' => __('<%- count %> products'), + 'displayEmpty' => __('No products in the selected range.'), + ], + ]; + + return $config; + } + /** * Returns min value of the slider. * * @return int */ - private function getMinValue() + protected function getMinValue() { return $this->getFilter()->getMinValue(); } @@ -143,7 +155,7 @@ private function getMinValue() * * @return int */ - private function getMaxValue() + protected function getMaxValue() { return $this->getFilter()->getMaxValue() + 1; } diff --git a/src/module-elasticsuite-catalog/Model/Layer/Filter/Price.php b/src/module-elasticsuite-catalog/Model/Layer/Filter/Price.php index ed58cf1fb..c9c5b35a4 100644 --- a/src/module-elasticsuite-catalog/Model/Layer/Filter/Price.php +++ b/src/module-elasticsuite-catalog/Model/Layer/Filter/Price.php @@ -105,6 +105,13 @@ public function addFacetToCollection() $facetConfig = ['nestedFilter' => ['price.customer_group_id' => $customerGroupId]]; + $calculation = $this->dataProvider->getRangeCalculationValue(); + if ($calculation === \Magento\Catalog\Model\Layer\Filter\DataProvider\Price::RANGE_CALCULATION_MANUAL) { + if ((int) $this->dataProvider->getRangeStepValue() > 0) { + $facetConfig['interval'] = (int) $this->dataProvider->getRangeStepValue(); + } + } + $productCollection = $this->getLayer()->getProductCollection(); $productCollection->addFacet($facetField, $facetType, $facetConfig); diff --git a/src/module-elasticsuite-catalog/view/frontend/web/js/range-slider-widget.js b/src/module-elasticsuite-catalog/view/frontend/web/js/range-slider-widget.js index ccf9a7cb8..914db796e 100644 --- a/src/module-elasticsuite-catalog/view/frontend/web/js/range-slider-widget.js +++ b/src/module-elasticsuite-catalog/view/frontend/web/js/range-slider-widget.js @@ -48,7 +48,8 @@ define(["jquery", 'Magento_Catalog/js/price-utils', 'mage/template', "jquery/ui" min: this.options.minValue, max: this.options.maxValue, values: [ this.from, this.to ], - slide: this._onSliderChange.bind(this) + slide: this._onSliderChange.bind(this), + step: this.options.step }); }, From acf6e7967ea91b1ae29ce89fb873162196aa94f6 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 14 Sep 2016 19:06:58 +0200 Subject: [PATCH 34/48] Ensure autocomplete to append correct relevance configuration, and the spellchecker to retrieve proper spelling type. --- .../Autocomplete/Product/DataProvider.php | 2 +- .../Search/Request/Builder.php | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/module-elasticsuite-catalog/Model/Autocomplete/Product/DataProvider.php b/src/module-elasticsuite-catalog/Model/Autocomplete/Product/DataProvider.php index faa2b6c25..a0424a562 100644 --- a/src/module-elasticsuite-catalog/Model/Autocomplete/Product/DataProvider.php +++ b/src/module-elasticsuite-catalog/Model/Autocomplete/Product/DataProvider.php @@ -150,7 +150,7 @@ private function getProductCollection() $terms = array_merge($terms, $suggestedTerms); } - $productCollection = $this->productCollectionFactory->create(); + $productCollection = $this->productCollectionFactory->create(['searchRequestName' => 'quick_search_container']); $productCollection->addSearchFilter($terms); $productCollection->setPageSize($this->getResultsPageSize()); $productCollection diff --git a/src/module-elasticsuite-core/Search/Request/Builder.php b/src/module-elasticsuite-core/Search/Request/Builder.php index fc9ca6fec..476127aaa 100644 --- a/src/module-elasticsuite-core/Search/Request/Builder.php +++ b/src/module-elasticsuite-core/Search/Request/Builder.php @@ -177,19 +177,19 @@ public function create( */ private function getSpellingType(ContainerConfigurationInterface $containerConfig, $queryText) { - $spellingType = SpellcheckerInterface::SPELLING_TYPE_EXACT; + if (is_array($queryText)) { + $queryText = implode(" ", $queryText); + } - if (!is_array($queryText)) { - $spellcheckRequestParams = [ - 'index' => $containerConfig->getIndexName(), - 'type' => $containerConfig->getTypeName(), - 'queryText' => $queryText, - 'cutoffFrequency' => $containerConfig->getRelevanceConfig()->getCutOffFrequency(), - ]; + $spellcheckRequestParams = [ + 'index' => $containerConfig->getIndexName(), + 'type' => $containerConfig->getTypeName(), + 'queryText' => $queryText, + 'cutoffFrequency' => $containerConfig->getRelevanceConfig()->getCutOffFrequency(), + ]; - $spellcheckRequest = $this->spellcheckRequestFactory->create($spellcheckRequestParams); - $spellingType = $this->spellchecker->getSpellingType($spellcheckRequest); - } + $spellcheckRequest = $this->spellcheckRequestFactory->create($spellcheckRequestParams); + $spellingType = $this->spellchecker->getSpellingType($spellcheckRequest); return $spellingType; } From 9e09436dbdc11f61e35064a49a0cf123ee4de1d8 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 14 Sep 2016 19:44:46 +0200 Subject: [PATCH 35/48] Ability to kill previous autocomplete queries on the fly. --- .../view/frontend/web/js/form-mini.js | 116 ++++++++++-------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js index e687161d4..f33db6c50 100644 --- a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js +++ b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js @@ -29,6 +29,7 @@ define([ */ _create: function () { this.templateCache = []; + this.currentRequest = null; this._initTemplates(); this._super(); }, @@ -198,64 +199,73 @@ define([ this.submitBtn.disabled = this._isEmpty(value); if (value.length >= parseInt(this.options.minSearchLength, 10)) { - $.get(this.options.url, {q: value}, $.proxy(function (data) { - var self = this; - var lastElement = false; - var content = this._getResultWrapper(); - var sectionDropdown = this._getSectionHeader(); - $.each(data, function(index, element) { - - if (!lastElement || (lastElement && lastElement.type !== element.type)) { - sectionDropdown = this._getSectionHeader(element.type); - } - var elementHtml = this._renderItem(element, index); + this.currentRequest = $.ajax({ + method: "GET", + url: this.options.url, + data:{q: value}, + // This function will ensure proper killing of the last Ajax call. + // In order to prevent requests of an old request to pop up later and replace results. + beforeSend: function() { if (this.currentRequest !== null) { this.currentRequest.abort(); }}.bind(this), + success: $.proxy(function (data) { + var self = this; + var lastElement = false; + var content = this._getResultWrapper(); + var sectionDropdown = this._getSectionHeader(); + $.each(data, function(index, element) { - sectionDropdown.append(elementHtml); + if (!lastElement || (lastElement && lastElement.type !== element.type)) { + sectionDropdown = this._getSectionHeader(element.type); + } - if (!lastElement || (lastElement && lastElement.type !== element.type)) { - content.append(sectionDropdown); - } + var elementHtml = this._renderItem(element, index); - lastElement = element; - }.bind(this)); - this.responseList.indexList = this.autoComplete.html(content) - .css(clonePosition) - .show() - .find(this.options.responseFieldElements + ':visible'); - - this._resetResponseList(false); - this.element.removeAttr('aria-activedescendant'); - - if (this.responseList.indexList.length) { - this._updateAriaHasPopup(true); - } else { - this._updateAriaHasPopup(false); - } - - this.responseList.indexList - .on('click', function (e) { - self.responseList.selected = $(this); - if (self.responseList.selected.attr("href")) { - window.location.href = self.responseList.selected.attr("href"); - e.stopPropagation(); - return false; - } - self.searchForm.trigger('submit'); - }) - .on('mouseenter mouseleave', function (e) { - self.responseList.indexList.removeClass(self.options.selectClass); - $(this).addClass(self.options.selectClass); - self.responseList.selected = $(e.target); - self.element.attr('aria-activedescendant', $(e.target).attr('id')); - }) - .on('mouseout', function () { - if (!self._getLastElement() && self._getLastElement().hasClass(self.options.selectClass)) { - $(this).removeClass(self.options.selectClass); - self._resetResponseList(false); + sectionDropdown.append(elementHtml); + + if (!lastElement || (lastElement && lastElement.type !== element.type)) { + content.append(sectionDropdown); } - }); - }, this)); + + lastElement = element; + }.bind(this)); + this.responseList.indexList = this.autoComplete.html(content) + .css(clonePosition) + .show() + .find(this.options.responseFieldElements + ':visible'); + + this._resetResponseList(false); + this.element.removeAttr('aria-activedescendant'); + + if (this.responseList.indexList.length) { + this._updateAriaHasPopup(true); + } else { + this._updateAriaHasPopup(false); + } + + this.responseList.indexList + .on('click', function (e) { + self.responseList.selected = $(this); + if (self.responseList.selected.attr("href")) { + window.location.href = self.responseList.selected.attr("href"); + e.stopPropagation(); + return false; + } + self.searchForm.trigger('submit'); + }) + .on('mouseenter mouseleave', function (e) { + self.responseList.indexList.removeClass(self.options.selectClass); + $(this).addClass(self.options.selectClass); + self.responseList.selected = $(e.target); + self.element.attr('aria-activedescendant', $(e.target).attr('id')); + }) + .on('mouseout', function () { + if (!self._getLastElement() && self._getLastElement().hasClass(self.options.selectClass)) { + $(this).removeClass(self.options.selectClass); + self._resetResponseList(false); + } + }); + },this) + }); } else { this._resetResponseList(true); this.autoComplete.hide(); From 23672d162366acaf1e9c24e42f806c02ac5b829f Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 14 Sep 2016 18:13:57 +0200 Subject: [PATCH 36/48] Added the ability to active/desactive a thesaurus. Only active thesauri are indexed. --- .../Api/Data/ThesaurusInterface.php | 21 ++++++++++++ .../Block/Adminhtml/Thesaurus/Edit/Form.php | 31 ++++++++++++++--- .../Model/ResourceModel/Indexer/Thesaurus.php | 1 + .../Model/Thesaurus.php | 22 +++++++++++++ .../Setup/UpgradeSchema.php | 33 ++++++++++++++++--- .../etc/module.xml | 2 +- .../i18n/en_US.csv | 2 ++ .../i18n/fr_FR.csv | 2 ++ ...mile_elasticsuite_thesaurus_grid_block.xml | 17 ++++++++++ 9 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/module-elasticsuite-thesaurus/Api/Data/ThesaurusInterface.php b/src/module-elasticsuite-thesaurus/Api/Data/ThesaurusInterface.php index 714f5cc24..a6b3fb638 100644 --- a/src/module-elasticsuite-thesaurus/Api/Data/ThesaurusInterface.php +++ b/src/module-elasticsuite-thesaurus/Api/Data/ThesaurusInterface.php @@ -46,6 +46,11 @@ interface ThesaurusInterface */ const TYPE = 'type'; + /** + * Constant for field is_active + */ + const IS_ACTIVE = 'is_active'; + /** * Name of the link thesaurus/expansion terms TABLE */ @@ -99,6 +104,13 @@ public function getType(); */ public function getStoreIds(); + /** + * Get Thesaurus status + * + * @return bool + */ + public function isActive(); + /** * Set Thesaurus ID * @@ -134,4 +146,13 @@ public function setType($type); * @return ThesaurusInterface */ public function setStoreIds($storeIds); + + /** + * Set Thesaurus status + * + * @param bool $status The thesaurus status + * + * @return ThesaurusInterface + */ + public function setIsActive($status); } diff --git a/src/module-elasticsuite-thesaurus/Block/Adminhtml/Thesaurus/Edit/Form.php b/src/module-elasticsuite-thesaurus/Block/Adminhtml/Thesaurus/Edit/Form.php index 998718f13..d183c43b6 100644 --- a/src/module-elasticsuite-thesaurus/Block/Adminhtml/Thesaurus/Edit/Form.php +++ b/src/module-elasticsuite-thesaurus/Block/Adminhtml/Thesaurus/Edit/Form.php @@ -22,6 +22,8 @@ /** * Thesaurus Edit form * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * * @category Smile * @package Smile\ElasticsuiteThesaurus * @author Romain Ruaud @@ -33,24 +35,32 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic */ private $systemStore; + /** + * @var Yesno + */ + private $booleanSource; /** * Constructor * - * @param Context $context Application context - * @param \Magento\Framework\Registry $registry The registry - * @param FormFactory $formFactory Form factory - * @param Store $systemStore Store Provider - * @param array $data Object data + * @param Context $context Application context + * @param \Magento\Framework\Registry $registry The registry + * @param FormFactory $formFactory Form factory + * @param Store $systemStore Store Provider + * @param Yesno $booleanSource Boolean Input Source Model + * @param array $data Object data */ public function __construct( Context $context, Registry $registry, FormFactory $formFactory, Store $systemStore, + Yesno $booleanSource, array $data = [] ) { $this->systemStore = $systemStore; + $this->booleanSource = $booleanSource; + parent::__construct($context, $registry, $formFactory, $data); } @@ -131,6 +141,17 @@ private function initBaseFields($fieldset, $model) ] ); + $fieldset->addField( + 'is_active', + 'select', + [ + 'name' => 'is_active', + 'label' => __('Active'), + 'title' => __('Active'), + 'values' => $this->booleanSource->toOptionArray(), + ] + ); + if (!$this->_storeManager->isSingleStoreMode()) { $field = $fieldset->addField( 'store_id', diff --git a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php index 647eabac3..d46bf154a 100644 --- a/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php +++ b/src/module-elasticsuite-thesaurus/Model/ResourceModel/Indexer/Thesaurus.php @@ -84,6 +84,7 @@ private function getBaseSelect($storeId, $type) ->join(['store' => $this->getTable(ThesaurusInterface::STORE_TABLE_NAME)], 'store.thesaurus_id = thesaurus.thesaurus_id', []) ->group(['thesaurus.thesaurus_id', 'terms.term_id']) ->where("thesaurus.type = ?", $type) + ->where('thesaurus.is_active = 1') ->where('store.store_id IN (?)', [0, $storeId]); return $select; diff --git a/src/module-elasticsuite-thesaurus/Model/Thesaurus.php b/src/module-elasticsuite-thesaurus/Model/Thesaurus.php index 3a9e88ce0..f42386190 100644 --- a/src/module-elasticsuite-thesaurus/Model/Thesaurus.php +++ b/src/module-elasticsuite-thesaurus/Model/Thesaurus.php @@ -220,6 +220,28 @@ public function getTermsData() return $this->termsData; } + /** + * Get Thesaurus status + * + * @return bool + */ + public function isActive() + { + return (bool) $this->getData(self::IS_ACTIVE); + } + + /** + * Set Thesaurus status + * + * @param bool $status The thesaurus status + * + * @return ThesaurusInterface + */ + public function setIsActive($status) + { + return $this->setData(self::IS_ACTIVE, (bool) $status); + } + /** * Internal Constructor * diff --git a/src/module-elasticsuite-thesaurus/Setup/UpgradeSchema.php b/src/module-elasticsuite-thesaurus/Setup/UpgradeSchema.php index 9addc6cc9..023127302 100644 --- a/src/module-elasticsuite-thesaurus/Setup/UpgradeSchema.php +++ b/src/module-elasticsuite-thesaurus/Setup/UpgradeSchema.php @@ -40,10 +40,16 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con { $setup->startSetup(); - $this->createThesaurusTable($setup); - $this->createThesaurusStoreTable($setup); - $this->createExpandedTermsTable($setup); - $this->createExpansionReferenceTable($setup); + if (version_compare($context->getVersion(), '0.0.2', '<')) { + $this->createThesaurusTable($setup); + $this->createThesaurusStoreTable($setup); + $this->createExpandedTermsTable($setup); + $this->createExpansionReferenceTable($setup); + } + + if (version_compare($context->getVersion(), '1.0.0', '<')) { + $this->appendIsActiveColumn($setup); + } $setup->endSetup(); } @@ -213,4 +219,23 @@ private function createExpandedTermsTable(SchemaSetupInterface $setup) $setup->getConnection()->createTable($table); } + + /** + * Add an "is_active" column to the Thesaurus table. + * + * @param \Magento\Framework\Setup\SchemaSetupInterface $setup Setup instance + */ + private function appendIsActiveColumn(SchemaSetupInterface $setup) + { + $setup->getConnection()->addColumn( + $setup->getTable(ThesaurusInterface::TABLE_NAME), + ThesaurusInterface::IS_ACTIVE, + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => 1, + 'comment' => 'If the Thesaurus is active', + ] + ); + } } diff --git a/src/module-elasticsuite-thesaurus/etc/module.xml b/src/module-elasticsuite-thesaurus/etc/module.xml index 46ef89914..778b95ad5 100644 --- a/src/module-elasticsuite-thesaurus/etc/module.xml +++ b/src/module-elasticsuite-thesaurus/etc/module.xml @@ -17,7 +17,7 @@ */ --> - + diff --git a/src/module-elasticsuite-thesaurus/i18n/en_US.csv b/src/module-elasticsuite-thesaurus/i18n/en_US.csv index e23f00b36..c8ae122ac 100644 --- a/src/module-elasticsuite-thesaurus/i18n/en_US.csv +++ b/src/module-elasticsuite-thesaurus/i18n/en_US.csv @@ -44,3 +44,5 @@ Expansions,Expansions Terms,Terms Action,Action Edit,Edit +Active,Active +Inactive,Inactive diff --git a/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv b/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv index 6319ec793..fb219e27b 100644 --- a/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv +++ b/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv @@ -44,3 +44,5 @@ Expansions,Expansions Terms,Termes Action,Action Edit,Editer +Active,Activé +Inactive,Désactivé diff --git a/src/module-elasticsuite-thesaurus/view/adminhtml/layout/smile_elasticsuite_thesaurus_grid_block.xml b/src/module-elasticsuite-thesaurus/view/adminhtml/layout/smile_elasticsuite_thesaurus_grid_block.xml index 97a144704..cca8786c3 100644 --- a/src/module-elasticsuite-thesaurus/view/adminhtml/layout/smile_elasticsuite_thesaurus_grid_block.xml +++ b/src/module-elasticsuite-thesaurus/view/adminhtml/layout/smile_elasticsuite_thesaurus_grid_block.xml @@ -93,6 +93,23 @@ 0 + + + Status + is_active + options + + + 0 + Inactive + + + 1 + Active + + + + action From 18cfd880104aea0496880ef434041320989789c4 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 21 Sep 2016 11:12:13 +0200 Subject: [PATCH 37/48] Make elasticsuite use the default (dummy) data provider for search suggestions. --- src/module-elasticsuite-core/etc/di.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index 17c2f103d..1ce32d569 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -347,4 +347,12 @@ + + + + + Magento\AdvancedSearch\Model\DataProvider\Suggestions + + + From b8099c972ada24e00ccfb8a9cc7b2da7bc36ea60 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Fri, 16 Sep 2016 12:24:02 +0200 Subject: [PATCH 38/48] Fixing install process compatibility with staging. --- .../Setup/InstallData.php | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/module-elasticsuite-catalog/Setup/InstallData.php b/src/module-elasticsuite-catalog/Setup/InstallData.php index c88d21e6a..84d9743b1 100644 --- a/src/module-elasticsuite-catalog/Setup/InstallData.php +++ b/src/module-elasticsuite-catalog/Setup/InstallData.php @@ -13,9 +13,11 @@ */ namespace Smile\ElasticsuiteCatalog\Setup; +use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Catalog\Model\Category; use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; @@ -24,6 +26,8 @@ /** * Catalog installer * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * * @category Smile * @package Smile\ElasticsuiteCatalog * @author Romain Ruaud @@ -43,13 +47,20 @@ class InstallData implements InstallDataInterface */ private $eavSetup; + /** + * @var MetadataPool + */ + private $metadataPool; + /** * Class Constructor * * @param EavSetupFactory $eavSetupFactory Eav setup factory. + * @param MetadataPool $metadataPool Metadata Pool. */ - public function __construct(EavSetupFactory $eavSetupFactory) + public function __construct(EavSetupFactory $eavSetupFactory, MetadataPool $metadataPool) { + $this->metadataPool = $metadataPool; $this->eavSetupFactory = $eavSetupFactory; } @@ -139,20 +150,23 @@ private function updateAttributeDefaultValue($entityTypeId, $attributeId, $value $attributeId = $this->eavSetup->getAttributeId($entityTypeId, $attributeId); } + // Retrieve the primary key name. May differs if the staging module is activated or not. + $linkField = $this->metadataPool->getMetadata(CategoryInterface::class)->getLinkField(); + $entitySelect = $this->getConnection()->select(); $entitySelect->from( $entityTable, - [new \Zend_Db_Expr("{$attributeId} as attribute_id"), 'entity_id', new \Zend_Db_Expr("{$value} as value")] + [new \Zend_Db_Expr("{$attributeId} as attribute_id"), $linkField, new \Zend_Db_Expr("{$value} as value")] ); if (!empty($excludedIds)) { - $entitySelect->where('entity_id NOT IN(?)', $excludedIds); + $entitySelect->where("entity_id NOT IN(?)", $excludedIds); } $insertQuery = $this->getConnection()->insertFromSelect( $entitySelect, $attributeTable, - ['attribute_id', 'entity_id', 'value'], + ['attribute_id', $linkField, 'value'], AdapterInterface::INSERT_ON_DUPLICATE ); From fc534ebb45a9696be22070b8b7a1c79303eb16e9 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Fri, 16 Sep 2016 17:12:36 +0200 Subject: [PATCH 39/48] Staging Indexation implementation. --- .../Datasource/AbstractAttributeData.php | 55 +++++++++++++++++-- .../ResourceModel/Eav/Indexer/Indexer.php | 33 +++++++++++ .../Fulltext/Datasource/AttributeData.php | 20 ++++++- .../Fulltext/Datasource/CategoryData.php | 45 +++++++++------ .../Fulltext/Datasource/InventoryData.php | 5 +- src/module-elasticsuite-catalog/etc/di.xml | 8 +++ 6 files changed, 142 insertions(+), 24 deletions(-) diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Fulltext/Datasource/AbstractAttributeData.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Fulltext/Datasource/AbstractAttributeData.php index bb25ee5d0..f077a7cf3 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Fulltext/Datasource/AbstractAttributeData.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Fulltext/Datasource/AbstractAttributeData.php @@ -13,6 +13,9 @@ namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Fulltext\Datasource; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Store\Model\StoreManagerInterface; use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection as AttributeCollection; @@ -37,6 +40,29 @@ class AbstractAttributeData extends Indexer 'used_for_sort_by' => ['operator' => '=', 'value' => 1], ]; + /** + * @var null|string + */ + private $entityTypeId = null; + + /** + * AbstractAttributeData constructor. + * + * @param \Magento\Framework\App\ResourceConnection $resource Resource Connection + * @param \Magento\Store\Model\StoreManagerInterface $storeManager Store Manager + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool Entity Metadata Pool + * @param string $entityType Entity Type + */ + public function __construct( + ResourceConnection $resource, + StoreManagerInterface $storeManager, + MetadataPool $metadataPool, + $entityType = null + ) { + $this->entityTypeId = $entityType; + parent::__construct($resource, $storeManager, $metadataPool); + } + /** * Allow to filter an attribute collection on attributes that are indexed into the search engine. * @@ -77,10 +103,16 @@ public function addIndexedFilterToAttributeCollection(AttributeCollection $attri */ public function getAttributesRawData($storeId, array $entityIds, $tableName, array $attributeIds) { - $select = $this->connection->select(); + $select = $this->connection->select(); + + // The field modelizing the link between entity table and attribute values table. Either row_id or entity_id. + $linkField = $this->getEntityMetaData($this->getEntityTypeId())->getLinkField(); + + // The legacy entity_id field. + $entityIdField = $this->getEntityMetaData($this->getEntityTypeId())->getIdentifierField(); $joinStoreValuesConditionClauses = [ - 't_default.entity_id = t_store.entity_id', + "t_default.$linkField = t_store.$linkField", 't_default.attribute_id = t_store.attribute_id', 't_store.store_id= ?', ]; @@ -90,13 +122,28 @@ public function getAttributesRawData($storeId, array $entityIds, $tableName, arr $storeId ); - $select->from(['t_default' => $tableName], ['entity_id', 'attribute_id']) + $select->from(['entity' => $this->getEntityMetaData($this->getEntityTypeId())->getEntityTable()], [$entityIdField]) + ->joinInner( + ['t_default' => $tableName], + new \Zend_Db_Expr("entity.{$linkField} = t_default.{$linkField}"), + ['attribute_id'] + ) ->joinLeft(['t_store' => $tableName], $joinStoreValuesCondition, []) ->where('t_default.store_id=?', 0) ->where('t_default.attribute_id IN (?)', $attributeIds) - ->where('t_default.entity_id IN (?)', $entityIds) + ->where("entity.{$entityIdField} IN (?)", $entityIds) ->columns(['value' => new \Zend_Db_Expr('COALESCE(t_store.value, t_default.value)')]); return $this->connection->fetchAll($select); } + + /** + * Get Entity Type Id. + * + * @return string + */ + protected function getEntityTypeId() + { + return $this->entityTypeId; + } } diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Indexer.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Indexer.php index 73242a943..c96c45c4f 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Indexer.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Eav/Indexer/Indexer.php @@ -15,6 +15,9 @@ namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Store\Model\StoreManagerInterface; use Smile\ElasticsuiteCore\Model\ResourceModel\Indexer\AbstractIndexer; /** @@ -32,6 +35,24 @@ class Indexer extends AbstractIndexer */ protected $storeManager; + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + + /** + * Indexer constructor. + * + * @param \Magento\Framework\App\ResourceConnection $resource Resource Connection + * @param \Magento\Store\Model\StoreManagerInterface $storeManager Store Manager + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool Metadata Pool + */ + public function __construct(ResourceConnection $resource, StoreManagerInterface $storeManager, MetadataPool $metadataPool) + { + parent::__construct($resource, $storeManager); + $this->metadataPool = $metadataPool; + } + /** * Retrieve store root category id. * @@ -49,4 +70,16 @@ protected function getRootCategoryId($store) return $this->storeManager->getGroup($storeGroupId)->getRootCategoryId(); } + + /** + * Retrieve Metadata for an entity + * + * @param string $entityType The entity + * + * @return EntityMetadataInterface + */ + protected function getEntityMetaData($entityType) + { + return $this->metadataPool->getMetadata($entityType); + } } diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/AttributeData.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/AttributeData.php index cbb6c0dfb..116d87797 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/AttributeData.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/AttributeData.php @@ -14,6 +14,8 @@ namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Datasource; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\EntityManager\MetadataPool; use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Fulltext\Datasource\AbstractAttributeData; use Magento\Framework\App\ResourceConnection; use Magento\Store\Model\StoreManagerInterface; @@ -50,14 +52,18 @@ class AttributeData extends AbstractAttributeData * * @param ResourceConnection $resource Database adpater. * @param StoreManagerInterface $storeManager Store manager. + * @param MetadataPool $metadataPool Metadata Pool. * @param ProductType $catalogProductType Product type. + * @param string $entityType Product entity type. */ public function __construct( ResourceConnection $resource, StoreManagerInterface $storeManager, - ProductType $catalogProductType + MetadataPool $metadataPool, + ProductType $catalogProductType, + $entityType = ProductInterface::class ) { - parent::__construct($resource, $storeManager); + parent::__construct($resource, $storeManager, $metadataPool, $entityType); $this->catalogProductType = $catalogProductType; } @@ -154,4 +160,14 @@ protected function getProductTypeInstance($typeId) return $this->productTypes[$typeId]; } + + /** + * Get Entity Id used by this indexer + * + * @return string + */ + protected function getEntityTypeId() + { + return ProductInterface::class; + } } diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php index 3abd77738..4258b2037 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/CategoryData.php @@ -14,8 +14,10 @@ namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Datasource; use Magento\Catalog\Api\Data\CategoryAttributeInterface; +use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Eav\Model\Config; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\StoreManagerInterface; use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer; @@ -51,14 +53,19 @@ class CategoryData extends Indexer /** * CategoryData constructor. * - * @param \Magento\Framework\App\ResourceConnection $resource Connection Resource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager The store manager - * @param \Magento\Eav\Model\Config $eavConfig EAV Configuration + * @param \Magento\Framework\App\ResourceConnection $resource Connection Resource + * @param \Magento\Store\Model\StoreManagerInterface $storeManager The store manager + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool Metadata Pool + * @param \Magento\Eav\Model\Config $eavConfig EAV Configuration */ - public function __construct(ResourceConnection $resource, StoreManagerInterface $storeManager, Config $eavConfig) - { + public function __construct( + ResourceConnection $resource, + StoreManagerInterface $storeManager, + MetadataPool $metadataPool, + Config $eavConfig + ) { $this->eavConfig = $eavConfig; - parent::__construct($resource, $storeManager); + parent::__construct($resource, $storeManager, $metadataPool); } /** @@ -162,9 +169,10 @@ private function loadCategoryNames($categoryIds, $storeId) if (!empty($loadCategoryIds)) { $select = $this->prepareCategoryNameSelect($loadCategoryIds, $storeId); + $entityIdField = $this->getEntityMetaData(CategoryInterface::class)->getIdentifierField(); foreach ($this->getConnection()->fetchAll($select) as $row) { - $categoryId = (int) $row['entity_id']; + $categoryId = (int) $row[$entityIdField]; $this->categoryNameCache[$storeId][$categoryId] = ''; if ((bool) $row['use_name']) { $this->categoryNameCache[$storeId][$categoryId] = $row['name']; @@ -188,20 +196,23 @@ private function prepareCategoryNameSelect($loadCategoryIds, $storeId) $rootCategoryId = (int) $this->storeManager->getStore($storeId)->getRootCategoryId(); $this->categoryNameCache[$storeId][$rootCategoryId] = ''; - $nameAttr = $this->getCategoryNameAttribute(); - $useNameAttr = $this->getUseNameInSearchAttribute(); + $nameAttr = $this->getCategoryNameAttribute(); + $useNameAttr = $this->getUseNameInSearchAttribute(); + $entityIdField = $this->getEntityMetaData(CategoryInterface::class)->getIdentifierField(); + $linkField = $this->getEntityMetaData(CategoryInterface::class)->getLinkField(); + $select = $this->connection->select(); - // Initialize retrieval of category name. - $select = $this->getConnection()->select() - ->from(['default_value' => $nameAttr->getBackendTable()], ['entity_id']) - ->where('default_value.entity_id != ?', $rootCategoryId) + $joinCondition = new \Zend_Db_Expr("cat.{$linkField} = default_value.{$linkField}"); + $select->from(['cat' => $this->getEntityMetaData(CategoryInterface::class)->getEntityTable()], [$entityIdField]) + ->joinInner(['default_value' => $nameAttr->getBackendTable()], $joinCondition, []) + ->where("cat.$entityIdField != ?", $rootCategoryId) ->where('default_value.store_id = ?', 0) ->where('default_value.attribute_id = ?', (int) $nameAttr->getAttributeId()) - ->where('default_value.entity_id IN (?)', $loadCategoryIds); + ->where("cat.$entityIdField IN (?)", $loadCategoryIds); // Join to check for use_name_in_product_search. $joinUseNameCond = sprintf( - "default_value.entity_id = use_name_default_value.entity_id" . + "default_value.$linkField = use_name_default_value.$linkField" . " AND use_name_default_value.attribute_id = %d AND use_name_default_value.store_id = %d", (int) $useNameAttr->getAttributeId(), 0 @@ -217,7 +228,7 @@ private function prepareCategoryNameSelect($loadCategoryIds, $storeId) // Multi store additional join to get scoped name value. $joinStoreNameCond = sprintf( - "default_value.entity_id = store_value.entity_id" . + "default_value.$linkField = store_value.$linkField" . " AND store_value.attribute_id = %d AND store_value.store_id = %d", (int) $nameAttr->getAttributeId(), (int) $storeId @@ -227,7 +238,7 @@ private function prepareCategoryNameSelect($loadCategoryIds, $storeId) // Multi store additional join to get scoped "use_name_in_product_search" value. $joinUseNameStoreCond = sprintf( - "default_value.entity_id = use_name_store_value.entity_id" . + "default_value.$linkField = use_name_store_value.$linkField" . " AND use_name_store_value.attribute_id = %d AND use_name_store_value.store_id = %d", (int) $useNameAttr->getAttributeId(), (int) $storeId diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php index e84ff800e..fc5db4f78 100644 --- a/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/InventoryData.php @@ -15,6 +15,7 @@ use Magento\CatalogInventory\Api\StockRegistryInterface; use \Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\StoreManagerInterface; use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer; @@ -48,19 +49,21 @@ class InventoryData extends Indexer * * @param ResourceConnection $resource Database adapter. * @param StoreManagerInterface $storeManager Store manager. + * @param MetadataPool $metadataPool Metadata Pool * @param StockRegistryInterface $stockRegistry Stock registry. * @param StockConfigurationInterface $stockConfiguration Stock configuration. */ public function __construct( ResourceConnection $resource, StoreManagerInterface $storeManager, + MetadataPool $metadataPool, StockRegistryInterface $stockRegistry, StockConfigurationInterface $stockConfiguration ) { $this->stockRegistry = $stockRegistry; $this->stockConfiguration = $stockConfiguration; - parent::__construct($resource, $storeManager); + parent::__construct($resource, $storeManager, $metadataPool); } /** diff --git a/src/module-elasticsuite-catalog/etc/di.xml b/src/module-elasticsuite-catalog/etc/di.xml index 01e2c7da2..1ce60220a 100644 --- a/src/module-elasticsuite-catalog/etc/di.xml +++ b/src/module-elasticsuite-catalog/etc/di.xml @@ -140,9 +140,17 @@ + + + Magento\Catalog\Api\Data\CategoryInterface + + + Smile\ElasticsuiteCatalog\Helper\CategoryAttribute + Smile\ElasticsuiteCatalog\Model\ResourceModel\Category\Indexer\Fulltext\Datasource\AttributeData From e03baa93bb04d5b52df6a87897d810be8a5cc9ca Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Tue, 20 Sep 2016 13:40:09 +0200 Subject: [PATCH 40/48] Layered Navigation Staging implementation. --- .../Block/Navigation.php | 65 +++++++++++++++++++ .../etc/frontend/di.xml | 15 +++++ .../catalog_category_view_type_layered.xml | 2 +- .../layout/catalogsearch_result_index.xml | 2 +- 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-catalog/Block/Navigation.php b/src/module-elasticsuite-catalog/Block/Navigation.php index 8f65dfacf..4a795ab12 100644 --- a/src/module-elasticsuite-catalog/Block/Navigation.php +++ b/src/module-elasticsuite-catalog/Block/Navigation.php @@ -14,6 +14,13 @@ namespace Smile\ElasticsuiteCatalog\Block; +use Magento\Catalog\Model\Layer\AvailabilityFlagInterface; +use Magento\Catalog\Model\Layer\FilterList; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Module\Manager; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Element\Template\Context; + /** * Custom implementation of the navigation block to apply facet coverage rate. * @@ -23,6 +30,64 @@ */ class Navigation extends \Magento\LayeredNavigation\Block\Navigation { + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Manager + */ + private $moduleManager; + + /** + * Navigation constructor. + * + * @param \Magento\Framework\View\Element\Template\Context $context Application context + * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver Layer Resolver + * @param \Magento\Catalog\Model\Layer\FilterList $filterList Filter List + * @param \Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag Visibility Flag + * @param \Magento\Framework\ObjectManagerInterface $objectManager Object Manager + * @param \Magento\Framework\Module\Manager $moduleManager Module Manager + * @param array $data Block Data + */ + public function __construct( + Context $context, + Resolver $layerResolver, + FilterList $filterList, + AvailabilityFlagInterface $visibilityFlag, + ObjectManagerInterface $objectManager, + Manager $moduleManager, + array $data + ) { + $this->objectManager = $objectManager; + $this->moduleManager = $moduleManager; + + parent::__construct($context, $layerResolver, $filterList, $visibilityFlag, $data); + } + + /** + * Check if we can show this block. + * According to @see \Magento\LayeredNavigationStaging\Block\Navigation::canShowBlock + * We should not show the block if staging is enabled and if we are currently previewing the results. + * + * @return bool + */ + public function canShowBlock() + { + if ($this->moduleManager->isEnabled('Magento_Staging')) { + try { + $versionManager = $this->objectManager->get('\Magento\Staging\Model\VersionManager'); + + return parent::canShowBlock() && !$versionManager->isPreviewVersion(); + } catch (\Exception $exception) { + return parent::canShowBlock(); + } + } + + return parent::canShowBlock(); + } + /** * @SuppressWarnings(PHPMD.CamelCaseMethodName) * diff --git a/src/module-elasticsuite-catalog/etc/frontend/di.xml b/src/module-elasticsuite-catalog/etc/frontend/di.xml index b98c380ec..6601521cc 100644 --- a/src/module-elasticsuite-catalog/etc/frontend/di.xml +++ b/src/module-elasticsuite-catalog/etc/frontend/di.xml @@ -104,4 +104,19 @@ Smile\ElasticsuiteCatalog\Model\Layer\Filter\Item\CategoryFactory + + + + + categoryFilterList + + + + + + searchFilterList + + + + diff --git a/src/module-elasticsuite-catalog/view/frontend/layout/catalog_category_view_type_layered.xml b/src/module-elasticsuite-catalog/view/frontend/layout/catalog_category_view_type_layered.xml index 8bda1c8f2..38e81b2f2 100644 --- a/src/module-elasticsuite-catalog/view/frontend/layout/catalog_category_view_type_layered.xml +++ b/src/module-elasticsuite-catalog/view/frontend/layout/catalog_category_view_type_layered.xml @@ -17,7 +17,7 @@ - + - + Date: Thu, 15 Sep 2016 18:38:02 +0200 Subject: [PATCH 41/48] Categories autocomplete feature implementation. --- .../Autocomplete/Category/DataProvider.php | 168 ++++++++ .../Autocomplete/Category/ItemFactory.php | 225 ++++++++++ .../Category/Fulltext/Collection.php | 395 ++++++++++++++++++ .../Setup/UpgradeData.php | 111 +++++ .../etc/adminhtml/system.xml | 8 + .../etc/config.xml | 3 + .../etc/elasticsuite_search_request.xml | 1 + .../etc/frontend/di.xml | 1 + .../etc/module.xml | 2 +- .../i18n/en_US.csv | 2 + .../i18n/fr_FR.csv | 2 + .../view/frontend/web/css/source/_module.less | 8 + .../web/template/autocomplete/category.html | 3 +- 13 files changed, 927 insertions(+), 2 deletions(-) create mode 100644 src/module-elasticsuite-catalog/Model/Autocomplete/Category/DataProvider.php create mode 100644 src/module-elasticsuite-catalog/Model/Autocomplete/Category/ItemFactory.php create mode 100644 src/module-elasticsuite-catalog/Model/ResourceModel/Category/Fulltext/Collection.php create mode 100644 src/module-elasticsuite-catalog/Setup/UpgradeData.php diff --git a/src/module-elasticsuite-catalog/Model/Autocomplete/Category/DataProvider.php b/src/module-elasticsuite-catalog/Model/Autocomplete/Category/DataProvider.php new file mode 100644 index 000000000..4421a1379 --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/Autocomplete/Category/DataProvider.php @@ -0,0 +1,168 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Model\Autocomplete\Category; + +use Magento\Search\Model\Autocomplete\DataProviderInterface; +use Magento\Search\Model\QueryFactory; +use Smile\ElasticsuiteCatalog\Helper\Autocomplete as ConfigurationHelper; +use Smile\ElasticsuiteCatalog\Model\ResourceModel\Category\Fulltext\CollectionFactory as CategoryCollectionFactory; +use Smile\ElasticsuiteCore\Model\Autocomplete\Terms\DataProvider as TermDataProvider; + +/** + * Catalog category autocomplete data provider. + * + * @category Smile + * @package Smile\ElasticSuiteCatalog + * @author Romain Ruaud + */ +class DataProvider implements DataProviderInterface +{ + /** + * Autocomplete type + */ + const AUTOCOMPLETE_TYPE = "category"; + + /** + * Autocomplete result item factory + * + * @var ItemFactory + */ + protected $itemFactory; + + /** + * Query factory + * + * @var QueryFactory + */ + protected $queryFactory; + + /** + * @var TermDataProvider + */ + protected $termDataProvider; + + /** + * @var CategoryCollectionFactory + */ + protected $categoryCollectionFactory; + + /** + * @var ConfigurationHelper + */ + protected $configurationHelper; + + /** + * @var string Autocomplete result type + */ + private $type; + + /** + * Constructor. + * + * @param ItemFactory $itemFactory Suggest item factory. + * @param QueryFactory $queryFactory Search query factory. + * @param TermDataProvider $termDataProvider Search terms suggester. + * @param CategoryCollectionFactory $categoryCollectionFactory Category collection factory. + * @param ConfigurationHelper $configurationHelper Autocomplete configuration helper. + * @param string $type Autocomplete provider type. + */ + public function __construct( + ItemFactory $itemFactory, + QueryFactory $queryFactory, + TermDataProvider $termDataProvider, + CategoryCollectionFactory $categoryCollectionFactory, + ConfigurationHelper $configurationHelper, + $type = self::AUTOCOMPLETE_TYPE + ) { + $this->itemFactory = $itemFactory; + $this->queryFactory = $queryFactory; + $this->termDataProvider = $termDataProvider; + $this->categoryCollectionFactory = $categoryCollectionFactory; + $this->configurationHelper = $configurationHelper; + $this->type = $type; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function getItems() + { + $result = []; + $categoryCollection = $this->getCategoryCollection(); + if ($categoryCollection) { + foreach ($categoryCollection as $category) { + $result[] = $this->itemFactory->create(['category' => $category, 'type' => $this->getType()]); + } + } + + return $result; + } + + /** + * List of search terms suggested by the search terms data provider. + * + * @return array + */ + private function getSuggestedTerms() + { + $terms = array_map( + function (\Magento\Search\Model\Autocomplete\Item $termItem) { + return $termItem->getTitle(); + }, + $this->termDataProvider->getItems() + ); + + return $terms; + } + + /** + * Suggested categories collection. + * Returns null if no suggested search terms. + * + * @return \Smile\ElasticsuiteCatalog\Model\ResourceModel\Category\Fulltext\Collection|null + */ + private function getCategoryCollection() + { + $categoryCollection = null; + $suggestedTerms = $this->getSuggestedTerms(); + $terms = [$this->queryFactory->get()->getQueryText()]; + + if (!empty($suggestedTerms)) { + $terms = array_merge($terms, $suggestedTerms); + } + + $categoryCollection = $this->categoryCollectionFactory->create(); + $categoryCollection->addSearchFilter($terms); + $categoryCollection->setPageSize($this->getResultsPageSize()); + + return $categoryCollection; + } + + /** + * Retrieve number of categories to display in autocomplete results + * + * @return int + */ + private function getResultsPageSize() + { + return $this->configurationHelper->getMaxSize($this->getType()); + } +} diff --git a/src/module-elasticsuite-catalog/Model/Autocomplete/Category/ItemFactory.php b/src/module-elasticsuite-catalog/Model/Autocomplete/Category/ItemFactory.php new file mode 100644 index 000000000..0718be170 --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/Autocomplete/Category/ItemFactory.php @@ -0,0 +1,225 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCatalog\Model\Autocomplete\Category; + +use Magento\Catalog\Model\CategoryFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\UrlInterface; + +/** + * Create an autocomplete item from a category. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class ItemFactory extends \Magento\Search\Model\Autocomplete\ItemFactory +{ + /** + * XML path for category url suffix + */ + const XML_PATH_CATEGORY_URL_SUFFIX = 'catalog/seo/category_url_suffix'; + + /** + * The offset to display on the beginning of the Breadcrumb + */ + const START_BREADCRUMB_OFFSET = 1; + + /** + * The offset to display on the end of the Breadcrumb + */ + const END_BREADCRUMB_OFFSET = 1; + + /** + * The string used when chunking + */ + const CHUNK_STRING = "..."; + + /** + * @var array An array containing category names, to use as local cache + */ + protected $categoryNames = []; + + /** + * @var \Magento\Framework\UrlInterface + */ + private $urlBuilder; + + /** + * @var null + */ + private $categoryUrlSuffix = null; + + /** + * @var \Magento\Catalog\Model\CategoryFactory|null + */ + private $categoryFactory = null; + + /** + * ItemFactory constructor. + * + * @param ObjectManagerInterface $objectManager The Object Manager + * @param UrlInterface $urlBuilder The Url Builder + * @param ScopeConfigInterface $scopeConfig The Scope Config + * @param CategoryFactory $categoryFactory Category Factory + */ + public function __construct( + ObjectManagerInterface $objectManager, + UrlInterface $urlBuilder, + ScopeConfigInterface $scopeConfig, + CategoryFactory $categoryFactory + ) { + parent::__construct($objectManager); + $this->urlBuilder = $urlBuilder; + $this->categoryUrlSuffix = $scopeConfig->getValue(self::XML_PATH_CATEGORY_URL_SUFFIX); + $this->categoryFactory = $categoryFactory; + } + + /** + * {@inheritDoc} + */ + public function create(array $data) + { + $data = $this->addCategoryData($data); + unset($data['category']); + + return parent::create($data); + } + + /** + * Load category data and append them to the original data. + * + * @param array $data Autocomplete item data. + * + * @return array + */ + private function addCategoryData($data) + { + $category = $data['category']; + + $documentSource = $category->getDocumentSource(); + + $categoryData = [ + 'title' => $documentSource['name'], + 'url' => $this->getCategoryUrl($category), + 'breadcrumb' => $this->getCategoryBreadcrumb($category), + ]; + + $data = array_merge($data, $categoryData); + + return $data; + } + + /** + * Retrieve category Url from the document source. + * Done from the document source to prevent having to use addUrlRewrite to result on category collection. + * + * @param \Magento\Catalog\Model\Category $category The category. + * + * @return string + */ + private function getCategoryUrl($category) + { + $documentSource = $category->getDocumentSource(); + + if ($documentSource && isset($documentSource['url_path'])) { + $urlPath = is_array($documentSource['url_path']) ? current($documentSource['url_path']) : $documentSource['url_path']; + + $url = trim($this->urlBuilder->getUrl($urlPath), '/') . $this->categoryUrlSuffix; + + return $url; + } + + return $category->getUrl(); + } + + /** + * Return a mini-breadcrumb for a category + * + * @param \Magento\Catalog\Model\Category $category The category + * + * @return array + */ + private function getCategoryBreadcrumb(\Magento\Catalog\Model\Category $category) + { + $chunkPath = $this->getChunkedPath($category); + $breadcrumb = []; + + foreach ($chunkPath as $categoryId) { + $breadcrumb[] = $this->getCategoryNameById($categoryId, $category->getStoreId()); + } + + return implode(' > ', $breadcrumb); + } + + /** + * Return chunked (if needed) path for a category + * + * A chunked path is the first 2 highest ancestors and the 2 lowests levels of path + * + * If path is not longer than 4, complete path is used + * + * @param \Magento\Catalog\Model\Category $category The category + * + * @return array + */ + private function getChunkedPath(\Magento\Catalog\Model\Category $category) + { + $path = $category->getPath(); + $rawPath = explode('/', $path); + + // First occurence is root category (1), second is root category of store. + $rawPath = array_slice($rawPath, 2); + + // Last occurence is the category displayed. + array_pop($rawPath); + + $chunkedPath = $rawPath; + + if (count($rawPath) > (self::START_BREADCRUMB_OFFSET + self::END_BREADCRUMB_OFFSET)) { + $chunkedPath = array_merge( + array_slice($rawPath, 0, self::START_BREADCRUMB_OFFSET), + [self::CHUNK_STRING], + array_slice($rawPath, -self::END_BREADCRUMB_OFFSET) + ); + } + + return $chunkedPath; + } + + /** + * Retrieve a category name by it's id, and store it in local cache + * + * @param int $categoryId The category Id + * @param int $storeId The store Id + * + * @return string + */ + private function getCategoryNameById($categoryId, $storeId) + { + if ($categoryId == self::CHUNK_STRING) { + return self::CHUNK_STRING; + } + + if (!isset($this->categoryNames[$categoryId])) { + $category = $this->categoryFactory->create(); + $categoryResource = $category->getResource(); + $this->categoryNames[$categoryId] = $categoryResource->getAttributeRawValue($categoryId, "name", $storeId); + } + + return $this->categoryNames[$categoryId]; + } +} diff --git a/src/module-elasticsuite-catalog/Model/ResourceModel/Category/Fulltext/Collection.php b/src/module-elasticsuite-catalog/Model/ResourceModel/Category/Fulltext/Collection.php new file mode 100644 index 000000000..7f29710e4 --- /dev/null +++ b/src/module-elasticsuite-catalog/Model/ResourceModel/Category/Fulltext/Collection.php @@ -0,0 +1,395 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Category\Fulltext; + +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteCore\Search\RequestInterface; + +/** + * Search engine category collection for Autocomplete. + * Basically a copy-pasted version of @see Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection + * + * @codingStandardsIgnoreStart + * @TODO Refactor/Mutualize all copy/pasted methods. + * @codingStandardsIgnoreEnd + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @category Smile + * @package Smile\ElasticSuiteCatalog + * @author Romain Ruaud + */ +class Collection extends \Magento\Catalog\Model\ResourceModel\Category\Collection +{ + /** + * @var \Smile\ElasticsuiteCore\Search\Request\Builder + */ + private $requestBuilder; + + /** + * @var \Magento\Search\Model\SearchEngine + */ + private $searchEngine; + + /** + * @var string + */ + private $searchRequestName; + + /** + * @var array + */ + private $filters = []; + + /** + * @var QueryInterface[] + */ + private $queryFilters = []; + + /** + * @var array + */ + private $facets = []; + + /** + * @var QueryResponse + */ + private $queryResponse; + + /** + * @var string + */ + private $queryText; + + /** + * @var boolean + */ + private $isSpellchecked = false; + + /** + * Collection constructor. + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * + * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory The Entity Factory + * @param \Psr\Log\LoggerInterface $logger The Logger + * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy Fetch Strategy + * @param \Magento\Framework\Event\ManagerInterface $eventManager Event Manager + * @param \Magento\Eav\Model\Config $eavConfig EAV Configuration + * @param \Magento\Framework\App\ResourceConnection $resource Resource Connection + * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory Entity Factory + * @param \Magento\Eav\Model\ResourceModel\Helper $resourceHelper Resource Helper + * @param \Magento\Framework\Validator\UniversalFactory $universalFactory Universal Factory + * @param \Magento\Store\Model\StoreManagerInterface $storeManager Store Manager + * @param \Smile\ElasticsuiteCore\Search\Request\Builder $requestBuilder Search request builder. + * @param \Magento\Search\Model\SearchEngine $searchEngine Search engine + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection Db Connection. + * @param string $searchRequestName Search request name. + */ + public function __construct( + \Magento\Framework\Data\Collection\EntityFactory $entityFactory, + \Psr\Log\LoggerInterface $logger, + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Eav\Model\Config $eavConfig, + \Magento\Framework\App\ResourceConnection $resource, + \Magento\Eav\Model\EntityFactory $eavEntityFactory, + \Magento\Eav\Model\ResourceModel\Helper $resourceHelper, + \Magento\Framework\Validator\UniversalFactory $universalFactory, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Smile\ElasticsuiteCore\Search\Request\Builder $requestBuilder, + \Magento\Search\Model\SearchEngine $searchEngine, + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, + $searchRequestName = 'category_search_container' + ) { + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $eavConfig, + $resource, + $eavEntityFactory, + $resourceHelper, + $universalFactory, + $storeManager, + $connection + ); + + $this->requestBuilder = $requestBuilder; + $this->searchEngine = $searchEngine; + $this->searchRequestName = $searchRequestName; + } + + /** + * {@inheritDoc} + */ + public function getSize() + { + if ($this->_totalRecords === null) { + $this->loadItemCounts(); + } + + return $this->_totalRecords; + } + + /** + * {@inheritDoc} + */ + public function setOrder($attribute, $dir = self::SORT_ORDER_DESC) + { + $this->_orders[$attribute] = $dir; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function addFieldToFilter($field, $condition = null) + { + $this->filters[$field] = $condition; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) + { + return $this->setOrder($attribute, $dir); + } + + /** + * Append a prebuilt (QueryInterface) query filter to the collection. + * + * @param QueryInterface $queryFilter Query filter. + * + * @return $this + */ + public function addQueryFilter(QueryInterface $queryFilter) + { + $this->queryFilters[] = $queryFilter; + + return $this; + } + + /** + * Add search query filter + * + * @param string $query Search query text. + * + * @return \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection + */ + public function addSearchFilter($query) + { + $this->queryText = $query; + + return $this; + } + + /** + * Append a facet to the collection + * + * @param string $field Facet field. + * @param string $facetType Facet type. + * @param array $facetConfig Facet config params. + * @param array $facetFilter Facet filter. + * + * @return \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection + */ + public function addFacet($field, $facetType, $facetConfig, $facetFilter = null) + { + $this->facets[$field] = ['type' => $facetType, 'filter' => $facetFilter, 'config' => $facetConfig]; + + return $this; + } + + /** + * Return field faceted data from faceted search result. + * + * @param string $field Facet field. + * + * @return array + */ + public function getFacetedData($field) + { + $this->_renderFilters(); + $result = []; + $aggregations = $this->queryResponse->getAggregations(); + + $bucket = $aggregations->getBucket($field); + + if ($bucket) { + foreach ($bucket->getValues() as $value) { + $metrics = $value->getMetrics(); + $result[$metrics['value']] = $metrics; + } + } + + return $result; + } + + /** + * Indicates if the collection is spellchecked or not. + * + * @return boolean + */ + public function isSpellchecked() + { + return $this->isSpellchecked; + } + + /** + * @SuppressWarnings(PHPMD.CamelCaseMethodName) + * + * {@inheritdoc} + */ + protected function _renderFiltersBefore() + { + $searchRequest = $this->prepareRequest(); + + $this->queryResponse = $this->searchEngine->search($searchRequest); + + // Update the item count. + $this->_totalRecords = $this->queryResponse->count(); + + // Filter search results. The pagination has to be resetted since it is managed by the engine itself. + $docIds = array_map( + function (\Magento\Framework\Api\Search\Document $doc) { + return (int) $doc->getId(); + }, + $this->queryResponse->getIterator()->getArrayCopy() + ); + + if (empty($docIds)) { + $docIds[] = 0; + } + + $this->getSelect()->where('e.entity_id IN (?)', ['in' => $docIds]); + $this->_pageSize = false; + + $this->isSpellchecked = $searchRequest->isSpellchecked(); + + parent::_renderFiltersBefore(); + } + + /** + * @SuppressWarnings(PHPMD.CamelCaseMethodName) + * + * {@inheritDoc} + */ + protected function _afterLoad() + { + // Resort items according the search response. + $originalItems = $this->_items; + $this->_items = []; + + foreach ($this->queryResponse->getIterator() as $document) { + $documentId = $document->getId(); + if (isset($originalItems[$documentId])) { + $originalItems[$documentId]->setDocumentScore($document->getScore()); + $originalItems[$documentId]->setDocumentSource($document->getSource()); + $this->_items[$documentId] = $originalItems[$documentId]; + } + } + + return parent::_afterLoad(); + } + + /** + * Prepare the search request before it will be executed. + * + * @return RequestInterface + */ + private function prepareRequest() + { + // Store id and request name. + $storeId = $this->getStoreId(); + $searchRequestName = $this->searchRequestName; + + // Pagination params. + $size = $this->_pageSize ? $this->_pageSize : 20; + $from = $size * (max(1, $this->_curPage) - 1); + + // Query text. + $queryText = $this->queryText; + + // Setup sort orders. + $sortOrders = $this->prepareSortOrders(); + + $searchRequest = $this->requestBuilder->create( + $storeId, + $searchRequestName, + $from, + $size, + $queryText, + $sortOrders, + $this->filters, + $this->queryFilters, + $this->facets + ); + + return $searchRequest; + } + + /** + * Prepare sort orders for the request builder. + * + * @return array() + */ + private function prepareSortOrders() + { + $sortOrders = []; + + foreach ($this->_orders as $attribute => $direction) { + $sortParams = ['direction' => $direction]; + $sortField = $attribute; + $sortOrders[$sortField] = $sortParams; + } + + return $sortOrders; + } + + /** + * Load items count : + * - collection size + * + * @return void + */ + private function loadItemCounts() + { + $storeId = $this->getStoreId(); + $requestName = $this->searchRequestName; + + // Query text. + $queryText = $this->queryText; + + $searchRequest = $this->requestBuilder->create( + $storeId, + $requestName, + 0, + 0, + $queryText, + [], + $this->filters, + $this->queryFilters + ); + + $searchResponse = $this->searchEngine->search($searchRequest); + + $this->_totalRecords = $searchResponse->count(); + } +} diff --git a/src/module-elasticsuite-catalog/Setup/UpgradeData.php b/src/module-elasticsuite-catalog/Setup/UpgradeData.php new file mode 100644 index 000000000..f74d1e77b --- /dev/null +++ b/src/module-elasticsuite-catalog/Setup/UpgradeData.php @@ -0,0 +1,111 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Setup; + +use Magento\Eav\Setup\EavSetup; +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Catalog\Model\Category; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Catalog Data Upgrade + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * EAV setup factory + * + * @var EavSetupFactory + */ + private $eavSetupFactory; + + /** + * @var EavSetup + */ + private $eavSetup; + + /** + * Class Constructor + * + * @param EavSetupFactory $eavSetupFactory Eav setup factory. + */ + public function __construct(EavSetupFactory $eavSetupFactory) + { + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * Upgrade the module data. + * + * @param ModuleDataSetupInterface $setup The setup interface + * @param ModuleContextInterface $context The module Context + * + * @return void + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + $this->eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); + + if (version_compare($context->getVersion(), '1.2.0', '<')) { + $this->updateCategorySearchableAttributes(); + } + + $setup->endSetup(); + } + + /** + * Update some categories attributes to have them indexed into ES. + * Basically : + * - Name (indexable and searchable + * - Description (indexable and searchable) + * - Url Path (indexable) + */ + private function updateCategorySearchableAttributes() + { + $setup = $this->eavSetup->getSetup(); + $connection = $setup->getConnection(); + $table = $setup->getTable('catalog_eav_attribute'); + + // Set Name and description indexable and searchable. + $attributeIds = [ + $this->eavSetup->getAttributeId(\Magento\Catalog\Model\Category::ENTITY, 'name'), + $this->eavSetup->getAttributeId(\Magento\Catalog\Model\Category::ENTITY, 'description'), + ]; + + foreach (['is_searchable', 'is_used_in_spellcheck'] as $configField) { + foreach ($attributeIds as $attributeId) { + $connection->update( + $table, + [$configField => 1], + $connection->quoteInto('attribute_id = ?', $attributeId) + ); + } + } + + // Set url_path indexable. + $urlPathAttributeId = $this->eavSetup->getAttributeId(\Magento\Catalog\Model\Category::ENTITY, 'url_path'); + $connection->update( + $table, + ['is_searchable' => 1], + $connection->quoteInto('attribute_id = ?', $urlPathAttributeId) + ); + } +} diff --git a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml index 4605fe070..f67aaee67 100644 --- a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml +++ b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml @@ -25,6 +25,14 @@ + + + + + integer + + + diff --git a/src/module-elasticsuite-catalog/etc/config.xml b/src/module-elasticsuite-catalog/etc/config.xml index 7146548bb..8bddf5689 100644 --- a/src/module-elasticsuite-catalog/etc/config.xml +++ b/src/module-elasticsuite-catalog/etc/config.xml @@ -25,6 +25,9 @@ 5 + + 3 + diff --git a/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml b/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml index dcbcbb66f..59b32cec3 100644 --- a/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml +++ b/src/module-elasticsuite-catalog/etc/elasticsuite_search_request.xml @@ -25,4 +25,5 @@ + diff --git a/src/module-elasticsuite-catalog/etc/frontend/di.xml b/src/module-elasticsuite-catalog/etc/frontend/di.xml index b98c380ec..00658ce05 100644 --- a/src/module-elasticsuite-catalog/etc/frontend/di.xml +++ b/src/module-elasticsuite-catalog/etc/frontend/di.xml @@ -63,6 +63,7 @@ Smile\ElasticsuiteCatalog\Model\Autocomplete\Product\DataProvider + Smile\ElasticsuiteCatalog\Model\Autocomplete\Category\DataProvider diff --git a/src/module-elasticsuite-catalog/etc/module.xml b/src/module-elasticsuite-catalog/etc/module.xml index 3dfa472a7..fe7b64041 100644 --- a/src/module-elasticsuite-catalog/etc/module.xml +++ b/src/module-elasticsuite-catalog/etc/module.xml @@ -17,7 +17,7 @@ */ --> - + diff --git a/src/module-elasticsuite-catalog/i18n/en_US.csv b/src/module-elasticsuite-catalog/i18n/en_US.csv index 1bbe8646b..384e7efd9 100755 --- a/src/module-elasticsuite-catalog/i18n/en_US.csv +++ b/src/module-elasticsuite-catalog/i18n/en_US.csv @@ -42,3 +42,5 @@ OK,OK Yes,Yes No,No "Maximum number of products to display in autocomplete results.","Nombre maximum de produits à afficher dans les résultats de l'autocomplétion." +"Category Autocomplete","Category Autocomplete" +"Maximum number of categories to display in autocomplete results.","Maximum number of categories to display in autocomplete results." diff --git a/src/module-elasticsuite-catalog/i18n/fr_FR.csv b/src/module-elasticsuite-catalog/i18n/fr_FR.csv index f4d85988c..cda3fdcc4 100755 --- a/src/module-elasticsuite-catalog/i18n/fr_FR.csv +++ b/src/module-elasticsuite-catalog/i18n/fr_FR.csv @@ -42,3 +42,5 @@ OK,OK Yes,Oui No,Non "Maximum number of products to display in autocomplete results.","Nombre maximum de produits à afficher dans les résultats de l'autocomplétion." +"Category Autocomplete","Autocomplétion des catégories" +"Maximum number of categories to display in autocomplete results.","Nombre maximum de catégories à afficher dans les résultats de l'autocomplétion." diff --git a/src/module-elasticsuite-catalog/view/frontend/web/css/source/_module.less b/src/module-elasticsuite-catalog/view/frontend/web/css/source/_module.less index 045aa9649..bdbad4028 100644 --- a/src/module-elasticsuite-catalog/view/frontend/web/css/source/_module.less +++ b/src/module-elasticsuite-catalog/view/frontend/web/css/source/_module.less @@ -82,4 +82,12 @@ margin: 5px 0; } } + + .category-mini-crumb { + font-style: italic; + display: block; + margin-bottom: 0.2em; + font-size: 85%; + color: #929292; + } } diff --git a/src/module-elasticsuite-catalog/view/frontend/web/template/autocomplete/category.html b/src/module-elasticsuite-catalog/view/frontend/web/template/autocomplete/category.html index b9091af39..95c23f711 100644 --- a/src/module-elasticsuite-catalog/view/frontend/web/template/autocomplete/category.html +++ b/src/module-elasticsuite-catalog/view/frontend/web/template/autocomplete/category.html @@ -13,7 +13,8 @@ * @license Open Software License ("OSL") v. 3.0 */ --> -
+
+ <%- data.breadcrumb %> <%- data.title %>
From ddac9320a576061ff26026acd067312a5a84b96a Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 21 Sep 2016 17:42:39 +0200 Subject: [PATCH 42/48] Disable output of the Magento_VisualMerchandiser module. --- .../etc/config.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/module-elasticsuite-virtual-category/etc/config.xml diff --git a/src/module-elasticsuite-virtual-category/etc/config.xml b/src/module-elasticsuite-virtual-category/etc/config.xml new file mode 100644 index 000000000..a12d8b6fd --- /dev/null +++ b/src/module-elasticsuite-virtual-category/etc/config.xml @@ -0,0 +1,26 @@ + + + + + + + 1 + + + + From 8e4b247780dda14ff03e2c3625f86fd6791514aa Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Fri, 23 Sep 2016 14:32:57 +0200 Subject: [PATCH 43/48] Restore missing field "use_name_in_product_search" into the category form ui component. --- .../i18n/en_US.csv | 1 + .../i18n/fr_FR.csv | 1 + .../adminhtml/ui_component/category_form.xml | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/module-elasticsuite-catalog/i18n/en_US.csv b/src/module-elasticsuite-catalog/i18n/en_US.csv index 384e7efd9..33171562e 100755 --- a/src/module-elasticsuite-catalog/i18n/en_US.csv +++ b/src/module-elasticsuite-catalog/i18n/en_US.csv @@ -44,3 +44,4 @@ No,No "Maximum number of products to display in autocomplete results.","Nombre maximum de produits à afficher dans les résultats de l'autocomplétion." "Category Autocomplete","Category Autocomplete" "Maximum number of categories to display in autocomplete results.","Maximum number of categories to display in autocomplete results." +"Use Category Name in product search","Use Category Name in product search" diff --git a/src/module-elasticsuite-catalog/i18n/fr_FR.csv b/src/module-elasticsuite-catalog/i18n/fr_FR.csv index cda3fdcc4..c9dfb296d 100755 --- a/src/module-elasticsuite-catalog/i18n/fr_FR.csv +++ b/src/module-elasticsuite-catalog/i18n/fr_FR.csv @@ -44,3 +44,4 @@ No,Non "Maximum number of products to display in autocomplete results.","Nombre maximum de produits à afficher dans les résultats de l'autocomplétion." "Category Autocomplete","Autocomplétion des catégories" "Maximum number of categories to display in autocomplete results.","Nombre maximum de catégories à afficher dans les résultats de l'autocomplétion." +"Use Category Name in product search","Utiliser le nom de la catégorie dans les résultats de recherche" diff --git a/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml b/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml index cb77002c8..d14c02dba 100644 --- a/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml +++ b/src/module-elasticsuite-catalog/view/adminhtml/ui_component/category_form.xml @@ -32,4 +32,26 @@
+
+ + + + 100 + boolean + checkbox + category + toggle + Use Category Name in product search + + 1 + 0 + + + false + + 1 + + + +
From 195e73eaf27a4bbc1e8394e4891a2604909f0c59 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Wed, 21 Sep 2016 15:11:11 +0200 Subject: [PATCH 44/48] Adding the ability to redirect to product page if there is only one search result. --- .../Observer/RedirectIfOneResult.php | 117 ++++++++++++++++++ .../etc/adminhtml/system.xml | 15 +++ .../etc/config.xml | 5 + .../etc/events.xml | 22 ++++ .../i18n/en_US.csv | 5 + .../i18n/fr_FR.csv | 5 + 6 files changed, 169 insertions(+) create mode 100644 src/module-elasticsuite-catalog/Observer/RedirectIfOneResult.php create mode 100644 src/module-elasticsuite-catalog/etc/events.xml diff --git a/src/module-elasticsuite-catalog/Observer/RedirectIfOneResult.php b/src/module-elasticsuite-catalog/Observer/RedirectIfOneResult.php new file mode 100644 index 000000000..0e7380336 --- /dev/null +++ b/src/module-elasticsuite-catalog/Observer/RedirectIfOneResult.php @@ -0,0 +1,117 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCatalog\Observer; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\CatalogSearch\Helper\Data; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use \Magento\Framework\Message\ManagerInterface; + +/** + * Observer that redirect to the product page if this is the only search result. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Romain Ruaud + */ +class RedirectIfOneResult implements ObserverInterface +{ + /** + * Constant for configuration field location. + */ + const REDIRECT_SETTINGS_CONFIG_XML_FLAG = 'smile_elasticsuite_catalogsearch_settings/catalogsearch/redirect_if_one_result'; + + /** + * Catalog Layer Resolver + * + * @var Resolver + */ + private $layerResolver; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var ManagerInterface + */ + private $messageManager; + + /** + * @var \Magento\CatalogSearch\Helper\Data + */ + private $helper; + + /** + * RedirectIfOneResult constructor. + * + * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver Layer Resolver + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig Scope Configuration + * @param \Magento\CatalogSearch\Helper\Data $catalogSearchHelper Catalog Search Helper + * @param \Magento\Framework\Message\ManagerInterface $messageManager Message Manager + */ + public function __construct( + Resolver $layerResolver, + ScopeConfigInterface $scopeConfig, + Data $catalogSearchHelper, + ManagerInterface $messageManager + ) { + $this->layerResolver = $layerResolver; + $this->scopeConfig = $scopeConfig; + $this->messageManager = $messageManager; + $this->helper = $catalogSearchHelper; + } + + /** + * Process redirect to the product page if this is the only search result. + * + * @param Observer $observer The observer + * @event controller_action_postdispatch_catalogsearch_result_index + * + * @return void + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + if ($this->scopeConfig->isSetFlag(self::REDIRECT_SETTINGS_CONFIG_XML_FLAG)) { + $layer = $this->layerResolver->get(); + $layerState = $layer->getState(); + + if (count($layerState->getFilters()) === 0) { + $productCollection = $layer->getProductCollection(); + if ($productCollection->getCurPage() === 1 && $productCollection->getSize() === 1) { + /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ + $product = $productCollection->getFirstItem(); + if ($product->getId()) { + $this->addRedirectMessage($product); + $observer->getControllerAction()->getResponse()->setRedirect($product->getProductUrl()); + } + } + } + } + } + + /** + * Append message to the customer session to inform he has been redirected + * + * @param \Magento\Catalog\Api\Data\ProductInterface $product The product being redirected to. + */ + private function addRedirectMessage(ProductInterface $product) + { + $message = __("%1 is the only product matching your '%2' research.", $product->getName(), $this->helper->getEscapedQueryText()); + $this->messageManager->addSuccessMessage($message); + } +} diff --git a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml index f67aaee67..8eff96870 100644 --- a/src/module-elasticsuite-catalog/etc/adminhtml/system.xml +++ b/src/module-elasticsuite-catalog/etc/adminhtml/system.xml @@ -34,5 +34,20 @@ + +
+ + smile_elasticsuite + Magento_Backend::smile_elasticsuite_autocomplete + + + + + + Magento\Config\Model\Config\Source\Yesno + + + +
diff --git a/src/module-elasticsuite-catalog/etc/config.xml b/src/module-elasticsuite-catalog/etc/config.xml index 8bddf5689..a3a599f1d 100644 --- a/src/module-elasticsuite-catalog/etc/config.xml +++ b/src/module-elasticsuite-catalog/etc/config.xml @@ -29,5 +29,10 @@ 3 + + + 1 + + diff --git a/src/module-elasticsuite-catalog/etc/events.xml b/src/module-elasticsuite-catalog/etc/events.xml new file mode 100644 index 000000000..a8034c070 --- /dev/null +++ b/src/module-elasticsuite-catalog/etc/events.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/module-elasticsuite-catalog/i18n/en_US.csv b/src/module-elasticsuite-catalog/i18n/en_US.csv index 33171562e..2be1ba117 100755 --- a/src/module-elasticsuite-catalog/i18n/en_US.csv +++ b/src/module-elasticsuite-catalog/i18n/en_US.csv @@ -45,3 +45,8 @@ No,No "Category Autocomplete","Category Autocomplete" "Maximum number of categories to display in autocomplete results.","Maximum number of categories to display in autocomplete results." "Use Category Name in product search","Use Category Name in product search" +"Catalog Search","Catalog Search" +"Catalog Search Configuration","Catalog Search Configuration" +"Redirect to product page if only one result","Rediriger vers la fiche produit pour un résultat unique" +"If there is only one product matching a given search query, the user will be redirect to this product page.","If there is only one product matching a given search query, the user will be redirect to this product page." +"%1 is the only product matching your '%2' research.","%1 is the only product matching your '%2' research." diff --git a/src/module-elasticsuite-catalog/i18n/fr_FR.csv b/src/module-elasticsuite-catalog/i18n/fr_FR.csv index c9dfb296d..33bbbb74f 100755 --- a/src/module-elasticsuite-catalog/i18n/fr_FR.csv +++ b/src/module-elasticsuite-catalog/i18n/fr_FR.csv @@ -45,3 +45,8 @@ No,Non "Category Autocomplete","Autocomplétion des catégories" "Maximum number of categories to display in autocomplete results.","Nombre maximum de catégories à afficher dans les résultats de l'autocomplétion." "Use Category Name in product search","Utiliser le nom de la catégorie dans les résultats de recherche" +"Catalog Search","Recherche Catalogue" +"Catalog Search Configuration","Configuration de la recherche catalogue" +"Redirect to product page if only one result","Rediriger vers la fiche produit pour un résultat unique" +"If there is only one product matching a given search query, the user will be redirect to this product page.","Si une recherche ne renvoie qu'un seul produit, l'utilisateur sera redirigé vers la fiche de ce produit." +"%1 is the only product matching your '%2' research.","%1 est le seul produit correspondant à votre recherche : '%2'" From cd55204a212c5103028dd432f75f86fe2040ddf4 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Mon, 26 Sep 2016 10:56:09 +0200 Subject: [PATCH 45/48] Set composer.json files to require Magento 2.1.* minimum. --- composer.json | 10 +++++----- src/module-elasticsuite-catalog-rule/composer.json | 2 +- src/module-elasticsuite-catalog/composer.json | 10 +++++----- src/module-elasticsuite-core/composer.json | 12 ++++++------ src/module-elasticsuite-swatches/composer.json | 12 ++++++------ src/module-elasticsuite-thesaurus/composer.json | 12 ++++++------ src/module-elasticsuite-tracker/composer.json | 2 +- .../composer.json | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/composer.json b/composer.json index 3a1c13f14..063e1babe 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ } ], "require": { - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", + "magento/framework": ">=100.1.0", + "magento/module-store": ">=100.1.0", + "magento/module-backend": ">=100.1.0", + "magento/module-catalog": ">=100.1.0", + "magento/module-catalog-search": ">=100.1.0", "magento/magento-composer-installer": "*", "elasticsearch/elasticsearch": "^2.1" }, diff --git a/src/module-elasticsuite-catalog-rule/composer.json b/src/module-elasticsuite-catalog-rule/composer.json index 6d883ebce..3c9f92256 100644 --- a/src/module-elasticsuite-catalog-rule/composer.json +++ b/src/module-elasticsuite-catalog-rule/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", + "magento/framework": ">=100.1.0", "smile/module-elasticsuite-catalog": "self.version" }, "version": "2.1.0", diff --git a/src/module-elasticsuite-catalog/composer.json b/src/module-elasticsuite-catalog/composer.json index 29d24e7aa..ac7243731 100644 --- a/src/module-elasticsuite-catalog/composer.json +++ b/src/module-elasticsuite-catalog/composer.json @@ -20,11 +20,11 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", + "magento/framework": ">=100.1.0", + "magento/module-store": ">=100.1.0", + "magento/module-backend": ">=100.1.0", + "magento/module-catalog": ">=100.1.0", + "magento/module-catalog-search": ">=100.1.0", "magento/magento-composer-installer": "*", "elasticsearch/elasticsearch": "2.1.*" }, diff --git a/src/module-elasticsuite-core/composer.json b/src/module-elasticsuite-core/composer.json index c34692633..a6e928090 100644 --- a/src/module-elasticsuite-core/composer.json +++ b/src/module-elasticsuite-core/composer.json @@ -20,12 +20,12 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-backend": "*", - "magento/module-search": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", + "magento/framework": ">=100.1.0", + "magento/module-store": ">=100.1.0", + "magento/module-backend": ">=100.1.0", + "magento/module-search": ">=100.1.0", + "magento/module-catalog": ">=100.1.0", + "magento/module-catalog-search": ">=100.1.0", "magento/magento-composer-installer": "*", "elasticsearch/elasticsearch": "^2.2" }, diff --git a/src/module-elasticsuite-swatches/composer.json b/src/module-elasticsuite-swatches/composer.json index 9990d23ba..b3604ab40 100644 --- a/src/module-elasticsuite-swatches/composer.json +++ b/src/module-elasticsuite-swatches/composer.json @@ -20,12 +20,12 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-backend": "*", - "magento/module-search": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", + "magento/framework": ">=100.1.0", + "magento/module-store": ">=100.1.0", + "magento/module-backend": ">=100.1.0", + "magento/module-search": ">=100.1.0", + "magento/module-catalog": ">=100.1.0", + "magento/module-catalog-search": ">=100.1.0", "magento/magento-composer-installer": "*", "elasticsearch/elasticsearch": "2.1.*" }, diff --git a/src/module-elasticsuite-thesaurus/composer.json b/src/module-elasticsuite-thesaurus/composer.json index b9fba463c..70d3bd5d8 100644 --- a/src/module-elasticsuite-thesaurus/composer.json +++ b/src/module-elasticsuite-thesaurus/composer.json @@ -20,12 +20,12 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-backend": "*", - "magento/module-search": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", + "magento/framework": ">=100.1.0", + "magento/module-store": ">=100.1.0", + "magento/module-backend": ">=100.1.0", + "magento/module-search": ">=100.1.0", + "magento/module-catalog": ">=100.1.0", + "magento/module-catalog-search": ">=100.1.0", "magento/magento-composer-installer": "*", "elasticsearch/elasticsearch": "2.1.*" }, diff --git a/src/module-elasticsuite-tracker/composer.json b/src/module-elasticsuite-tracker/composer.json index 93ec2ded7..fa3cd3284 100644 --- a/src/module-elasticsuite-tracker/composer.json +++ b/src/module-elasticsuite-tracker/composer.json @@ -19,7 +19,7 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", + "magento/framework": ">=100.1.0", "magento/magento-composer-installer": "*" }, "version": "2.1.0", diff --git a/src/module-elasticsuite-virtual-category/composer.json b/src/module-elasticsuite-virtual-category/composer.json index 139fcc5db..32ed33632 100644 --- a/src/module-elasticsuite-virtual-category/composer.json +++ b/src/module-elasticsuite-virtual-category/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/framework": "*", + "magento/framework": ">=100.1.0", "smile/module-elasticsuite-catalog-rule": "self.version" }, "version": "2.1.0", From c2444230bb68ecc27fbd64ddec18353ce072d644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Mon, 26 Sep 2016 11:13:59 +0200 Subject: [PATCH 46/48] Fix category filter inconsistent beahvior in fulltext search. Now reset others filters. --- .../Model/Layer/Filter/Item/Category.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php b/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php index 980780cec..e97447308 100644 --- a/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php +++ b/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php @@ -27,7 +27,17 @@ class Category extends \Magento\Catalog\Model\Layer\Filter\Item */ public function getUrl() { - $url = parent::getUrl(); + $query = [ + $this->getFilter()->getRequestVar() => $this->getValue(), + // exclude current page from urls + $this->_htmlPagerBlock->getPageVarName() => null, + ]; + + foreach ($this->getFilter()->getLayer()->getState()->getFilters() as $currentFilterItem) { + $query[$currentFilterItem->getFilter()->getRequestVar()] = null; + } + + $url = $this->_url->getUrl('*/*/*', ['_current' => true, '_use_rewrite' => true, '_query' => $query]); if ($this->getUrlRewrite()) { $url = $this->getUrlRewrite(); From e18c4304430303876a293fc39885906379a37dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Mon, 26 Sep 2016 11:27:00 +0200 Subject: [PATCH 47/48] Fix checkstyle --- .../Model/Layer/Filter/Item/Category.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php b/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php index e97447308..42ff96148 100644 --- a/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php +++ b/src/module-elasticsuite-catalog/Model/Layer/Filter/Item/Category.php @@ -28,8 +28,7 @@ class Category extends \Magento\Catalog\Model\Layer\Filter\Item public function getUrl() { $query = [ - $this->getFilter()->getRequestVar() => $this->getValue(), - // exclude current page from urls + $this->getFilter()->getRequestVar() => $this->getValue(), $this->_htmlPagerBlock->getPageVarName() => null, ]; From d02ddffb0dda980e9aa815af3f81d01cdd5dc266 Mon Sep 17 00:00:00 2001 From: Romain Ruaud Date: Mon, 26 Sep 2016 12:19:27 +0200 Subject: [PATCH 48/48] Fixing french translations that were present in english files. --- src/module-elasticsuite-catalog/i18n/en_US.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module-elasticsuite-catalog/i18n/en_US.csv b/src/module-elasticsuite-catalog/i18n/en_US.csv index 2be1ba117..a5d0a9f47 100755 --- a/src/module-elasticsuite-catalog/i18n/en_US.csv +++ b/src/module-elasticsuite-catalog/i18n/en_US.csv @@ -41,12 +41,12 @@ OK,OK "Is Spellchecked","Is Spellchecked" Yes,Yes No,No -"Maximum number of products to display in autocomplete results.","Nombre maximum de produits à afficher dans les résultats de l'autocomplétion." +"Maximum number of products to display in autocomplete results.","Maximum number of products to display in autocomplete results." "Category Autocomplete","Category Autocomplete" "Maximum number of categories to display in autocomplete results.","Maximum number of categories to display in autocomplete results." "Use Category Name in product search","Use Category Name in product search" "Catalog Search","Catalog Search" "Catalog Search Configuration","Catalog Search Configuration" -"Redirect to product page if only one result","Rediriger vers la fiche produit pour un résultat unique" +"Redirect to product page if only one result","Redirect to product page if only one result" "If there is only one product matching a given search query, the user will be redirect to this product page.","If there is only one product matching a given search query, the user will be redirect to this product page." "%1 is the only product matching your '%2' research.","%1 is the only product matching your '%2' research."