Skip to content

Commit 499aa68

Browse files
authored
Merge pull request #1616 from algolia/release/3.14.2
Release/3.14.2
2 parents b29cd68 + f15a7ec commit 499aa68

File tree

10 files changed

+354
-13
lines changed

10 files changed

+354
-13
lines changed

Api/RecommendManagementInterface.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Api;
4+
5+
interface RecommendManagementInterface
6+
{
7+
/**
8+
* @param string $productId
9+
* @return array
10+
*/
11+
public function getBoughtTogetherRecommendation(string $productId): array;
12+
13+
/**
14+
* @param string $productId
15+
* @return array
16+
*/
17+
public function getRelatedProductsRecommendation(string $productId): array;
18+
19+
/**
20+
* @return array
21+
*/
22+
public function getTrendingItemsRecommendation(): array;
23+
24+
/**
25+
* @param string $productId
26+
* @return array
27+
*/
28+
public function getLookingSimilarRecommendation(string $productId): array;
29+
}

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGE LOG
22

3+
## 3.14.2
4+
5+
### Updates
6+
- Trained model check before enabling recommend
7+
38
## 3.14.1
49

510
### Updates

Helper/ConfigHelper.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,28 +120,28 @@ class ConfigHelper
120120
public const EXTRA_SETTINGS_ADDITIONAL_SECTIONS =
121121
'algoliasearch_extra_settings/extra_settings/additional_sections_extra_settings';
122122
public const MAGENTO_DEFAULT_CACHE_TIME = 'system/full_page_cache/ttl';
123-
protected const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled';
124-
protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled';
125-
protected const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled_in_cart_page';
126-
protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled_in_cart_page';
123+
public const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled';
124+
public const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled';
125+
public const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled_in_cart_page';
126+
public const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled_in_cart_page';
127127
protected const NUM_OF_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_PRODUCTS = 'algoliasearch_recommend/recommend/frequently_bought_together/num_of_frequently_bought_together_products';
128128
protected const NUM_OF_RECOMMEND_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/related_product/num_of_related_products';
129129
protected const IS_REMOVE_RELATED_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/related_product/is_remove_core_related_products_block';
130130
protected const IS_REMOVE_UPSELL_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/frequently_bought_together/is_remove_core_upsell_products_block';
131-
protected const IS_RECOMMEND_TRENDING_ITEMS_ENABLED = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled';
131+
public const IS_RECOMMEND_TRENDING_ITEMS_ENABLED = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled';
132132
protected const IS_RECOMMEND_LOOKING_SIMILAR_ENABLED = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled';
133133
protected const NUM_OF_LOOKING_SIMILAR = 'algoliasearch_recommend/recommend/looking_similar/num_of_products';
134134
protected const NUM_OF_TRENDING_ITEMS = 'algoliasearch_recommend/recommend/trends_item/num_of_trending_items';
135135
protected const TREND_ITEMS_FACET_NAME = 'algoliasearch_recommend/recommend/trends_item/facet_name';
136136
protected const TREND_ITEMS_FACET_VALUE = 'algoliasearch_recommend/recommend/trends_item/facet_value';
137-
protected const IS_TREND_ITEMS_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_pdp';
138-
protected const IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_cart_page';
137+
public const IS_TREND_ITEMS_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_pdp';
138+
public const IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_cart_page';
139139
protected const IS_ADDTOCART_ENABLED_IN_FREQUENTLY_BOUGHT_TOGETHER = 'algoliasearch_recommend/recommend/frequently_bought_together/is_addtocart_enabled';
140140
protected const IS_ADDTOCART_ENABLED_IN_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/related_product/is_addtocart_enabled';
141141
protected const IS_ADDTOCART_ENABLED_IN_TRENDS_ITEM = 'algoliasearch_recommend/recommend/trends_item/is_addtocart_enabled';
142142
protected const IS_ADDTOCART_ENABLED_IN_LOOKING_SIMILAR = 'algoliasearch_recommend/recommend/looking_similar/is_addtocart_enabled';
143-
protected const IS_LOOKING_SIMILAR_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_pdp';
144-
protected const IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_cart_page';
143+
public const IS_LOOKING_SIMILAR_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_pdp';
144+
public const IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_cart_page';
145145
protected const LOOKING_SIMILAR_TITLE = 'algoliasearch_recommend/recommend/looking_similar/title';
146146
public const LEGACY_USE_VIRTUAL_REPLICA_ENABLED = 'algoliasearch_instant/instant/use_virtual_replica';
147147
protected const AUTOCOMPLETE_KEYBORAD_NAVIAGATION = 'algoliasearch_autocomplete/autocomplete/navigator';

