diff --git a/Controller/Ajax/Analytics.php b/Controller/Ajax/Analytics.php new file mode 100644 index 00000000..ea699614 --- /dev/null +++ b/Controller/Ajax/Analytics.php @@ -0,0 +1,77 @@ +resultJsonFactory->create(); + if ($this->config->isAnalyticsEnabled()) { + $type = $this->getRequest()->getParam('type'); + $profileKey = $this->config->getProfileKey(); + + $tweakwiseRequest = $this->requestFactory->create(); + $tweakwiseRequest->setProfileKey($profileKey); + $value = $this->getRequest()->getParam('value'); + + if ($type === 'product') { + $tweakwiseRequest->setParameter('productKey', $value); + $tweakwiseRequest->setPath('pageview'); + } elseif ($type === 'search') { + $tweakwiseRequest->setParameter('searchTerm', $value); + $tweakwiseRequest->setPath('search'); + } + + if (!empty($tweakwiseRequest->getPath())) { + try { + $this->client->request($tweakwiseRequest); + $result->setData(['success' => true]); + } catch (\Exception $e) { + $result->setData( + [ + 'success' => false, + 'message' => $e->getMessage() + ] + ); + } + } + } + + return $result; + } +} diff --git a/Model/Client.php b/Model/Client.php index 18ee7fcf..74916bc1 100644 --- a/Model/Client.php +++ b/Model/Client.php @@ -20,12 +20,12 @@ use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Request as HttpRequest; -use GuzzleHttp\Psr7\Uri; use GuzzleHttp\RequestOptions; use Magento\Framework\Profiler; use Psr\Http\Message\ResponseInterface; use SimpleXMLElement; use GuzzleHttp\Client as HttpClient; +use Magento\Framework\UrlInterface; class Client { @@ -71,8 +71,6 @@ class Client * @param Logger $log * @param ResponseFactory $responseFactory * @param EndpointManager $endpointManager - * @param Timer $timer - * @param ModuleInformation $moduleInformation */ public function __construct( Config $config, @@ -80,7 +78,7 @@ public function __construct( ResponseFactory $responseFactory, EndpointManager $endpointManager, Timer $timer, - private readonly ModuleInformation $moduleInformation + private UrlInterface $urlBuilder ) { $this->config = $config; $this->log = $log; @@ -99,8 +97,7 @@ protected function getClient(): HttpClient RequestOptions::TIMEOUT => self::REQUEST_TIMEOUT, RequestOptions::HEADERS => [ 'user-agent' => $this->config->getUserAgentString(), - 'Accept-Encoding' => 'gzip, deflate', - 'TWN-Source' => $this->moduleInformation->getModuleVersion(), + 'Accept-Encoding' => 'gzip, deflate' ] ]; $this->client = new HttpClient($options); @@ -114,10 +111,42 @@ protected function getClient(): HttpClient * @return HttpRequest */ protected function createHttpRequest(Request $tweakwiseRequest): HttpRequest + { + if ($tweakwiseRequest->isPostRequest()) { + return $this->createPostRequest($tweakwiseRequest); + } else { + return $this->createGetRequest($tweakwiseRequest); + } + } + + /** + * @param Request $tweakwiseRequest + * @return HttpRequest + */ + protected function createPostRequest(Request $tweakwiseRequest): HttpRequest + { + $path = $tweakwiseRequest->getPath(); + $headers = []; + + $headers['Content-Type'] = 'application/json'; + $headers['Instance-Key'] = $this->config->getGeneralAuthenticationKey(); + $body = json_encode($tweakwiseRequest->getParameters()); + //post request are used for the analytics api + $uri = $this->urlBuilder->getUrl($tweakwiseRequest->getApiUrl() . '/' . $path); + return new HttpRequest('POST', $uri, $headers, $body); + } + + /** + * @param Request $tweakwiseRequest + * @return HttpRequest + */ + protected function createGetRequest(Request $tweakwiseRequest): HttpRequest { $path = $tweakwiseRequest->getPath(); $pathSuffix = $tweakwiseRequest->getPathSuffix(); + $headers = []; + if ($path === "recommendations/featured") { if ($this->config->getRecommendationsFeaturedCategory()) { $tweakwiseRequest->setCategory(); @@ -138,8 +167,7 @@ protected function createHttpRequest(Request $tweakwiseRequest): HttpRequest } $uri = new Uri($url); - - return new HttpRequest('GET', $uri); + return new HttpRequest('GET', $uri, $headers); } /** @@ -208,6 +236,10 @@ public function handleRequestSuccess( ) ); + if ($statusCode === 204) { + return $this->responseFactory->create($tweakwiseRequest, []); + } + if ($statusCode !== 200) { throw new ApiException( sprintf( diff --git a/Model/Client/Request.php b/Model/Client/Request.php index 233124bf..901f06fa 100644 --- a/Model/Client/Request.php +++ b/Model/Client/Request.php @@ -326,4 +326,21 @@ public function setProfileKey(string $profileKey) $this->setParameter('tn_profilekey', $profileKey); return $this; } + + /** + * @return string|null + */ + public function setParameterArray(string $parameter, array $value) + { + $this->parameters[$parameter] = $value; + return $this; + } + + /** + * @return string|null + */ + public function isPostRequest() + { + return false; + } } diff --git a/Model/Client/Request/AnalyticsRequest.php b/Model/Client/Request/AnalyticsRequest.php new file mode 100644 index 00000000..10abb1b7 --- /dev/null +++ b/Model/Client/Request/AnalyticsRequest.php @@ -0,0 +1,69 @@ +apiUrl; + } + + /** + * @return void + */ + public function setProfileKey(string $profileKey) + { + $this->setParameter('ProfileKey', $profileKey); + } + + /** + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * @return string + */ + public function getProfileKey() + { + $profileKey = $this->getCookie($this->config->getPersonalMerchandisingCookieName()); + if (!$profileKey) { + $profileKey = $this->generateProfileKey(); + $this->setCookie('profileKey', $profileKey); + } + + return $profileKey; + } +} diff --git a/Model/Client/Request/PurchaseRequest.php b/Model/Client/Request/PurchaseRequest.php new file mode 100644 index 00000000..094ee3aa --- /dev/null +++ b/Model/Client/Request/PurchaseRequest.php @@ -0,0 +1,54 @@ +apiUrl; + } + + /** + * @param string $profileKey + * + * @return void + */ + public function setProfileKey(string $profileKey) + { + $this->setParameter('ProfileKey', $profileKey); + } +} diff --git a/Model/Config.php b/Model/Config.php index f356aac4..9f1cfe3a 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -408,7 +408,13 @@ public function isSearchBannersActive(Store $store = null) */ public function getPersonalMerchandisingCookieName(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/personal_merchandising/cookie_name', $store); + $cookie = $this->getStoreConfig('tweakwise/personal_merchandising/cookie_name', $store); + + if (empty($cookie)) { + $cookie = 'tw_analytics'; + } + + return $cookie; } /** diff --git a/Model/PersonalMerchandisingConfig.php b/Model/PersonalMerchandisingConfig.php new file mode 100644 index 00000000..066859cf --- /dev/null +++ b/Model/PersonalMerchandisingConfig.php @@ -0,0 +1,82 @@ +getStoreConfig('tweakwise/personal_merchandising/analytics_enabled', $store); + } + + /** + * @return string|null + */ + public function getProfileKey() + { + $profileKey = $this->cookieManager->getCookie( + $this->getPersonalMerchandisingCookieName(), + null + ); + + if ($this->isAnalyticsEnabled()) { + if ($profileKey === null) { + $profileKey = $this->generateProfileKey(); + $this->cookieManager->setPublicCookie( + $this->getPersonalMerchandisingCookieName(), + $profileKey, + $this->getCookieMetadata() + ); + } + } + + return $profileKey; + } + + /** + * @return string + */ + private function getCookieMetadata() + { + return $this->cookieMetadataFactory + ->createPublicCookieMetadata() + ->setDuration(86400) + ->setPath('/') + ->setSecure(true); + } + + /** + * @return string + */ + private function generateProfileKey() + { + return uniqid('', true); + } +} diff --git a/Observer/TweakwiseCheckout.php b/Observer/TweakwiseCheckout.php new file mode 100644 index 00000000..2be13a8e --- /dev/null +++ b/Observer/TweakwiseCheckout.php @@ -0,0 +1,86 @@ +config->isAnalyticsEnabled('tweakwise/personal_merchandising/analytics_enabled')) { + $order = $observer->getEvent()->getOrder(); + // Get the order items + $items = $order->getAllItems(); + + $this->sendCheckout($items); + } + } + + /** + * @param Items $items + * + * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function sendCheckout($items) + { + $storeId = (int)$this->storeManager->getStore()->getId(); + $profileKey = $this->config->getProfileKey(); + $tweakwiseRequest = $this->requestFactory->create(); + + $tweakwiseRequest->setParameter('profileKey', $profileKey); + $tweakwiseRequest->setPath('purchase'); + + foreach ($items as $item) { + $productTwId[] = $this->helper->getTweakwiseId($storeId, (int)$item->getProductId()); + } + + $tweakwiseRequest->setParameterArray('ProductKeys', $productTwId); + + // @phpcs:disable + try { + $this->client->request($tweakwiseRequest); + } catch (\Exception $e) { + // Do nothing so that the checkout process can continue + } + // @phpcs:enable + } +} diff --git a/ViewModel/PersonalMerchandisingAnalytics.php b/ViewModel/PersonalMerchandisingAnalytics.php new file mode 100644 index 00000000..a67f6ba0 --- /dev/null +++ b/ViewModel/PersonalMerchandisingAnalytics.php @@ -0,0 +1,92 @@ +request->getParam('id'); + $storeId = $this->storeManager->getStore()->getId(); + + if (!$productId) { + return '0'; + } + + return $this->helper->getTweakwiseId((int)$storeId, (int)$productId); + } + + /** + * Get the API URL. + * + * @return string + */ + public function getApiUrl(): string + { + return 'https://navigator-analytics.tweakwise.com/api/'; + } + + /** + * Get the instance key. + * + * @return string + */ + public function getInstanceKey(): string + { + return $this->tweakwiseConfig->getGeneralAuthenticationKey(); + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getCookieName(): string + { + return $this->tweakwiseConfig->getPersonalMerchandisingCookieName(); + } + + /** + * Get the search query. + * + * @return string + */ + public function getSearchQuery(): string + { + return $this->request->getParam('q'); + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 0503db62..097860e5 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -235,9 +235,14 @@ setting below) + + + Magento\Config\Model\Config\Source\Yesno + Note only enable this if you don't use javascript to send productviews/search/purchases requests to tweakwise + - Name of cookie which holds tweakwise profile information, this is usually set in the tweakwise measure script. + Name of cookie which holds tweakwise profile information, this is usually set in the tweakwise measure script. Or when analytics is enabled, it is done automaticly 1 diff --git a/etc/config.xml b/etc/config.xml index 8b86c9e7..6183c6b7 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -42,6 +42,7 @@ cid 3600 + 0 0 diff --git a/etc/di.xml b/etc/di.xml index 0a1374fa..5b732537 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -235,6 +235,16 @@ Tweakwise\Magento2Tweakwise\Model\Client\RequestFactory\FacetAttributeRequest + + + Tweakwise\Magento2Tweakwise\Model\Client\RequestFactory\PurchaseRequest + + + + + Tweakwise\Magento2Tweakwise\Model\Client\RequestFactory\AnalyticsRequest + + @@ -418,5 +428,14 @@ Tweakwise\Magento2Tweakwise\Model\Client\Request\Recommendations\FeaturedRequest - + + + Tweakwise\Magento2Tweakwise\Model\Client\Request\PurchaseRequest + + + + + Tweakwise\Magento2Tweakwise\Model\Client\Request\AnalyticsRequest + + diff --git a/etc/events.xml b/etc/events.xml index 48e717da..8486f01e 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -5,4 +5,7 @@ instance="Tweakwise\Magento2Tweakwise\Observer\CreateTweakwiseSlugsAfterSaveAttribute" /> + + + diff --git a/view/frontend/layout/catalog_product_view.xml b/view/frontend/layout/catalog_product_view.xml index b6a0211b..4290a020 100644 --- a/view/frontend/layout/catalog_product_view.xml +++ b/view/frontend/layout/catalog_product_view.xml @@ -10,17 +10,25 @@ - + + - - Tweakwise\Magento2Tweakwise\ViewModel\LinkedProductListItem - + Tweakwise\Magento2Tweakwise\ViewModel\PersonalMerchandisingAnalytics + product - - + + + + + + Tweakwise\Magento2Tweakwise\ViewModel\LinkedProductListItem + + + + diff --git a/view/frontend/layout/catalogsearch_result_index.xml b/view/frontend/layout/catalogsearch_result_index.xml index 7fcff54f..8ffe3fce 100644 --- a/view/frontend/layout/catalogsearch_result_index.xml +++ b/view/frontend/layout/catalogsearch_result_index.xml @@ -15,6 +15,12 @@ + + + Tweakwise\Magento2Tweakwise\ViewModel\PersonalMerchandisingAnalytics + search + + Tweakwise\Magento2Tweakwise\Model\NavigationConfig\Search @@ -28,6 +34,13 @@ + + + + Tweakwise\Magento2Tweakwise\ViewModel\ProductListItem + + + Tweakwise\Magento2Tweakwise\Model\NavigationConfig\Search diff --git a/view/frontend/templates/analytics.phtml b/view/frontend/templates/analytics.phtml new file mode 100644 index 00000000..482bee6c --- /dev/null +++ b/view/frontend/templates/analytics.phtml @@ -0,0 +1,21 @@ +getData('viewModel'); +$analyticsType = $block->getData('analyticsType'); +$value = $analyticsType === 'product' ? $viewModel->getProductKey() : $viewModel->getSearchQuery(); +?> + + + diff --git a/view/frontend/web/js/analytics.js b/view/frontend/web/js/analytics.js new file mode 100644 index 00000000..599e5f06 --- /dev/null +++ b/view/frontend/web/js/analytics.js @@ -0,0 +1,21 @@ +define('Tweakwise_Magento2Tweakwise/js/analytics', ['jquery'], function($) { + 'use strict'; + + return function(config) { + $(document).ready(function() { + var requestData = { + type: config.type, + value: config.value + }; + + $.ajax({ + url: '/tweakwise/ajax/analytics', + method: 'POST', + data: requestData, + error: function(error) { + console.error('Tweakwise API call failed:', error); + } + }); + }); + }; +});