diff --git a/app/code/community/Algolia/Algoliasearch/Block/Checkout/Success/Conversion.php b/app/code/community/Algolia/Algoliasearch/Block/Checkout/Success/Conversion.php new file mode 100644 index 00000000..f459f16c --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/Block/Checkout/Success/Conversion.php @@ -0,0 +1,46 @@ +getLastOrderId()) { + $this->_order = Mage::getModel('sales/order')->load($orderId); + } + } + + /** + * @return string + */ + public function getOrderItemsConversionJson() + { + $orderItemsData = array(); + $orderItems = $this->_order->getAllVisibleItems(); + + /** @var Item $item */ + foreach ($orderItems as $item) { + if ($item->getData('algoliasearch_query_param') !== '') { + $orderItemsData[$item->getProductId()] = json_decode($item->getData('algoliasearch_query_param')); + } + } + + return Mage::helper('core')->jsonEncode($orderItemsData); + } + + public function _toHtml() + { + if ($this->_order + && Mage::helper('algoliasearch/config')->isClickConversionAnalyticsEnabled($this->_order->getStoreId()) + && Mage::helper('algoliasearch/config')->getConversionAnalyticsMode($this->_order->getStoreId()) === 'place_order' + ) { + return parent::_toHtml(); + } + + return ''; + } +} diff --git a/app/code/community/Algolia/Algoliasearch/Model/Observer/Conversion.php b/app/code/community/Algolia/Algoliasearch/Model/Observer/Conversion.php new file mode 100644 index 00000000..47eba6cf --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/Model/Observer/Conversion.php @@ -0,0 +1,80 @@ +isClickConversionAnalyticsEnabled($storeId) + && Mage::helper('algoliasearch/config')->getConversionAnalyticsMode($storeId) === 'place_order'; + } + + /** + * @param array $params + * @return bool + */ + protected function _hasRequiredParameters($params = array()) + { + foreach ($this->_analyticsParams as $requiredParam) { + if (!isset($params[$requiredParam])) { + return false; + } + } + + return true; + } + + /** + * @event catalog_controller_product_init_before + */ + public function setAlgoliaParamsToSession(Varien_Event_Observer $observer) + { + $checkoutSession = Mage::getSingleton('checkout/session'); + if (!$this->_isOrderConversionTrackingEnabled($checkoutSession->getQuote()->getStoreId())) { + return; + } + + /** @var Mage_Core_Controller_Front_Action $controllerAction */ + $controllerAction = $observer->getEvent()->getControllerAction(); + $params = $controllerAction->getRequest()->getParams(); + + if (!$this->_hasRequiredParameters($params)) { + return; + } + + $conversionData = array( + 'queryID' => $params['queryID'], + 'indexName' => $params['indexName'], + 'objectID' => $params['objectID'], + ); + + $session = Mage::getSingleton('core/session', array('name' => 'frontend')); + $session->setData('algolia_conversion_parameters', Mage::helper('core')->jsonEncode($conversionData)); + } + + /** + * @event checkout_cart_product_add_after + */ + public function saveAlgoliaParamToQuoteItem(Varien_Event_Observer $observer) + { + /** @var Mage_Sales_Model_Quote_Item $quoteItem */ + $quoteItem = $observer->getEvent()->getQuoteItem(); + /** @var Mage_Catalog_Model_Product $product */ + $product = $observer->getEvent()->getProduct(); + + if ($this->_isOrderConversionTrackingEnabled($product->getStoreId())) { + $session = Mage::getSingleton('core/session'); + $quoteItem->setData('algoliasearch_query_param', $session->getData('algolia_conversion_parameters')); + $session->unsetData('algolia_conversion_parameters'); + } + } +} diff --git a/app/code/community/Algolia/Algoliasearch/etc/config.xml b/app/code/community/Algolia/Algoliasearch/etc/config.xml index 3c3907be..ae1cdcc5 100644 --- a/app/code/community/Algolia/Algoliasearch/etc/config.xml +++ b/app/code/community/Algolia/Algoliasearch/etc/config.xml @@ -2,7 +2,7 @@ - 1.16.0 + 1.17.0 @@ -148,6 +148,27 @@ + + + + + singleton + algoliasearch/observer_conversion + setAlgoliaParamsToSession + + + + + + + + singleton + algoliasearch/observer_conversion + saveAlgoliaParamToQuoteItem + + + + @@ -181,6 +202,13 @@ + + + + * + + + diff --git a/app/code/community/Algolia/Algoliasearch/sql/algoliasearch_setup/mysql4-upgrade-1.16.0-1.17.0.php b/app/code/community/Algolia/Algoliasearch/sql/algoliasearch_setup/mysql4-upgrade-1.16.0-1.17.0.php new file mode 100644 index 00000000..d81cae3c --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/sql/algoliasearch_setup/mysql4-upgrade-1.16.0-1.17.0.php @@ -0,0 +1,28 @@ +startSetup(); + +$setup = new Mage_Sales_Model_Resource_Setup('core_setup'); + +$setup->addAttribute( + 'quote_item', + 'algoliasearch_query_param', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'grid' => false, + 'comment' => 'AlgoliaSearch Conversion Query Parameters' + ) +); + +$setup->addAttribute( + 'order_item', + 'algoliasearch_query_param', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'grid' => false, + 'comment' => 'AlgoliaSearch Conversion Query Parameters' + ) +); + +$installer->endSetup(); diff --git a/app/design/frontend/base/default/layout/algoliasearch.xml b/app/design/frontend/base/default/layout/algoliasearch.xml index e3b60d05..588bfba5 100644 --- a/app/design/frontend/base/default/layout/algoliasearch.xml +++ b/app/design/frontend/base/default/layout/algoliasearch.xml @@ -101,4 +101,10 @@ + + + + + + diff --git a/app/design/frontend/base/default/template/algoliasearch/autocomplete/product.phtml b/app/design/frontend/base/default/template/algoliasearch/autocomplete/product.phtml index 5ddaef4a..4ae82751 100644 --- a/app/design/frontend/base/default/template/algoliasearch/autocomplete/product.phtml +++ b/app/design/frontend/base/default/template/algoliasearch/autocomplete/product.phtml @@ -19,7 +19,13 @@ if ($config->isCustomerGroupsEnabled($storeId)) { \ No newline at end of file diff --git a/app/design/frontend/base/default/template/algoliasearch/instantsearch/hit-item.phtml b/app/design/frontend/base/default/template/algoliasearch/instantsearch/hit-item.phtml index a3bedf8f..a35ba245 100644 --- a/app/design/frontend/base/default/template/algoliasearch/instantsearch/hit-item.phtml +++ b/app/design/frontend/base/default/template/algoliasearch/instantsearch/hit-item.phtml @@ -19,7 +19,12 @@ if ($config->isCustomerGroupsEnabled($storeId)) { diff --git a/js/algoliasearch/click_conversion_analytics.js b/js/algoliasearch/click_conversion_analytics.js index 3e76ade5..324758b8 100644 --- a/js/algoliasearch/click_conversion_analytics.js +++ b/js/algoliasearch/click_conversion_analytics.js @@ -1,89 +1,111 @@ algoliaBundle.$(function ($) { AlgoliaAnalytics.init({ - applicationID: algoliaConfig.applicationId, + appId: algoliaConfig.applicationId, apiKey: algoliaConfig.instant.apiKey }); // "Click" in autocomplete $(algoliaConfig.autocomplete.selector).each(function () { - $(this).on('autocomplete:selected', function (e, suggestion) { - trackClick(suggestion.objectID, suggestion.__position, suggestion.__queryID); + $(this).on('autocomplete:selected', function (e, suggestion, dataset) { + + var sources = analyticsHelper.sources; + var source = sources.filter(function(src) { + return src.name == dataset; + }); + + if (source.length > 0) { + var source = source[0]; + trackClick(source.indexName, suggestion.objectID, suggestion.__position, suggestion.__queryID); + } }); }); // "Click" on instant search page $(document).on('click', algoliaConfig.ccAnalytics.ISSelector, function() { var $this = $(this); - trackClick($this.data('objectid'), $this.data('position')); + var lastResults = analyticsHelper.getLastResults(); + + // want to track results returned + if (lastResults) { + trackClick(lastResults.index, $this.data('objectid'), $this.data('position'), $this.data('queryid')); + } }); // "Add to cart" conversion if (algoliaConfig.ccAnalytics.conversionAnalyticsMode === 'add_to_cart') { - $(document).on('click', algoliaConfig.ccAnalytics.addToCartSelector, function () { - var objectId = $(this).data('objectid') || algoliaConfig.productId; - - if (!objectId) { - var postData = $(this).data('post'); - if (!postData || !postData.data.product) { - return; - } + function getQueryParamFromCurrentUrl(queryParamName) { + var url = window.location.href; + var regex = new RegExp('[?&]' + queryParamName + '(=([^&#]*)|&|#|$)'); + var results = regex.exec(url); + if (!results || !results[2]) return ''; + return results[2]; + } - objectId = postData.data.product; - } + $(document).on('click', algoliaConfig.ccAnalytics.addToCartSelector, function () { + var objectId = $(this).data('objectid') || getQueryParamFromCurrentUrl('objectID'); + var queryId = $(this).data('queryid') || getQueryParamFromCurrentUrl('queryID'); + var index = algoliaConfig.indexName + "_products" || getQueryParamFromCurrentUrl('index'); - // "setTimeout" ensures "trackConversion" is always triggered AFTER "trackClick" - // when clicking "Add to cart" on instant search results page - setTimeout(function () { - trackConversion(objectId); - }, 0); + trackConversion(index, objectId, queryId); }); } - // "Place order" conversions - // "algoliaConfig.ccAnalytics.orderedProductIds" are set only on checkout success page - if (algoliaConfig.ccAnalytics.conversionAnalyticsMode === 'place_order' - && algoliaConfig.ccAnalytics.orderedProductIds.length > 0) { - $.each(algoliaConfig.ccAnalytics.orderedProductIds, function (i, objectId) { - trackConversion(objectId); - }); + if (algoliaConfig.ccAnalytics.conversionAnalyticsMode === 'place_order') { + + if (typeof algoliaOrderConversionJson !== 'undefined') { + $.each(algoliaOrderConversionJson, function(idx, itemData) { + if (itemData && itemData.objectID) { + trackConversion(itemData.indexName, itemData.objectID, itemData.queryID); + } + }); + } } }); -algolia.registerHook('beforeInstantsearchInit', function (instantsearchOptions) { - instantsearchOptions.searchParameters['clickAnalytics'] = true; +var analyticsHelper = {}; - return instantsearchOptions; +algolia.registerHook('beforeAutocompleteSources', function(sources) { + analyticsHelper.sources = sources; + return sources; }); algolia.registerHook('beforeInstantsearchStart', function (search) { search.once('render', function() { - AlgoliaAnalytics.initSearch({ - getQueryID: function() { - return search.helper.lastResults && search.helper.lastResults._rawResults[0].queryID - } - }); + analyticsHelper.getLastResults = function () { + return search.helper.lastResults; + } }); - return search; }); +algolia.registerHook('beforeWidgetInitialization', function (allWidgetConfiguration) { + allWidgetConfiguration['hits'] + return allWidgetConfiguration; +}); -function trackClick(objectID, position, queryId) { +algolia.registerHook('beforeInstantsearchInit', function (instantsearchOptions) { + instantsearchOptions.searchParameters['clickAnalytics'] = true; + return instantsearchOptions; +}); + +function trackClick(index, objectID, position, queryId) { var clickData = { - objectID: objectID.toString(), - position: parseInt(position) + index: index, + eventName: "Clicked item", + objectIDs: [objectID.toString()], + positions: [parseInt(position)], + queryID: queryId }; - if (queryId) { - clickData.queryID = queryId; - } - - AlgoliaAnalytics.click(clickData); + AlgoliaAnalytics.clickedObjectIDsAfterSearch(clickData); } -function trackConversion(objectID) { - AlgoliaAnalytics.conversion({ - objectID: objectID.toString() +function trackConversion(index, objectID, queryId) { + AlgoliaAnalytics.convertedObjectIDsAfterSearch({ + index: index, + eventName: "Conversion", + objectIDs: [objectID.toString()], + queryID: queryId, }); -} \ No newline at end of file +} diff --git a/js/algoliasearch/instantsearch.js b/js/algoliasearch/instantsearch.js index ab146457..4e800875 100644 --- a/js/algoliasearch/instantsearch.js +++ b/js/algoliasearch/instantsearch.js @@ -1,5 +1,14 @@ document.addEventListener("DOMContentLoaded", function (event) { algoliaBundle.$(function ($) { + + function makeUrlForInsights(baseUrl, objectID, queryID, indexName) { + var _baseUrl = baseUrl.indexOf('?') === -1 ? baseUrl + '?' : baseUrl; + return _baseUrl + $.param({ + queryID: queryID, + objectID: objectID, + indexName: indexName, + }); + } /** We have nothing to do here if instantsearch is not enabled **/ if (!algoliaConfig.instant.enabled || !(algoliaConfig.isSearchPage || !algoliaConfig.autocomplete.enabled)) { @@ -302,6 +311,8 @@ document.addEventListener("DOMContentLoaded", function (event) { hit.algoliaConfig = window.algoliaConfig; hit.__position = hit.__hitIndex + 1; + hit.__queryID = search.helper.lastResults.queryID; + hit.__urlForInsights = makeUrlForInsights(hit.url, hit.objectID, hit.__queryID, search.helper.lastResults.index); return hit; } @@ -331,7 +342,13 @@ document.addEventListener("DOMContentLoaded", function (event) { results.hits[i] = transformHit(results.hits[i], algoliaConfig.priceKey, search.helper); results.hits[i].isAddToCartEnabled = algoliaConfig.instant.isAddToCartEnabled; results.hits[i].__position = (state.page * state.hitsPerPage) + ++hitIndex; - + results.hits[i].__queryID = results.queryID; + results.hits[i].__urlForInsights = makeUrlForInsights( + results.hits[i].url, + results.hits[i].objectID, + results.hits[i].__queryID, + results.index + ); results.hits[i].algoliaConfig = window.algoliaConfig; } @@ -593,4 +610,6 @@ document.addEventListener("DOMContentLoaded", function (event) { return options; } -}); + +}) +; diff --git a/js/algoliasearch/internals/frontend/common.js b/js/algoliasearch/internals/frontend/common.js index eb844d2c..7cc5acf0 100644 --- a/js/algoliasearch/internals/frontend/common.js +++ b/js/algoliasearch/internals/frontend/common.js @@ -157,6 +157,19 @@ document.addEventListener("DOMContentLoaded", function (e) { } } + if (hit.__queryID) { + var insightsDataUrlString = $.param({ + queryID: hit.__queryID, + objectID: hit.objectID, + indexName: hit.__indexName + }); + if (hit.url.indexOf('?') > -1) { + hit.urlForInsights = hit.url + insightsDataUrlString + } else { + hit.urlForInsights = hit.url + '?' + insightsDataUrlString; + } + } + return hit; }; @@ -205,11 +218,13 @@ document.addEventListener("DOMContentLoaded", function (e) { return template; }, suggestion: function (hit, payload) { - hit = transformHit(hit, algoliaConfig.priceKey) - hit.displayKey = hit.displayKey || hit.name; + hit.__indexName = algoliaConfig.indexName + "_" + section.name; hit.__queryID = payload.queryID; hit.__position = payload.hits.indexOf(hit) + 1; + hit = transformHit(hit, algoliaConfig.priceKey); + hit.displayKey = hit.displayKey || hit.name; + return algoliaConfig.autocomplete.templates[section.name].render(hit); } } @@ -371,6 +386,8 @@ document.addEventListener("DOMContentLoaded", function (e) { source.templates.header = '
' + (section.label ? section.label : section.name) + '
'; } + source.indexName = algoliaConfig.indexName + "_" + section.name; + return source; }; diff --git a/js/algoliasearch/internals/frontend/search-insights.js b/js/algoliasearch/internals/frontend/search-insights.js index 186687cd..601c0ec7 100644 --- a/js/algoliasearch/internals/frontend/search-insights.js +++ b/js/algoliasearch/internals/frontend/search-insights.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.AlgoliaAnalytics=t()}(this,function(){"use strict";var e,t,n,r,i=function(){var e=this;if(this.storeClick=function(t,n){var r=JSON.parse(localStorage.getItem("insights-store"))||{};r[t]={queryID:n,eventTimestamp:(new Date).getTime()},localStorage.setItem(e._storageKey,JSON.stringify(r))},this.getConversionObjectID=function(t){var n=e.getStorageData()[t];return n||!1},this.cleanOldStorage=function(e,t){var n=Object.keys(t);return n.length>0?n.reduce(function(n,r){var i=t[r];return Math.abs(e-i.eventTimestamp)/36e5<=6&&(n[r]=i),n},{}):{}},this.getStorageData=function(){return JSON.parse(localStorage.getItem("insights-store"))||{}},!function(e){try{var t=window[e],n="__storage_test__";return t.setItem(n,n),t.removeItem(n),!0}catch(e){return e instanceof DOMException&&(22===e.code||1014===e.code||"QuotaExceededError"===e.name||"NS_ERROR_DOM_QUOTA_REACHED"===e.name)&&0!==t.length}}("localStorage"))throw new Error("LocalStorage is not available");this._storageKey="insights-store";var t=(new Date).getTime(),n=this.getStorageData(),r=this.cleanOldStorage(t,n);localStorage.setItem(this._storageKey,JSON.stringify(r))},o=function(e,t,n){var r=new Date;r.setTime(r.getTime()+24*n*60*60*1e3);var i="expires="+r.toUTCString();document.cookie=e+"="+t+";"+i+";path=/"},a=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},s=function(e){var t=function(e){for(var t=e+"=",n=decodeURIComponent(document.cookie).split(";"),r=0;r0;)r[n]=arguments[n+1];e&&s(t[e])&&t[e].apply(t,r)}),o=e[r];o.queue=o.queue||[];var i=o.queue;i.forEach(function(e){var t=[].slice.call(e),r=t[0],o=t.slice(1);n.apply(void 0,[r].concat(o))}),i.push=function(e){var t=[].slice.call(e),r=t[0],o=t.slice(1);n.apply(void 0,[r].concat(o))}}}.call(g,window),g}); \ No newline at end of file