Model/RecommendManagement.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Algolia\AlgoliaSearch\Model;
5+
6+
use Algolia\AlgoliaSearch\Api\RecommendClient;
7+
use Algolia\AlgoliaSearch\Api\RecommendManagementInterface;
8+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
9+
use Algolia\AlgoliaSearch\Service\IndexNameFetcher;
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
12+
class RecommendManagement implements RecommendManagementInterface
13+
{
14+
/**
15+
* @var null|RecommendClient
16+
*/
17+
protected ?RecommendClient $client = null;
18+
19+
/**
20+
* @param ConfigHelper $configHelper
21+
* @param IndexNameFetcher $indexNameFetcher
22+
*/
23+
public function __construct(
24+
protected readonly ConfigHelper $configHelper,
25+
protected readonly IndexNameFetcher $indexNameFetcher
26+
){}
27+
28+
/**
29+
* @return RecommendClient
30+
*/
31+
protected function getClient(): RecommendClient
32+
{
33+
if ($this->client === null) {
34+
$this->client = RecommendClient::create(
35+
$this->configHelper->getApplicationID(),
36+
$this->configHelper->getAPIKey()
37+
);
38+
}
39+
return $this->client;
40+
}
41+
42+
/**
43+
* @param string $productId
44+
* @return array
45+
* @throws NoSuchEntityException
46+
*/
47+
public function getBoughtTogetherRecommendation(string $productId): array
48+
{
49+
return $this->getRecommendations($productId, 'bought-together');
50+
}
51+
52+
/**
53+
* @param string $productId
54+
* @return array
55+
* @throws NoSuchEntityException
56+
*/
57+
public function getRelatedProductsRecommendation(string $productId): array
58+
{
59+
return $this->getRecommendations($productId, 'related-products');
60+
}
61+
62+
/**
63+
* @return array
64+
* @throws NoSuchEntityException
65+
*/
66+
public function getTrendingItemsRecommendation(): array
67+
{
68+
return $this->getRecommendations('', 'trending-items');
69+
}
70+
71+
/**
72+
* @param string $productId
73+
* @return array
74+
* @throws NoSuchEntityException
75+
*/
76+
public function getLookingSimilarRecommendation(string $productId): array
77+
{
78+
return $this->getRecommendations($productId, 'bought-together');
79+
}
80+
81+
/**
82+
* @param string $productId
83+
* @param string $model
84+
* @param float|int $threshold
85+
* @return array
86+
* @throws NoSuchEntityException
87+
*/
88+
protected function getRecommendations(string $productId, string $model, float|int $threshold = 50): array
89+
{
90+
$request['indexName'] = $this->indexNameFetcher->getIndexName('_products');
91+
$request['model'] = $model;
92+
$request['threshold'] = $threshold;
93+
if (!empty($productId)) {
94+
$request['objectID'] = $productId;
95+
}
96+
97+
$client = $this->getClient();
98+
$recommendations = $client->getRecommendations(
99+
[
100+
'requests' => [
101+
$request
102+
],
103+
],
104+
);
105+
106+
return $recommendations['results'][0] ?? [];
107+
}
108+
}

