diff --git a/Block/Cart/Recommend.php b/Block/Cart/Recommend.php new file mode 100644 index 000000000..884f64831 --- /dev/null +++ b/Block/Cart/Recommend.php @@ -0,0 +1,54 @@ +checkoutSession = $checkoutSession; + $this->configHelper = $configHelper; + } + + /** + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getAllCartItems() + { + $cartItems = array(); + $itemCollection = $this->checkoutSession->getQuote()->getAllVisibleItems(); + foreach ( $itemCollection as $item) { + $cartItems[] = $item->getProductId(); + } + return array_unique($cartItems); + } + +} diff --git a/Block/Configuration.php b/Block/Configuration.php index c5f41b524..61d1cbd39 100755 --- a/Block/Configuration.php +++ b/Block/Configuration.php @@ -165,8 +165,20 @@ public function getConfiguration() 'recommend' => [ 'enabledFBT' => $config->isRecommendFrequentlyBroughtTogetherEnabled(), 'enabledRelated' => $config->isRecommendRelatedProductsEnabled(), + 'enabledFBTInCart' => $config->isRecommendFrequentlyBroughtTogetherEnabledOnCartPage(), + 'enabledRelatedInCart' => $config->isRecommendRelatedProductsEnabledOnCartPage(), 'limitFBTProducts' => $config->getNumberOfFrequentlyBoughtTogetherProducts(), 'limitRelatedProducts' => $config->getNumberOfRelatedProducts(), + 'limitTrendingItems' => $config->getNumberOfTrendingItems(), + 'enabledTrendItems' => $config->isRecommendTrendingItemsEnabled(), + 'trendItemFacetName' => $config->getTrendingItemsFacetName(), + 'trendItemFacetValue' => $config->getTrendingItemsFacetValue(), + 'isTrendItemsEnabledInPDP' => $config->isTrendItemsEnabledInPDP(), + 'isTrendItemsEnabledInCartPage' => $config->isTrendItemsEnabledInShoppingCart(), + 'isAddToCartEnabledInFBT' => $config->isAddToCartEnabledInFrequentlyBoughtTogether(), + 'isAddToCartEnabledInRelatedProduct' => $config->isAddToCartEnabledInRelatedProducts(), + 'isAddToCartEnabledInTrendsItem' => $config->isAddToCartEnabledInTrendsItem(), + 'addToCartParams' => $addToCartParams, ], 'extensionVersion' => $config->getExtensionVersion(), 'applicationId' => $config->getApplicationID(), diff --git a/Block/Widget/TrendsItem.php b/Block/Widget/TrendsItem.php new file mode 100644 index 000000000..08dd0f336 --- /dev/null +++ b/Block/Widget/TrendsItem.php @@ -0,0 +1,39 @@ +mathRandom = $mathRandom; + parent::__construct( + $context, + $data + ); + } + + /** + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function generateUniqueToken() + { + return $this->mathRandom->getRandomString(5); + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 96212af2e..d97f37bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # CHANGE LOG +## 3.9.0 + +### New Features +- Trends Recommendations: We have added the ability to add Trending Items to the PDP and the shopping cart page. More information can be found here. We also provide a Trending Items widget that can be used to add Trending Items to any page. +- Added an option to show Recommend Related and Frequently Bought Together products on the shopping cart page. +- Added an option to enable the Add To Cart button for all types of recommended products (Related, Frequently Bought Together, and Trending Items). +- Added Algolia Recommend dashboard link on the Magento dashboard +- Added Algolia Search extensions release notes link in the Magento admin to be able to access release notes easily. +- Implemented Recommended Product click event using personalization. + +### UPDATES +- Refactored the Algolia Insight functionality in the extension code base per Magento standard (moved the observer directory in the module root). +- Refactored the autocomplete 2.0 code to make it more developer-friendly to allow for customization per customer needs. +- Collated all autocomplete-specific logic in a single autocomplete.js file and segregated JS-based templates that control the layout of the different autocomplete sources to be more developer-friendly. This enables the customers to easily override the layout of the autocomplete menu in the custom theme and the extension. + + +### FIXES +- Click event in autocomplete +- Autocomplete errors if the product is not assigned a category and indexed into Algolia +- Issues with the price attribute in autocomplete when price attribute is set to Non-Retrievable +- The autocomplete in Query merchandiser (in the Magento admin) shows products from the default store on switching stores [Fixed] +- Issues with triggering Add to Cart Conversion for Configurable Product +- Issues with indexer not updating when product goes out of stock when the last of the inventory is done + + ## 3.8.1 ### UPDATES diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index 58e8871db..0817ff877 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -109,12 +109,23 @@ class ConfigHelper public const DEFAULT_MAX_RECORD_SIZE = 10000; - public const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/is_frequently_bought_together_enabled'; - public const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/is_related_products_enabled'; - public const NUM_OF_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_PRODUCTS = 'algoliasearch_recommend/recommend/num_of_frequently_bought_together_products'; - public const NUM_OF_RECOMMEND_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/num_of_related_products'; - public const IS_REMOVE_RELATED_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/is_remove_core_related_products_block'; - public const IS_REMOVE_UPSELL_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/is_remove_core_upsell_products_block'; + protected const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled'; + protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled'; + 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'; + protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled_in_cart_page'; + protected const NUM_OF_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_PRODUCTS = 'algoliasearch_recommend/recommend/frequently_bought_together/num_of_frequently_bought_together_products'; + protected const NUM_OF_RECOMMEND_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/related_product/num_of_related_products'; + protected const IS_REMOVE_RELATED_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/related_product/is_remove_core_related_products_block'; + protected const IS_REMOVE_UPSELL_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/frequently_bought_together/is_remove_core_upsell_products_block'; + protected const IS_RECOMMEND_TRENDING_ITEMS_ENABLED = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled'; + protected const NUM_OF_TRENDING_ITEMS = 'algoliasearch_recommend/recommend/trends_item/num_of_trending_items'; + protected const TREND_ITEMS_FACET_NAME = 'algoliasearch_recommend/recommend/trends_item/facet_name'; + protected const TREND_ITEMS_FACET_VALUE = 'algoliasearch_recommend/recommend/trends_item/facet_value'; + protected const IS_TREND_ITEMS_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_pdp'; + protected const IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_cart_page'; + protected const IS_ADDTOCART_ENABLED_IN_FREQUENTLY_BOUGHT_TOGETHER = 'algoliasearch_recommend/recommend/frequently_bought_together/is_addtocart_enabled'; + protected const IS_ADDTOCART_ENABLED_IN_RELATED_PRODUCTS= 'algoliasearch_recommend/recommend/related_product/is_addtocart_enabled'; + protected const IS_ADDTOCART_ENABLED_IN_TRENDS_ITEM= 'algoliasearch_recommend/recommend/trends_item/is_addtocart_enabled'; private $configInterface; private $objectManager; @@ -452,6 +463,26 @@ public function isRecommendRelatedProductsEnabled($storeId = null) return $this->configInterface->isSetFlag(self::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED, ScopeInterface::SCOPE_STORE, $storeId); } + /** + * @param int $storeId + * + * @return int + */ + public function isRecommendFrequentlyBroughtTogetherEnabledOnCartPage($storeId = null) + { + return $this->configInterface->isSetFlag(self::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE, ScopeInterface::SCOPE_STORE, $storeId); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isRecommendRelatedProductsEnabledOnCartPage($storeId = null) + { + return $this->configInterface->isSetFlag(self::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE, ScopeInterface::SCOPE_STORE, $storeId); + } + /** * @param int $storeId * @@ -499,6 +530,121 @@ public function getNumberOfFrequentlyBoughtTogetherProducts($storeId = null) $storeId ); } + + /** + * @param int $storeId + * + * @return int + */ + public function isRecommendTrendingItemsEnabled($storeId = null) + { + return (int) $this->configInterface->getValue( + self::IS_RECOMMEND_TRENDING_ITEMS_ENABLED, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return int + */ + public function getNumberOfTrendingItems($storeId = null) + { + return (int) $this->configInterface->getValue( + self::NUM_OF_TRENDING_ITEMS, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + + /** + * @param int $storeId + * + * @return string + */ + public function getTrendingItemsFacetName($storeId = null) + { + return $this->configInterface->getValue( + self::TREND_ITEMS_FACET_NAME, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return string + */ + public function getTrendingItemsFacetValue($storeId = null) + { + return $this->configInterface->getValue( + self::TREND_ITEMS_FACET_VALUE, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isTrendItemsEnabledInPDP($storeId = null) + { + return (int) $this->configInterface->getValue( + self::IS_TREND_ITEMS_ENABLED_IN_PDP, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isTrendItemsEnabledInShoppingCart($storeId = null) + { + return (int) $this->configInterface->getValue( + self::IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isAddToCartEnabledInFrequentlyBoughtTogether($storeId = null) + { + return $this->configInterface->isSetFlag(self::IS_ADDTOCART_ENABLED_IN_FREQUENTLY_BOUGHT_TOGETHER, ScopeInterface::SCOPE_STORE, $storeId); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isAddToCartEnabledInRelatedProducts($storeId = null) + { + return $this->configInterface->isSetFlag(self::IS_ADDTOCART_ENABLED_IN_RELATED_PRODUCTS, ScopeInterface::SCOPE_STORE, $storeId); + } + + /** + * @param int $storeId + * + * @return int + */ + public function isAddToCartEnabledInTrendsItem($storeId = null) + { + return $this->configInterface->isSetFlag(self::IS_ADDTOCART_ENABLED_IN_TRENDS_ITEM, ScopeInterface::SCOPE_STORE, $storeId); + } public function useAdaptiveImage($storeId = null) { diff --git a/Helper/Configuration/AssetHelper.php b/Helper/Configuration/AssetHelper.php index 15528de2e..24bf60010 100644 --- a/Helper/Configuration/AssetHelper.php +++ b/Helper/Configuration/AssetHelper.php @@ -71,10 +71,15 @@ class AssetHelper extends \Magento\Framework\App\Helper\AbstractHelper 'icon' => 'iconFaq', ], [ - 'title' => 'Issues', - 'url' => 'https://github.com/algolia/algoliasearch-magento-2/issues/', + 'title' => 'Support', + 'url' => 'https://www.algolia.com/support/?contact=', 'icon' => 'iconIssues', ], + [ + 'title' => 'Release Notes', + 'url' => 'https://github.com/algolia/algoliasearch-magento-2/releases', + 'icon' => 'iconDocs', + ] ], 'algoliasearch_autocomplete' => [ [ diff --git a/Helper/Configuration/NoticeHelper.php b/Helper/Configuration/NoticeHelper.php index 6d06839d4..0738e8439 100644 --- a/Helper/Configuration/NoticeHelper.php +++ b/Helper/Configuration/NoticeHelper.php @@ -43,6 +43,7 @@ class NoticeHelper extends \Magento\Framework\App\Helper\AbstractHelper 'getVersionNotice', 'getClickAnalyticsNotice', 'getPersonalizationNotice', + 'getRecommendNotice', ]; /** @var array[] */ @@ -317,4 +318,26 @@ public function getNewVersionNotification() { return $this->extensionNotification->checkVersion(); } + + /** + * Function created for adding the Algolia Dashboard link in the Magento recommend system configuration + * @return void + */ + protected function getRecommendNotice() + { + if (!$this->configHelper->getApplicationID()) { + return; + } + $noticeContent = '

Algolia Dashboard

+

Configure your Recommend models on the Algolia Dashboard

'; + + $selector = '#algoliasearch_recommend_recommend'; + $method = 'after'; + + $this->notices[] = [ + 'selector' => $selector, + 'method' => $method, + 'message' => $noticeContent, + ]; + } } diff --git a/Helper/Configuration/PersonalizationHelper.php b/Helper/Configuration/PersonalizationHelper.php index 7088c8dd5..3112088b0 100644 --- a/Helper/Configuration/PersonalizationHelper.php +++ b/Helper/Configuration/PersonalizationHelper.php @@ -34,10 +34,15 @@ class PersonalizationHelper extends \Magento\Framework\App\Helper\AbstractHelper /** @var ConfigResourceInterface */ private $configResourceInterface; + /** + * @param \Magento\Framework\App\Helper\Context $context + * @param ScopeConfigInterface $configInterface + * @param ConfigResourceInterface $configResourceInterface + */ public function __construct( \Magento\Framework\App\Helper\Context $context, - ScopeConfigInterface $configInterface, - ConfigResourceInterface $configResourceInterface + ScopeConfigInterface $configInterface, + ConfigResourceInterface $configResourceInterface ) { $this->configInterface = $configInterface; $this->configResourceInterface = $configResourceInterface; diff --git a/Model/Observer.php b/Model/Observer.php index ec1c9bff5..2d9a8a262 100755 --- a/Model/Observer.php +++ b/Model/Observer.php @@ -14,12 +14,19 @@ */ class Observer implements ObserverInterface { - private $config; - private $registry; - private $storeManager; - private $pageConfig; - private $request; + protected $config; + protected $registry; + protected $storeManager; + protected $pageConfig; + protected $request; + /** + * @param ConfigHelper $configHelper + * @param Registry $registry + * @param StoreManagerInterface $storeManager + * @param PageConfig $pageConfig + * @param \Magento\Framework\App\Request\Http $http + */ public function __construct( ConfigHelper $configHelper, Registry $registry, diff --git a/Model/Observer/Insights/CheckoutCartProductAddAfter.php b/Model/Observer/Insights/CheckoutCartProductAddAfter.php deleted file mode 100644 index 227ae13fb..000000000 --- a/Model/Observer/Insights/CheckoutCartProductAddAfter.php +++ /dev/null @@ -1,90 +0,0 @@ -dataHelper = $dataHelper; - $this->insightsHelper = $insightsHelper; - $this->logger = $logger; - } - - /** - * @return \Algolia\AlgoliaSearch\Helper\ConfigHelper - */ - public function getConfigHelper() - { - return $this->insightsHelper->getConfigHelper(); - } - - /** - * @param Observer $observer - * ['quote_item' => $result, 'product' => $product] - */ - public function execute(Observer $observer) - { - /** @var \Magento\Quote\Model\Quote\Item $quoteItem */ - $quoteItem = $observer->getEvent()->getQuoteItem(); - /** @var \Magento\Catalog\Model\Product $product */ - $product = $observer->getEvent()->getProduct(); - $storeId = $quoteItem->getStoreId(); - - if ($this->getConfigHelper()->isClickConversionAnalyticsEnabled($storeId) - && $this->getConfigHelper()->getConversionAnalyticsMode($storeId) === 'place_order' - && $product->hasData('queryId')) { - $quoteItem->setData('algoliasearch_query_param', $product->getData('queryId')); - } - - if (!$this->insightsHelper->isAddedToCartTracked($storeId)) { - return; - } - - $userClient = $this->insightsHelper->getUserInsightsClient(); - - if ($this->getConfigHelper()->isClickConversionAnalyticsEnabled($storeId) - && $this->getConfigHelper()->getConversionAnalyticsMode($storeId) === 'add_to_cart') { - if ($product->hasData('queryId')) { - try { - $userClient->convertedObjectIDsAfterSearch( - __('Added to Cart'), - $this->dataHelper->getIndexName('_products', $storeId), - [$product->getId()], - $product->getData('queryId') - ); - } catch (\Exception $e) { - $this->logger->critical($e); - } - } - } else { - try { - $userClient->convertedObjectIDs( - __('Added to Cart'), - $this->dataHelper->getIndexName('_products', $storeId), - [$product->getId()] - ); - } catch (\Exception $e) { - $this->logger->critical($e); - } - } - } -} diff --git a/Model/Observer/Insights/CheckoutCartProductAddBefore.php b/Model/Observer/Insights/CheckoutCartProductAddBefore.php deleted file mode 100644 index ebce030d0..000000000 --- a/Model/Observer/Insights/CheckoutCartProductAddBefore.php +++ /dev/null @@ -1,44 +0,0 @@ -configHelper = $configHelper; - $this->insightsHelper = $insightsHelper; - } - - /** - * @param Observer $observer - * ['info' => $requestInfo, 'product' => $product] - */ - public function execute(Observer $observer) - { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $observer->getEvent()->getProduct(); - $requestInfo = $observer->getEvent()->getInfo(); - - if (isset($requestInfo['queryID']) && $requestInfo['queryID'] != '') { - $product->setData('queryId', $requestInfo['queryID']); - } - } -} diff --git a/Observer/Insights/CheckoutCartProductAddAfter.php b/Observer/Insights/CheckoutCartProductAddAfter.php new file mode 100644 index 000000000..364244752 --- /dev/null +++ b/Observer/Insights/CheckoutCartProductAddAfter.php @@ -0,0 +1,108 @@ +dataHelper = $dataHelper; + $this->insightsHelper = $insightsHelper; + $this->logger = $logger; + $this->coreSession = $coreSession; + $this->configHelper = $this->insightsHelper->getConfigHelper(); + $this->personalizationHelper = $this->insightsHelper->getPersonalizationHelper(); + } + + /** + * @param Observer $observer + * ['quote_item' => $result, 'product' => $product] + */ + public function execute(Observer $observer) + { + /** @var Item $quoteItem */ + $quoteItem = $observer->getEvent()->getQuoteItem(); + /** @var Product $product */ + $product = $observer->getEvent()->getProduct(); + $storeId = $quoteItem->getStoreId(); + + if (!$this->insightsHelper->isAddedToCartTracked($storeId) && !$this->insightsHelper->isOrderPlacedTracked($storeId)) { + return; + } + + $userClient = $this->insightsHelper->getUserInsightsClient(); + $queryId = $this->coreSession->getQueryId(); + if ($this->configHelper->isClickConversionAnalyticsEnabled($storeId) && $queryId) { + $conversionAnalyticsMode = $this->configHelper->getConversionAnalyticsMode($storeId); + switch ($conversionAnalyticsMode) { + case 'place_order': + $quoteItem->setData('algoliasearch_query_param', $queryId); + break; + case 'add_to_cart': + try { + $userClient->convertedObjectIDsAfterSearch( + __('Added to Cart'), + $this->dataHelper->getIndexName('_products', $storeId), + [$product->getId()], + $queryId + ); + } catch (Exception $e) { + $this->logger->critical($e); + } + } + } + + if ($this->personalizationHelper->isPersoEnabled($storeId) && $this->personalizationHelper->isCartAddTracked($storeId) && (!$this->configHelper->isClickConversionAnalyticsEnabled($storeId) || $this->configHelper->getConversionAnalyticsMode($storeId) != 'add_to_cart')) { + try { + $userClient->convertedObjectIDs( + __('Added to Cart'), + $this->dataHelper->getIndexName('_products', $storeId), + [$product->getId()] + ); + } catch (Exception $e) { + $this->logger->critical($e); + } + } + } +} \ No newline at end of file diff --git a/Observer/Insights/CheckoutCartProductAddBefore.php b/Observer/Insights/CheckoutCartProductAddBefore.php new file mode 100644 index 000000000..b9034b430 --- /dev/null +++ b/Observer/Insights/CheckoutCartProductAddBefore.php @@ -0,0 +1,35 @@ +coreSession = $coreSession; + } + + /** + * @param Observer $observer + * ['info' => $requestInfo, 'product' => $product] + */ + public function execute(Observer $observer) + { + $requestInfo = $observer->getEvent()->getInfo(); + + if (isset($requestInfo['queryID']) && $requestInfo['queryID'] != '') { + $this->coreSession->setQueryId($requestInfo['queryID']); + } + } +} diff --git a/Model/Observer/Insights/CheckoutOnepageControllerSuccessAction.php b/Observer/Insights/CheckoutOnepageControllerSuccessAction.php similarity index 75% rename from Model/Observer/Insights/CheckoutOnepageControllerSuccessAction.php rename to Observer/Insights/CheckoutOnepageControllerSuccessAction.php index b416a5fd6..c688fed95 100644 --- a/Model/Observer/Insights/CheckoutOnepageControllerSuccessAction.php +++ b/Observer/Insights/CheckoutOnepageControllerSuccessAction.php @@ -1,27 +1,34 @@ insightsHelper = $insightsHelper; $this->orderFactory = $orderFactory; $this->logger = $logger; - } - - /** - * @return \Algolia\AlgoliaSearch\Helper\ConfigHelper - */ - public function getConfigHelper() - { - return $this->insightsHelper->getConfigHelper(); + $this->configHelper = $this->insightsHelper->getConfigHelper(); } /** * @param Observer $observer * - * @return $this|void + * @return $this */ public function execute(Observer $observer) { - /** @var \Magento\Sales\Model\Order $order */ + /** @var Order $order */ $order = $observer->getEvent()->getOrder(); if (!$order) { @@ -71,10 +71,10 @@ public function execute(Observer $observer) $userClient = $this->insightsHelper->getUserInsightsClient(); $orderItems = $order->getAllVisibleItems(); - if ($this->getConfigHelper()->isClickConversionAnalyticsEnabled($order->getStoreId()) - && $this->getConfigHelper()->getConversionAnalyticsMode($order->getStoreId()) === 'place_order') { + if ($this->configHelper->isClickConversionAnalyticsEnabled($order->getStoreId()) + && $this->configHelper->getConversionAnalyticsMode($order->getStoreId()) === 'place_order') { $queryIds = []; - /** @var \Magento\Sales\Model\Order\Item $item */ + /** @var Item $item */ foreach ($orderItems as $item) { if ($item->hasData('algoliasearch_query_param')) { $queryId = $item->getData('algoliasearch_query_param'); @@ -92,17 +92,18 @@ public function execute(Observer $observer) $userClient->convertedObjectIDsAfterSearch( __('Placed Order'), $this->dataHelper->getIndexName('_products', $order->getStoreId()), - $productIds, + array_unique($productIds), $queryId ); - } catch (\Exception $e) { + } catch (Exception $e) { + $this->logger->critical($e); continue; // skip item } } } } else { $productIds = []; - /** @var \Magento\Sales\Model\Order\Item $item */ + /** @var Item $item */ foreach ($orderItems as $item) { $productIds[] = $item->getProductId(); @@ -116,9 +117,9 @@ public function execute(Observer $observer) $userClient->convertedObjectIDs( __('Placed Order'), $this->dataHelper->getIndexName('_products', $order->getStoreId()), - $productIds + array_unique($productIds) ); - } catch (\Exception $e) { + } catch (Exception $e) { $this->logger->critical($e); } } diff --git a/Model/Observer/Insights/CustomerLogin.php b/Observer/Insights/CustomerLogin.php similarity index 68% rename from Model/Observer/Insights/CustomerLogin.php rename to Observer/Insights/CustomerLogin.php index 817ecee22..81e65e6e9 100644 --- a/Model/Observer/Insights/CustomerLogin.php +++ b/Observer/Insights/CustomerLogin.php @@ -1,29 +1,24 @@ configHelper = $configHelper; $this->insightsHelper = $insightsHelper; } @@ -33,7 +28,7 @@ public function __construct( */ public function execute(Observer $observer) { - /** @var \Magento\Customer\Model\Customer $customer */ + /** @var Customer $customer */ $customer = $observer->getEvent()->getCustomer(); if ($this->insightsHelper->getPersonalizationHelper()->isPersoEnabled($customer->getStoreId())) { diff --git a/Model/Observer/Insights/WishlistProductAddAfter.php b/Observer/Insights/WishlistProductAddAfter.php similarity index 62% rename from Model/Observer/Insights/WishlistProductAddAfter.php rename to Observer/Insights/WishlistProductAddAfter.php index 7133e9e5e..1dc53ba62 100644 --- a/Model/Observer/Insights/WishlistProductAddAfter.php +++ b/Observer/Insights/WishlistProductAddAfter.php @@ -1,23 +1,30 @@ dataHelper = $dataHelper; $this->personalisationHelper = $personalisationHelper; $this->insightsHelper = $insightsHelper; + $this->logger = $logger; } /** @@ -42,9 +52,9 @@ public function __construct( */ public function execute(Observer $observer) { - /** @var \Magento\Sales\Model\Order $order */ + /** @var Order $order */ $items = $observer->getEvent()->getItems(); - /** @var \Magento\Wishlist\Model\Item $firstItem */ + /** @var Item $firstItem */ $firstItem = $items[0]; if (!$this->personalisationHelper->isPersoEnabled($firstItem->getStoreId()) @@ -55,15 +65,19 @@ public function execute(Observer $observer) $userClient = $this->insightsHelper->getUserInsightsClient(); $productIds = []; - /** @var \Magento\Wishlist\Model\Item $item */ + /** @var Item $item */ foreach ($items as $item) { $productIds[] = $item->getProductId(); } - $userClient->convertedObjectIDs( - __('Added to Wishlist'), - $this->dataHelper->getIndexName('_products', $firstItem->getStoreId()), - $productIds - ); + try { + $userClient->convertedObjectIDs( + __('Added to Wishlist'), + $this->dataHelper->getIndexName('_products', $firstItem->getStoreId()), + $productIds + ); + } catch (Exception $e) { + $this->logger->critical($e); + } } } diff --git a/Observer/ReindexProductOnLastItemPurchase.php b/Observer/ReindexProductOnLastItemPurchase.php new file mode 100644 index 000000000..9de3c6333 --- /dev/null +++ b/Observer/ReindexProductOnLastItemPurchase.php @@ -0,0 +1,90 @@ +objectManager = $objectManager; + $this->moduleManager = $moduleManager; + $this->productRepository = $productRepository; + $this->indexer = $indexerRegistry->get('algolia_products'); + } + + /** + * @param EventObserver $observer + * @return void + */ + public function execute(EventObserver $observer) + { + /** @var \Magento\Sales\Model\Order\Shipment $shipment */ + $shipment = $observer->getEvent()->getShipment(); + if ($shipment->getOrigData('entity_id')) { + return; + } + + // Add product to Algolia Indexing Queue if last item was purchased and check if Magento MSI related modules are enabled. + if ($this->moduleManager->isEnabled('Magento_Inventory')) { + $isSingleMode = $this->objectManager->create(\Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface::class); + $defaultSourceProvider = $this->objectManager->create(\Magento\InventoryCatalogApi\Api\DefaultSourceProviderInterface::class); + + if (!empty($shipment->getExtensionAttributes()) + && !empty($shipment->getExtensionAttributes()->getSourceCode())) { + $sourceCode = $shipment->getExtensionAttributes()->getSourceCode(); + } elseif ($isSingleMode->execute()) { + $sourceCode = $defaultSourceProvider->getCode(); + } + + foreach ($shipment->getAllItems() as $item) { + $getSourceItemBySku = $this->objectManager->create(\Magento\InventoryApi\Api\GetSourceItemsBySkuInterface::class); + $sourceItemList = $getSourceItemBySku->execute($item->getSku()); + foreach ($sourceItemList as $source) { + if ($source->getSourceCode() == $sourceCode) { + if ($source->getQuantity() < 1) { + $this->indexer->reindexRow($item->getProductId()); + } + } + } + } + } + } +} diff --git a/Observer/ReindexProductOnLastItemPurchaseIfMsiDisable.php b/Observer/ReindexProductOnLastItemPurchaseIfMsiDisable.php new file mode 100644 index 000000000..0bc528e9b --- /dev/null +++ b/Observer/ReindexProductOnLastItemPurchaseIfMsiDisable.php @@ -0,0 +1,73 @@ +moduleManager = $moduleManager; + $this->productRepository = $productRepository; + $this->indexer = $indexerRegistry->get('algolia_products'); + } + + /** + * @param Observer $observer + * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function execute(Observer $observer) + { + // Add product to Algolia Indexing Queue if last item was purchased and if Magento MSI related modules are disabled. + if (!$this->moduleManager->isEnabled('Magento_Inventory')) { + $quote = $observer->getEvent()->getQuote(); + $productIds = []; + foreach ($quote->getAllItems() as $item) { + $productIds[$item->getProductId()] = $item->getProductId(); + $children = $item->getChildrenItems(); + if ($children) { + foreach ($children as $childItem) { + $productIds[$childItem->getProductId()] = $childItem->getProductId(); + } + } + } + + if ($productIds) { + $productToReindex = []; + foreach ($productIds as $productId) { + $product = $this->productRepository->getById($productId); + $stockInfo = $product->getData('quantity_and_stock_status'); + if ($stockInfo['qty'] < 1) { + $productToReindex[] = $productId; + } + } + $this->indexer->reindexList($productToReindex); + } + } + } +} diff --git a/Plugin/RemovePdpProductsBlock.php b/Plugin/RemovePdpProductsBlock.php index e765c6171..22b3ccb90 100644 --- a/Plugin/RemovePdpProductsBlock.php +++ b/Plugin/RemovePdpProductsBlock.php @@ -30,7 +30,7 @@ public function __construct(ConfigHelper $configHelper) */ public function afterToHtml(AbstractBlock $subject, $result) { - if (($subject->getNameInLayout() === self::RELATED_BLOCK_NAME && $this->_configHelper->isRemoveCoreRelatedProductsBlock()) || ($subject->getNameInLayout() === self::UPSELL_BLOCK_NAME && $this->_configHelper->isRemoveUpsellProductsBlock())) { + if (($subject->getNameInLayout() === self::RELATED_BLOCK_NAME && $this->_configHelper->isRecommendRelatedProductsEnabled() && $this->_configHelper->isRemoveCoreRelatedProductsBlock()) || ($subject->getNameInLayout() === self::UPSELL_BLOCK_NAME && $this->_configHelper->isRecommendFrequentlyBroughtTogetherEnabled() && $this->_configHelper->isRemoveUpsellProductsBlock())) { return ''; } diff --git a/README.md b/README.md index 5b68fd158..cf0a8fcf7 100755 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ Algolia Search for Magento 2 ================== -![Latest version](https://img.shields.io/badge/latest-3.8.1-green) +![Latest version](https://img.shields.io/badge/latest-3.9.0-green) ![Magento 2](https://img.shields.io/badge/Magento-2.4.x-orange) -![PHP](https://img.shields.io/badge/PHP-8.1-blue) +![PHP](https://img.shields.io/badge/PHP-8.1,7.4-blue) [![CircleCI](https://circleci.com/gh/algolia/algoliasearch-magento-2/tree/master.svg?style=svg)](https://circleci.com/gh/algolia/algoliasearch-magento-2/tree/master) @@ -119,7 +119,7 @@ Depending on the extension version you are using, you could have a different PHP | v1.x | [1.28.0](https://github.com/algolia/algoliasearch-client-php/tree/1.28.0) | | v2.x | [2.5.1](https://github.com/algolia/algoliasearch-client-php/tree/2.5.1) | | v3.x | [2.5.1](https://github.com/algolia/algoliasearch-client-php/tree/2.5.1) | -| v3.6.x | [3.2.0](https://github.com/algolia/algoliasearch-client-php/tree/3.2.0) | +| >= v3.6.x | [3.2.0](https://github.com/algolia/algoliasearch-client-php/tree/3.2.0) | Refer to these docs when customising your Algolia Magento extension backend: - [Indexing](https://www.algolia.com/doc/integration/magento-2/how-it-works/indexing/) diff --git a/Setup/Patch/Schema/RecommendConfigPatch.php b/Setup/Patch/Schema/RecommendConfigPatch.php new file mode 100644 index 000000000..aa364b30f --- /dev/null +++ b/Setup/Patch/Schema/RecommendConfigPatch.php @@ -0,0 +1,75 @@ +config = $config; + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * @return array|string[] + */ + public static function getDependencies() + { + return []; + } + + /** + * @return array|string[] + */ + public function getAliases() + { + return []; + } + + /** + * @return RecommendConfigPatch|void + */ + public function apply() + { + $movedConfigDirectives = [ + 'algoliasearch_recommend/recommend/is_frequently_bought_together_enabled' => 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled', + 'algoliasearch_recommend/recommend/is_related_products_enabled' => 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled', + 'algoliasearch_recommend/recommend/num_of_frequently_bought_together_products' => 'algoliasearch_recommend/recommend/frequently_bought_together/num_of_frequently_bought_together_products', + 'algoliasearch_recommend/recommend/num_of_related_products' => 'algoliasearch_recommend/recommend/related_product/num_of_related_products', + 'algoliasearch_recommend/recommend/is_remove_core_related_products_block' => 'algoliasearch_recommend/recommend/related_product/is_remove_core_related_products_block', + 'algoliasearch_recommend/recommend/is_remove_core_upsell_products_block' => 'algoliasearch_recommend/recommend/frequently_bought_together/is_remove_core_upsell_products_block' + ]; + + $this->moduleDataSetup->getConnection()->startSetup(); + $connection = $this->moduleDataSetup->getConnection(); + $table = $connection->getTableName('core_config_data'); + foreach ($movedConfigDirectives as $from => $to) { + try { + $connection->query('UPDATE ' . $table . ' SET path = "' . $to . '" WHERE path = "' . $from . '"'); + } catch (\Magento\Framework\DB\Adapter\DuplicateException $e) { + // + } + } + $this->moduleDataSetup->getConnection()->endSetup(); + } +} diff --git a/composer.json b/composer.json index ab0888f17..bed1b8c62 100644 --- a/composer.json +++ b/composer.json @@ -3,10 +3,10 @@ "description": "Algolia Search integration for Magento 2", "type": "magento2-module", "license": ["MIT"], - "version": "3.8.1", + "version": "3.9.0", "require": { "magento/framework": "~102.0|~103.0", - "algolia/algoliasearch-client-php": "^3.2", + "algolia/algoliasearch-client-php": "3.2", "guzzlehttp/guzzle": "^6.3.3|^7.3.0", "ext-json": "*", "ext-PDO": "*", diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index 57336ec8d..62132a089 100755 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -62,4 +62,8 @@ + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 8482973eb..9c7178841 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -479,69 +479,171 @@
]]> - - - Magento\Config\Model\Config\Source\Yesno - - Enabling Frequently Bought Together products can potentially break your design on PDP page and some work will be required to have a good integration with your theme. - ]]> - - - - validate-digits - - - + + + + Magento\Config\Model\Config\Source\Yesno + + Enabling Frequently Bought Together products can potentially break your design on PDP page and some work will be required to have a good integration with your theme. + ]]> + + + + + Magento\Config\Model\Config\Source\Yesno + + Enabling Frequently Bought Together products can potentially break your design on shopping cart page and some work will be required to have a good integration with your theme. + ]]> + + + + required-entry validate-digits validate-greater-than-zero + + + - - - 1 - - - - - Magento\Config\Model\Config\Source\Yesno - - Enabling Related Products can potentially break your design on PDP page and some work will be required to have a good integration with your theme. - ]]> - - - - - validate-digits - - - - - - 1 - - - - - Magento\Config\Model\Config\Source\Yesno - - - - - - - Magento\Config\Model\Config\Source\Yesno - - + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Magento\Config\Model\Config\Source\Yesno + + - - + + + 1 + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + Enabling Related Products can potentially break your design on PDP page and some work will be required to have a good integration with your theme. + ]]> + + + + + Magento\Config\Model\Config\Source\Yesno + + Enabling Related Products can potentially break your design on shopping cart page and some work will be required to have a good integration with your theme. + ]]> + + + + required-entry validate-digits validate-greater-than-zero + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + 1 + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + + + + + + + + + + required-entry validate-digits validate-greater-than-zero + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + +
diff --git a/etc/config.xml b/etc/config.xml index 766b52138..269d72f12 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -44,8 +44,15 @@ - 6 - 6 + + 6 + + + 6 + + + 6 + diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 000000000..e20ffd004 --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml index 0b14a3beb..c164f0c7c 100755 --- a/etc/frontend/events.xml +++ b/etc/frontend/events.xml @@ -9,19 +9,19 @@ - + - + - + - + - + - \ No newline at end of file + diff --git a/etc/module.xml b/etc/module.xml index defdf19a7..14805cd98 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,6 +1,6 @@ - + @@ -26,6 +26,7 @@ + diff --git a/etc/widget.xml b/etc/widget.xml new file mode 100644 index 000000000..33549d88d --- /dev/null +++ b/etc/widget.xml @@ -0,0 +1,22 @@ + + + + + You can allow Algolia Recommend Trending items to appear anywhere you want + + + + How many products do you want to display in the Trending Items?. + 6 + + + + Please Input Valid facet attribute name EX:- color,size etc. + + + + Please Enter Valid Facet Value. + + + + diff --git a/view/adminhtml/templates/query/edit/merchandising.phtml b/view/adminhtml/templates/query/edit/merchandising.phtml index 231935d5a..f52722c55 100644 --- a/view/adminhtml/templates/query/edit/merchandising.phtml +++ b/view/adminhtml/templates/query/edit/merchandising.phtml @@ -334,7 +334,7 @@ $isConfig = [ var initAutocomplete = function() { var client = algoliaAdminBundle.algoliasearch(config.appId, config.apiKey), - index = client.initIndex(config.indexName), + index = client.initIndex(window.algoliaSearch.helper.state.index), template = algoliaAdminBundle.Hogan.compile($('#algolia_merchandising_autocomplete_hit').html()), options = { hitsPerPage: config.searchParameters.hitsPerPage + 5, diff --git a/view/frontend/layout/algolia_search_handle.xml b/view/frontend/layout/algolia_search_handle.xml index 3bdab2932..d41bc1e73 100755 --- a/view/frontend/layout/algolia_search_handle.xml +++ b/view/frontend/layout/algolia_search_handle.xml @@ -6,11 +6,13 @@ \ No newline at end of file diff --git a/view/frontend/templates/autocomplete/category.phtml b/view/frontend/templates/autocomplete/category.phtml deleted file mode 100644 index 79c009e98..000000000 --- a/view/frontend/templates/autocomplete/category.phtml +++ /dev/null @@ -1,32 +0,0 @@ - - diff --git a/view/frontend/templates/autocomplete/menu.phtml b/view/frontend/templates/autocomplete/menu.phtml deleted file mode 100644 index 9ffb24012..000000000 --- a/view/frontend/templates/autocomplete/menu.phtml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/view/frontend/templates/autocomplete/page.phtml b/view/frontend/templates/autocomplete/page.phtml deleted file mode 100644 index 53909b668..000000000 --- a/view/frontend/templates/autocomplete/page.phtml +++ /dev/null @@ -1,14 +0,0 @@ - - \ No newline at end of file diff --git a/view/frontend/templates/autocomplete/product.phtml b/view/frontend/templates/autocomplete/product.phtml deleted file mode 100644 index aa9250fae..000000000 --- a/view/frontend/templates/autocomplete/product.phtml +++ /dev/null @@ -1,61 +0,0 @@ -getPriceKey(); - -$origFormatedVar = 'price' . $priceKey . '_original_formated'; -$tierFormatedVar = 'price' . $priceKey . '_tier_formated' - -?> - - - diff --git a/view/frontend/templates/autocomplete/suggestion.phtml b/view/frontend/templates/autocomplete/suggestion.phtml deleted file mode 100644 index 0f235cbad..000000000 --- a/view/frontend/templates/autocomplete/suggestion.phtml +++ /dev/null @@ -1,28 +0,0 @@ - - - - \ No newline at end of file diff --git a/view/frontend/templates/instant/hit.phtml b/view/frontend/templates/instant/hit.phtml index 05311a36e..35efc7562 100644 --- a/view/frontend/templates/instant/hit.phtml +++ b/view/frontend/templates/instant/hit.phtml @@ -90,6 +90,7 @@ $tierFormatedVar = $block->escapeHtml('price' . $priceKey . '_tier_formated'); {{#isAddToCartEnabled}}
+ diff --git a/view/frontend/templates/recommend/cart/recommend_items.phtml b/view/frontend/templates/recommend/cart/recommend_items.phtml new file mode 100644 index 000000000..1cd9b9335 --- /dev/null +++ b/view/frontend/templates/recommend/cart/recommend_items.phtml @@ -0,0 +1,18 @@ +getAllCartItems(); +?> +
+
+
+ diff --git a/view/frontend/templates/recommend/products.phtml b/view/frontend/templates/recommend/products.phtml index c2b56dccf..57581d88f 100644 --- a/view/frontend/templates/recommend/products.phtml +++ b/view/frontend/templates/recommend/products.phtml @@ -6,8 +6,14 @@ $product = $block->getProduct(); ?>
-
+
+
diff --git a/view/frontend/templates/recommend/widget/trends-item.phtml b/view/frontend/templates/recommend/widget/trends-item.phtml new file mode 100644 index 000000000..d15f6c577 --- /dev/null +++ b/view/frontend/templates/recommend/widget/trends-item.phtml @@ -0,0 +1,19 @@ +generateUniqueToken(); +?> +
+ \ No newline at end of file diff --git a/view/frontend/web/autocomplete.js b/view/frontend/web/autocomplete.js index 082ded326..0ee6a13fd 100755 --- a/view/frontend/web/autocomplete.js +++ b/view/frontend/web/autocomplete.js @@ -2,7 +2,7 @@ let algoliaAutocomplete; let suggestionSection = false; let algoliaFooter; let productResult = []; -requirejs(['algoliaBundle'], function(algoliaBundle) { +requirejs(['algoliaBundle', 'pagesHtml', 'categoriesHtml', 'productsHtml', 'suggestionsHtml', 'additionalHtml', 'domReady!'], function(algoliaBundle, pagesHtml, categoriesHtml, productsHtml, suggestionsHtml, additionalHtml) { algoliaAutocomplete = algoliaBundle; algoliaBundle.$(function ($) { @@ -11,19 +11,6 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { return; } - /** - * Set autocomplete templates - * For templating is used Hogan library - * Docs: http://twitter.github.io/hogan.js/ - **/ - algoliaConfig.autocomplete.templates = { - suggestions: algoliaBundle.Hogan.compile($('#autocomplete_suggestions_template').html()), - products: algoliaBundle.Hogan.compile($('#autocomplete_products_template').html()), - categories: algoliaBundle.Hogan.compile($('#autocomplete_categories_template').html()), - pages: algoliaBundle.Hogan.compile($('#autocomplete_pages_template').html()), - additionalSection: algoliaBundle.Hogan.compile($('#autocomplete_extra_template').html()) - }; - /** * Initialise Algolia client * Docs: https://www.algolia.com/doc/api-client/getting-started/instantiate-client-index/ @@ -33,6 +20,300 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { var searchClient = algoliaBundle.algoliasearch(algoliaConfig.applicationId, algoliaConfig.apiKey); + // autocomplete code moved from common.js to autocomplete.js + window.transformAutocompleteHit = function (hit, price_key, $, helper) { + if (Array.isArray(hit.categories)) + hit.categories = hit.categories.join(', '); + + if (hit._highlightResult.categories_without_path && Array.isArray(hit.categories_without_path)) { + hit.categories_without_path = $.map(hit._highlightResult.categories_without_path, function (category) { + return category.value; + }); + + hit.categories_without_path = hit.categories_without_path.join(', '); + } + + var matchedColors = []; + + if (helper && algoliaConfig.useAdaptiveImage === true) { + if (hit.images_data && helper.state.facetsRefinements.color) { + matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone + } + + if (hit.images_data && helper.state.disjunctiveFacetsRefinements.color) { + matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone + } + } + + if (Array.isArray(hit.color)) { + var colors = []; + + $.each(hit._highlightResult.color, function (i, color) { + if (color.matchLevel === undefined || color.matchLevel === 'none') { + return; + } + + colors.push(color.value); + + if (algoliaConfig.useAdaptiveImage === true) { + var matchedColor = color.matchedWords.join(' '); + if (hit.images_data && color.fullyHighlighted && color.fullyHighlighted === true) { + matchedColors.push(matchedColor); + } + } + }); + + colors = colors.join(', '); + hit._highlightResult.color = { value: colors }; + } + else { + if (hit._highlightResult.color && hit._highlightResult.color.matchLevel === 'none') { + hit._highlightResult.color = { value: '' }; + } + } + + if (algoliaConfig.useAdaptiveImage === true) { + $.each(matchedColors, function (i, color) { + color = color.toLowerCase(); + + if (hit.images_data[color]) { + hit.image_url = hit.images_data[color]; + hit.thumbnail_url = hit.images_data[color]; + + return false; + } + }); + } + + if (hit._highlightResult.color && hit._highlightResult.color.value && hit.categories_without_path) { + if (hit.categories_without_path.indexOf('') === -1 && hit._highlightResult.color.value.indexOf('') !== -1) { + hit.categories_without_path = ''; + } + } + + if (Array.isArray(hit._highlightResult.name)) + hit._highlightResult.name = hit._highlightResult.name[0]; + + if (Array.isArray(hit.price)) { + hit.price = hit.price[0]; + if (hit['price'] !== undefined && price_key !== '.' + algoliaConfig.currencyCode + '.default' && hit['price'][algoliaConfig.currencyCode][price_key.substr(1) + '_formated'] !== hit['price'][algoliaConfig.currencyCode]['default_formated']) { + hit['price'][algoliaConfig.currencyCode][price_key.substr(1) + '_original_formated'] = hit['price'][algoliaConfig.currencyCode]['default_formated']; + } + + if (hit['price'][algoliaConfig.currencyCode]['default_original_formated'] + && hit['price'][algoliaConfig.currencyCode]['special_to_date']) { + var priceExpiration = hit['price'][algoliaConfig.currencyCode]['special_to_date']; + + if (algoliaConfig.now > priceExpiration + 1) { + hit['price'][algoliaConfig.currencyCode]['default_formated'] = hit['price'][algoliaConfig.currencyCode]['default_original_formated']; + hit['price'][algoliaConfig.currencyCode]['default_original_formated'] = false; + } + } + } + + // Add to cart parameters + var action = algoliaConfig.instant.addToCartParams.action + 'product/' + hit.objectID + '/'; + + var correctFKey = getCookie('form_key'); + + if(correctFKey != "" && algoliaConfig.instant.addToCartParams.formKey != correctFKey) { + algoliaConfig.instant.addToCartParams.formKey = correctFKey; + } + + hit.addToCart = { + 'action': action, + 'uenc': AlgoliaBase64.mageEncode(action), + 'formKey': algoliaConfig.instant.addToCartParams.formKey + }; + + if (hit.__autocomplete_queryID) { + + hit.urlForInsights = hit.url; + + if (algoliaConfig.ccAnalytics.enabled + && algoliaConfig.ccAnalytics.conversionAnalyticsMode !== 'disabled') { + var insightsDataUrlString = $.param({ + queryID: hit.__autocomplete_queryID, + objectID: hit.objectID, + indexName: hit.__autocomplete_indexName + }); + if (hit.url.indexOf('?') > -1) { + hit.urlForInsights += insightsDataUrlString + } else { + hit.urlForInsights += '?' + insightsDataUrlString; + } + } + } + + return hit; + }; + + window.getAutocompleteSource = function (section, algolia_client, $, i) { + if (section.hitsPerPage <= 0) + return null; + + var options = { + hitsPerPage: section.hitsPerPage, + analyticsTags: 'autocomplete', + clickAnalytics: true, + distinct: true + }; + + var source; + + if (section.name === "products") { + options.facets = ['categories.level0']; + options.numericFilters = 'visibility_search=1'; + options.ruleContexts = ['magento_filters', '']; // Empty context to keep backward compatibility for already created rules in dashboard + + options = algolia.triggerHooks('beforeAutocompleteProductSourceOptions', options); + + source = { + name: section.name, + hitsPerPage: section.hitsPerPage, + paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), + options:options, + templates: { + noResults() { + return productsHtml.getNoResultHtml(); + }, + item({ item, components, html }) { + if(suggestionSection){ + algoliaAutocomplete.$('.aa-Panel').addClass('productColumn2'); + algoliaAutocomplete.$('.aa-Panel').removeClass('productColumn1'); + }else{ + algoliaAutocomplete.$('.aa-Panel').removeClass('productColumn2'); + algoliaAutocomplete.$('.aa-Panel').addClass('productColumn1'); + } + if(algoliaFooter && algoliaFooter !== undefined && algoliaFooter !== null && algoliaAutocomplete.$('#algoliaFooter').length === 0){ + algoliaAutocomplete.$('.aa-PanelLayout').append(algoliaFooter); + } + var _data = transformAutocompleteHit(item, algoliaConfig.priceKey, $); + return productsHtml.getProductsHtml(_data, components, html); + }, + footer({html}) { + var keys = []; + for (var i = 0; i 0) { + orsTab = []; + for (var i = 0; i < keys.length && i < 2; i++) { + orsTab.push( + { + url:keys[i].url, + name:keys[i].key + } + ); + } + } + + var allUrl = algoliaConfig.baseUrl + '/catalogsearch/result/?q=' + encodeURIComponent(productResult[0].query); + return productsHtml.getFooterHtml(html, orsTab, allUrl, productResult) + } + } + }; + } + else if (section.name === "categories") + { + if (section.name === "categories" && algoliaConfig.showCatsNotIncludedInNavigation === false) { + options.numericFilters = 'include_in_menu=1'; + } + source = { + name: section.name || i, + hitsPerPage: section.hitsPerPage, + paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), + options:options, + templates: { + noResults() { + return categoriesHtml.getNoResultHtml(); + }, + header() { + return categoriesHtml.getHeaderHtml(section); + }, + item({ item, components, html }) { + return categoriesHtml.getCategoriesHtml(item, components, html); + } + } + }; + } + else if (section.name === "pages") + { + source = { + name: section.name || i, + hitsPerPage: section.hitsPerPage, + paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), + options:options, + templates: { + noResults() { + return pagesHtml.getNoResultHtml(); + }, + header() { + return pagesHtml.getHeaderHtml(section); + }, + item({ item, components, html }) { + return pagesHtml.getPagesHtml(item, components, html); + } + } + }; + } + else if (section.name === "suggestions") + { + var suggestions_index = algolia_client.initIndex(algoliaConfig.indexName + "_suggestions"); + var products_index = algolia_client.initIndex(algoliaConfig.indexName + "_products"); + + source = { + displayKey: 'query', + name: section.name, + hitsPerPage: section.hitsPerPage, + paramName: suggestions_index, + options:options, + templates: { + item({ item, html }) { + return html`
Suggestion List
`; + } + } + }; + } else { + /** If is not products, categories, pages or suggestions, it's additional section **/ + source = { + paramName: algolia_client.initIndex(algoliaConfig.indexName + "_section_" + section.name), + displayKey: 'value', + name: section.name || i, + hitsPerPage: section.hitsPerPage, + options:options, + templates: { + noResults() { + return additionalHtml.getNoResultHtml(); + }, + header() { + return additionalHtml.getHeaderHtml(section); + }, + item({ item, components, html }) { + return additionalHtml.getAdditionalHtml(item, components, html); + } + } + }; + } + + return source; + }; /** Add products and categories that are required sections **/ /** Add autocomplete menu sections **/ if (algoliaConfig.autocomplete.nbOfProductsSuggestions > 0) { @@ -47,36 +328,26 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { algoliaConfig.autocomplete.sections.unshift({ hitsPerPage: algoliaConfig.autocomplete.nbOfQueriesSuggestions, label: '', name: "suggestions"}); } - /** Setup autocomplete data sources **/ - var sources = [], - i = 0; - $.each(algoliaConfig.autocomplete.sections, function (name, section) { - var source = getAutocompleteSource(section, algolia_client, $, i); - - if (source) { - sources.push(source); - } - - /** Those sections have already specific placeholder, so do not use the default aa-dataset-{i} class **/ - if (section.name !== 'suggestions' && section.name !== 'products') { - i++; - } - }); - /** * Setup the autocomplete search input * For autocomplete feature is used Algolia's autocomplete.js library * Docs: https://github.com/algolia/autocomplete.js **/ $(algoliaConfig.autocomplete.selector).each(function (i) { - var menu = $(this); + let querySuggestionsPlugin = ""; var options = { - hint: false, - templates: { - dropdownMenu: '#menu-template' + container: '#algoliaAutocomplete', + placeholder: 'Search for products, categories, ...', + debug: algoliaConfig.autocomplete.isDebugEnabled, + detachedMediaQuery: 'none', + onSubmit(data){ + if(data.state.query && data.state.query !== null && data.state.query !== ""){ + window.location.href = `/catalogsearch/result/?q=${data.state.query}`; + } + }, + getSources({query, setContext}) { + return autocompleteConfig; }, - dropdownMenuContainer: "#algolia-autocomplete-container", - debug: algoliaConfig.autocomplete.isDebugEnabled }; if (isMobile() === true) { @@ -88,7 +359,7 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { algoliaFooter = ''; } - sources = algolia.triggerHooks('beforeAutocompleteSources', sources, algolia_client, algoliaBundle); + sources = algolia.triggerHooks('beforeAutocompleteSources', algoliaConfig.autocomplete.sections, algolia_client, algoliaBundle); options = algolia.triggerHooks('beforeAutocompleteOptions', options); // Keep for backward compatibility @@ -103,7 +374,23 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { options = hookResult.shift(); } - let querySuggestionsPlugin = ""; + /** Setup autocomplete data sources **/ + var sources = [], + i = 0; + $.each(algoliaConfig.autocomplete.sections, function (name, section) { + var source = getAutocompleteSource(section, algolia_client, $, i); + + if (source) { + sources.push(source); + } + + /** Those sections have already specific placeholder, + * so do not use the default aa-dataset-{i} class to specify the placeholder **/ + if (section.name !== 'suggestions' && section.name !== 'products') { + i++; + } + }); + let autocompleteConfig = []; sources.forEach(function(data){ if(data.name === "suggestions"){ @@ -126,9 +413,7 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { }, item(params) { const { item, html } = params; - return html` - ${item.query} - `; + return suggestionsHtml.getSuggestionsHtml(item, html) }, }, }; @@ -144,21 +429,13 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { { indexName: data.paramName.indexName, query, - params: { - hitsPerPage: data.hitsPerPage, - distinct: true, - facets: ['categories.level0'], - numericFilters: 'visibility_search=1', - ruleContexts: ['magento_filters', ''], - analyticsTags: 'autocomplete', - clickAnalytics: true - }, + params: data.options, }, ], transformResponse({ results, hits }) { productResult = results; return hits; - }, + }, }); }, templates: data.templates, @@ -173,11 +450,7 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { { indexName: data.paramName.indexName, query, - params: { - hitsPerPage: data.hitsPerPage, - distinct: true, - numericFilters: data.name === "categories" && algoliaConfig.showCatsNotIncludedInNavigation === false ? 'include_in_menu=1' : '', - }, + params: data.options, }, ], }); @@ -186,20 +459,33 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { }) } }); - algoliaAutocomplete.autocomplete({ - container: '#algoliaAutocomplete', - placeholder: 'Search for products, categories, ...', - detachedMediaQuery: 'none', - plugins: [querySuggestionsPlugin], - onSubmit(data){ - if(data.state.query && data.state.query !== null && data.state.query !== ""){ - window.location.href = `/catalogsearch/result/?q=${data.state.query}`; - } - }, - getSources({query, setContext}) { - return autocompleteConfig; - }, - }); + + options.plugins = [querySuggestionsPlugin]; + /** Bind autocomplete feature to the input */ + var algoliaAutocompleteInstance = algoliaAutocomplete.autocomplete(options); + algoliaAutocompleteInstance = algolia.triggerHooks('afterAutocompleteStart', algoliaAutocompleteInstance); + + //Autocomplete insight click conversion + if (algoliaConfig.ccAnalytics.enabled + && algoliaConfig.ccAnalytics.conversionAnalyticsMode !== 'disabled') { + jQuery(document).on('click', '.algoliasearch-autocomplete-hit', function(){ + var $this = $(this); + if ($this.data('clicked')) return; + let itemUrl = jQuery(this).attr('href'); + let eventData = algoliaInsights.buildEventData( + 'Clicked', getHitsUrlParameter(itemUrl, 'objectID'), getHitsUrlParameter(itemUrl, 'indexName'), 1, getHitsUrlParameter(itemUrl, 'queryID') + ); + algoliaInsights.trackClick(eventData); + $this.attr('data-clicked', true); + }); + } }); }); + + function getHitsUrlParameter(url, name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(url); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); + } }); diff --git a/view/frontend/web/insights.js b/view/frontend/web/insights.js index 3eac5d0c7..fa948757c 100644 --- a/view/frontend/web/insights.js +++ b/view/frontend/web/insights.js @@ -1,3 +1,4 @@ +var algoliaInsights; requirejs([ 'jquery', 'algoliaAnalytics', @@ -6,7 +7,7 @@ requirejs([ algoliaAnalytics = algoliaAnalyticsWrapper.default; - var algoliaInsights = { + algoliaInsights = { config: null, defaultIndexName: null, isTracking: false, @@ -108,7 +109,7 @@ requirejs([ }); - if (this.config.ccAnalytics.enabled || this.config.personalization.enabled) { + if (this.config.ccAnalytics.enabled) { $(document).on('click', this.config.ccAnalytics.ISSelector, function() { var $this = $(this); if ($this.data('clicked')) return; @@ -275,4 +276,4 @@ requirejs([ return algoliaInsights; -}); \ No newline at end of file +}); diff --git a/view/frontend/web/internals/autocomplete.css b/view/frontend/web/internals/autocomplete.css index b49dcc741..fc7fb1926 100755 --- a/view/frontend/web/internals/autocomplete.css +++ b/view/frontend/web/internals/autocomplete.css @@ -24,8 +24,12 @@ font-size: 10px; color: #666; overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + height: 30px; + max-height: 30px; + display: -webkit-box; } #algolia-autocomplete-container .aa-dropdown-menu .info-without-thumb .details em, .aa-Panel .info-without-thumb .details em { @@ -147,12 +151,14 @@ } #algolia-autocomplete-container .aa-dropdown-menu .algoliasearch-autocomplete-hit, .aa-Panel .algoliasearch-autocomplete-hit { - display: block; + display: inline-block; position: relative; padding: 5px 10px; color: #000; text-align: left; text-decoration: none; + width: 96%; + overflow: hidden; } #algolia-autocomplete-container .aa-dropdown-menu .other-sections .aa-dataset-suggestions .algoliasearch-autocomplete-hit { @@ -460,6 +466,7 @@ position: absolute; right: 0px; height: 32px; + box-shadow: none; } #algoliaAutocomplete .aa-ClearButton{ position: absolute; @@ -511,6 +518,11 @@ width: 220px !important; } } +@media (max-width: 768px) { + #algolia-autocomplete-container .aa-dropdown-menu .algoliasearch-autocomplete-hit .info, .aa-Panel .algoliasearch-autocomplete-hit .info{ + padding-left: 15px; + } +} @media (min-width: 769px) { .aa-Panel.productColumn2 .aa-PanelLayout section:nth-child(1){ grid-area: 1 / 1 / 2 / 2; @@ -598,3 +610,7 @@ .aa-Panel .aa-PanelLayout section .aa-SourceNoResults{ padding: 5px; } + +.clear-query-autocomplete { + display: none !important; +} diff --git a/view/frontend/web/internals/common.js b/view/frontend/web/internals/common.js index 9a07b1fb1..542f646f1 100755 --- a/view/frontend/web/internals/common.js +++ b/view/frontend/web/internals/common.js @@ -196,361 +196,6 @@ requirejs(['algoliaBundle'], function(algoliaBundle) { return hit; }; - window.transformAutocompleteHit = function (hit, price_key, helper) { - if (Array.isArray(hit.categories)) - hit.categories = hit.categories.join(', '); - - if (hit._highlightResult.categories_without_path && Array.isArray(hit.categories_without_path)) { - hit.categories_without_path = $.map(hit._highlightResult.categories_without_path, function (category) { - return category.value; - }); - - hit.categories_without_path = hit.categories_without_path.join(', '); - } - - var matchedColors = []; - - if (helper && algoliaConfig.useAdaptiveImage === true) { - if (hit.images_data && helper.state.facetsRefinements.color) { - matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone - } - - if (hit.images_data && helper.state.disjunctiveFacetsRefinements.color) { - matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone - } - } - - if (Array.isArray(hit.color)) { - var colors = []; - - $.each(hit._highlightResult.color, function (i, color) { - if (color.matchLevel === undefined || color.matchLevel === 'none') { - return; - } - - colors.push(color.value); - - if (algoliaConfig.useAdaptiveImage === true) { - var matchedColor = color.matchedWords.join(' '); - if (hit.images_data && color.fullyHighlighted && color.fullyHighlighted === true) { - matchedColors.push(matchedColor); - } - } - }); - - colors = colors.join(', '); - hit._highlightResult.color = { value: colors }; - } - else { - if (hit._highlightResult.color && hit._highlightResult.color.matchLevel === 'none') { - hit._highlightResult.color = { value: '' }; - } - } - - if (algoliaConfig.useAdaptiveImage === true) { - $.each(matchedColors, function (i, color) { - color = color.toLowerCase(); - - if (hit.images_data[color]) { - hit.image_url = hit.images_data[color]; - hit.thumbnail_url = hit.images_data[color]; - - return false; - } - }); - } - - if (hit._highlightResult.color && hit._highlightResult.color.value && hit.categories_without_path) { - if (hit.categories_without_path.indexOf('') === -1 && hit._highlightResult.color.value.indexOf('') !== -1) { - hit.categories_without_path = ''; - } - } - - if (Array.isArray(hit._highlightResult.name)) - hit._highlightResult.name = hit._highlightResult.name[0]; - - if (Array.isArray(hit.price)) - hit.price = hit.price[0]; - - if (hit['price'] !== undefined && price_key !== '.' + algoliaConfig.currencyCode + '.default' && hit['price'][algoliaConfig.currencyCode][price_key.substr(1) + '_formated'] !== hit['price'][algoliaConfig.currencyCode]['default_formated']) { - hit['price'][algoliaConfig.currencyCode][price_key.substr(1) + '_original_formated'] = hit['price'][algoliaConfig.currencyCode]['default_formated']; - } - - if (hit['price'][algoliaConfig.currencyCode]['default_original_formated'] - && hit['price'][algoliaConfig.currencyCode]['special_to_date']) { - var priceExpiration = hit['price'][algoliaConfig.currencyCode]['special_to_date']; - - if (algoliaConfig.now > priceExpiration + 1) { - hit['price'][algoliaConfig.currencyCode]['default_formated'] = hit['price'][algoliaConfig.currencyCode]['default_original_formated']; - hit['price'][algoliaConfig.currencyCode]['default_original_formated'] = false; - } - } - - // Add to cart parameters - var action = algoliaConfig.instant.addToCartParams.action + 'product/' + hit.objectID + '/'; - - var correctFKey = getCookie('form_key'); - - if(correctFKey != "" && algoliaConfig.instant.addToCartParams.formKey != correctFKey) { - algoliaConfig.instant.addToCartParams.formKey = correctFKey; - } - - hit.addToCart = { - 'action': action, - 'uenc': AlgoliaBase64.mageEncode(action), - 'formKey': algoliaConfig.instant.addToCartParams.formKey - }; - - if (hit.__autocomplete_queryID) { - - hit.urlForInsights = hit.url; - - if (algoliaConfig.ccAnalytics.enabled - && algoliaConfig.ccAnalytics.conversionAnalyticsMode !== 'disabled') { - var insightsDataUrlString = $.param({ - queryID: hit.__autocomplete_queryID, - objectID: hit.objectID, - indexName: hit.__autocomplete_indexName - }); - if (hit.url.indexOf('?') > -1) { - hit.urlForInsights += insightsDataUrlString - } else { - hit.urlForInsights += '?' + insightsDataUrlString; - } - } - } - - return hit; - }; - - window.getAutocompleteSource = function (section, algolia_client, $, i) { - if (section.hitsPerPage <= 0) - return null; - - var options = { - hitsPerPage: section.hitsPerPage, - analyticsTags: 'autocomplete', - clickAnalytics: true - }; - - var source; - - if (section.name === "products") { - options.facets = ['categories.level0']; - options.numericFilters = 'visibility_search=1'; - options.ruleContexts = ['magento_filters', '']; // Empty context to keep BC for already create rules in dashboard - - options = algolia.triggerHooks('beforeAutocompleteProductSourceOptions', options); - - source = { - name: section.name, - hitsPerPage: section.hitsPerPage, - paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), - templates: { - noResults() { - return 'No results.'; - }, - item({ item, components, html }) { - if(suggestionSection){ - algoliaAutocomplete.$('.aa-Panel').addClass('productColumn2'); - algoliaAutocomplete.$('.aa-Panel').removeClass('productColumn1'); - }else{ - algoliaAutocomplete.$('.aa-Panel').removeClass('productColumn2'); - algoliaAutocomplete.$('.aa-Panel').addClass('productColumn1'); - } - if(algoliaFooter && algoliaFooter !== undefined && algoliaFooter !== null && algoliaAutocomplete.$('#algoliaFooter').length === 0){ - algoliaAutocomplete.$('.aa-PanelLayout').append(algoliaFooter); - } - var _data = transformAutocompleteHit(item, algoliaConfig.priceKey); - var color = ''; - if (_data._highlightResult.color !== undefined) - { - color = _data._highlightResult.color.value; - } - var origFormatedVar = algoliaConfig.origFormatedVar; - var tierFormatedvar = algoliaConfig.tierFormatedVar; - if (algoliaConfig.priceGroup == null) { - return html` -
${_data.name || ''}
-
- ${components.Highlight({hit: _data, attribute: 'name'}) || ''} -
- ${color != '' ? html `color : ${components.Highlight({hit: _data, attribute: 'color'})}` : - html `in ${components.Highlight({hit: _data, attribute: 'categories_without_path'})}`} -
-
- - ${_data['price'][algoliaConfig.currencyCode]['default_formated']} - - - ${_data['price'][algoliaConfig.currencyCode]['default_original_formated'] != null ? - html `${_data['price'][algoliaConfig.currencyCode]['default_original_formated']}` : ''} -
-
-
`; - } else { - return html` -
${_data.name || ''}
-
- ${components.Highlight({hit: _data, attribute: 'name'}) || ''} -
- ${color != '' ? html `color : ${components.Highlight({hit: _data, attribute: 'color'})}` : - html `in ${components.Highlight({hit: _data, attribute: 'categories_without_path'})}`} -
-
- - ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup+'_formated']} - - - ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup+'_original_formated'] != null ? - html `${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup+'_original_formated']}` : ''} - - ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup+'_tier_formated'] != null ? - html ` As low as ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup+'_tier_formated']}` : '' } -
-
-
`; - } - }, - footer({html}) { - var keys = []; - for (var i = 0; i 0) { - orsTab = []; - for (var i = 0; i < keys.length && i < 2; i++) { - orsTab.push( - { - url:keys[i].url, - name:keys[i].key - } - ); - } - } - - var allUrl = algoliaConfig.baseUrl + '/catalogsearch/result/?q=' + encodeURIComponent(productResult[0].query); - if(orsTab && orsTab.length > 0 && algoliaConfig.instant.enabled) { - return html ``; - }else{ - return html ``; - } - } - } - }; - } - else if (section.name === "categories") - { - if (section.name === "categories" && algoliaConfig.showCatsNotIncludedInNavigation === false) { - options.numericFilters = 'include_in_menu=1'; - } - source = { - name: section.name || i, - hitsPerPage: section.hitsPerPage, - paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), - templates: { - noResults() { - return 'No results.'; - }, - header() { - return section.name; - }, - item({ item, components, html }) { - return html`${components.Highlight({ hit: item, attribute: 'path' })} (${item.product_count})` - } - } - }; - } - else if (section.name === "pages") - { - source = { - name: section.name || i, - hitsPerPage: section.hitsPerPage, - paramName:algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), - templates: { - noResults() { - return 'No results.'; - }, - header() { - return section.name; - }, - item({ item, components, html }) { - return html` -
- ${components.Highlight({ hit: item, attribute: 'name' })} -
- ${item.content} -
-
-
-
`; - } - } - }; - } - else if (section.name === "suggestions") - { - var suggestions_index = algolia_client.initIndex(algoliaConfig.indexName + "_suggestions"); - var products_index = algolia_client.initIndex(algoliaConfig.indexName + "_products"); - - source = { - displayKey: 'query', - name: section.name, - hitsPerPage: section.hitsPerPage, - paramName: suggestions_index, - templates: { - item({ item, html }) { - return html`
Suggestion List
`; - } - } - }; - } else { - /** If is not products, categories, pages or suggestions, it's additional section **/ - var index = algolia_client.initIndex(algoliaConfig.indexName + "_section_" + section.name); - - source = { - paramName: algolia_client.initIndex(algoliaConfig.indexName + "_section_" + section.name), - displayKey: 'value', - name: section.name || i, - hitsPerPage: section.hitsPerPage, - templates: { - noResults() { - return 'No results.'; - }, - header() { - return section.name; - }, - item({ item, components, html }) { - return html`${components.Highlight({ hit: item, attribute: 'value' })}`; - } - } - }; - } - - return source; - }; - window.fixAutocompleteCssHeight = function () { if ($(document).width() > 768) { $(".other-sections").css('min-height', '0'); diff --git a/view/frontend/web/internals/recommend.css b/view/frontend/web/internals/recommend.css index 125f7ac87..34a5072ab 100644 --- a/view/frontend/web/internals/recommend.css +++ b/view/frontend/web/internals/recommend.css @@ -14,22 +14,51 @@ .recommend-component { margin-bottom: 80px; } -#relatedProducts .auc-Recommend-list, #frequentlyBoughtTogether .auc-Recommend-list{ +#relatedProducts .auc-Recommend-list, #frequentlyBoughtTogether .auc-Recommend-list, .trendsItem .auc-Recommend-list{ flex-wrap: wrap; justify-content: flex-start; } -#relatedProducts li, #frequentlyBoughtTogether li { +#relatedProducts li, #frequentlyBoughtTogether li, .trendsItem li { display: flex; justify-content: center; width: 16.66666667%; } -#relatedProducts li a, #frequentlyBoughtTogether li a { +#relatedProducts li a, #frequentlyBoughtTogether li a, .trendsItem li a { color: inherit; display: block; } -#relatedProducts .product-name, #frequentlyBoughtTogether .product-name{ +#relatedProducts .product-name, #frequentlyBoughtTogether .product-name, #trendItems .product-name, .trendsItem .product-name{ text-align: center; width: 150px; + text-overflow: ellipsis; + overflow: hidden; + max-height: 42px; + height: 42px; + white-space: normal; + line-height: 2rem; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; +} +#trendItems a, #trendItems a:hover, .trendsItem a, .trendsItem a:hover{ + color:#333; +} +.auc-Recommend-item .product-details { + text-align: center; +} +#trendItems .auc-Recommend-list{ + flex-wrap: wrap; + justify-content: flex-start; +} +.product-details .action.primary, .action-primary{ + background: #f4f4f4; + border: 1px solid #f4f4f4; + color: #666666; +} +.product-details .action.primary:hover, .action-primary:hover { + border-color: #1979c3; + background: #1979c3; + color: #FFFFFF; } @media (min-width: 768px) and (max-width: 1023px) { #relatedProducts li, #frequentlyBoughtTogether li { diff --git a/view/frontend/web/internals/template/autocomplete/additional-section.js b/view/frontend/web/internals/template/autocomplete/additional-section.js new file mode 100644 index 000000000..ed161b942 --- /dev/null +++ b/view/frontend/web/internals/template/autocomplete/additional-section.js @@ -0,0 +1,15 @@ +define([], function () { + return { + getAdditionalHtml: function (item, components, html) { + return html`${components.Highlight({ hit: item, attribute: 'value' })}`; + }, + + getHeaderHtml: function (section) { + return section.name; + }, + + getNoResultHtml: function () { + return 'No Results'; + } + }; +}); diff --git a/view/frontend/web/internals/template/autocomplete/categories.js b/view/frontend/web/internals/template/autocomplete/categories.js new file mode 100644 index 000000000..090beafd1 --- /dev/null +++ b/view/frontend/web/internals/template/autocomplete/categories.js @@ -0,0 +1,17 @@ +define([], function () { + return { + getCategoriesHtml: function (item, components, html) { + return html ` + ${components.Highlight({ hit: item, attribute: 'path' })} (${item.product_count}) + `; + }, + + getHeaderHtml: function (section) { + return section.name; + }, + + getNoResultHtml: function () { + return 'No Results'; + } + }; +}); diff --git a/view/frontend/web/internals/template/autocomplete/pages.js b/view/frontend/web/internals/template/autocomplete/pages.js new file mode 100644 index 000000000..858059a80 --- /dev/null +++ b/view/frontend/web/internals/template/autocomplete/pages.js @@ -0,0 +1,23 @@ +define([], function () { + return { + getPagesHtml: function (item, components, html) { + return html` +
+ ${components.Highlight({hit: item, attribute: 'name'})} +
+ ${components.Highlight({hit: item, attribute: 'content'})} +
+
+
+
`; + }, + + getHeaderHtml: function (section) { + return section.name; + }, + + getNoResultHtml: function () { + return 'No Results'; + } + }; +}); diff --git a/view/frontend/web/internals/template/autocomplete/products.js b/view/frontend/web/internals/template/autocomplete/products.js new file mode 100644 index 000000000..b6ea27fdb --- /dev/null +++ b/view/frontend/web/internals/template/autocomplete/products.js @@ -0,0 +1,72 @@ +define([], function () { + return { + getProductsHtml: function (_data, components, html) { + var color = ''; + if (_data._highlightResult.color !== undefined) { + color = _data._highlightResult.color.value; + } + var origFormatedVar = algoliaConfig.origFormatedVar; + var tierFormatedvar = algoliaConfig.tierFormatedVar; + if (algoliaConfig.priceGroup == null) { + return html` +
${_data.name || ''}
+
+ ${components.Highlight({hit: _data, attribute: 'name'}) || ''} +
+ ${color && color != '' ? html `color : ${components.Highlight({hit: _data, attribute: 'color'})}` : + _data.categories_without_path && _data.categories_without_path.length != 0 ? html `in ${components.Highlight({hit: _data, attribute: 'categories_without_path'})}` : ''} +
+ ${_data['price'] !== undefined ? html `
+ + ${_data['price'][algoliaConfig.currencyCode]['default_formated']} + + ${_data['price'][algoliaConfig.currencyCode]['default_original_formated'] != null ? html` + ${_data['price'][algoliaConfig.currencyCode]['default_original_formated']}` : ''} +
` : ''} +
+
`; + } else { + return html` +
${_data.name || ''}
+
+ ${components.Highlight({hit: _data, attribute: 'name'}) || ''} +
+ ${color && color != '' ? html `color : ${components.Highlight({hit: _data, attribute: 'color'})}` : + _data.categories_without_path && _data.categories_without_path.length != 0 ? html `in ${components.Highlight({hit: _data, attribute: 'categories_without_path'})}` : ''} +
+ ${_data['price'] !== undefined ? html `
+ + ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup + '_formated']} + + ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup + '_original_formated'] != null ? html` + ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup + '_original_formated']}` : ''} + + ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup + '_tier_formated'] != null ? html` + As low as ${_data['price'][algoliaConfig.currencyCode][algoliaConfig.priceGroup + '_tier_formated']}` : ''} +
` : ''} +
+
`; + } + }, + + getHeaderHtml: function (section) { + return section.name; + }, + + getNoResultHtml: function () { + return 'No Results'; + }, + + getFooterHtml: function (html, orsTab, allUrl, productResult) { + if(orsTab && orsTab.length > 0 && algoliaConfig.instant.enabled) { + return html ``; + }else{ + return html ``; + } + } + }; +}); diff --git a/view/frontend/web/internals/template/autocomplete/suggestions.js b/view/frontend/web/internals/template/autocomplete/suggestions.js new file mode 100644 index 000000000..3b1d86344 --- /dev/null +++ b/view/frontend/web/internals/template/autocomplete/suggestions.js @@ -0,0 +1,17 @@ +define([], function () { + return { + getSuggestionsHtml: function (item, html) { + return html` + ${item.query} + `; + }, + + getPagesHeaderHtml: function (section) { + return section.name; + }, + + getNoResultHtml: function () { + return 'No Results'; + } + }; +}); diff --git a/view/frontend/web/recommend.js b/view/frontend/web/recommend.js index 5d1d89403..7c32e4e23 100644 --- a/view/frontend/web/recommend.js +++ b/view/frontend/web/recommend.js @@ -1,64 +1,132 @@ requirejs([ + 'jquery', 'recommend', 'recommendJs', - 'algoliaBundle' -], function (recommend, recommendJs, algoliaBundle) { + 'algoliaBundle', + 'mage/translate' +], function ($, recommend, recommendJs, algoliaBundle, $tr) { this.config = algoliaConfig; this.defaultIndexName = algoliaConfig.indexName + '_products'; const appId = this.config.applicationId; const apiKey = this.config.apiKey; const recommendClient = recommend(appId, apiKey); const indexName = this.defaultIndexName; - // --- Add the current product objectID here --- - const currentObjectID = objectId; - if (this.config.recommend.enabledFBT) { - recommendJs.frequentlyBoughtTogether({ - container: '#frequentlyBoughtTogether', + if ($('body').hasClass('catalog-product-view') || $('body').hasClass('checkout-cart-index')) { + // --- Add the current product objectID here --- + const currentObjectID = algoliObjectId; + if ((this.config.recommend.enabledFBT && $('body').hasClass('catalog-product-view')) || (this.config.recommend.enabledFBTInCart && $('body').hasClass('checkout-cart-index'))) { + recommendJs.frequentlyBoughtTogether({ + container: '#frequentlyBoughtTogether', + recommendClient, + indexName, + objectIDs: currentObjectID, + maxRecommendations: this.config.recommend.limitFBTProducts, + itemComponent({item, createElement, Fragment}) { + if (config.recommend.isAddToCartEnabledInFBT) { + return renderRecommendDataWithAddToCart(item, createElement); + }else{ + return renderRecommendData(item, createElement); + } + }, + }); + } + if ((this.config.recommend.enabledRelated && $('body').hasClass('catalog-product-view')) || (this.config.recommend.enabledRelatedInCart && $('body').hasClass('checkout-cart-index'))) { + recommendJs.relatedProducts({ + container: '#relatedProducts', + recommendClient, + indexName, + objectIDs: currentObjectID, + maxRecommendations: this.config.recommend.limitRelatedProducts, + itemComponent({item, createElement, Fragment}) { + if (config.recommend.isAddToCartEnabledInRelatedProduct) { + return renderRecommendDataWithAddToCart(item, createElement);; + }else{ + return renderRecommendData(item, createElement) + } + }, + }); + } + } + + if ((this.config.recommend.isTrendItemsEnabledInPDP && $('body').hasClass('catalog-product-view')) || (this.config.recommend.isTrendItemsEnabledInCartPage && $('body').hasClass('checkout-cart-index'))) { + recommendJs.trendingItems({ + container: '#trendItems', + facetName: this.config.recommend.trendItemFacetName ? this.config.recommend.trendItemFacetName : '', + facetValue: this.config.recommend.trendItemFacetValue ? this.config.recommend.trendItemFacetValue : '', recommendClient, indexName, - objectIDs: [currentObjectID], - maxRecommendations: this.config.recommend.limitFBTProducts, + maxRecommendations: this.config.recommend.limitTrendingItems, itemComponent({item, createElement, Fragment}) { - return createElement( - 'div', - null, - createElement( - 'div', - {className: "product-details"}, - createElement( - 'a', - {className: "product-url", href: item.url}, - createElement('img', {className: "product-img", src: item.image_url}, item.image_url), - createElement('p', {className: "product-name"}, item.name) - ) - ) - ); + if (config.recommend.isAddToCartEnabledInTrendsItem) { + return renderRecommendDataWithAddToCart(item, createElement);; + }else{ + return renderRecommendData(item, createElement) + } }, }); - } - if (this.config.recommend.enabledRelated) { - recommendJs.relatedProducts({ - container: '#relatedProducts', + }else if(this.config.recommend.enabledTrendItems && typeof recommendTrendContainer !== "undefined"){ + let containerValue = "#"+recommendTrendContainer; + recommendJs.trendingItems({ + container: containerValue, + facetName: facetName ? facetName : '', + facetValue: facetValue ? facetValue : '', recommendClient, indexName, - objectIDs: [currentObjectID], - maxRecommendations: this.config.recommend.limitRelatedProducts, + maxRecommendations: numOfTrendsItem ? parseInt(numOfTrendsItem) : this.config.recommend.limitTrendingItems, itemComponent({item, createElement, Fragment}) { - return createElement( - 'div', - null, - createElement( - 'div', - {className: "product-details"}, - createElement( - 'a', - {className: "product-url", href: item.url}, - createElement('img', {className: "product-img", src: item.image_url}, item.image_url), - createElement('p', {className: "product-name"}, item.name) - ) - ) - ); + if (config.recommend.isAddToCartEnabledInTrendsItem) { + return renderRecommendDataWithAddToCart(item, createElement);; + }else{ + return renderRecommendData(item, createElement) + } }, }); } + function renderRecommendData(item, createElement){ + this.config = algoliaConfig; + this.defaultIndexName = algoliaConfig.indexName + '_products'; + return createElement( + 'div', + null, + createElement( + 'div', + {className: "product-details"}, + createElement( + 'a', + {className: "recommend-item product-url", href: item.url, 'data-objectId': item.objectID, 'data-index': this.defaultIndexName}, + createElement('img', {className: "product-img", src: item.image_url}, item.image_url), + createElement('p', {className: "product-name"}, item.name) + ) + ) + ); + } + function renderRecommendDataWithAddToCart(item, createElement){ + let correctFKey = getCookie('form_key'); + let action = config.recommend.addToCartParams.action + 'product/' + item.objectID + '/'; + if(correctFKey != "" && config.recommend.addToCartParams.formKey != correctFKey) { + config.recommend.addToCartParams.formKey = correctFKey; + } + this.config = algoliaConfig; + this.defaultIndexName = algoliaConfig.indexName + '_products'; + return createElement( + 'div', + null, + createElement( + 'div', + {className: "product-details"}, + createElement( + 'a', + {className: "recommend-item product-url", href: item.url, 'data-objectId': item.objectID, 'data-index': this.defaultIndexName}, + createElement('img', {className: "product-img", src: item.image_url}, item.image_url), + createElement('p', {className: "product-name"}, item.name), + createElement('form', {className: 'addTocartForm', action: action, method: 'post'}, + createElement('input', {type: 'hidden', name: 'form_key',value: config.recommend.addToCartParams.formKey}), + createElement('input', {type: 'hidden', name:'unec', value: AlgoliaBase64.mageEncode(action)}), + createElement('input', {type: 'hidden', name:'product', value: item.objectID}), + createElement('button', {type: 'submit', className: 'action tocart primary'}, $tr('Add To Cart')) + ) + ) + ) + ); + } });