├── .eslintignore
├── i18n
├── en_US.csv
└── fr_FR.csv
├── doc
└── static
│ ├── rating_sort.png
│ └── rating_filter.png
├── .gitignore
├── .codeclimate.yml
├── registration.php
├── .travis.yml
├── view
└── frontend
│ ├── web
│ ├── template
│ │ └── rating-filter.html
│ └── css
│ │ └── source
│ │ └── _module.less
│ └── layout
│ ├── catalog_category_view_type_layered.xml
│ └── catalogsearch_result_index.xml
├── etc
├── elasticsuite_indices.xml
├── module.xml
├── frontend
│ └── di.xml
└── di.xml
├── Model
├── Layer
│ ├── FilterList.php
│ └── Filter
│ │ └── Rating.php
├── ResourceModel
│ └── Product
│ │ └── Indexer
│ │ └── Fulltext
│ │ └── Datasource
│ │ └── RatingData.php
└── Product
│ └── Indexer
│ └── Fulltext
│ └── Datasource
│ └── RatingData.php
├── composer.json
├── Block
└── Navigation
│ └── Renderer
│ └── Rating.php
├── README.md
├── Search
└── Request
│ └── Product
│ └── Attribute
│ └── Aggregation
│ └── Rating.php
├── Setup
├── InstallData.php
├── UpgradeData.php
└── RatingSetup.php
├── Plugin
└── Search
│ └── Request
│ └── Product
│ └── Attribute
│ └── AggregationResolver.php
└── .eslintrc
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 |
--------------------------------------------------------------------------------
/i18n/en_US.csv:
--------------------------------------------------------------------------------
1 | "%1 / 5 and more","%1 / 5 and more"
2 | "%1 / 5","%1 / 5"
3 | "and up","and up"
4 |
--------------------------------------------------------------------------------
/i18n/fr_FR.csv:
--------------------------------------------------------------------------------
1 | "%1 / 5 and more","%1 / 5 et plus"
2 | "%1 / 5","%1 / 5"
3 | "and up","et plus"
4 |
--------------------------------------------------------------------------------
/doc/static/rating_sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-elasticsuite-rating/HEAD/doc/static/rating_sort.png
--------------------------------------------------------------------------------
/doc/static/rating_filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-elasticsuite-rating/HEAD/doc/static/rating_filter.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Standard composer ignored paths
2 | composer.phar
3 | /vendor/
4 |
5 | # Standard IDEs ignored paths
6 | .metadata
7 | *.tmp
8 | *.bak
9 | *.swp
10 | *~.nib
11 | local.properties
12 | .settings/
13 | .loadpath
14 | .project
15 | .buildpath
16 | .idea/
17 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - javascript
8 | - php
9 | eslint:
10 | enabled: true
11 | fixme:
12 | enabled: true
13 | phan:
14 | enabled: true
15 | config:
16 | file_extensions: php
17 | ignore-undeclared: true
18 | ratings:
19 | paths:
20 | - "**.js"
21 | - "**.php"
22 | exclude_paths:
23 | - src/*/Test
24 | - vendor/*
25 | - Resources/*
26 |
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
12 | * @copyright 2016 Smile
13 | * @license Open Software License ("OSL") v. 3.0
14 | */
15 |
16 | \Magento\Framework\Component\ComponentRegistrar::register(
17 | \Magento\Framework\Component\ComponentRegistrar::MODULE,
18 | 'Smile_ElasticsuiteRating',
19 | __DIR__
20 | );
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - '5.6'
5 | - '7.0'
6 |
7 | install: [
8 | "mkdir -p app/etc var",
9 | "echo \"{\\\"http-basic\\\":{\\\"repo.magento.com\\\":{\\\"username\\\":\\\"${MAGENTO_USERNAME}\\\",\\\"password\\\":\\\"${MAGENTO_PASSWORD}\\\"}}}\" > auth.json",
10 | "composer install --prefer-dist"
11 | ]
12 |
13 | cache:
14 | directories:
15 | - $HOME/.composer/cache
16 |
17 | script:
18 | - vendor/bin/phpcs --ignore=/vendor/,/app/ --standard=vendor/smile/magento2-smilelab-phpcs/phpcs-standards/SmileLab --extensions=php ./
19 | - vendor/bin/phpmd ./ text vendor/smile/magento2-smilelab-phpmd/phpmd-rulesets/rulset.xml --exclude vendor
20 |
--------------------------------------------------------------------------------
/view/frontend/web/template/rating-filter.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/view/frontend/web/css/source/_module.less:
--------------------------------------------------------------------------------
1 | // /**
2 | // * DISCLAIMER
3 | // *
4 | // * Do not edit or add to this file if you wish to upgrade this module to newer
5 | // * versions in the future.
6 | // *
7 | // *
8 | // * @category Smile
9 | // * @package Smile\ElasticsuiteRating
10 | // * @author Romain Ruaud
11 | // * @copyright 2017 Smile
12 | // * @license Open Software License ("OSL") v. 3.0
13 | // */
14 |
15 | .filter-options-content {
16 | a.rating-filter {
17 | &:hover {
18 | padding-top: 5px;
19 | }
20 |
21 | .rating-summary {
22 | display: inline-block;
23 | }
24 |
25 | > span {
26 | vertical-align: top;
27 | padding-top: 2px;
28 | display: inline-block;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/etc/elasticsuite_indices.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/view/frontend/layout/catalog_category_view_type_layered.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/view/frontend/layout/catalogsearch_result_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Model/Layer/FilterList.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Model\Layer;
14 |
15 | /**
16 | * Override of FilterList to add custom renderer for Rating Filter.
17 | *
18 | * @category Smile
19 | * @package Smile\ElasticsuiteRating
20 | * @author Romain Ruaud
21 | */
22 | class FilterList extends \Smile\ElasticsuiteCatalog\Model\Layer\FilterList
23 | {
24 | /**
25 | * Rating filter name
26 | */
27 | const RATING_FILTER = 'rating';
28 |
29 | /**
30 | * {@inheritDoc}
31 | */
32 | protected function getAttributeFilterClass(\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute)
33 | {
34 | $filterClassName = parent::getAttributeFilterClass($attribute);
35 |
36 | if ($attribute->getAttributeCode() === 'ratings_summary') {
37 | $filterClassName = $this->filterTypes[self::RATING_FILTER];
38 | }
39 |
40 | return $filterClassName;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/etc/frontend/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
24 | - Smile\ElasticsuiteRating\Model\Layer\Filter\Rating
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | - Smile\ElasticsuiteRating\Model\Layer\Filter\Rating
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "smile/module-elasticsuite-rating",
3 | "type" : "magento2-module",
4 | "license" : "OSL-3.0",
5 | "authors" : [
6 | {
7 | "name" : "Tony DEPLANQUE",
8 | "email" : "todep@smile.fr"
9 | },
10 | {
11 | "name" : "Romain Ruaud",
12 | "email" : "romain.ruaud@smile.fr"
13 | }
14 | ],
15 | "description" : "Smile ElasticSuite - Rating search module.",
16 | "homepage" : "https://github.com/Smile-SA/magento2-module-elasticsuite-rating",
17 | "keywords" : [
18 | "magento",
19 | "magento2",
20 | "elasticsearch",
21 | "search",
22 | "merchandising",
23 | "ratings"
24 | ],
25 | "repositories": [
26 | {
27 | "type": "composer",
28 | "url": "https://repo.magento.com/"
29 | }
30 | ],
31 | "require" : {
32 | "magento/framework" : ">=102.0.0",
33 | "magento/magento-composer-installer" : "*",
34 | "magento/module-review" : ">=100.1.0",
35 | "smile/elasticsuite" : "^2.8.0"
36 | },
37 | "require-dev" : {
38 | "smile/magento2-smilelab-quality-suite" : "~2.1.0|~2.2.0"
39 | },
40 | "autoload" : {
41 | "files" : [
42 | "registration.php"
43 | ],
44 | "psr-4" : {
45 | "Smile\\ElasticsuiteRating\\" : ""
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | -
25 |
- Smile\ElasticsuiteRating\Model\Product\Indexer\Fulltext\Datasource\RatingData
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Block/Navigation/Renderer/Rating.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Block\Navigation\Renderer;
14 |
15 | /**
16 | * Rating Filter renderer block
17 | *
18 | * @category Smile
19 | * @package Smile\ElasticsuiteRating
20 | * @author Romain Ruaud
21 | */
22 | class Rating extends \Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Attribute
23 | {
24 | /**
25 | * {@inheritDoc}
26 | */
27 | public function getJsLayout()
28 | {
29 | $filterItems = $this->getFilter()->getItems();
30 |
31 | $jsLayoutConfig = [
32 | 'component' => self::JS_COMPONENT,
33 | 'hasMoreItems' => false,
34 | 'template' => 'Smile_ElasticsuiteRating/rating-filter',
35 | 'maxSize' => count($filterItems),
36 | ];
37 |
38 | foreach ($filterItems as $item) {
39 | $jsLayoutConfig['items'][] = $item->toArray(['label', 'count', 'url', 'is_selected']);
40 | }
41 |
42 | return json_encode($jsLayoutConfig);
43 | }
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | protected function canRenderFilter()
49 | {
50 | return $this->getFilter() instanceof \Smile\ElasticsuiteRating\Model\Layer\Filter\Rating;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ElasticSuite Ratings
2 |
3 | This module is a plugin for [ElasticSuite](https://github.com/Smile-SA/elasticsuite).
4 |
5 | It allows to display Magento2 user's ratings as a facet filter, and also as a sort order.
6 |
7 | ### Benefits
8 |
9 | - You will see a new facet allowing to **filter on products average ratings**.
10 |
11 | 
12 |
13 |
14 | - You will also be able to use the ratings as a **sort order** in category pages and search results.
15 |
16 | 
17 |
18 |
19 |
20 | ### Requirements
21 |
22 | The module requires :
23 |
24 | - [ElasticSuite](https://github.com/Smile-SA/elasticsuite) > 2.3.*
25 |
26 | - Magento2 CE/EE Edition
27 |
28 | ### Quick Start Guide
29 |
30 | 1. Install the module via Composer :
31 |
32 | ``` composer require smile/module-elasticsuite-rating ```
33 |
34 | 2. Enable it
35 |
36 | ``` bin/magento module:enable Smile_ElasticsuiteRating ```
37 |
38 | 3. Install the module and rebuild the DI cache
39 |
40 | ``` bin/magento setup:upgrade ```
41 |
42 | 4. Process a full reindex of catalogsearch index to reindex the Ratings data
43 |
44 | ``` bin/magento index:reindex catalogsearch_fulltext ```
45 |
46 |
47 | ### How to use
48 |
49 | The module is adding a new Product Attribute, called **ratings_summary**.
50 |
51 | Since this is managed via an attribute, you are able to :
52 |
53 | - display/hide it on category pages (via the __Is Filterable__ option)
54 | - display/hide it on search pages (via the __Is Filterable In Search__ option)
55 | - allow/disallow it for sort order (via the __Used for Sort By__ option)
56 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Product/Indexer/Fulltext/Datasource/RatingData.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2016 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Model\ResourceModel\Product\Indexer\Fulltext\Datasource;
14 |
15 | use Magento\Framework\App\ResourceConnection;
16 | use Magento\Store\Model\StoreManagerInterface;
17 | use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer;
18 |
19 | /**
20 | * Catalog Rating Data source resource model
21 | *
22 | * @category Smile
23 | * @package Smile\ElasticsuiteRating
24 | * @author Tony DEPLANQUE
25 | */
26 | class RatingData extends Indexer
27 | {
28 | /**
29 | * Load rating data for a list of product ids and a given store.
30 | *
31 | * @param integer $storeId Store id.
32 | * @param array $productIds Product ids list.
33 | *
34 | * @return array
35 | */
36 | public function loadRatingData($storeId, $productIds)
37 | {
38 | $select = $this->getConnection()->select()
39 | ->from(
40 | ['res' => $this->getTable('review_entity_summary')],
41 | [
42 | 'entity_pk_value as product_id',
43 | 'avg(rating_summary) as ratings_summary',
44 | ]
45 | )
46 | ->where('res.store_id = ?', $storeId)
47 | ->where('res.entity_pk_value IN(?)', $productIds)
48 | ->group('entity_pk_value');
49 |
50 | return $this->getConnection()->fetchAll($select);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Search/Request/Product/Attribute/Aggregation/Rating.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2019 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 |
14 | namespace Smile\ElasticsuiteRating\Search\Request\Product\Attribute\Aggregation;
15 |
16 | use Smile\ElasticsuiteCatalog\Search\Request\Product\Attribute\AggregationInterface;
17 | use Smile\ElasticsuiteCore\Search\Request\BucketInterface;
18 |
19 | /**
20 | * Aggregation builder for product ratings.
21 | *
22 | * @category Smile
23 | * @package Smile\ElasticsuiteRating
24 | * @author Romain Ruaud
25 | */
26 | class Rating implements AggregationInterface
27 | {
28 | /**
29 | * Default interval, based on 0-100 divided in five stars.
30 | */
31 | const RATING_AGG_INTERVAL = 20;
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function getAggregationData(\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute)
37 | {
38 | $bucketConfig = [
39 | 'name' => $this->getFilterField($attribute),
40 | 'type' => BucketInterface::TYPE_HISTOGRAM,
41 | 'minDocCount' => 1,
42 | 'interval' => (int) self::RATING_AGG_INTERVAL,
43 | ];
44 |
45 | return $bucketConfig;
46 | }
47 |
48 | /**
49 | * Retrieve ES filter field.
50 | *
51 | * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute Attribute
52 | *
53 | * @return string
54 | */
55 | private function getFilterField(\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute)
56 | {
57 | $field = $attribute->getAttributeCode();
58 |
59 | return $field;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/Setup/InstallData.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Setup;
14 |
15 | use Magento\Framework\Setup\InstallDataInterface;
16 | use Magento\Framework\Setup\ModuleContextInterface;
17 | use Magento\Framework\Setup\ModuleDataSetupInterface;
18 | use Magento\Eav\Setup\EavSetupFactory;
19 |
20 | /**
21 | * ElasticsuiteRating Install Data Script.
22 | *
23 | * @category Smile
24 | * @package Smile\ElasticsuiteRating
25 | * @author Romain Ruaud
26 | */
27 | class InstallData implements InstallDataInterface
28 | {
29 | /**
30 | * @var \Magento\Eav\Setup\EavSetupFactory
31 | */
32 | private $eavSetupFactory;
33 |
34 | /**
35 | * @var \Smile\ElasticsuiteRating\Setup\RatingSetup
36 | */
37 | private $ratingSetup;
38 |
39 | /**
40 | * InstallData constructor.
41 | *
42 | * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory EAV Setup Factory
43 | * @param \Smile\ElasticsuiteRating\Setup\RatingSetup $ratingSetup Rating Setup
44 | */
45 | public function __construct(EavSetupFactory $eavSetupFactory, RatingSetup $ratingSetup)
46 | {
47 | $this->eavSetupFactory = $eavSetupFactory;
48 | $this->ratingSetup = $ratingSetup;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
55 | {
56 | $setup->startSetup();
57 |
58 | $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
59 |
60 | $this->ratingSetup->createRatingAttributes($eavSetup);
61 |
62 | $setup->endSetup();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Setup/UpgradeData.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Setup;
14 |
15 | use Magento\Framework\Setup\ModuleContextInterface;
16 | use Magento\Framework\Setup\ModuleDataSetupInterface;
17 | use Magento\Framework\Setup\UpgradeDataInterface;
18 | use Magento\Eav\Setup\EavSetupFactory;
19 |
20 | /**
21 | * ElasticsuiteRating Upgrade Data Script.
22 | *
23 | * @category Smile
24 | * @package Smile\ElasticsuiteRating
25 | * @author Romain Ruaud
26 | */
27 | class UpgradeData implements UpgradeDataInterface
28 | {
29 | /**
30 | * @var \Magento\Eav\Setup\EavSetupFactory
31 | */
32 | private $eavSetupFactory;
33 |
34 | /**
35 | * @var \Smile\ElasticsuiteRating\Setup\RatingSetup
36 | */
37 | private $ratingSetup;
38 |
39 | /**
40 | * UpgradeData constructor.
41 | *
42 | * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory EAV Setup Factory
43 | * @param \Smile\ElasticsuiteRating\Setup\RatingSetup $ratingSetup Rating Setup
44 | */
45 | public function __construct(EavSetupFactory $eavSetupFactory, RatingSetup $ratingSetup)
46 | {
47 | $this->eavSetupFactory = $eavSetupFactory;
48 | $this->ratingSetup = $ratingSetup;
49 | }
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
54 | {
55 | $setup->startSetup();
56 |
57 | if (version_compare($context->getVersion(), '1.1.0', '<')) {
58 | $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
59 | $this->ratingSetup->createRatingAttributes($eavSetup);
60 | }
61 | if (version_compare($context->getVersion(), '1.2.0', '<')) {
62 | $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
63 | $this->ratingSetup->renameRatingAttribute($eavSetup);
64 | }
65 |
66 | $setup->endSetup();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Plugin/Search/Request/Product/Attribute/AggregationResolver.php:
--------------------------------------------------------------------------------
1 |
11 | * @copyright 2019 Smile
12 | * @license Open Software License ("OSL") v. 3.0
13 | */
14 | namespace Smile\ElasticsuiteRating\Plugin\Search\Request\Product\Attribute;
15 |
16 | /**
17 | * Plugin to set aggregation builder for ratings.
18 | *
19 | * @category Smile
20 | * @package Smile\ElasticsuiteRating
21 | * @author Romain Ruaud
22 | */
23 | class AggregationResolver
24 | {
25 | /**
26 | * Rating Summary attribute code.
27 | */
28 | const RATING_SUMMARY_ATTRIBUTE = 'ratings_summary';
29 |
30 | /**
31 | * @var \Smile\ElasticsuiteRating\Search\Request\Product\Attribute\Aggregation\Rating
32 | */
33 | private $ratingAggregation;
34 |
35 | /**
36 | * AggregationResolver constructor.
37 | *
38 | * @param \Smile\ElasticsuiteRating\Search\Request\Product\Attribute\Aggregation\Rating $ratingAggregation Rating Aggregation
39 | */
40 | public function __construct(\Smile\ElasticsuiteRating\Search\Request\Product\Attribute\Aggregation\Rating $ratingAggregation)
41 | {
42 | $this->ratingAggregation = $ratingAggregation;
43 | }
44 |
45 | /**
46 | * Set default facet size to 0 for swatches attributes before adding it as aggregation.
47 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
48 | *
49 | * @param \Smile\ElasticsuiteCatalog\Search\Request\Product\Attribute\AggregationResolver $subject Aggregation Resolver
50 | * @param array $result Aggregation Config
51 | * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute Attribute
52 | *
53 | * @return array
54 | */
55 | public function afterGetAggregationData(
56 | \Smile\ElasticsuiteCatalog\Search\Request\Product\Attribute\AggregationResolver $subject,
57 | $result,
58 | $attribute
59 | ) {
60 | if ($attribute->getAttributeCode() === self::RATING_SUMMARY_ATTRIBUTE) {
61 | $result = $this->ratingAggregation->getAggregationData($attribute);
62 | }
63 |
64 | return $result;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Model/Product/Indexer/Fulltext/Datasource/RatingData.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2016 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Model\Product\Indexer\Fulltext\Datasource;
14 |
15 | use Smile\ElasticsuiteCore\Api\Index\DatasourceInterface;
16 | use Smile\ElasticsuiteRating\Model\ResourceModel\Product\Indexer\Fulltext\Datasource\RatingData as ResourceModel;
17 |
18 | /**
19 | * Ratings Datasource
20 | *
21 | * @category Smile
22 | * @package Smile\ElasticsuiteRating
23 | * @author Tony DEPLANQUE
24 | */
25 | class RatingData implements DatasourceInterface
26 | {
27 | /**
28 | * @var ResourceModel
29 | */
30 | private $resourceModel;
31 |
32 | /**
33 | * Constructor.
34 | *
35 | * @param ResourceModel $resourceModel Resource model.
36 | */
37 | public function __construct(ResourceModel $resourceModel)
38 | {
39 | $this->resourceModel = $resourceModel;
40 | }
41 |
42 | /**
43 | * Add rating data to the index data.
44 | *
45 | * {@inheritdoc}
46 | */
47 | public function addData($storeId, array $indexData)
48 | {
49 | $ratingData = $this->resourceModel->loadRatingData($storeId, array_keys($indexData));
50 |
51 | array_walk($indexData, [$this, 'fillRatingsData']);
52 |
53 | foreach ($ratingData as $ratingDataRow) {
54 | $productId = (int) $ratingDataRow['product_id'];
55 | $indexData[$productId]['ratings_summary'] = (float) $ratingDataRow['ratings_summary'];
56 |
57 | if (!isset($indexData[$productId]['indexed_attributes'])) {
58 | $indexData[$productId]['indexed_attributes'] = ['ratings_summary'];
59 | } elseif (!in_array('ratings_summary', $indexData[$productId]['indexed_attributes'])) {
60 | // Add ratings_summary only one time.
61 | $indexData[$productId]['indexed_attributes'][] = 'ratings_summary';
62 | }
63 | }
64 |
65 | return $indexData;
66 | }
67 |
68 | /**
69 | * Fill rating summary field with 0.
70 | *
71 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) Used via a callback.
72 | *
73 | * @param array $productData Product index data
74 | */
75 | private function fillRatingsData(&$productData)
76 | {
77 | $productData['ratings_summary'] = 0;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Setup/RatingSetup.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 | namespace Smile\ElasticsuiteRating\Setup;
14 |
15 | use Magento\Catalog\Api\Data\ProductAttributeInterface;
16 |
17 | /**
18 | * ElasticsuiteRating Setup
19 | *
20 | * @category Smile
21 | * @package Smile\ElasticsuiteRating
22 | * @author Romain Ruaud
23 | */
24 | class RatingSetup
25 | {
26 | /**
27 | * @var \Magento\Eav\Model\Config $eavConfig
28 | */
29 | private $eavConfig;
30 |
31 | /**
32 | * VirtualCategorySetup constructor.
33 | *
34 | * @param \Magento\Eav\Model\Config $eavConfig EAV Config.
35 | */
36 | public function __construct(\Magento\Eav\Model\Config $eavConfig)
37 | {
38 | $this->eavConfig = $eavConfig;
39 | }
40 |
41 | /**
42 | * Create product rating attribute.
43 | *
44 | * @param \Magento\Eav\Setup\EavSetup $eavSetup EAV module Setup
45 | */
46 | public function createRatingAttributes($eavSetup)
47 | {
48 | $entity = ProductAttributeInterface::ENTITY_TYPE_CODE;
49 | $eavSetup->addAttribute(
50 | $entity,
51 | 'ratings_summary',
52 | [
53 | 'type' => 'decimal',
54 | 'label' => 'Product Rating',
55 | 'input' => 'hidden',
56 | 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
57 | 'required' => false,
58 | 'default' => 0,
59 | 'visible' => true,
60 | 'sort_order' => 200,
61 | 'visible_on_front' => 0,
62 | 'searchable' => 1,
63 | 'visible_in_advanced_search' => 0,
64 | 'filterable' => 1,
65 | 'filterable_in_search' => 1,
66 | 'is_used_in_grid' => 0,
67 | 'is_visible_in_grid' => 0,
68 | 'is_filterable_in_grid' => 0,
69 | 'used_for_sort_by' => 1,
70 | ]
71 | );
72 |
73 | $attributeId = $eavSetup->getAttributeId($entity, 'ratings_summary');
74 | $defaultAttributeSet = $eavSetup->getAttributeSetId($entity, 'Default');
75 | $defaultGroup = $eavSetup->getAttributeGroupId($entity, $defaultAttributeSet, 'General');
76 |
77 | $eavSetup->addAttributeToSet($entity, $defaultAttributeSet, $defaultGroup, $attributeId);
78 | }
79 |
80 | /**
81 | * Rename rating_summary to ratings_summary
82 | *
83 | * @param \Magento\Eav\Setup\EavSetup $eavSetup EAV module Setup
84 | */
85 | public function renameRatingAttribute($eavSetup)
86 | {
87 | $entity = ProductAttributeInterface::ENTITY_TYPE_CODE;
88 | if ($eavSetup->getAttributeId($entity, 'rating_summary') !== false) {
89 | $eavSetup->updateAttribute($entity, 'rating_summary', ['attribute_code' => 'ratings_summary']);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Model/Layer/Filter/Rating.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2017 Smile
11 | * @license Open Software License ("OSL") v. 3.0
12 | */
13 |
14 | namespace Smile\ElasticsuiteRating\Model\Layer\Filter;
15 |
16 | /**
17 | * Products Rating Filter Model
18 | *
19 | * @category Smile
20 | * @package Smile\ElasticsuiteRating
21 | * @author Romain Ruaud
22 | */
23 | class Rating extends \Smile\ElasticsuiteCatalog\Model\Layer\Filter\Attribute
24 | {
25 | /**
26 | * Default interval, based on 0-100 divided in five stars.
27 | */
28 | const RATING_AGG_INTERVAL = 20;
29 |
30 | /**
31 | * {@inheritDoc}
32 | */
33 | public function apply(\Magento\Framework\App\RequestInterface $request)
34 | {
35 | $value = $request->getParam($this->_requestVar);
36 |
37 | if (null !== $value) {
38 | $this->currentFilterValue = $value;
39 |
40 | /** @var \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection $productCollection */
41 | $productCollection = $this->getLayer()->getProductCollection();
42 |
43 | $productCollection->addFieldToFilter($this->getFilterField(), ['gte' => $value]);
44 | $layerState = $this->getLayer()->getState();
45 |
46 | $filterLabel = __('%1 / 5 and more', $value / self::RATING_AGG_INTERVAL);
47 | if (($value / self::RATING_AGG_INTERVAL) === 5) {
48 | $filterLabel = __('%1 / 5', $value / self::RATING_AGG_INTERVAL);
49 | }
50 | $filter = $this->_createItem($filterLabel, $this->currentFilterValue);
51 |
52 | $layerState->addFilter($filter);
53 | }
54 |
55 | return $this;
56 | }
57 |
58 | /**
59 | * Retrieve ES filter field.
60 | *
61 | * @return string
62 | */
63 | protected function getFilterField()
64 | {
65 | $field = $this->getAttributeModel()->getAttributeCode();
66 |
67 | return $field;
68 | }
69 |
70 | /**
71 | * @SuppressWarnings(PHPMD.CamelCaseMethodName)
72 | * {@inheritDoc}
73 | */
74 | protected function _initItems()
75 | {
76 | $data = $this->_getItemsData();
77 | $items = [];
78 | foreach ($data as $itemData) {
79 | $items[] = $this->_createItem($itemData['label'], $itemData['value'], $itemData['count']);
80 | }
81 | $this->_items = $items;
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * @SuppressWarnings(PHPMD.CamelCaseMethodName)
88 | * {@inheritDoc}
89 | */
90 | protected function _getItemsData()
91 | {
92 | /** @var \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection $productCollection */
93 | $productCollection = $this->getLayer()->getProductCollection();
94 |
95 | $optionsFacetedData = $productCollection->getFacetedData($this->getFilterField());
96 |
97 | $items = [];
98 |
99 | $sumCount = 0;
100 | $maxValue = current(array_keys($optionsFacetedData));
101 |
102 | while (($maxValue = $maxValue - self::RATING_AGG_INTERVAL) && $maxValue > 0) {
103 | if (!isset($optionsFacetedData[$maxValue])) {
104 | $optionsFacetedData[$maxValue] = ['count' => 0];
105 | }
106 | }
107 | krsort($optionsFacetedData);
108 |
109 | $minCount = !empty($optionsFacetedData) ? min(array_column($optionsFacetedData, 'count')) : 0;
110 |
111 | if (!empty($this->currentFilterValue) || $minCount < $productCollection->getSize()) {
112 | foreach ($optionsFacetedData as $value => $data) {
113 | $sumCount += (int) $data['count'];
114 | $items[$value] = [
115 | 'label' => $value,
116 | 'value' => $value,
117 | 'count' => $sumCount,
118 | ];
119 | }
120 | }
121 |
122 | return $items;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | ecmaFeatures:
2 | modules: true
3 | jsx: true
4 |
5 | env:
6 | amd: true
7 | browser: true
8 | es6: true
9 | jquery: true
10 | node: true
11 |
12 | # http://eslint.org/docs/rules/
13 | rules:
14 | # Possible Errors
15 | comma-dangle: [2, never]
16 | no-cond-assign: 2
17 | no-console: 0
18 | no-constant-condition: 2
19 | no-control-regex: 2
20 | no-debugger: 2
21 | no-dupe-args: 2
22 | no-dupe-keys: 2
23 | no-duplicate-case: 2
24 | no-empty: 2
25 | no-empty-character-class: 2
26 | no-ex-assign: 2
27 | no-extra-boolean-cast: 2
28 | no-extra-parens: 0
29 | no-extra-semi: 2
30 | no-func-assign: 2
31 | no-inner-declarations: [2, functions]
32 | no-invalid-regexp: 2
33 | no-irregular-whitespace: 2
34 | no-negated-in-lhs: 2
35 | no-obj-calls: 2
36 | no-regex-spaces: 2
37 | no-sparse-arrays: 2
38 | no-unexpected-multiline: 2
39 | no-unreachable: 2
40 | use-isnan: 2
41 | valid-jsdoc: 0
42 | valid-typeof: 2
43 |
44 | # Best Practices
45 | accessor-pairs: 2
46 | block-scoped-var: 0
47 | complexity: [2, 6]
48 | consistent-return: 0
49 | curly: 0
50 | default-case: 0
51 | dot-location: 0
52 | dot-notation: 0
53 | eqeqeq: 2
54 | guard-for-in: 2
55 | no-alert: 2
56 | no-caller: 2
57 | no-case-declarations: 2
58 | no-div-regex: 2
59 | no-else-return: 0
60 | no-empty-label: 2
61 | no-empty-pattern: 2
62 | no-eq-null: 2
63 | no-eval: 2
64 | no-extend-native: 2
65 | no-extra-bind: 2
66 | no-fallthrough: 2
67 | no-floating-decimal: 0
68 | no-implicit-coercion: 0
69 | no-implied-eval: 2
70 | no-invalid-this: 0
71 | no-iterator: 2
72 | no-labels: 0
73 | no-lone-blocks: 2
74 | no-loop-func: 2
75 | no-magic-number: 0
76 | no-multi-spaces: 0
77 | no-multi-str: 0
78 | no-native-reassign: 2
79 | no-new-func: 2
80 | no-new-wrappers: 2
81 | no-new: 2
82 | no-octal-escape: 2
83 | no-octal: 2
84 | no-proto: 2
85 | no-redeclare: 2
86 | no-return-assign: 2
87 | no-script-url: 2
88 | no-self-compare: 2
89 | no-sequences: 0
90 | no-throw-literal: 0
91 | no-unused-expressions: 2
92 | no-useless-call: 2
93 | no-useless-concat: 2
94 | no-void: 2
95 | no-warning-comments: 0
96 | no-with: 2
97 | radix: 2
98 | vars-on-top: 0
99 | wrap-iife: 2
100 | yoda: 0
101 |
102 | # Strict
103 | strict: 0
104 |
105 | # Variables
106 | init-declarations: 0
107 | no-catch-shadow: 2
108 | no-delete-var: 2
109 | no-label-var: 2
110 | no-shadow-restricted-names: 2
111 | no-shadow: 0
112 | no-undef-init: 2
113 | no-undef: 0
114 | no-undefined: 0
115 | no-unused-vars: 0
116 | no-use-before-define: 0
117 |
118 | # Node.js and CommonJS
119 | callback-return: 2
120 | global-require: 2
121 | handle-callback-err: 2
122 | no-mixed-requires: 0
123 | no-new-require: 0
124 | no-path-concat: 2
125 | no-process-exit: 2
126 | no-restricted-modules: 0
127 | no-sync: 0
128 |
129 | # Stylistic Issues
130 | array-bracket-spacing: 0
131 | block-spacing: 0
132 | brace-style: 0
133 | camelcase: 0
134 | comma-spacing: 0
135 | comma-style: 0
136 | computed-property-spacing: 0
137 | consistent-this: 0
138 | eol-last: 0
139 | func-names: 0
140 | func-style: 0
141 | id-length: 0
142 | id-match: 0
143 | indent: 0
144 | jsx-quotes: 0
145 | key-spacing: 0
146 | linebreak-style: 0
147 | lines-around-comment: 0
148 | max-depth: 0
149 | max-len: 0
150 | max-nested-callbacks: 0
151 | max-params: 0
152 | max-statements: [2, 30]
153 | new-cap: 0
154 | new-parens: 0
155 | newline-after-var: 0
156 | no-array-constructor: 0
157 | no-bitwise: 0
158 | no-continue: 0
159 | no-inline-comments: 0
160 | no-lonely-if: 0
161 | no-mixed-spaces-and-tabs: 0
162 | no-multiple-empty-lines: 0
163 | no-negated-condition: 0
164 | no-nested-ternary: 0
165 | no-new-object: 0
166 | no-plusplus: 0
167 | no-restricted-syntax: 0
168 | no-spaced-func: 0
169 | no-ternary: 0
170 | no-trailing-spaces: 0
171 | no-underscore-dangle: 0
172 | no-unneeded-ternary: 0
173 | object-curly-spacing: 0
174 | one-var: 0
175 | operator-assignment: 0
176 | operator-linebreak: 0
177 | padded-blocks: 0
178 | quote-props: 0
179 | quotes: 0
180 | require-jsdoc: 0
181 | semi-spacing: 0
182 | semi: 0
183 | sort-vars: 0
184 | space-after-keywords: 0
185 | space-before-blocks: 0
186 | space-before-function-paren: 0
187 | space-before-keywords: 0
188 | space-in-parens: 0
189 | space-infix-ops: 0
190 | space-return-throw-case: 0
191 | space-unary-ops: 0
192 | spaced-comment: 0
193 | wrap-regex: 0
194 |
195 | # ECMAScript 6
196 | arrow-body-style: 0
197 | arrow-parens: 0
198 | arrow-spacing: 0
199 | constructor-super: 0
200 | generator-star-spacing: 0
201 | no-arrow-condition: 0
202 | no-class-assign: 0
203 | no-const-assign: 0
204 | no-dupe-class-members: 0
205 | no-this-before-super: 0
206 | no-var: 0
207 | object-shorthand: 0
208 | prefer-arrow-callback: 0
209 | prefer-const: 0
210 | prefer-reflect: 0
211 | prefer-spread: 0
212 | prefer-template: 0
213 | require-yield: 0
214 |
--------------------------------------------------------------------------------