diff --git a/Block/Gtm.php b/Block/Gtm.php index aaeec08..5ded54f 100644 --- a/Block/Gtm.php +++ b/Block/Gtm.php @@ -30,6 +30,23 @@ public function __construct( $this->configProvider = $configProvider; } + /** + * Retrieve container id + * + * @return string + */ + protected function getStapeContainerId() + { + $containerId = str_ireplace('GTM-', '', $this->configProvider->getContainerId()); + $params = $this->configProvider->getContainerIdParams(); + $containerId = http_build_query(array_merge( + ['id' => $this->_escaper->escapeJs($containerId)], + $this->getAdditionalQueryParams() + )); + + return http_build_query(array_merge([$params['prefix'] => base64_encode($containerId)], $params['suffix'])); + } + /** * Retrieve domain * @@ -47,7 +64,11 @@ public function getDomain() */ public function getLoader() { - return trim($this->configProvider->getCustomLoader() ?: 'gtm', '/'); + if (!$customLoader = $this->configProvider->getCustomLoader()) { + return 'gtm'; + } + + return implode('', [$this->configProvider->getCustomLoaderPrefix(), $customLoader]); } /** @@ -57,10 +78,11 @@ public function getLoader() */ public function getContainerId() { - if ($this->configProvider->getCustomDomain() && $this->configProvider->getCustomLoader()) { - return str_ireplace('GTM-', '', $this->configProvider->getContainerId()); + if ($this->configProvider->getCustomLoader()) { + return $this->getStapeContainerId(); } - return $this->configProvider->getContainerId(); + + return $this->_escaper->escapeJs($this->configProvider->getContainerId()); } /** @@ -115,4 +137,27 @@ public function getIdParamName() { return $this->configProvider->getCustomLoader() && $this->configProvider->getCustomDomain() ? 'st' : 'id'; } + + /** + * Retrieve analytics param + * + * @return string + */ + public function getAnalyticsParam() + { + return $this->configProvider->isStapeAnalyticsEnabled() && !empty($this->configProvider->getCustomLoader()) + ? 'y' : ''; + } + + /** + * Retrieve additional query params + * + * @return string[] + */ + public function getAdditionalQueryParams() + { + return array_filter([ + 'as' => $this->getAnalyticsParam(), + ]); + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3595fa5..0260908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +v1.0.19 +- Implemented custom GTM loader generation logic with prefix as well as container id + v1.0.18 - Fixed potential issue with sending multiple purchase webhook events. diff --git a/Model/Backend/CustomLoader.php b/Model/Backend/CustomLoader.php new file mode 100644 index 0000000..201c4e2 --- /dev/null +++ b/Model/Backend/CustomLoader.php @@ -0,0 +1,139 @@ +randomString = $randomString; + $this->randomSuffix = $randomSuffix; + $this->configFactory = $configFactory; + $this->jsonSerializer = $jsonSerializer; + } + + /** + * Save loader prefix + * + * @return void + * @throws \Exception + */ + protected function saveLoaderPrefix() + { + $prefix = $this->randomString->generate(5); + $config = $this->configFactory->create(); + $config->setScope($this->getScope()); + $config->setScopeId($this->getScopeId()); + $config->setSection('stape_gtm'); + $config->setDataByPath(ConfigProvider::XPATH_GTM_LOADER_PREFIX, $prefix); + $config->save(); + } + + /** + * Save container id params + * + * @return void + * @throws \Exception + */ + protected function saveContainerIdParams() + { + parse_str($this->randomSuffix->generate($this->getValue()), $suffix); + $value = $this->jsonSerializer->serialize([ + 'prefix' => $this->randomString->generate(), + 'suffix' => $suffix, + ]); + + $config = $this->configFactory->create(); + $config->setScope($this->getScope()); + $config->setScopeId($this->getScopeId()); + $config->setSection('stape_gtm'); + $config->setDataByPath(ConfigProvider::XPATH_GTM_CONTAINER_ID_PARAMS, $value); + $config->save(); + } + + /** + * Actions after saving + * + * @return CustomLoader + */ + public function afterSave() + { + $loader = $this->getValue(); + + try { + + $prefix = $this->_config->getValue(ConfigProvider::XPATH_GTM_LOADER_PREFIX, $this->getScope(), $this->getScopeId()); + + if (!empty($loader) && empty($prefix)) { + $this->saveLoaderPrefix(); + } + + $params = $this->_config->getValue( + ConfigProvider::XPATH_GTM_CONTAINER_ID_PARAMS, + $this->getScope(), + $this->getScopeId() + ); + + if (!empty($loader) && empty($params)) { + $this->saveContainerIdParams(); + } + } catch (\Throwable $e) { + + } + + return parent::afterSave(); + } +} diff --git a/Model/ConfigProvider.php b/Model/ConfigProvider.php index 5b1f1cb..4c9faf4 100644 --- a/Model/ConfigProvider.php +++ b/Model/ConfigProvider.php @@ -3,6 +3,7 @@ namespace Stape\Gtm\Model; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Store\Model\ScopeInterface; class ConfigProvider @@ -17,6 +18,11 @@ class ConfigProvider */ public const XPATH_GTM_CONTAINER_ID = 'stape_gtm/general/container_id'; + /* + * XPATH for GTM container id params config value + */ + public const XPATH_GTM_CONTAINER_ID_PARAMS = 'stape_gtm/general/container_id_params'; + /* * XPATH for GTM domain config value */ @@ -27,6 +33,16 @@ class ConfigProvider */ public const XPATH_GTM_LOADER = 'stape_gtm/general/custom_loader'; + /* + * XPATH for GTM Custom Loader prefix config value + */ + public const XPATH_GTM_LOADER_PREFIX = 'stape_gtm/general/custom_loader_prefix'; + + /* + * XPATH for Stape analytics + */ + public const XPATH_GTM_STAPE_ANALYTICS_ENABLED = 'stape_gtm/general/stape_analytics_enabled'; + /* * XPATH for GTM Cookie Keeper config value */ @@ -67,14 +83,23 @@ class ConfigProvider */ private $scopeConfig; + /** + * @var Json $jsonSerializer + */ + private $jsonSerializer; + /** * Define class dependencies * * @param ScopeConfigInterface $scopeConfig + * @param Json $jsonSerializer */ - public function __construct(ScopeConfigInterface $scopeConfig) - { + public function __construct( + ScopeConfigInterface $scopeConfig, + Json $jsonSerializer + ) { $this->scopeConfig = $scopeConfig; + $this->jsonSerializer = $jsonSerializer; } /** @@ -121,6 +146,48 @@ public function getCustomLoader($scopeCode = null) return $this->scopeConfig->getValue(self::XPATH_GTM_LOADER, ScopeInterface::SCOPE_STORE, $scopeCode); } + /** + * Retrieve encoded customer loader + * + * @param string|int $scopeCode + * @return string + */ + public function getCustomLoaderPrefix($scopeCode = null) + { + return $this->scopeConfig->getValue(self::XPATH_GTM_LOADER_PREFIX, ScopeInterface::SCOPE_STORE, $scopeCode); + } + + /** + * Retrieve container id params + * + * @param string|int $scopeCode + * @return array + */ + public function getContainerIdParams($scopeCode = null) + { + $value = $this->scopeConfig->getValue( + self::XPATH_GTM_CONTAINER_ID_PARAMS, + ScopeInterface::SCOPE_STORE, + $scopeCode + ); + return $this->jsonSerializer->unserialize($value ?? ''); + } + + /** + * Check if stape analytics is enabled + * + * @param string|int $scopeCode + * @return bool + */ + public function isStapeAnalyticsEnabled($scopeCode = null) + { + return $this->scopeConfig->isSetFlag( + self::XPATH_GTM_STAPE_ANALYTICS_ENABLED, + ScopeInterface::SCOPE_STORE, + $scopeCode + ); + } + /** * Check if cookie keeper should be used * @@ -135,7 +202,7 @@ public function useCookieKeeper($scopeCode = null) /** * Check if datalayer e-commerce events tracking is enabled * - * @param string}null $scopeCode + * @param string|null $scopeCode * @return bool */ public function ecommerceEventsEnabled($scopeCode = null) diff --git a/Model/Data/RandomString.php b/Model/Data/RandomString.php new file mode 100644 index 0000000..c7a84fc --- /dev/null +++ b/Model/Data/RandomString.php @@ -0,0 +1,32 @@ + mb_substr(md5($loaderId), 0, 8)]); + $options = [ + $default, + 'page=1', + 'page=2', + 'page=3', + 'sort=asc', + 'sort=desc', + ]; + $key = rand(0, count($options) - 1); + return $options[$key] ?? $default; + } +} diff --git a/Setup/Patch/Data/PatchCustomLoader.php b/Setup/Patch/Data/PatchCustomLoader.php new file mode 100644 index 0000000..08563d0 --- /dev/null +++ b/Setup/Patch/Data/PatchCustomLoader.php @@ -0,0 +1,106 @@ +moduleDataSetup = $moduleDataSetup; + $this->configProvider = $configProvider; + $this->customLoaderFactory = $configValueFactory; + $this->storeManager = $storeManager; + } + + private function updateConfig($scopeCode) + { + if ($customLoader = $this->configProvider->getCustomLoader($scopeCode)) { + /** @var \Magento\Framework\App\Config\Value $configValue */ + $configValue = $this->customLoaderFactory->create() + ->setPath(ConfigProvider::XPATH_GTM_LOADER) + ->setValue($customLoader) + ->setScope($scopeCode ?? ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + + $configValue->afterSave(); + return true; + } + return false; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->startSetup(); + + + $stores = $this->storeManager->getStores(true); + + foreach ($stores as $store) { + if ($this->updateConfig($store->getCode())) { + break; + } + } + + $this->moduleDataSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/ViewModel/Cart.php b/ViewModel/Cart.php index 2017c8e..0fed9b3 100644 --- a/ViewModel/Cart.php +++ b/ViewModel/Cart.php @@ -100,6 +100,7 @@ public function getJson() return $this->json->serialize([ 'event' => 'view_cart_stape', + 'ecomm_pagetype' => 'basket', 'cart_quantity' => (int) $quote->getItemsQty(), 'cart_total' => $this->priceCurrency->round($quote->getBaseGrandTotal()), 'ecommerce' => [ diff --git a/ViewModel/Category.php b/ViewModel/Category.php index 4f4eeee..66902e4 100644 --- a/ViewModel/Category.php +++ b/ViewModel/Category.php @@ -108,6 +108,7 @@ public function getJson() { return $this->json->serialize([ 'event' => 'view_collection_stape', + 'ecomm_pagetype' => 'category', 'ecommerce' => [ 'currency' => $this->storeManager->getStore()->getCurrentCurrency()->getCode(), 'item_list_name' => $this->getCategoryName(), diff --git a/ViewModel/Checkout.php b/ViewModel/Checkout.php index be97fee..bd1f6bf 100644 --- a/ViewModel/Checkout.php +++ b/ViewModel/Checkout.php @@ -100,6 +100,7 @@ public function getJson() return $this->json->serialize([ 'event' => 'begin_checkout_stape', + 'ecomm_pagetype' => 'basket', 'cart_quantity' => (int) $quote->getItemsQty(), 'cart_total' => $this->priceCurrency->round($quote->getBaseGrandTotal()), 'ecommerce' => [ diff --git a/ViewModel/Product.php b/ViewModel/Product.php index 075c44b..ff3cef6 100644 --- a/ViewModel/Product.php +++ b/ViewModel/Product.php @@ -144,6 +144,7 @@ public function getJson() { return $this->json->serialize([ 'event' => 'view_item_stape', + 'ecomm_pagetype' => 'product', 'ecommerce' => [ 'currency' => $this->storeManager->getStore()->getCurrentCurrency()->getCode(), 'items' => array_filter([ diff --git a/ViewModel/Success.php b/ViewModel/Success.php index a4e749d..3e35b15 100644 --- a/ViewModel/Success.php +++ b/ViewModel/Success.php @@ -130,6 +130,7 @@ public function getJson() return $this->json->serialize([ 'event' => 'purchase_stape', + 'ecomm_pagetype' => 'purchase', 'user_data' => [ 'first_name' => $address->getFirstname(), 'last_name' => $address->getLastname(), diff --git a/composer.json b/composer.json index 1a7ca53..623208f 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": [ "GPL-3.0-only" ], - "version": "1.0.18", + "version": "1.0.19", "require": { "php": ">=7.4.0" }, diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 2700d4d..cd5eb82 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -17,7 +17,7 @@ Magento\Config\Model\Config\Source\Enabledisable - + 1 @@ -25,17 +25,23 @@ Enter the WEB Google Tag Manager ID, should be formatted as "GTM-XXXXXX".]]> - + stape.io sGTM hosting you can find custom domain of your sGTM container URL following this guide. Otherwise you can find sGTM container URL in the container settings. Custom domain is used to set first party cookies. Leave empty if you want to use googletagmanager.com domain.]]> validate-https-url - + + Stape\Gtm\Model\Backend\CustomLoader this guide to find stape container identifier. This feature is available only if you use stape.io sGTM hosting and enabled Custom Loader power up. Custom loader allows you to increase the accuracy of tracking. ]]> - + + + Magento\Config\Model\Config\Source\Yesno + here.]]> + + Magento\Config\Model\Config\Source\Yesno prolong cookie lifetime in Safari and other browsers with ITP. This option available only if you use stape.io sGTM hosting and set up Cookie Keeper power up.]]> diff --git a/etc/module.xml b/etc/module.xml index 5553851..2e41787 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + diff --git a/view/base/templates/html/gtm/advanced.phtml b/view/base/templates/html/gtm/advanced.phtml index 5cc5fe2..21cdb73 100644 --- a/view/base/templates/html/gtm/advanced.phtml +++ b/view/base/templates/html/gtm/advanced.phtml @@ -5,6 +5,6 @@ // phpcs:disable ?> - + diff --git a/view/base/templates/html/gtm/default.phtml b/view/base/templates/html/gtm/default.phtml index 4968f75..b701fc6 100644 --- a/view/base/templates/html/gtm/default.phtml +++ b/view/base/templates/html/gtm/default.phtml @@ -10,6 +10,6 @@ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'escapeUrl($block->getGtmUrl());?>.js?escapeUrl($block->getIdParamName()); ?>='+i+dl;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','escapeHtmlAttr($block->getContainerId()); ?>'); + })(window,document,'script','dataLayer','getContainerId(); ?>'); diff --git a/view/frontend/web/js/action/set-payment-information-mixin.js b/view/frontend/web/js/action/set-payment-information-mixin.js index 8afec8e..f2eb39c 100644 --- a/view/frontend/web/js/action/set-payment-information-mixin.js +++ b/view/frontend/web/js/action/set-payment-information-mixin.js @@ -46,6 +46,7 @@ define([ window.dataLayer.push({ event: 'payment_info_stape', + ecomm_pagetype: 'basket', user_data: { first_name: address.firstname, last_name: address.lastname, diff --git a/view/frontend/web/js/datalayer.js b/view/frontend/web/js/datalayer.js index a0d1a8c..defdb03 100644 --- a/view/frontend/web/js/datalayer.js +++ b/view/frontend/web/js/datalayer.js @@ -70,6 +70,7 @@ define([ window.dataLayer.push({ event: 'add_to_cart_stape', + ecomm_pagetype: 'product', ecommerce: { currency: config?.data?.ecommerce?.currency, items: [ @@ -101,6 +102,7 @@ define([ window.dataLayer.push({ event: 'remove_from_cart_stape', + ecomm_pagetype: 'product', ecommerce: { currency: config?.data?.ecommerce?.currency, items: [