Observer/RecommendSettings.php

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Algolia\AlgoliaSearch\Observer;
5+
6+
use Algolia\AlgoliaSearch\Api\RecommendManagementInterface;
7+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Product\Visibility;
10+
use Magento\Framework\Api\SearchCriteriaBuilder;
11+
use Magento\Framework\App\Config\Storage\WriterInterface;
12+
use Magento\Framework\Event\Observer;
13+
use Magento\Framework\Event\ObserverInterface;
14+
use Magento\Framework\Exception\LocalizedException;
15+
16+
class RecommendSettings implements ObserverInterface
17+
{
18+
const QUANTITY_AND_STOCK_STATUS = 'quantity_and_stock_status';
19+
const STATUS = 'status';
20+
const VISIBILITY = 'visibility';
21+
22+
/**
23+
* @var string
24+
*/
25+
protected string $productId = '';
26+
27+
/**
28+
* @param ConfigHelper $configHelper
29+
* @param WriterInterface $configWriter
30+
* @param ProductRepositoryInterface $productRepository
31+
* @param RecommendManagementInterface $recommendManagement
32+
* @param SearchCriteriaBuilder $searchCriteriaBuilder
33+
*/
34+
public function __construct(
35+
protected readonly ConfigHelper $configHelper,
36+
protected readonly WriterInterface $configWriter,
37+
protected readonly ProductRepositoryInterface $productRepository,
38+
protected readonly RecommendManagementInterface $recommendManagement,
39+
protected readonly SearchCriteriaBuilder $searchCriteriaBuilder
40+
){}
41+
42+
/**
43+
* @param Observer $observer
44+
* @throws LocalizedException
45+
*/
46+
public function execute(Observer $observer): void
47+
{
48+
foreach ($observer->getData('changed_paths') as $changedPath) {
49+
// Validate before enable FBT on PDP or on cart page
50+
if ((
51+
$changedPath == ConfigHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED
52+
&& $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabled()
53+
) || (
54+
$changedPath == ConfigHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE
55+
&& $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabledOnCartPage()
56+
)) {
57+
$this->validateFrequentlyBroughtTogether($changedPath);
58+
}
59+
60+
// Validate before enable related products on PDP or on cart page
61+
if ((
62+
$changedPath == ConfigHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED
63+
&& $this->configHelper->isRecommendRelatedProductsEnabled()
64+
) || (
65+
$changedPath == ConfigHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE
66+
&& $this->configHelper->isRecommendRelatedProductsEnabledOnCartPage()
67+
)) {
68+
$this->validateRelatedProducts($changedPath);
69+
}
70+
71+
// Validate before enable trending items on PDP or on cart page
72+
if ((
73+
$changedPath == ConfigHelper::IS_TREND_ITEMS_ENABLED_IN_PDP
74+
&& $this->configHelper->isTrendItemsEnabledInPDP()
75+
) || (
76+
$changedPath == ConfigHelper::IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART
77+
&& $this->configHelper->isTrendItemsEnabledInShoppingCart()
78+
)) {
79+
$this->validateTrendingItems($changedPath);
80+
}
81+
82+
// Validate before enable looking similar on PDP or on cart page
83+
if ((
84+
$changedPath == ConfigHelper::IS_LOOKING_SIMILAR_ENABLED_IN_PDP
85+
&& $this->configHelper->isLookingSimilarEnabledInPDP()
86+
) || (
87+
$changedPath == ConfigHelper::IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART
88+
&& $this->configHelper->isLookingSimilarEnabledInShoppingCart()
89+
)) {
90+
$this->validateLookingSimilar($changedPath);
91+
}
92+
}
93+
}
94+
95+
/**
96+
* @param string $changedPath
97+
* @return void
98+
* @throws LocalizedException
99+
*/
100+
protected function validateFrequentlyBroughtTogether(string $changedPath): void
101+
{
102+
$this->validateRecommendation($changedPath, 'getBoughtTogetherRecommendation', 'Frequently Bought Together');
103+
}
104+
105+
/**
106+
* @param string $changedPath
107+
* @return void
108+
* @throws LocalizedException
109+
*/
110+
protected function validateRelatedProducts(string $changedPath): void
111+
{
112+
$this->validateRecommendation($changedPath, 'getRelatedProductsRecommendation', 'Related Products');
113+
}
114+
115+
/**
116+
* @param string $changedPath
117+
* @return void
118+
* @throws LocalizedException
119+
*/
120+
protected function validateTrendingItems(string $changedPath): void
121+
{
122+
$this->validateRecommendation($changedPath, 'getTrendingItemsRecommendation', 'Trending Items');
123+
}
124+
125+
/**
126+
* @param string $changedPath
127+
* @return void
128+
* @throws LocalizedException
129+
*/
130+
protected function validateLookingSimilar(string $changedPath): void
131+
{
132+
$this->validateRecommendation($changedPath, 'getLookingSimilarRecommendation', 'Looking Similar');
133+
}
134+
135+
/**
136+
* @param string $changedPath - config path to be reverted if validation failed
137+
* @param string $recommendationMethod - name of method to call to retrieve method from RecommendManagementInterface
138+
* @param string $modelName - user friendly name to refer to model in error messaging
139+
* @return void
140+
* @throws LocalizedException
141+
*/
142+
protected function validateRecommendation(string $changedPath, string $recommendationMethod, string $modelName): void
143+
{
144+
try {
145+
$recommendations = $this->recommendManagement->$recommendationMethod($this->getProductId());
146+
if (empty($recommendations['renderingContent'])) {
147+
throw new LocalizedException(__(
148+
"It appears that there is no trained model available for Algolia application ID %1.",
149+
$this->configHelper->getApplicationID()
150+
));
151+
}
152+
} catch (\Exception $e) {
153+
$this->configWriter->save($changedPath, 0);
154+
throw new LocalizedException(__(
155+
"Unable to save %1 Recommend configuration due to the following error: %2",
156+
$modelName,
157+
$e->getMessage()
158+
)
159+
);
160+
}
161+
}
162+
163+
/**
164+
* @return string - Product ID string for use in API calls
165+
* @throws LocalizedException
166+
*/
167+
protected function getProductId(): string
168+
{
169+
if ($this->productId === '') {
170+
$searchCriteria = $this->searchCriteriaBuilder
171+
->addFilter(self::STATUS, 1)
172+
->addFilter(self::QUANTITY_AND_STOCK_STATUS, 1)
173+
->addFilter(
174+
self::VISIBILITY,
175+
[
176+
Visibility::VISIBILITY_IN_CATALOG,
177+
Visibility::VISIBILITY_IN_SEARCH,
178+
Visibility::VISIBILITY_BOTH
179+
],
180+
'in')
181+
->setPageSize(1)
182+
->create();
183+
$result = $this->productRepository->getList($searchCriteria);
184+
$items = $result->getItems();
185+
$firstProduct = reset($items);
186+
if ($firstProduct) {
187+
$this->productId = (string) $firstProduct->getId();
188+
} else {
189+
throw new LocalizedException(__("Unable to locate product to validate Recommend model."));
190+
}
191+
}
192+
193+
return $this->productId;
194+
}
195+
}

0 commit comments

Comments
 (0)