├── .github ├── FUNDING.yml └── issue_template.md ├── view └── frontend │ ├── requirejs-config.js │ ├── layout │ ├── netopia_payment_success.xml │ ├── catalog_category_view.xml │ ├── catalog_product_view.xml │ ├── checkout_onepage_success.xml │ ├── multishipping_checkout_success.xml │ ├── checkout_cart_index.xml │ ├── default.xml │ ├── checkout_index_index.xml │ └── onestepcheckout_index_index.xml │ ├── templates │ ├── iframe.phtml │ └── js.phtml │ └── web │ └── js │ └── datalayer.js ├── registration.php ├── etc ├── module.xml ├── frontend │ ├── di.xml │ ├── events.xml │ └── sections.xml ├── acl.xml ├── config.xml ├── csp_whitelist.xml └── adminhtml │ └── system.xml ├── Helper ├── Product.php ├── DataLayerItem.php └── Data.php ├── Model ├── Config │ └── Source │ │ └── GdprOption.php ├── DataLayerEvent.php ├── Customer.php ├── Cart.php └── Order.php ├── DataLayer ├── OrderData │ ├── OrderProvider.php │ ├── OrderItemProvider.php │ ├── OrderAbstract.php │ └── OrderItemAbstract.php ├── QuoteData │ ├── QuoteProvider.php │ ├── QuoteItemProvider.php │ ├── QuoteAbstract.php │ └── QuoteItemAbstract.php ├── CategoryData │ ├── CategoryProvider.php │ └── CategoryAbstract.php └── ProductData │ ├── ProductProvider.php │ ├── ProductImpressionProvider.php │ ├── ProductAbstract.php │ └── ProductImpressionAbstract.php ├── i18n └── en_US.csv ├── CustomerData └── JsDataLayer.php ├── Block ├── Data │ ├── Customer.php │ ├── Order.php │ ├── Cart.php │ ├── Checkout.php │ ├── Category.php │ └── Product.php ├── Adminhtml │ └── System │ │ └── Config │ │ └── Form │ │ ├── Field │ │ └── ItemVariantFormat.php │ │ └── Composer │ │ └── Version.php ├── GtmCode.php ├── DataLayer.php └── DataLayerAbstract.php ├── Observer └── Frontend │ └── OrderSuccessPageViewObserver.php ├── composer.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: [srenon] 4 | custom: ['https://www.magepal.com/google-tag-manager.html?utm_source=gtm&utm_medium=github%20sponsor'] 5 | -------------------------------------------------------------------------------- /view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | map: { 3 | '*': { 4 | magepalGtmDatalayer: 'MagePal_GoogleTagManager/js/datalayer' 5 | } 6 | }, 7 | shim: { 8 | 'MagePal_GoogleTagManager/js/datalayer': ['Magento_Customer/js/customer-data'] 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/frontend/layout/netopia_payment_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/frontend/layout/catalog_category_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/frontend/layout/catalog_product_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_onepage_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/frontend/layout/multishipping_checkout_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Helper/Product.php: -------------------------------------------------------------------------------- 1 | _urlBuilder->getBaseUrl(['_type' => UrlInterface::URL_TYPE_MEDIA]) 19 | . 'catalog/product' . ($product->getData('image') ?: $product->getData('small_image')); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | MagePal\GoogleTagManager\CustomerData\JsDataLayer 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /view/frontend/templates/iframe.phtml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | isAdvancedSettingsEnabled()): ?> 13 | 15 | 16 | getAdvancedSettingsIframeCode() ?> 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | dataLayer 14 | 0 15 | 2 16 | 0 17 | 18 | 19 | 0 20 | user_allowed_save_cookie 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_cart_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | list 14 | cart 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_index_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | list 14 | checkout 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /view/frontend/layout/onestepcheckout_index_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | list 14 | checkout 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Model/Config/Source/GdprOption.php: -------------------------------------------------------------------------------- 1 | self::USE_COOKIE_RESTRICTION_MODE, 'label' => __('Use Magento Cookie Restriction Mode')], 24 | ['value' => self::IF_COOKIE_EXIST, 'label' => __('Enable Google Analytics Tracking if Cookie Exist')], 25 | ['value' => self::IF_COOKIE_NOT_EXIST, 'label' => __('Disable Google Analytics Tracking if Cookie Exist')] 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DataLayer/OrderData/OrderProvider.php: -------------------------------------------------------------------------------- 1 | orderProviders = $orderProviders; 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public function getData() 25 | { 26 | $data = $this->getTransactionData(); 27 | $arraysToMerge = []; 28 | 29 | foreach ($this->getOrderProviders() as $orderProvider) { 30 | $orderProvider->setOrder($this->getOrder())->setTransactionData($data); 31 | $arraysToMerge[] = $orderProvider->getData(); 32 | } 33 | 34 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DataLayer/OrderData/OrderItemProvider.php: -------------------------------------------------------------------------------- 1 | orderItemProviders = $orderItemProviders; 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public function getData() 25 | { 26 | $data = $this->getItemData(); 27 | $arraysToMerge = []; 28 | 29 | foreach ($this->getOrderItemProviders() as $orderItemProvider) { 30 | $orderItemProvider->setItem($this->getItem())->setItemData($data); 31 | $arraysToMerge[] = $orderItemProvider->getData(); 32 | } 33 | 34 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /i18n/en_US.csv: -------------------------------------------------------------------------------- 1 | "Module Version","Module Version" 2 | "Composer Version","Composer Version" 3 | "Version","Version" 4 | "Enable","Enable" 5 | "Account Number","Account Number" 6 | "GDPR","GDPR" 7 | "Ignore Cookie Restriction Mode","Ignore Cookie Restriction Mode" 8 | "Cookie Name","Cookie Name" 9 | "Disable tracking if cookie exist","Disable tracking if cookie exist" 10 | "To enable disable tracking goto Stores > Configuration > General > Web > Default Cookie Settings > Cookie Restriction Mode","To enable disable tracking goto Stores > Configuration > General > Web > Default Cookie Settings > Cookie Restriction Mode" 11 | "General Data Protection Regulation (GDPR)","General Data Protection Regulation (GDPR)" 12 | "Use Magento Cookie Restriction Mode","Use Magento Cookie Restriction Mode" 13 | "Enable Google Analytics Tracking if Cookie Exist","Enable Google Analytics Tracking if Cookie Exist" 14 | "Disable Google Analytics Tracking if Cookie Exist","Disable Google Analytics Tracking if Cookie Exist" 15 | "GTM Container","GTM Container" 16 | "Enable Multiple Environments","Enable Multiple Environments" 17 | "Code","Code" 18 | -------------------------------------------------------------------------------- /DataLayer/QuoteData/QuoteProvider.php: -------------------------------------------------------------------------------- 1 | quoteProviders = $quoteProviders; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getData() 26 | { 27 | $data = $this->getTransactionData(); 28 | $arraysToMerge = []; 29 | 30 | /** @var QuoteAbstract $quoteProvider */ 31 | foreach ($this->getQuoteProviders() as $quoteProvider) { 32 | $quoteProvider->setQuote($this->getQuote())->setTransactionData($data); 33 | $arraysToMerge[] = $quoteProvider->getData(); 34 | } 35 | 36 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DataLayer/CategoryData/CategoryProvider.php: -------------------------------------------------------------------------------- 1 | categoryProviders = $categoryProviders; 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public function getData() 25 | { 26 | $data = $this->getcategoryData(); 27 | $arraysToMerge = []; 28 | 29 | /** @var CategoryProvider $categoryProvider */ 30 | foreach ($this->getCategoryProviders() as $categoryProvider) { 31 | $categoryProvider->setCategory($this->getCategory())->setCategoryData($data); 32 | $arraysToMerge[] = $categoryProvider->getData(); 33 | } 34 | 35 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DataLayer/ProductData/ProductProvider.php: -------------------------------------------------------------------------------- 1 | productProviders = $productProviders; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getData() 26 | { 27 | $data = $this->getProductData(); 28 | $arraysToMerge = []; 29 | 30 | /** @var ProductAbstract $productProvider */ 31 | foreach ($this->getProductProviders() as $productProvider) { 32 | $productProvider->setProduct($this->getProduct())->setProductData($data); 33 | $arraysToMerge[] = $productProvider->getData(); 34 | } 35 | 36 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DataLayer/QuoteData/QuoteItemProvider.php: -------------------------------------------------------------------------------- 1 | quoteItemProviders = $quoteItemProviders; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getData() 26 | { 27 | $data = $this->getItemData(); 28 | $arraysToMerge = []; 29 | 30 | /** @var QuoteItemAbstract $quoteItemProvider */ 31 | foreach ($this->getQuoteItemProviders() as $quoteItemProvider) { 32 | $quoteItemProvider->setItem($this->getItem())->setItemData($data); 33 | $arraysToMerge[] = $quoteItemProvider->getData(); 34 | } 35 | 36 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #### Magento version #: 7 | 8 | #### Edition (EE, CE, OS, etc): 9 | 10 | #### Expected behavior: 11 | 12 | #### Actual behavior: 13 | 14 | #### Steps to reproduce: 15 | 16 | #### Preconditions 17 | 18 | 19 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /DataLayer/ProductData/ProductImpressionProvider.php: -------------------------------------------------------------------------------- 1 | productImpressionProviders = $productImpressionProviders; 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function getData() 27 | { 28 | $data = $this->getItemData(); 29 | $arraysToMerge = []; 30 | 31 | /** @var ProductImpressionAbstract $productImpressionProvider */ 32 | foreach ($this->getProductImpressionProviders() as $productImpressionProvider) { 33 | $productImpressionProvider->setProduct($this->getProduct())->setItemData($data); 34 | $arraysToMerge[] = $productImpressionProvider->getData(); 35 | } 36 | 37 | return empty($arraysToMerge) ? $data : array_merge($data, ...$arraysToMerge); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CustomerData/JsDataLayer.php: -------------------------------------------------------------------------------- 1 | gtmCustomer = $gtmCustomer; 35 | $this->gtmCart = $gtmCart; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | * @return array 41 | */ 42 | public function getSectionData() 43 | { 44 | return [ 45 | 'customer' => $this->gtmCustomer->getCustomer(), 46 | 'cart' => $this->gtmCart->getCart() 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Block/Data/Customer.php: -------------------------------------------------------------------------------- 1 | gtmCustomer = $gtmCustomer; 33 | parent::__construct($context, $data); 34 | } 35 | /** 36 | * Add product data to datalayer 37 | * 38 | * @return $this 39 | */ 40 | protected function _prepareLayout() 41 | { 42 | /** @var $tm DataLayer */ 43 | $tm = $this->getParentBlock(); 44 | $tm->addVariable('customer', $this->gtmCustomer->getCustomer()); 45 | 46 | return $this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Model/DataLayerEvent.php: -------------------------------------------------------------------------------- 1 | '{{value}}', 22 | self::LONG_FORMAT => '{{label}} / {{value}}' 23 | ]; 24 | 25 | /** 26 | * Options getter 27 | * 28 | * @return array 29 | */ 30 | public function toOptionArray() 31 | { 32 | return [ 33 | ['label' => __('Option Value (XS/Black)'), 'value' => self::SHORT_FORMAT], 34 | ['label' => __('Option Name : Option Value (Size : XS / Color : Black)'), 'value' => self::LONG_FORMAT] 35 | ]; 36 | } 37 | 38 | /** 39 | * Get options in "key-value" format 40 | * 41 | * @return array 42 | */ 43 | public function toArray() 44 | { 45 | return [ 46 | [self::SHORT_FORMAT => __('Option Value (XS/Black)')], 47 | [self::LONG_FORMAT => __('Option Name : Option Value (Size : XS / Color : Black)')] 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Model/Customer.php: -------------------------------------------------------------------------------- 1 | customerSession = $customerSession; 31 | parent::__construct($data); 32 | } 33 | 34 | /** 35 | * Get Customer array 36 | * 37 | * @return array 38 | */ 39 | public function getCustomer() 40 | { 41 | $isLoggedIn = $this->getCustomerSession()->isLoggedIn(); 42 | $data = [ 43 | 'isLoggedIn' => $isLoggedIn, 44 | ]; 45 | 46 | if ($isLoggedIn) { 47 | $data['id'] = $this->getCustomerSession()->getCustomerId(); 48 | $data['groupId'] = $this->getCustomerSession()->getCustomerGroupId(); 49 | } 50 | 51 | return $data; 52 | } 53 | 54 | /** 55 | * @return Session 56 | */ 57 | public function getCustomerSession() 58 | { 59 | return $this->customerSession; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DataLayer/ProductData/ProductAbstract.php: -------------------------------------------------------------------------------- 1 | productData; 39 | } 40 | 41 | /** 42 | * @param array $productData 43 | * @return ProductAbstract 44 | */ 45 | public function setProductData(array $productData) 46 | { 47 | $this->productData = $productData; 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return Product 53 | */ 54 | public function getProduct() 55 | { 56 | return $this->product; 57 | } 58 | 59 | /** 60 | * @param Product $product 61 | * @return ProductAbstract 62 | */ 63 | public function setProduct(Product $product) 64 | { 65 | $this->product = $product; 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return array|ProductAbstract[] 71 | */ 72 | public function getProductProviders() 73 | { 74 | return $this->productProviders; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DataLayer/OrderData/OrderAbstract.php: -------------------------------------------------------------------------------- 1 | transactionData; 40 | } 41 | 42 | /** 43 | * @param array $transactionData 44 | * @return OrderAbstract 45 | */ 46 | public function setTransactionData(array $transactionData) 47 | { 48 | $this->transactionData = $transactionData; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @param Order $order 54 | * @return OrderAbstract 55 | */ 56 | public function setOrder(Order $order) 57 | { 58 | $this->order = $order; 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return Order 64 | */ 65 | public function getOrder() 66 | { 67 | return $this->order; 68 | } 69 | 70 | /** 71 | * @return OrderProvider[] 72 | */ 73 | public function getOrderProviders() 74 | { 75 | return $this->orderProviders; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DataLayer/QuoteData/QuoteAbstract.php: -------------------------------------------------------------------------------- 1 | transactionData; 40 | } 41 | 42 | /** 43 | * @param array $transactionData 44 | * @return QuoteAbstract 45 | */ 46 | public function setTransactionData(array $transactionData) 47 | { 48 | $this->transactionData = $transactionData; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return Quote 54 | */ 55 | public function getQuote() 56 | { 57 | return $this->quote; 58 | } 59 | 60 | /** 61 | * @param Quote $quote 62 | * @return QuoteAbstract 63 | */ 64 | public function setQuote(Quote $quote) 65 | { 66 | $this->quote = $quote; 67 | return $this; 68 | } 69 | 70 | /** 71 | * @return QuoteProvider[] 72 | */ 73 | public function getQuoteProviders() 74 | { 75 | return $this->quoteProviders; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DataLayer/CategoryData/CategoryAbstract.php: -------------------------------------------------------------------------------- 1 | categoryData; 40 | } 41 | 42 | /** 43 | * @param array $categoryData 44 | * @return CategoryAbstract 45 | */ 46 | public function setCategoryData(array $categoryData) 47 | { 48 | $this->categoryData = $categoryData; 49 | return $this; 50 | } 51 | 52 | /** 53 | */ 54 | public function getCategory() 55 | { 56 | return $this->category; 57 | } 58 | 59 | /** 60 | * @param Category $category 61 | * @return CategoryAbstract 62 | */ 63 | public function setCategory(Category $category) 64 | { 65 | $this->category = $category; 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return array|CategoryProvider[] 71 | */ 72 | public function getCategoryProviders() 73 | { 74 | return $this->categoryProviders; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Block/Data/Order.php: -------------------------------------------------------------------------------- 1 | orderDataArray = $orderDataArray; 36 | } 37 | 38 | /** 39 | * Render information about specified orders and their items 40 | * 41 | * @return void|string 42 | * @throws NoSuchEntityException 43 | */ 44 | public function addOrderLayer() 45 | { 46 | $transactions = $this->orderDataArray->setOrderIds($this->getOrderIds())->getOrderLayer(); 47 | 48 | if (!empty($transactions)) { 49 | /** @var $tm DataLayer */ 50 | $tm = $this->getParentBlock(); 51 | foreach ($transactions as $order) { 52 | $tm->addCustomDataLayer($order); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Block/Data/Cart.php: -------------------------------------------------------------------------------- 1 | gtmCart = $gtmCart; 37 | parent::__construct($context, $data); 38 | } 39 | 40 | /** 41 | * Add product data to datalayer 42 | * 43 | * @return $this 44 | * @throws LocalizedException 45 | * @throws NoSuchEntityException 46 | */ 47 | protected function _prepareLayout() 48 | { 49 | /** @var $tm DataLayer */ 50 | $tm = $this->getParentBlock(); 51 | 52 | $data = [ 53 | 'event' => DataLayerEvent::CART_PAGE_EVENT, 54 | 'cart' => $this->gtmCart->getCart() 55 | ]; 56 | 57 | $tm->addVariable('list', 'cart'); 58 | $tm->addCustomDataLayerByEvent(DataLayerEvent::CART_PAGE_EVENT, $data); 59 | return $this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Block/Data/Checkout.php: -------------------------------------------------------------------------------- 1 | gtmCart = $gtmCart; 37 | parent::__construct($context, $data); 38 | } 39 | 40 | /** 41 | * Add product data to datalayer 42 | * 43 | * @return $this 44 | * @throws LocalizedException 45 | * @throws NoSuchEntityException 46 | */ 47 | protected function _prepareLayout() 48 | { 49 | /** @var $tm DataLayer */ 50 | $tm = $this->getParentBlock(); 51 | 52 | $data = [ 53 | 'event' => DataLayerEvent::CHECKOUT_PAGE_EVENT, 54 | 'cart' => $this->gtmCart->getCart() 55 | ]; 56 | 57 | $tm->addVariable('list', 'checkout'); 58 | $tm->addCustomDataLayerByEvent(DataLayerEvent::CHECKOUT_PAGE_EVENT, $data); 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Observer/Frontend/OrderSuccessPageViewObserver.php: -------------------------------------------------------------------------------- 1 | _layout = $layout; 37 | $this->_storeManager = $storeManager; 38 | } 39 | 40 | /** 41 | * Add order information into GA block to render on checkout success pages 42 | * 43 | * @param EventObserver $observer 44 | * @return void 45 | */ 46 | public function execute(EventObserver $observer) 47 | { 48 | $orderIds = $observer->getEvent()->getOrderIds(); 49 | if (empty($orderIds) || !is_array($orderIds)) { 50 | return; 51 | } 52 | 53 | /** @var BlockInterface $block */ 54 | $block = $this->_layout->getBlock('magepal_gtm_datalayer'); 55 | if ($block) { 56 | $block->setOrderIds($orderIds); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magepal/magento2-googletagmanager", 3 | "description": "Google Tag Manager (GTM) for Magento 2 with Advance Data Layer", 4 | "keywords": [ 5 | "magento 2", 6 | "gtm", 7 | "google tag manager", 8 | "ga", 9 | "data layer", 10 | "magento2", 11 | "tag Manager", 12 | "google analytics", 13 | "magento gtm", 14 | "magento2 analytics", 15 | "ga tag manager", 16 | "magento tag manager", 17 | "integrate gtm magento", 18 | "order conversion gtm magento" 19 | ], 20 | "license": [ 21 | "proprietary" 22 | ], 23 | "homepage": "https://www.magepal.com/google-tag-manager.html", 24 | "support": { 25 | "email": "support@magepal.com", 26 | "issues": "https://github.com/magepal/magento2-google-tag-manager/issues/" 27 | }, 28 | "authors": [ 29 | { 30 | "name": "Renon Stewart", 31 | "email": "renon@magepal.com", 32 | "homepage": "https://www.magepal.com/", 33 | "role": "Leader" 34 | } 35 | ], 36 | "require": { 37 | "php": "~7.3.0|~7.4.0|~8.1.0|~8.2.0|~8.3.0|~8.4.0", 38 | "magento/module-backend": "102.0.*", 39 | "magento/framework": "103.0.*", 40 | "magepal/magento2-core": ">=1.1.11" 41 | }, 42 | "suggest" : { 43 | "magepal/magento2-enhanced-ecommerce": "Get more from Google Tag Manager with Enhanced E-commerce. Learn more at https://www.magepal.com/enhanced-ecommerce-for-google-tag-manager.html", 44 | "magepal/magento2-google-analytics4": "Prepare for the future with Google Analytics 4. Learn more at https://www.magepal.com/google-analytics-4-for-google-tag-manager.html" 45 | }, 46 | "type": "magento2-module", 47 | "version": "3.0.1", 48 | "autoload": { 49 | "files": [ 50 | "registration.php" 51 | ], 52 | "psr-4": { 53 | "MagePal\\GoogleTagManager\\": "" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /etc/frontend/sections.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 11 | 12 |
13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 |
40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 |
49 | 50 | 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /DataLayer/OrderData/OrderItemAbstract.php: -------------------------------------------------------------------------------- 1 | itemData; 46 | } 47 | 48 | /** 49 | * @param array $itemData 50 | * @return OrderItemAbstract 51 | */ 52 | public function setItemData(array $itemData) 53 | { 54 | $this->itemData = $itemData; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return Item 60 | */ 61 | public function getItem() 62 | { 63 | return $this->item; 64 | } 65 | 66 | /** 67 | * @param Item $item 68 | * @return OrderItemAbstract 69 | */ 70 | public function setItem(Item $item) 71 | { 72 | $this->item = $item; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return OrderItemProvider[] 78 | */ 79 | public function getOrderItemProviders() 80 | { 81 | return $this->orderItemProviders; 82 | } 83 | 84 | /** 85 | * @return mixed 86 | */ 87 | public function getListType() 88 | { 89 | return $this->listType; 90 | } 91 | 92 | /** 93 | * @param mixed $listType 94 | * @return OrderItemAbstract 95 | */ 96 | public function setListType($listType) 97 | { 98 | $this->listType = $listType; 99 | return $this; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Block/GtmCode.php: -------------------------------------------------------------------------------- 1 | _gtmHelper = $gtmHelper; 32 | parent::__construct($context, $data); 33 | } 34 | 35 | /** 36 | * Get Account Id 37 | * 38 | * @return string 39 | */ 40 | public function getAccountId() 41 | { 42 | return $this->_gtmHelper->getAccountId(); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getDataLayerName() 49 | { 50 | return $this->_gtmHelper->getDataLayerName(); 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getEmbeddedAccountId() 57 | { 58 | return $this->_gtmHelper->isMultiContainerEnabled() ? 59 | $this->getAccountId() . $this->_gtmHelper->getMultiContainerCode() : $this->getAccountId(); 60 | } 61 | 62 | /** 63 | * Render tag manager JS 64 | * 65 | * @return string 66 | */ 67 | protected function _toHtml() 68 | { 69 | if (!$this->_gtmHelper->isEnabled()) { 70 | return ''; 71 | } 72 | 73 | return parent::_toHtml(); 74 | } 75 | 76 | /** 77 | * @param null $store_id 78 | * @return bool 79 | */ 80 | public function isAdvancedSettingsEnabled($store_id = null) 81 | { 82 | return $this->_gtmHelper->isAdvancedSettingsEnabled($store_id); 83 | } 84 | 85 | /** 86 | * @param null $store_id 87 | * @return string 88 | */ 89 | public function getAdvancedSettingsIframeCode($store_id = null) 90 | { 91 | return $this->_gtmHelper->getAdvancedSettingsIframeCode($store_id); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /etc/csp_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | http://www.googletagmanager.com/ 7 | https://www.googletagmanager.com/ 8 | 9 | 10 | 11 | 12 | https://www.googletagmanager.com/ 13 | https://www.googletagmanager.com/ 14 | 15 | 16 | 17 | 18 | http://www.googleadservices.com/ 19 | http://www.google-analytics.com/ 20 | https://www.googleadservices.com/ 21 | https://www.google-analytics.com/ 22 | 23 | 24 | 25 | 26 | http://www.googleadservices.com/ 27 | http://www.google-analytics.com/ 28 | https://www.googleadservices.com/ 29 | https://www.google-analytics.com/ 30 | https://www.google.com/ 31 | www.googletagmanager.com 32 | 33 | 34 | 35 | 36 | http://stats.g.doubleclick.net/ 37 | https://stats.g.doubleclick.net/ 38 | http://www.google-analytics.com/ 39 | https://www.google-analytics.com/ 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /DataLayer/ProductData/ProductImpressionAbstract.php: -------------------------------------------------------------------------------- 1 | item; 47 | } 48 | 49 | /** 50 | * @param array $item 51 | * @return ProductImpressionAbstract 52 | */ 53 | public function setItemData(array $item) 54 | { 55 | $this->item = $item; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return Product 61 | */ 62 | public function getProduct() 63 | { 64 | return $this->product; 65 | } 66 | 67 | /** 68 | * @param Product $product 69 | * @return ProductImpressionAbstract 70 | */ 71 | public function setProduct(Product $product) 72 | { 73 | $this->product = $product; 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return array|ProductImpressionAbstract[] 79 | */ 80 | public function getProductImpressionProviders() 81 | { 82 | return $this->productImpressionProviders; 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | public function getListType() 89 | { 90 | return $this->listType; 91 | } 92 | 93 | /** 94 | * @param string $listType 95 | * @return ProductImpressionAbstract 96 | */ 97 | public function setListType(string $listType) 98 | { 99 | $this->listType = $listType; 100 | return $this; 101 | } 102 | 103 | /** 104 | * @return string 105 | */ 106 | public function getItemListName() 107 | { 108 | return $this->itemListName; 109 | } 110 | 111 | /** 112 | * @param string $itemListName 113 | * @return ProductImpressionAbstract 114 | */ 115 | public function setItemListName(string $itemListName) 116 | { 117 | $this->itemListName = $itemListName; 118 | return $this; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /DataLayer/QuoteData/QuoteItemAbstract.php: -------------------------------------------------------------------------------- 1 | itemData; 52 | } 53 | 54 | /** 55 | * @param array $itemData 56 | * @return QuoteItemAbstract 57 | */ 58 | public function setItemData(array $itemData) 59 | { 60 | $this->itemData = $itemData; 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return Item 66 | */ 67 | public function getItem() 68 | { 69 | return $this->item; 70 | } 71 | 72 | /** 73 | * @param Item $item 74 | * @return QuoteItemAbstract 75 | */ 76 | public function setItem(Item $item) 77 | { 78 | $this->item = $item; 79 | return $this; 80 | } 81 | 82 | /** 83 | * @return array|QuoteItemAbstract[] 84 | */ 85 | public function getQuoteItemProviders() 86 | { 87 | return $this->quoteItemProviders; 88 | } 89 | 90 | /** 91 | * @return mixed 92 | */ 93 | public function getListType() 94 | { 95 | return $this->listType; 96 | } 97 | 98 | /** 99 | * @param mixed $listType 100 | * @return QuoteItemAbstract 101 | */ 102 | public function setListType($listType) 103 | { 104 | $this->listType = $listType; 105 | return $this; 106 | } 107 | 108 | /** 109 | * @return mixed 110 | */ 111 | public function getActionType() 112 | { 113 | return $this->actionType; 114 | } 115 | 116 | /** 117 | * @param mixed $actionType 118 | * @return QuoteItemAbstract 119 | */ 120 | public function setActionType($actionType) 121 | { 122 | $this->actionType = $actionType; 123 | return $this; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /view/frontend/templates/js.phtml: -------------------------------------------------------------------------------- 1 | getDataLayerName(); 11 | $accountId = $block->getAccountId(); 12 | $containerCode = $block->getEmbeddedCode() ? "+'{$block->getEmbeddedCode()}'" : ''; 13 | ?> 14 | 15 | 16 | 20 | renderTag('script', [], $scriptString, false) ?> 21 | 22 | isGdprEnabled() && $block->addJsInHead() && !$block->isAdvancedSettingsEnabled()): ?> 23 | 31 | renderTag('script', [], $scriptString, false) ?> 32 | renderTag('script', [], $block->getDataLayerJs(), false) ?> 33 | 34 | 35 | isAdvancedSettingsEnabled()): ?> 36 | renderTag('script', [], $block->getAdvancedSettingsJsCode(), false) ?> 37 | renderTag('script', [], $block->getDataLayerJs(), false) ?> 38 | 39 | 40 | isGdprEnabled() || !$block->addJsInHead()) && !$block->isAdvancedSettingsEnabled()) : ?> 41 | 59 | 60 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Block/DataLayer.php: -------------------------------------------------------------------------------- 1 | _gtmHelper->isEnabled()) { 29 | return ''; 30 | } 31 | 32 | /** @var $blockOnepageOrder Order */ 33 | if ($this->getOrderIds() && $blockOnepageOrder = $this->getChildBlock("magepal_gtm_block_order")) { 34 | $blockOnepageOrder->setOrderIds($this->getOrderIds())->addOrderLayer(); 35 | } 36 | 37 | return parent::_toHtml(); 38 | } 39 | 40 | /** 41 | * Get Account Id 42 | * 43 | * @return string 44 | */ 45 | public function getAccountId() 46 | { 47 | return $this->_gtmHelper->getAccountId(); 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getEmbeddedCode() 54 | { 55 | return $this->_gtmHelper->isMultiContainerEnabled() ? $this->_gtmHelper->getMultiContainerCode() : ''; 56 | } 57 | 58 | /** 59 | * @param null $store_id 60 | * @return int 61 | */ 62 | public function getGdprOption($store_id = null) 63 | { 64 | return $this->_gtmHelper->getGdprOption($store_id); 65 | } 66 | 67 | /** 68 | * @param null $store_id 69 | * @return string 70 | */ 71 | public function getCookieRestrictionName($store_id = null) 72 | { 73 | if ($this->_gtmHelper->getGdprOption($store_id) == GdprOption::USE_COOKIE_RESTRICTION_MODE) { 74 | return Cookie::IS_USER_ALLOWED_SAVE_COOKIE; 75 | } else { 76 | return $this->_gtmHelper->getCookieRestrictionName($store_id) ? 77 | $this->_gtmHelper->getCookieRestrictionName($store_id) : Cookie::IS_USER_ALLOWED_SAVE_COOKIE; 78 | } 79 | } 80 | 81 | /** 82 | * @param null $store_id 83 | * @return int 84 | */ 85 | public function isGdprEnabled($store_id = null) 86 | { 87 | return (int) $this->_gtmHelper->isGdprEnabled($store_id); 88 | } 89 | 90 | /** 91 | * @param null $store_id 92 | * @return int 93 | */ 94 | public function addJsInHead($store_id = null) 95 | { 96 | return (int) $this->_gtmHelper->addJsInHead($store_id); 97 | } 98 | 99 | /** 100 | * @param null $store_id 101 | * @return bool 102 | */ 103 | public function isAdvancedSettingsEnabled($store_id = null) 104 | { 105 | return $this->_gtmHelper->isAdvancedSettingsEnabled($store_id); 106 | } 107 | 108 | /** 109 | * @param null $store_id 110 | * @return string 111 | */ 112 | public function getAdvancedSettingsJsCode($store_id = null) 113 | { 114 | return $this->_gtmHelper->getAdvancedSettingsJsCode($store_id); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Composer/Version.php: -------------------------------------------------------------------------------- 1 | deploymentConfig = $deploymentConfig; 52 | $this->componentRegistrar = $componentRegistrar; 53 | $this->readFactory = $readFactory; 54 | parent::__construct($context, $data); 55 | } 56 | 57 | /** 58 | * Render button 59 | * 60 | * @param AbstractElement $element 61 | * @return string 62 | */ 63 | public function render(AbstractElement $element) 64 | { 65 | // Remove scope label 66 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); 67 | return parent::render($element); 68 | } 69 | 70 | /** 71 | * Return element html 72 | * 73 | * @param AbstractElement $element 74 | * @return string 75 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 76 | */ 77 | protected function _getElementHtml(AbstractElement $element) 78 | { 79 | return 'v' . $this->getVersion(); 80 | } 81 | 82 | /** 83 | * Get Module version number 84 | * 85 | * @return string 86 | */ 87 | public function getVersion() 88 | { 89 | return $this->getComposerVersion($this->getModuleName()); 90 | } 91 | 92 | /** 93 | * Get module composer version 94 | * 95 | * @param $moduleName 96 | * @return string 97 | */ 98 | public function getComposerVersion($moduleName) 99 | { 100 | $path = $this->componentRegistrar->getPath( 101 | ComponentRegistrar::MODULE, 102 | $moduleName 103 | ); 104 | 105 | try { 106 | $directoryRead = $this->readFactory->create($path); 107 | $composerJsonData = $directoryRead->readFile('composer.json'); 108 | 109 | if ($composerJsonData) { 110 | $data = json_decode($composerJsonData); 111 | return !empty($data->version) ? $data->version : __('Unknown'); 112 | } 113 | } catch (Exception $e) { 114 | return 'Unknown'; 115 | } 116 | 117 | return 'Unknown'; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Block/Data/Category.php: -------------------------------------------------------------------------------- 1 | _catalogData = $catalogData; 54 | $this->_coreRegistry = $registry; 55 | parent::__construct($context, $data); 56 | $this->categoryProvider = $categoryProvider; 57 | } 58 | 59 | /** 60 | * Retrieve current category model object 61 | * 62 | * @return \Magento\Catalog\Model\Category 63 | */ 64 | public function getCurrentCategory() 65 | { 66 | if (!$this->hasData('current_category')) { 67 | $this->setData('current_category', $this->_coreRegistry->registry('current_category')); 68 | } 69 | return $this->getData('current_category'); 70 | } 71 | 72 | /** 73 | * Add category data to datalayer 74 | * 75 | * @return $this 76 | */ 77 | protected function _prepareLayout() 78 | { 79 | /** @var $tm DataLayer */ 80 | $tm = $this->getParentBlock(); 81 | 82 | /** @var $category ProductCategory */ 83 | $category = $this->getCurrentCategory(); 84 | 85 | if ($category) { 86 | $categoryData = [ 87 | 'id' => $category->getId(), 88 | 'name' => $category->getName(), 89 | 'path' => $this->getCategoryPath() 90 | ]; 91 | 92 | $categoryData = $this->categoryProvider 93 | ->setCategory($category) 94 | ->setCategoryData($categoryData) 95 | ->getData(); 96 | 97 | $data = [ 98 | 'event' => DataLayerEvent::CATEGORY_PAGE_EVENT, 99 | 'category' => $categoryData 100 | ]; 101 | 102 | $tm->addVariable('list', 'category'); 103 | $tm->addCustomDataLayerByEvent(DataLayerEvent::CATEGORY_PAGE_EVENT, $data); 104 | } 105 | 106 | return $this; 107 | } 108 | 109 | public function getCategoryPath() 110 | { 111 | $titleArray = []; 112 | $breadCrumbs = $this->_catalogData->getBreadcrumbPath(); 113 | 114 | foreach ($breadCrumbs as $breadCrumb) { 115 | $titleArray[] = $breadCrumb['label']; 116 | } 117 | 118 | return implode(" > ", $titleArray); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Block/Data/Product.php: -------------------------------------------------------------------------------- 1 | catalogHelper = $context->getCatalogHelper(); 49 | parent::__construct($context, $data); 50 | $this->productHelper = $productHelper; 51 | $this->productProvider = $productProvider; 52 | } 53 | 54 | /** 55 | * Add product data to datalayer 56 | * 57 | * @return $this 58 | */ 59 | protected function _prepareLayout() 60 | { 61 | /** @var $tm DataLayer */ 62 | $tm = $this->getParentBlock(); 63 | 64 | if ($product = $this->getProduct()) { 65 | $productData = [ 66 | 'id' => $product->getId(), 67 | 'sku' => $product->getSku(), 68 | 'parent_sku' => $product->getData('sku'), 69 | 'product_type' => $product->getTypeId(), 70 | 'name' => $product->getName(), 71 | 'price' => $this->productHelper->getProductPrice($product), 72 | 'attribute_set_id' => $product->getAttributeSetId(), 73 | 'path' => implode(" > ", $this->getBreadCrumbPath()), 74 | 'category' => $this->getProductCategoryName(), 75 | 'image_url' => $this->productHelper->getImageUrl($product) 76 | ]; 77 | 78 | $productData = $this->productProvider->setProduct($product)->setProductData($productData)->getData(); 79 | 80 | $data = [ 81 | 'event' => DataLayerEvent::PRODUCT_PAGE_EVENT, 82 | 'product' => $productData 83 | ]; 84 | 85 | $tm->addVariable('list', 'detail'); 86 | $tm->addCustomDataLayerByEvent(DataLayerEvent::PRODUCT_PAGE_EVENT, $data); 87 | } 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * Get category name from breadcrumb 94 | * 95 | * @return string 96 | */ 97 | protected function getProductCategoryName() 98 | { 99 | $categoryName = ''; 100 | 101 | try { 102 | $categoryArray = $this->getBreadCrumbPath(); 103 | 104 | if (count($categoryArray) > 1) { 105 | end($categoryArray); 106 | $categoryName = prev($categoryArray); 107 | } elseif ($this->getProduct()) { 108 | $category = $this->getProduct()->getCategoryCollection()->addAttributeToSelect('name')->getFirstItem(); 109 | $categoryName = ($category) ? $category->getName() : ''; 110 | } 111 | } catch (Exception $e) { 112 | $categoryName = ''; 113 | } 114 | 115 | return $categoryName; 116 | } 117 | 118 | /** 119 | * Get bread crumb path 120 | * 121 | * @return array 122 | */ 123 | protected function getBreadCrumbPath() 124 | { 125 | $titleArray = []; 126 | $breadCrumbs = $this->catalogHelper->getBreadcrumbPath() ?? []; 127 | 128 | foreach ($breadCrumbs as $breadCrumb) { 129 | $titleArray[] = $breadCrumb['label']; 130 | } 131 | 132 | return $titleArray; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /view/frontend/web/js/datalayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © MagePal LLC. All rights reserved. 3 | * See COPYING.txt for license details. 4 | * https://www.magepal.com | support@magepal.com 5 | */ 6 | 7 | define([ 8 | 'Magento_Customer/js/customer-data', 9 | 'jquery', 10 | 'underscore', 11 | 'mage/cookies' 12 | ], function (customerData, $, _) { 13 | 'use strict'; 14 | 15 | var lastPushedCart = {}; 16 | var lastPushedCustomer = {}; 17 | 18 | //check if object contain keys 19 | function objectKeyExist(object) 20 | { 21 | return _.some(object, function (o) { 22 | return !_.isEmpty(_.pick(o, ['customer', 'cart'])); 23 | }) 24 | } 25 | 26 | //Update datalayer 27 | function updateDataLayer(_gtmDataLayer, _dataObject, _forceUpdate) 28 | { 29 | var customer = {isLoggedIn : false}, 30 | cart = {hasItems: false}; 31 | 32 | if (_gtmDataLayer !== undefined && (!objectKeyExist(_gtmDataLayer) || _forceUpdate)) { 33 | if (_.isObject(_dataObject) && _.has(_dataObject, 'customer')) { 34 | customer = _dataObject.customer; 35 | } 36 | 37 | if (_.isObject(_dataObject) && _.has(_dataObject, 'cart')) { 38 | cart = _dataObject.cart; 39 | } 40 | 41 | if (!_.isEqual(lastPushedCart, cart) || !_.isEqual(lastPushedCustomer, customer)) { 42 | $('body').trigger('mpCustomerSession', [customer, cart, _gtmDataLayer]); 43 | _gtmDataLayer.push({'event': 'mpCustomerSession', 'customer': customer, 'cart': cart}); 44 | 45 | lastPushedCustomer = customer; 46 | lastPushedCart = cart; 47 | } 48 | } 49 | } 50 | 51 | function isTrackingAllowed(config) 52 | { 53 | var allowServices = false, 54 | allowedCookies, 55 | allowedWebsites; 56 | 57 | if (!config.isGdprEnabled || (!config.isGdprEnabled && !config.addJsInHeader)) { 58 | allowServices = true; 59 | } else if (config.isCookieRestrictionModeEnabled && config.gdprOption === 1) { 60 | allowedCookies = $.mage.cookies.get(config.cookieName); 61 | 62 | if (allowedCookies !== null) { 63 | allowedWebsites = JSON.parse(allowedCookies); 64 | 65 | if (allowedWebsites[config.currentWebsite] === 1) { 66 | allowServices = true; 67 | } 68 | } 69 | } else if (config.gdprOption === 2) { 70 | allowServices = $.mage.cookies.get(config.cookieName) !== null; 71 | } else if (config.gdprOption === 3) { 72 | allowServices = $.mage.cookies.get(config.cookieName) === null; 73 | } 74 | 75 | return allowServices; 76 | } 77 | 78 | //load gtm 79 | function initTracking(dataLayerName, accountId, containerCode) 80 | { 81 | $(document).trigger('gtm:beforeInitialize'); 82 | 83 | (function (w, d, s, l, i) { 84 | w[l] = w[l] || []; 85 | w[l].push({ 86 | 'gtm.start': 87 | new Date().getTime(), event: 'gtm.js' 88 | }); 89 | var f = d.getElementsByTagName(s)[0], 90 | j = d.createElement(s), dl = l != dataLayerName ? '&l=' + l : ''; 91 | j.async = true; 92 | j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl + containerCode; 93 | f.parentNode.insertBefore(j, f); 94 | })(window, document, 'script', dataLayerName, accountId); 95 | 96 | $(document).trigger('gtm:afterInitialize'); 97 | } 98 | 99 | function pushData(dataLayerName, dataLayer) 100 | { 101 | if (_.isArray(dataLayer)) { 102 | _.each(dataLayer, function (data) { 103 | window[dataLayerName].push(data); 104 | }); 105 | } 106 | } 107 | 108 | return function (config) { 109 | 110 | window[config.dataLayer] = window[config.dataLayer] || []; 111 | 112 | if (_.has(config, 'accountId') && isTrackingAllowed(config)) { 113 | initTracking(config.dataLayer, config.accountId, config.containerCode); 114 | pushData(config.dataLayer, config.data); 115 | } 116 | 117 | var dataObject = customerData.get('magepal-gtm-jsdatalayer'); 118 | var gtmDataLayer = window[config.dataLayer]; 119 | 120 | 121 | dataObject.subscribe(function (_dataObject) { 122 | updateDataLayer(gtmDataLayer, _dataObject, true); 123 | }, this); 124 | 125 | if (!_.contains(customerData.getExpiredKeys(), 'magepal-gtm-jsdatalayer')) { 126 | updateDataLayer(gtmDataLayer, dataObject(), false); 127 | } 128 | 129 | } 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /Model/Cart.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 68 | $this->dataLayerItemHelper = $dataLayerItemHelper; 69 | $this->_escaper = $escaper; 70 | $this->quoteProvider = $quoteProvider; 71 | $this->quoteItemProvider = $quoteItemProvider; 72 | parent::__construct($data); 73 | } 74 | 75 | /** 76 | * Get cart array 77 | * 78 | * @return array 79 | * @throws LocalizedException 80 | * @throws NoSuchEntityException 81 | */ 82 | public function getCart() 83 | { 84 | $quote = $this->getQuote(); 85 | 86 | $cart = []; 87 | 88 | $cart['hasItems'] = false; 89 | 90 | if ($quote->getItemsCount()) { 91 | $items = []; 92 | foreach ($quote->getAllVisibleItems() as $item) { 93 | $itemData = [ 94 | 'sku' => $item->getSku(), 95 | 'parent_sku' => $item->getProduct() ? $item->getProduct()->getData('sku') : $item->getSku(), 96 | 'name' => $item->getName(), 97 | 'product_type' => $item->getProductType(), 98 | 'price' => $this->dataLayerItemHelper->formatPrice($item->getPrice()), 99 | 'price_incl_tax' => $this->dataLayerItemHelper->formatPrice($item->getPriceInclTax()), 100 | 'discount_amount' => $this->dataLayerItemHelper->formatPrice($item->getDiscountAmount()), 101 | 'tax_amount' => $this->dataLayerItemHelper->formatPrice($item->getTaxAmount()), 102 | 'quantity' => $item->getQty() * 1, 103 | ]; 104 | 105 | if ($variant = $this->dataLayerItemHelper->getItemVariant($item)) { 106 | $itemData['variant'] = $variant; 107 | } 108 | 109 | if (!empty($category = $this->dataLayerItemHelper->getCategories($item))) { 110 | $itemData['categories'] = $category; 111 | $itemData['category'] = $this->dataLayerItemHelper->getFirstCategory($item); 112 | } 113 | 114 | $items[] = $this->quoteItemProvider 115 | ->setItem($item) 116 | ->setItemData($itemData) 117 | ->setActionType(QuoteItemProvider::ACTION_VIEW_CART) 118 | ->setListType(QuoteItemProvider::LIST_TYPE_GENERIC) 119 | ->getData(); 120 | } 121 | 122 | if (count($items) > 0) { 123 | $cart['hasItems'] = true; 124 | $cart['items'] = $items; 125 | } 126 | 127 | $cart['total'] = $this->dataLayerItemHelper->formatPrice($quote->getGrandTotal()); 128 | $cart['itemCount'] = $quote->getItemsCount() * 1; 129 | $cart['cartQty'] = $quote->getItemsQty() * 1; 130 | 131 | //set coupon code 132 | $coupon = $quote->getCouponCode(); 133 | 134 | $cart['hasCoupons'] = $coupon ? true : false; 135 | 136 | if ($coupon) { 137 | $cart['couponCode'] = $coupon; 138 | } 139 | } 140 | 141 | return $this->quoteProvider->setQuote($this->getQuote())->setTransactionData($cart)->getData(); 142 | } 143 | 144 | /** 145 | * Get active quote 146 | * 147 | * @return Quote 148 | * @throws LocalizedException 149 | * @throws NoSuchEntityException 150 | */ 151 | public function getQuote() 152 | { 153 | return $this->checkoutSession->getQuote(); 154 | } 155 | 156 | /** 157 | * Escape quotes in java scripts 158 | * 159 | * @param string|array $data 160 | * @param string $quote 161 | * @return string|array 162 | */ 163 | public function escapeJsQuote($data, $quote = '\'') 164 | { 165 | return $this->_escaper->escapeJsQuote($data, $quote); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Helper/DataLayerItem.php: -------------------------------------------------------------------------------- 1 | scopeConfig->isSetFlag( 34 | 'googletagmanager/general/item_variant_layer', 35 | ScopeInterface::SCOPE_STORE, 36 | $store_id 37 | ); 38 | } 39 | /** 40 | * @param null $store_id 41 | * @return string 42 | */ 43 | public function getItemVariantFormat($store_id = null) 44 | { 45 | $formatId = $this->scopeConfig->getValue( 46 | 'googletagmanager/general/item_variant_format', 47 | ScopeInterface::SCOPE_STORE, 48 | $store_id 49 | ); 50 | 51 | if (in_array($formatId, ItemVariantFormat::FORMAT)) { 52 | return $formatId; 53 | } else { 54 | return ItemVariantFormat::DEFAULT_FORMAT; 55 | } 56 | } 57 | 58 | /** 59 | * @param OrderItem $item | QuoteItem $item 60 | * @return array 61 | * @throws \Magento\Framework\Exception\LocalizedException 62 | */ 63 | public function getCategories($item) 64 | { 65 | if (!$this->isCategoryLayerEnabled() || !$item->getProduct()) { 66 | return []; 67 | } 68 | 69 | if (!array_key_exists($item->getItemId(), $this->categories)) { 70 | $collection = $item->getProduct()->getCategoryCollection()->addAttributeToSelect('name'); 71 | $categories = []; 72 | 73 | if ($collection->getSize()) { 74 | foreach ($collection as $category) { 75 | if (!in_array($category->getName(), $categories)) { 76 | $categories[] = $category->getName(); 77 | } 78 | } 79 | } 80 | 81 | $this->categories[$item->getItemId()] = $categories; 82 | } 83 | 84 | return $this->categories[$item->getItemId()]; 85 | } 86 | 87 | /** 88 | * @param OrderItem|QuoteItem $item 89 | * @return string 90 | */ 91 | public function getFirstCategory($item) 92 | { 93 | if ($item instanceof OrderItem || $item instanceof QuoteItem) { 94 | $categories = $this->getCategories($item); 95 | 96 | if (count($categories)) { 97 | return $categories[0]; 98 | } 99 | } 100 | 101 | return ''; 102 | } 103 | 104 | /** 105 | * @param array $options 106 | * @return array 107 | */ 108 | public function getItemOptions($options) 109 | { 110 | $result = []; 111 | 112 | if ($options && is_array($options)) { 113 | if (isset($options['options'])) { 114 | $result = array_merge($result, $options['options']); 115 | } 116 | if (isset($options['additional_options'])) { 117 | $result = array_merge($result, $options['additional_options']); 118 | } 119 | if (isset($options['attributes_info'])) { 120 | $result = array_merge($result, $options['attributes_info']); 121 | } 122 | } 123 | 124 | return (array) $result; 125 | } 126 | 127 | /** 128 | * @param $item 129 | * @return mixed|string 130 | */ 131 | public function getItemVariant($item) 132 | { 133 | if (!$this->isItemVariantLayerEnabled()) { 134 | return ''; 135 | } 136 | 137 | if (!array_key_exists($item->getItemId(), $this->variants)) { 138 | if ($item instanceof OrderItem) { 139 | $productOptions = $this->getItemOptions($item->getProductOptions()); 140 | } elseif ($item instanceof QuoteItem 141 | && $item->getProduct() 142 | && $item->getProduct()->getCustomOption('simple_product') 143 | && $item->getProduct()->getCustomOption('simple_product')->getProduct() 144 | ) { 145 | $itemOptionInstance = $item->getProduct()->getTypeInstance()->getOrderOptions($item->getProduct()); 146 | $productOptions = $this->getItemOptions($itemOptionInstance); 147 | } else { 148 | $productOptions = ''; 149 | } 150 | 151 | $this->variants[$item->getItemId()] = $this->getItemVariantOption($productOptions); 152 | } 153 | 154 | return $this->variants[$item->getItemId()]; 155 | } 156 | 157 | /** 158 | * @param $productOptions 159 | * @return string 160 | */ 161 | public function getItemVariantOption($productOptions) 162 | { 163 | $result = []; 164 | $format = ItemVariantFormat::FORMAT; 165 | $title = ''; 166 | 167 | if (is_array($productOptions)) { 168 | foreach ($productOptions as $productOption) { 169 | $template = []; 170 | 171 | if (is_array($productOption) && array_key_exists('value', $productOption)) { 172 | $template['{{value}}'] = $productOption['value']; 173 | } 174 | 175 | if (is_array($productOption) && array_key_exists('label', $productOption)) { 176 | $template['{{label}}'] = $productOption['label']; 177 | } 178 | 179 | if (!empty($template)) { 180 | $result[] = str_replace( 181 | array_keys($template), 182 | array_values($template), 183 | $format[$this->getItemVariantFormat()] 184 | ); 185 | } 186 | } 187 | } 188 | 189 | if (!empty($result)) { 190 | $title = implode(' / ', $result); 191 | } 192 | 193 | return $title; 194 | } 195 | 196 | /** 197 | * @param $item 198 | * @param $qty 199 | * @return array 200 | */ 201 | public function getProductObject($item, $qty) 202 | { 203 | $product = [ 204 | 'name' => $item->getName(), 205 | 'id' => $item->getSku(), 206 | 'price' => $this->formatPrice($item->getPrice()), 207 | 'quantity' => $qty * 1, 208 | 'parent_sku' => $item->getProduct() ? $item->getProduct()->getData('sku') : $item->getSku() 209 | ]; 210 | 211 | if ($item->getItemId()) { 212 | $product['item_id'] = $item->getItemId(); 213 | } 214 | 215 | if (!$item->getPrice() && $item->getProduct()) { 216 | $product['price'] = $this->formatPrice($item->getProduct()->getPrice()); 217 | } 218 | 219 | if ($variant = $this->getItemVariant($item)) { 220 | $product['variant'] = $variant; 221 | } 222 | 223 | if (!empty($category = $this->getFirstCategory($item))) { 224 | $product['category'] = $category; 225 | } 226 | 227 | return $product; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Block/DataLayerAbstract.php: -------------------------------------------------------------------------------- 1 | _gtmHelper = $gtmHelper; 71 | parent::__construct($context, $data); 72 | $this->_init(); 73 | } 74 | 75 | /** 76 | * @return $this 77 | * @throws NoSuchEntityException 78 | */ 79 | protected function _init() 80 | { 81 | $this->addVariable('ecommerce', ['currencyCode' => $this->getStoreCurrencyCode()]); 82 | $this->addVariable('pageType', $this->_request->getFullActionName()); 83 | $this->addVariable('list', 'other'); 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Return data layer json 90 | * 91 | * @return array 92 | */ 93 | public function getDataLayer() 94 | { 95 | $this->_eventManager->dispatch( 96 | $this->dataLayerEventName, 97 | ['dataLayer' => $this] 98 | ); 99 | 100 | return $this->processDataLayer(); 101 | } 102 | 103 | /** 104 | * @return array 105 | */ 106 | public function processDataLayer() 107 | { 108 | $result = []; 109 | 110 | if (!empty($this->getVariables())) { 111 | $result[] = $this->getVariables(); 112 | } 113 | 114 | if (!empty($this->_additionalVariables) && is_array($this->_additionalVariables)) { 115 | foreach ($this->_additionalVariables as $custom) { 116 | $result[] = $custom; 117 | } 118 | } 119 | 120 | if (!empty($this->customVariables)) { 121 | ksort($this->customVariables); 122 | foreach ($this->customVariables as $priorityVariable) { 123 | foreach ($priorityVariable as $data) { 124 | $result[] = $data; 125 | } 126 | } 127 | } 128 | 129 | return $result; 130 | } 131 | 132 | /** 133 | * @return string 134 | */ 135 | public function getDataLayerJson() 136 | { 137 | return json_encode($this->getDataLayer()); 138 | } 139 | 140 | /** 141 | * @return string 142 | */ 143 | public function getDataLayerJs() 144 | { 145 | $result = []; 146 | 147 | foreach ($this->getDataLayer() as $data) { 148 | $result[] = sprintf("window.%s.push(%s);\n", $this->getDataLayerName(), json_encode($data)); 149 | } 150 | 151 | return implode("\n", $result); 152 | } 153 | 154 | /** 155 | * Add Variables 156 | * @param string $name 157 | * @param string|array $value 158 | * @return $this 159 | */ 160 | public function addVariable($name, $value) 161 | { 162 | if (!empty($name)) { 163 | $this->_variables[$name] = $value; 164 | } 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Return Data Layer Variables 171 | * 172 | * @return array 173 | */ 174 | public function getVariables() 175 | { 176 | return $this->_variables; 177 | } 178 | 179 | /** 180 | * Add variable to the custom push data layer 181 | * 182 | * @deprecated - use addCustomDataLayer and addCustomDataLayerByEvent 183 | * @param $name 184 | * @param null $value 185 | * @return $this 186 | */ 187 | public function addAdditionalVariable($name, $value = null) 188 | { 189 | if (is_array($name)) { 190 | $this->_additionalVariables[] = $name; 191 | } else { 192 | $this->_additionalVariables[] = [$name => $value]; 193 | } 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Add variable to the custom push data layer 200 | * 201 | * @param array $data 202 | * @param int $priority 203 | * @param null $group 204 | * @return $this 205 | */ 206 | public function addCustomDataLayer($data, $priority = 0, $group = null) 207 | { 208 | $priority = (int) $priority; 209 | 210 | if (is_array($data) && empty($group)) { 211 | $this->customVariables[$priority][] = $data; 212 | } elseif (is_array($data) && !empty($group)) { 213 | if (array_key_exists($priority, $this->customVariables) 214 | && array_key_exists($group, $this->customVariables[$priority]) 215 | ) { 216 | $this->customVariables[$priority][$group] = array_merge( 217 | $this->customVariables[$priority][$group], 218 | $data 219 | ); 220 | } else { 221 | $this->customVariables[$priority][$group] = $data; 222 | } 223 | } 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * @param $event 230 | * @param $data 231 | * @param int $priority 232 | * @return $this 233 | */ 234 | public function addCustomDataLayerByEvent($event, $data, $priority = 20) 235 | { 236 | if (!empty($event)) { 237 | $data['event'] = $event; 238 | $this->addCustomDataLayer($data, $priority, $event); 239 | } 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * Format Price 246 | * 247 | * @param $price 248 | * @return float 249 | */ 250 | public function formatPrice($price) 251 | { 252 | return $this->_gtmHelper->formatPrice($price); 253 | } 254 | 255 | /** 256 | * @return string 257 | */ 258 | public function getDataLayerName() 259 | { 260 | if (!$this->getData('data_layer_name')) { 261 | $this->setData('data_layer_name', $this->_gtmHelper->getDataLayerName()); 262 | } 263 | return $this->getData('data_layer_name'); 264 | } 265 | 266 | /** 267 | * @return string 268 | * @throws NoSuchEntityException 269 | */ 270 | public function getStoreCurrencyCode() 271 | { 272 | return $this->_storeManager->getStore()->getCurrentCurrency()->getCode(); 273 | } 274 | 275 | /** 276 | * Return cookie restriction mode value. 277 | * 278 | * @return int 279 | */ 280 | public function isCookieRestrictionModeEnabled() 281 | { 282 | return (int) $this->_gtmHelper->isCookieRestrictionModeEnabled(); 283 | } 284 | /** 285 | * Return current website id. 286 | * 287 | * @return int 288 | * @throws LocalizedException 289 | */ 290 | public function getCurrentWebsiteId() 291 | { 292 | return $this->_storeManager->getWebsite()->getId(); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /Helper/Data.php: -------------------------------------------------------------------------------- 1 | scopeConfig->getValue( 52 | self::XML_PATH_ACCOUNT, 53 | ScopeInterface::SCOPE_STORE, 54 | $store_id 55 | ); 56 | 57 | $active = $this->scopeConfig->isSetFlag( 58 | self::XML_PATH_ACTIVE, 59 | ScopeInterface::SCOPE_STORE, 60 | $store_id 61 | ); 62 | 63 | return $accountId && $active; 64 | } 65 | 66 | /** 67 | * @param null $store_id 68 | * @return string 69 | */ 70 | public function getCookieRestrictionName($store_id = null) 71 | { 72 | return $this->scopeConfig->getValue( 73 | 'googletagmanager/gdpr/restriction_cookie_name', 74 | ScopeInterface::SCOPE_STORE, 75 | $store_id 76 | ); 77 | } 78 | 79 | /** 80 | * @param null $store_id 81 | * @return bool 82 | */ 83 | public function isGdprEnabled($store_id = null) 84 | { 85 | return $this->scopeConfig->isSetFlag( 86 | 'googletagmanager/gdpr/enabled', 87 | ScopeInterface::SCOPE_STORE, 88 | $store_id 89 | ); 90 | } 91 | 92 | /** 93 | * @param null $store_id 94 | * @return int 95 | */ 96 | public function getGdprOption($store_id = null) 97 | { 98 | return (int) $this->scopeConfig->getValue( 99 | 'googletagmanager/gdpr/option', 100 | ScopeInterface::SCOPE_STORE, 101 | $store_id 102 | ); 103 | } 104 | 105 | /** 106 | * @param null $store_id 107 | * @return int 108 | */ 109 | public function addJsInHead($store_id = null) 110 | { 111 | return (int) $this->scopeConfig->isSetFlag( 112 | 'googletagmanager/gdpr/add_js_in_header', 113 | ScopeInterface::SCOPE_STORE, 114 | $store_id 115 | ); 116 | } 117 | 118 | /** 119 | * @param null $store_id 120 | * @return int 121 | */ 122 | public function isMultiContainerEnabled($store_id = null) 123 | { 124 | return (int) $this->scopeConfig->isSetFlag( 125 | 'googletagmanager/gtm_container/enabled', 126 | ScopeInterface::SCOPE_STORE, 127 | $store_id 128 | ); 129 | } 130 | 131 | /** 132 | * @param null $store_id 133 | * @return string 134 | */ 135 | public function getMultiContainerCode($store_id = null) 136 | { 137 | return trim($this->scopeConfig->getValue( 138 | 'googletagmanager/gtm_container/code', 139 | ScopeInterface::SCOPE_STORE, 140 | $store_id 141 | )); 142 | } 143 | 144 | /** 145 | * Check if cookie restriction mode is enabled for this store 146 | * Fix issue in 2.1.9 147 | * @return bool 148 | */ 149 | public function isCookieRestrictionModeEnabled() 150 | { 151 | return $this->scopeConfig->getValue( 152 | self::XML_PATH_COOKIE_RESTRICTION, 153 | ScopeInterface::SCOPE_STORE 154 | ); 155 | } 156 | 157 | /** 158 | * Get Tag Manager Account ID 159 | * 160 | * @param null $store_id 161 | * @return null | string 162 | */ 163 | public function getAccountId($store_id = null) 164 | { 165 | return $this->scopeConfig->getValue( 166 | self::XML_PATH_ACCOUNT, 167 | ScopeInterface::SCOPE_STORE, 168 | $store_id 169 | ); 170 | } 171 | 172 | /** 173 | * Format Price 174 | * 175 | * @param $price 176 | * @return float 177 | */ 178 | public function formatPrice($price) 179 | { 180 | return (float)sprintf('%.2F', $price); 181 | } 182 | 183 | /** 184 | * @param null $store_id 185 | * @return string 186 | */ 187 | public function getDataLayerName($store_id = null) 188 | { 189 | if (!$this->_dataLayerName) { 190 | $this->_dataLayerName = $this->scopeConfig->getValue( 191 | self::XML_PATH_DATALAYER_NAME, 192 | ScopeInterface::SCOPE_STORE, 193 | $store_id 194 | ); 195 | } 196 | return $this->_dataLayerName; 197 | } 198 | 199 | /** 200 | * @param $name 201 | * @return $this 202 | */ 203 | public function setDataLayerName($name) 204 | { 205 | $this->_dataLayerName = $name; 206 | return $this; 207 | } 208 | 209 | /** 210 | * @param null $store_id 211 | * @return bool 212 | */ 213 | public function isCategoryLayerEnabled($store_id = null) 214 | { 215 | return $this->scopeConfig->isSetFlag( 216 | 'googletagmanager/general/category_layer', 217 | ScopeInterface::SCOPE_STORE, 218 | $store_id 219 | ); 220 | } 221 | 222 | /** 223 | * @param null $store_id 224 | * @return bool 225 | */ 226 | public function isAdvancedSettingsEnabled($store_id = null) 227 | { 228 | return $this->scopeConfig->isSetFlag( 229 | 'googletagmanager/advanced_settings/enabled', 230 | ScopeInterface::SCOPE_STORE, 231 | $store_id 232 | ); 233 | } 234 | 235 | /** 236 | * @param null $store_id 237 | * @return string 238 | */ 239 | public function getAdvancedSettingsJsCode($store_id = null) 240 | { 241 | return $this->scopeConfig->getValue( 242 | 'googletagmanager/advanced_settings/js_code', 243 | ScopeInterface::SCOPE_STORE, 244 | $store_id 245 | ); 246 | } 247 | 248 | /** 249 | * @param null $store_id 250 | * @return string 251 | */ 252 | public function getAdvancedSettingsIframeCode($store_id = null) 253 | { 254 | return $this->scopeConfig->getValue( 255 | 'googletagmanager/advanced_settings/iframe_code', 256 | ScopeInterface::SCOPE_STORE, 257 | $store_id 258 | ); 259 | } 260 | 261 | /** 262 | * @param ProductInterface $product 263 | * @param array $viewItem 264 | */ 265 | public function addCategoryElements($product, &$viewItem) 266 | { 267 | if (!$this->isCategoryLayerEnabled() || !$product) { 268 | return; 269 | } 270 | 271 | $categoryList = []; 272 | $index = 1; 273 | $categoryCollection = $product->getCategoryCollection(); 274 | $categories = $categoryCollection->addAttributeToSelect('name'); 275 | 276 | if (array_key_exists('item_category', $viewItem)) { 277 | $index = 2; 278 | $categoryList[] = $viewItem['item_category']; 279 | } 280 | 281 | foreach ($categories as $category) { 282 | if (!in_array($category->getName(), $categoryList)) { 283 | $categoryList[] = $category->getName(); 284 | 285 | if ($index == 1) { 286 | $viewItem['item_category'] = $category->getName(); 287 | $index++; 288 | } else { 289 | $viewItem['item_category' . $index] = $category->getName(); 290 | $index++; 291 | } 292 | 293 | if ($index >= 5) { 294 | break; 295 | } 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * @param $product 302 | * @return float 303 | */ 304 | public function getProductPrice($product) 305 | { 306 | $price = 0; 307 | 308 | /** @var $product ProductInterface */ 309 | if ($product) { 310 | $price = $product 311 | ->getPriceInfo() 312 | ->getPrice(FinalPrice::PRICE_CODE) 313 | ->getAmount() 314 | ->getBaseAmount() ?: 0; 315 | } 316 | 317 | if (!$price) { 318 | if ($product->getTypeId() == Type::TYPE_SIMPLE) { 319 | $price = $product->getPrice(); 320 | } else { 321 | $price = $product->getFinalPrice(); 322 | } 323 | } 324 | 325 | return $this->formatPrice($price); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /Model/Order.php: -------------------------------------------------------------------------------- 1 | gtmHelper = $gtmHelper; 87 | $this->_salesOrderCollection = $salesOrderCollection; 88 | $this->_escaper = $escaper; 89 | $this->orderProvider = $orderProvider; 90 | $this->orderItemProvider = $orderItemProvider; 91 | $this->dataLayerItemHelper = $dataLayerItemHelper; 92 | } 93 | 94 | /** 95 | * Render information about specified orders and their items 96 | * 97 | * @return array|bool 98 | * @throws NoSuchEntityException 99 | */ 100 | public function getOrderLayer() 101 | { 102 | $collection = $this->getOrderCollection(); 103 | 104 | if (!$collection) { 105 | return false; 106 | } 107 | 108 | $result = []; 109 | 110 | /* @var SalesOrder $order */ 111 | 112 | foreach ($collection as $order) { 113 | $transaction = $this->getTransactionDetail($order); 114 | $data = $this->orderProvider->setOrder($order)->setTransactionData($transaction)->getData(); 115 | $result[] = $data; 116 | 117 | // retain backward comparability with gtm.orderComplete event 118 | if ($transaction['event'] !== DataLayerEvent::PURCHASE_EVENT) { 119 | $data['event'] = DataLayerEvent::PURCHASE_EVENT; 120 | $result[] = $data; 121 | } 122 | } 123 | 124 | return $result; 125 | } 126 | 127 | public function getTransactionDetail($order) 128 | { 129 | return [ 130 | 'event' => DataLayerEvent::GTM_ORDER_COMPLETE_EVENT, 131 | 'transactionId' => $order->getIncrementId(), 132 | 'transactionAffiliation' => $this->escapeReturn($order->getStoreName()), 133 | 'transactionTotal' => $this->gtmHelper->formatPrice($order->getBaseGrandTotal()), 134 | 'transactionSubTotal' => $this->gtmHelper->formatPrice($order->getBaseSubtotal()), 135 | 'transactionShipping' => $this->gtmHelper->formatPrice($order->getBaseShippingAmount()), 136 | 'transactionTax' => $this->gtmHelper->formatPrice($order->getTaxAmount()), 137 | 'transactionCouponCode' => $order->getCouponCode() ? $order->getCouponCode() : '', 138 | 'transactionDiscount' => $this->gtmHelper->formatPrice($order->getDiscountAmount()), 139 | 'transactionProducts' => $this->getItemTransactionDetail($order), 140 | 'order' => $this->getOrderDataLayer($order) 141 | ]; 142 | } 143 | 144 | public function getItemTransactionDetail($order) 145 | { 146 | $products = []; 147 | /* @var Item $item */ 148 | foreach ($order->getAllVisibleItems() as $item) { 149 | $product = [ 150 | 'sku' => $item->getSku(), 151 | 'name' => $item->getName(), 152 | 'price' => $this->gtmHelper->formatPrice($item->getBasePrice()), 153 | 'quantity' => $item->getQtyOrdered() * 1 154 | ]; 155 | 156 | if ($category = $this->dataLayerItemHelper->getFirstCategory($item)) { 157 | $product['category'] = $category; 158 | } 159 | 160 | $products[] = $this->orderItemProvider 161 | ->setItem($item) 162 | ->setItemData($product) 163 | ->setListType(OrderItemProvider::LIST_TYPE_GOOGLE) 164 | ->getData(); 165 | } 166 | 167 | return $products; 168 | } 169 | 170 | /** 171 | * Get order collection 172 | * 173 | * @return bool|Collection|null 174 | */ 175 | public function getOrderCollection() 176 | { 177 | $orderIds = $this->getOrderIds(); 178 | if (empty($orderIds) || !is_array($orderIds)) { 179 | return false; 180 | } 181 | 182 | if (!$this->_orderCollection) { 183 | $this->_orderCollection = $this->_salesOrderCollection->create(); 184 | $this->_orderCollection->addFieldToFilter('entity_id', ['in' => $orderIds]); 185 | } 186 | 187 | return $this->_orderCollection; 188 | } 189 | 190 | /** 191 | * Escape quotes in java scripts 192 | * 193 | * @param string|array $data 194 | * @param string $quote 195 | * @return string|array 196 | */ 197 | public function escapeJsQuote($data, $quote = '\'') 198 | { 199 | return $this->_escaper->escapeJsQuote($this->escapeReturn($data), $quote); 200 | } 201 | 202 | /** 203 | * @param $data 204 | * @return string 205 | */ 206 | public function escapeReturn($data) 207 | { 208 | return trim(str_replace(["\r\n", "\r", "\n"], ' ', $data)); 209 | } 210 | 211 | /** 212 | * @param SalesOrder $order 213 | * @return array 214 | * @throws LocalizedException 215 | */ 216 | public function getOrderDataLayer(SalesOrder $order) 217 | { 218 | /* @var SalesOrder $order */ 219 | /* @var Item $item */ 220 | $products = []; 221 | foreach ($order->getAllVisibleItems() as $item) { 222 | $product = [ 223 | 'sku' => $item->getSku(), 224 | 'id' => $item->getSku(), 225 | 'parent_sku' => $item->getProduct() ? $item->getProduct()->getData('sku') : $item->getSku(), 226 | 'name' => $item->getProductOptionByCode('simple_name') ?: $item->getName(), 227 | 'parent_name' => $item->getName(), 228 | 'price' => $this->gtmHelper->formatPrice($item->getBasePrice()), 229 | 'price_incl_tax' => $this->dataLayerItemHelper->formatPrice($item->getPriceInclTax()), 230 | 'quantity' => $item->getQtyOrdered() * 1, 231 | 'subtotal' => $this->gtmHelper->formatPrice($item->getBaseRowTotal()), 232 | 'product_type' => $item->getProductType(), 233 | 'product_id' => $item->getProductId(), 234 | 'discount_amount' => $this->gtmHelper->formatPrice($item->getDiscountAmount()), 235 | 'discount_percent' => $this->gtmHelper->formatPrice($item->getDiscountPercent()), 236 | 'tax_amount' => $this->gtmHelper->formatPrice($item->getTaxAmount()), 237 | 'is_virtual' => (bool)$item->getIsVirtual(), 238 | ]; 239 | 240 | if ($variant = $this->dataLayerItemHelper->getItemVariant($item)) { 241 | $product['variant'] = $variant; 242 | } 243 | 244 | if ($categories = $this->dataLayerItemHelper->getCategories($item)) { 245 | $product['categories'] = $categories; 246 | } 247 | 248 | $products[] = $this->orderItemProvider 249 | ->setItem($item) 250 | ->setItemData($product) 251 | ->setListType(OrderItemProvider::LIST_TYPE_GENERIC) 252 | ->getData(); 253 | } 254 | 255 | return [ 256 | 'order_id' => $order->getIncrementId(), 257 | 'store_name' => $this->escapeReturn($order->getStoreName()), 258 | 'total' => $this->gtmHelper->formatPrice($order->getBaseGrandTotal()), 259 | 'subtotal' => $this->gtmHelper->formatPrice($order->getBaseSubtotal()), 260 | 'shipping' => $this->gtmHelper->formatPrice($order->getBaseShippingAmount()), 261 | 'tax' => $this->gtmHelper->formatPrice($order->getTaxAmount()), 262 | 'coupon_code' => $order->getCouponCode() ?: '' , 263 | 'coupon_name' => $order->getDiscountDescription() ?: '', 264 | 'discount' => $this->gtmHelper->formatPrice($order->getDiscountAmount()), 265 | 'payment_method' => $this->getPaymentMethod($order), 266 | 'shipping_method' => ['title' => $order->getShippingDescription(), 'code' => $order->getShippingMethod()], 267 | 'is_virtual' => (bool)$order->getIsVirtual(), 268 | 'is_guest_checkout' => (bool)$order->getCustomerIsGuest(), 269 | 'items' => $products 270 | ]; 271 | } 272 | 273 | /** 274 | * @param $order 275 | * @return array 276 | */ 277 | public function getPaymentMethod(SalesOrder $order) 278 | { 279 | $method = [ 280 | 'title' => '', 281 | 'code' => '' 282 | ]; 283 | 284 | try { 285 | /** @var Payment $payment */ 286 | $payment = $order->getPayment(); 287 | 288 | if (!$payment) { 289 | return $method; 290 | } 291 | 292 | $methodInstance = $payment->getMethodInstance(); 293 | $method = [ 294 | 'title' => $methodInstance->getTitle(), 295 | 'code' => $methodInstance->getCode() 296 | ]; 297 | } catch (Exception $e) { 298 | /** return empty method */ 299 | } 300 | 301 | return $method; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | magepal 17 | MagePal_GoogleTagManager::googletagmanager 18 | 19 | 20 | 1 21 | 22 | 24 | Copyright © 2025 MagePal, LLC 25 | Support 26 | Documentation 27 | Latest Version 28 | About Extension 29 | 30 |
31 | Upgrade to the next generation of tracking from Google. Google Analytics 4 comes with a bunch of new features 32 | that make it very similar, yet more powerful than Enhanced Ecommerce. 33 | Gain access to GA4 new approach to privacy-first tracking, channel measurement, and AI based predictive data with our new Google Analytics 4 extension. 34 |
35 |
36 | Adding Facebook Pixel, Bing UET, or other third-party JavaScript to your site using Google Tag Manager? 37 | Learn how simple and easy it is to integrate any third party service with our new DataLayer extension. 38 |
39 |
40 | Want to learn more about your customers? Gain valuable insight on your customers shopping behavior, sales performance and more. 41 | Upgrade to our new Enhanced E-commerce today, 42 | to take full advantage of Google Analytics most valuable features and reports. 43 |
44 |
45 | ]]> 46 |
47 | 48 | 49 | MagePal\GoogleTagManager\Block\Adminhtml\System\Config\Form\Composer\Version 50 | 51 |
52 | 53 | 54 | 55 | 56 | Magento\Config\Model\Config\Source\Yesno 57 | 58 | 59 | 60 | e.g GTM-XXXXXX 61 | required-entry validate-no-html-tags 62 | 63 | 1 64 | 65 | 66 | 67 | 68 | Magento\Config\Model\Config\Source\Yesno 69 | 70 | 1 71 | 72 | 73 | 74 | 75 | MagePal\GoogleTagManager\Block\Adminhtml\System\Config\Form\Field\ItemVariantFormat 76 | 77 | 1 78 | 1 79 | 80 | 81 | 82 | 83 | 84 | Magento\Config\Model\Config\Source\Yesno 85 | 86 | 1 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | Magento\Config\Model\Config\Source\Yesno 95 | 96 | 97 | 98 | Magento\Config\Model\Config\Source\Yesno 99 | 100 | 0 101 | 102 | Note: Data Layer events firing sequence may change. 103 | 104 | 105 | 106 | MagePal\GoogleTagManager\Model\Config\Source\GdprOption 107 | 108 | 1 109 | 110 | 111 | 112 | 113 | 114 | 2,3 115 | 1 116 | 117 | required-entry alphanumeric 118 | 119 | 120 | 121 | To enable disable tracking goto Stores > Configuration > General > Web > Default Cookie Settings > Cookie Restriction Mode 122 | 123 | 124 | 1 125 | 1 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | Magento\Config\Model\Config\Source\Yesno 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | required-entry 143 | 144 | 1 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | required-entry 154 | 155 | 1 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | Magento\Config\Model\Config\Source\Yesno 164 | 165 | 166 | 167 | 168 | 169 | 170 | required-entry 171 | 172 | 1 173 | 174 | 175 | 176 |
177 |
178 |
179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MagePal Adobe Commerce Extensions 2 | 3 | # Google Tag Manager for Magento 2 / Adobe Commerce with Advance Data Layer 4 | 5 | [![Total Downloads](https://poser.okvpn.org/magepal/magento2-googletagmanager/downloads)](https://packagist.org/packages/magepal/magento2-googletagmanager) 6 | [![Latest Stable Version](https://poser.okvpn.org/magepal/magento2-googletagmanager/v/stable)](https://packagist.org/packages/magepal/magento2-googletagmanager) 7 | 8 | #### Whether you are a small Magento retailer or an Adobe Commerce Enterprise customer, our suite of Google Tag Manager extensions will help you integrate the most challenging GTM projects within days, instead of spending weeks or months creating custom solutions. 9 | For Magento 2.0.x, 2.1.x, 2.2.x, 2.3.x and 2.4.x 10 | 11 | Magento Enhanced Ecommerce for Google Tag Manager 12 | 13 | ### What is Google Tag Manager 14 | Google Tag Manager (GTM) is a user-friendly, powerful and essential integration for every Magento store. It simplifies 15 | the process of adding, editing and managing third-party JavaScript tags and other snippets of code on your Magento site. 16 | With GTM, you can quickly and easily add Facebook tags, AdWords Conversion Tracking, Re-marketing, Bing UET, SnapChat, 17 | DoubleClick code, Google Analytics, and many more in a breeze without the need for a developer to make changes to your 18 | Magento code providing the data is available to Google Tag Manager. 19 | 20 | Google Tag Manager makes running your digital marketing campaigns much easier when calibrating with multiple department and Ad agencies by making available the right set of tools so that everyone can get their job done quickly without relying on developers. 21 | 22 | Without having the all data you need at your fingertips your integration will become a difficult, time-consuming and messy since each developer will only focus on the current task at hand instead of focusing on writing reusable components for future integration. 23 | 24 | Our extension provides a vast array of over 60 preconfigure data layer elements to make integrating your Magento store with any other third-party service a breeze using Google Tag Manager. 25 | Extracting, customizing and adding your own custom data from your Magento store to Google Tag Manager is as easy as 10 lines of code using our easy to customize APIs. 26 | 27 | >:warning: Google Tag Manager 2.7.0 has some breaking changes to Enhanced Ecommerce. Please download the latest version of Enhanced Ecommerce 1.7.0 or greater from www.magepal.com account. 28 | 29 | 30 | ### Why use our Google Tag Manager extension? 31 | Adding Google Tag Manager code snippet to the header section of your Magento / Adobe Commerce store may seem like the ideal, 32 | and most efficient way to add GTM to your site. But this will not be sufficient and limit your ability to take 33 | full advantage of GTM when integrating third-parties tracking codes that require data from your Magento stores, 34 | such as product name, price, items added to cart, order items, total, shipping amount or any other data. Our extension 35 | provides hundreds of data elements and events to accomplish any integration and provides the building block to make 36 | your next integration a success. With a few lines of code, you can quickly extend our extension to accomplish your 37 | most challenging integration. Google Tag Manager is only as powerful as the data layer powering it. 38 | Learn more about [customizing Google Tag Manger](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#api). 39 | 40 | ### Google Analytics 4 41 | Upgrade to the next generation of tracking from Google. [Google Analytics 4](https://www.magepal.com/google-analytics-4-for-google-tag-manager.html?utm_source=ga4%20for%20Google%20Tag%20Manager&utm_medium=github) comes with a bunch of key features that make it very different and more powerful than Enhanced Ecommerce. 42 | Gain access to GA4 new approach to privacy-first tracking, channel measurement, and AI based predictive data with MagePal Google Analytics 4 Extension. 43 | 44 | ### Google Analytics Enhanced E-commerce 45 | Want to track more? Upgrade to our new [Enhanced E-commerce for Google Tag Manager](https://www.magepal.com/enhanced-ecommerce-for-google-tag-manager.html?utm_source=Enhanced%20Ecommerce%20for%20Google%20Tag%20Manager&utm_medium=github) to take full advantage of Google Analytics most powerful e-commerce features. 46 | Gain valuable insight and increase your conversion rate by leveraging Google Enhanced Ecommerce to better understand your user actions and behaviors. 47 | 48 | Learn more about our [Google Enhanced Ecommerce](https://www.magepal.com/enhanced-ecommerce-for-google-tag-manager.html?utm_source=Enhanced%20Ecommerce%20for%20Google%20Tag%20Manager&utm_medium=github) extension today. A small increase in your store’s conversion rate can make a giant impact on your revenue. 49 | 50 | ### Third Party Integration with Google Tag Manager 51 | Adding Facebook pixel, Bing UAT, SnapChat or any other third-party code snippet to your website but frustrated by 52 | all the hassle and time it takes to configure Google Tag Manager? Learn how simple and easy it is to integrate any 53 | tracking code to your Magento store with our new [DataLayer extension](https://www.magepal.com/datalayer-for-google-tag-manager.html?utm_source=data%20layer%20for%20Google%20Tag%20Manager&utm_medium=github). 54 | 55 | ### General Data Protection Regulation (GDPR) Support 56 | Now you can quickly disable analytic tracking for customers' who do not want to by track by enabling Cookie Restriction 57 | Mode or base on existing or non-existing cookie. 58 | 59 | - Stores > Configuration > General > Web > Default Cookie Settings > Cookie Restriction Mode. 60 | 61 | Please Note: Merchants should consult with their own legal counsel to ensure that they are compliant with the GDPR. 62 | 63 | ### Get more from Google Tag Manager with our add-on Extensions 64 | 65 | | Features | GTM | EE | GA4 | DL | 66 | |-----------------------------|:---:|:---:|:---:|:---:| 67 | | Global Page Tracking | X | X | X | | 68 | | Order Conversion Tracking | X | X | X | | 69 | | Page Type Event | | X | X | | 70 | | Product Clicks | | X | X | | 71 | | Product Detail Impressions | | X | X | | 72 | | Add to Cart | | X | X | | 73 | | Remove from Cart | | X | X | | 74 | | Checkout Steps | | X | X | | 75 | | Order Refunds | | X | X | | 76 | | Admin Order Tracking | | X | X | | 77 | | Access DataLayer using JS | | | | X | 78 | | Bing UET Tracking | | | | X | 79 | | Full Facebook Tracking | | | | X | 80 | | Custom Image Pixel Tracking | | | | X | 81 | | Custom iFrame Tracking | | | | X | 82 | | Third-Party Integration | | | | X | 83 | | Extend individual page type | | | | X | 84 | 85 | GTM - [Google Tag Manager for Magento 2 Extension](https://www.magepal.com/magento2/extensions/google-tag-manager.html) 86 | 87 | GA4 - [Google Analytics 4 for Google Tag Manager Extension](https://www.magepal.com/magento2/extensions/google-analytics-4-for-google-tag-manager.html) 88 | 89 | EE - [Enhanced E-commerce for Google Tag Manager Extension](https://www.magepal.com/magento2/extensions/enhanced-ecommerce-for-google-tag-manager.html) 90 | 91 | DL - [DataLayer for Google Tag Manager Extension](https://www.magepal.com/magento2/extensions/datalayer-for-google-tag-manager.html) 92 | 93 | 94 | ### Features 95 | * Quick and easy setup 96 | * Add tag via XML layout and/or observer 97 | * Advance Data layer with over 60+ data elements 98 | * Fully customizable with 10 lines of code 99 | * General Data Protection Regulation (GDPR) Support 100 | * GTM Multiple Environments Support 101 | * Content Security Policies Support 102 | 103 | ### Benefits of using Google Tag Manager with Magento 104 | There are a number of benefits to using GTM with Magento: 105 | 106 | - One Centralized Tag Management source - Google tag Manager is one of the tops, and most widely used JavaScript tag 107 | management, therefore, anyone with Google Tag Manager experience will have all the knowledge they need to make edits 108 | to your site. 109 | - Little to No Technically Knowledge - Digital marketer agencies with so tech skills can quickly make and publish 110 | changes to Google Tag Manager without needing to call in developers. 111 | - Version Control - Every change to your Google Tag Manager container is track with a history of who and what was changed. 112 | - Easy to Use - Google Tag Manager is very simple and easy to use. You can easily export your GTM configuration in a 113 | text file that could be saved and reimport. 114 | - Reduce Number of Magento Extensions Needed - Installing individual extensions for AdWords, Facebook tracking, 115 | Snapchat, Microsoft Bing is time-consuming and resource intensive on your Magento store. Using Tag Manager you only 116 | need to install and maintaining one extension. 117 | - Eliminate Themes and Order Success Page Edits - 99% of merchants, developers and agencies don't know or use best 118 | practice when inserting javascript tracking code snippets to a Magento store, and often just add hardcode each 119 | javascript code snippets at random places within the themes files which make it unmaintainable over time as you switch 120 | between different service provider. 121 | 122 | ### How to Customize Google Tag Manager Extension 123 | Need to add more data to your data layer or change existing data to meet your client needs? 124 | Add, changing or removing information from the data layer to meet your client needs is as simple as adding few lines of 125 | php and di.xml code. See our documentation to learn more about 126 | [how to customizing Google Tag Manger](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#api). 127 | 128 | 129 | ### Documentation & Installation Guide 130 | 131 | [How to Installing Google Tag Manager](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#installation) 132 | 133 | [How to setup Google Tag Manager](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#configuration) 134 | 135 | [How to customizing Google Tag Manager](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#api) 136 | 137 | [Google Tag Manger Data Layer attributes](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#datalayer) 138 | 139 | [How to debugging Google Tag Manager](https://www.magepal.com/help/docs/google-tag-manager-for-magento/#debug) 140 | 141 | ### Composer Installation 142 | 143 | ```bash 144 | composer require magepal/magento2-googletagmanager 145 | ``` 146 | 147 | ### Data layer attributes 148 | 149 | Our Magento extension provide a vast array of over 60 preconfigure data layer elements to make integrating your Magento store with any third-party service a breeze using Google Tag Manager. 150 | 151 | ### Triggered Events 152 | 153 | ##### Home Page Events 154 | * Events 155 | * homePage**, allPage**, cmsIndexIndexPage**, mpCustomerSession 156 | 157 | ##### Category Page Events 158 | * Events 159 | * productImpression*, categoryPage**, allPage**, catalogCategoryViewPage**, mpCustomerSession 160 | * productClick*, addToCart*, productListSwatchClicked**, productListSwatchSelected** 161 | * view_item_list***, select_item***, add_to_cart*** 162 | 163 | ##### Product Detail Page Events 164 | * Events 165 | * productDetail*, productImpression*, productPage**, allPage**, catalogProductViewPage**, mpCustomerSession 166 | * productClick*, addToCart*, removeFromCart*, productDetailSwatchClicked**, productDetailSwatchSelected**, addToCartItemOutOfStock*, addToCartItemOptionRequired*, addToCartItemInvalidQtyIncrements* 167 | * view_item***, select_item***, add_to_cart***, view_item_list*** 168 | 169 | ##### Shopping Cart Page Events 170 | * Events 171 | * cartPage**, allPage**, checkoutCartIndexPage**, productImpression*, mpCustomerSession 172 | * productClick*, addToCart*, removeFromCart* 173 | * select_item***, add_to_cart***, remove_from_cart***, view_item_list*** 174 | 175 | ##### Checkout Page Events 176 | * Events 177 | * checkoutPage**, allPage**, checkoutIndexIndexPage**, checkout*, checkoutOption*, mpCustomerSession 178 | * checkoutEmailValidation*, shippingMethodAdded*, checkoutShippingStepCompleted*, checkoutShippingStepFailed*, paymentMethodAdded*, checkoutPaymentStepFailed*, checkoutPaymentStepCompleted* 179 | * begin_checkout***, add_shipping_info***, add_payment_info*** 180 | 181 | ##### Order Confirmation Page Events 182 | * Events 183 | * purchase*, orderSuccessPage**, allPage**, checkoutOnepageSuccessPage** 184 | 185 | ##### Other Events 186 | * Events 187 | * compareProductAdded**, compareProductRemoved**, wishlistProductAdded**, wishlistProductRemoved**, customerLoginAfter**, customerRegisterAfter**, newsletterSubscriberAdded** newsletterUnsubscribed** 188 | 189 | ### Data Layer Variables 190 | 191 | #### Customer 192 | * Trigger: event equals mpCustomerSession 193 | * customer.isLoggedIn 194 | * customer.id 195 | * customer.groupId 196 | * order.email_sha1** 197 | * order.email** 198 | * order.customer_id** 199 | 200 | #### Product Impression 201 | * Trigger: event equals productImpression 202 | * ecommerce.impressions[].name* 203 | * ecommerce.impressions[].id* 204 | * ecommerce.impressions[].price* 205 | * ecommerce.impressions[].list* 206 | * ecommerce.impressions[].position* 207 | * ecommerce.impressions[].category* 208 | 209 | 210 | #### Category 211 | * Trigger: event equals categoryPage 212 | * category.id 213 | * category.name 214 | * category.path 215 | 216 | #### Search Page 217 | * Trigger: event equals searchPage 218 | * search_term* 219 | 220 | #### Product Detail Page 221 | * Trigger: event equals productPage 222 | * product.id 223 | * product.name 224 | * product.sku 225 | * product.parent_sku 226 | * product.price 227 | * product.product_type 228 | * product.attribute_set_id 229 | * product.path 230 | * product.image_url 231 | 232 | * Trigger: event equals productDetail 233 | * ecommerce.currencyCode* 234 | * ecommerce.products[].id* 235 | * ecommerce.products[].name* 236 | * ecommerce.products[].category* 237 | * ecommerce.products[].price* 238 | 239 | #### Cart 240 | * Trigger: event equals cartPage 241 | * cart.hasItems 242 | * cart.items[].sku 243 | * cart.items[].parent_sku 244 | * cart.items[].product_type 245 | * cart.items[].name 246 | * cart.items[].parent_name 247 | * cart.items[].price 248 | * cart.items[].price_incl_tax 249 | * cart.items[].discount_amount 250 | * cart.items[].tax_amount 251 | * cart.items[].quantity 252 | * cart.total 253 | * cart.itemCount 254 | * cart.itemQty 255 | * cart.hasCoupons 256 | * cart.couponCode 257 | 258 | ##### Add to Cart 259 | * Trigger: event equals addToCart 260 | * ecommerce.add.products[].id* 261 | * ecommerce.add.products[].name* 262 | * ecommerce.add.products[].price* 263 | * ecommerce.add.products[].quantity* 264 | * ecommerce.add.products[].parent_sku* 265 | * ecommerce.add.products[].variant* 266 | * ecommerce.add.products[].category* 267 | 268 | ##### Remove from Cart 269 | * Trigger: event equals removeFromCart 270 | * ecommerce.remove.products[].id* 271 | * ecommerce.remove.products[].name* 272 | * ecommerce.remove.products[].price* 273 | * ecommerce.remove.products[].quantity* 274 | * ecommerce.remove.products[].variant* 275 | * ecommerce.remove.products[].category* 276 | 277 | ### Global Data Layer 278 | 279 | * Trigger: event equals addToCart 280 | * cart.add.products[].id* 281 | * cart.add.products[].name* 282 | * cart.add.products[].price* 283 | * cart.add.products[].quantity* 284 | * cart.add.products[].parent_sku* 285 | * cart.add.products[].variant* 286 | * cart.add.products[].category* 287 | 288 | * Trigger: event equals removeFromCart 289 | * cart.remove.products[].id* 290 | * cart.remove.products[].name* 291 | * cart.remove.products[].price* 292 | * cart.remove.products[].quantity* 293 | * cart.add.products[].parent_sku* 294 | * cart.remove.products[].variant* 295 | * cart.remove.products[].category* 296 | 297 | #### Order 298 | * Trigger: event equals purchase (Google Analytics) 299 | * transactionId 300 | * transactionAffiliation 301 | * transactionTotal 302 | * transactionShipping 303 | * transactionTax 304 | * transactionCouponCode 305 | * transactionDiscount 306 | * transactionSubTotal 307 | * transactionProducts[].sku 308 | * transactionProducts[].parent_sku 309 | * transactionProducts[].product_type 310 | * transactionProducts[].name 311 | * transactionProducts[].price 312 | * transactionProducts[].quantity 313 | 314 | * Additional Order Date (Generic) 315 | * order.order_id 316 | * order.store_name 317 | * order.total 318 | * order.subtotal 319 | * order.shipping 320 | * order.tax 321 | * order.coupon_code 322 | * order.coupon_name 323 | * order.discount 324 | * order.payment_method.title 325 | * order.payment_method.code 326 | * order.shipping_method.title 327 | * order.shipping_method.code 328 | * order.is_virtual 329 | * order.is_guest_checkout 330 | * order.email_sha1** 331 | * order.email** 332 | * order.customer_id** 333 | * order.has_previous_order** 334 | * order.is_first_order** 335 | * order.previous_order_count** 336 | * order.is_new_customer** 337 | * order.items[].sku 338 | * order.items[].id 339 | * order.items[].parent_sku 340 | * order.items[].product_id 341 | * order.items[].name 342 | * order.items[].parent_name 343 | * order.items[].price 344 | * order.items[].price_incl_tax 345 | * order.items[].quantity 346 | * order.items[].subtotal 347 | * order.items[].product_type 348 | * order.items[].discount_amount 349 | * order.items[].discount_percent 350 | * order.items[].tax_amount 351 | * order.items[].is_virtual 352 | * order.items[].variant 353 | * order.items[].categories 354 | 355 | `*` - Data layer provide by our [Enhanced Ecommerce Extension](https://www.magepal.com/enhanced-ecommerce-for-google-tag-manager.html) 356 | 357 | `**` - Data layer provide by our [Data Layer Extension](https://www.magepal.com/magento2/extensions/datalayer-for-google-tag-manager.html) 358 | 359 | `***` - Data layer provide by our [Google Analytics 4 Extension](https://www.magepal.com/google-analytics-4-for-google-tag-manager.html) 360 | 361 | Contribution 362 | --- 363 | Want to contribute to this extension? The quickest way is to open a [pull request on GitHub](https://help.github.com/articles/using-pull-requests). 364 | 365 | 366 | Support 367 | --- 368 | If you encounter any problems or bugs, please open an issue on [GitHub](https://github.com/magepal/magento2-googletagmanager/issues). For fast Premium Support visit our [Google Tag Manager](https://www.magepal.com/magento2/extensions/google-tag-manager.html?utm_source=GTM&utm_medium=Premium%20Support) product page for detail. 369 | 370 | Need help to set up or want to customize our extension to meet your business needs? Please email support@magepal.com and if we like your idea we will add this feature for free or at a discounted rate. 371 | 372 | Magento 2 / Adobe Commerce Extensions 373 | --- 374 | 375 | - [Enhanced E-commerce](https://www.magepal.com/magento2/extensions/enhanced-ecommerce-for-google-tag-manager.html) 376 | - [Enhanced Success Page for Magento 2](https://www.magepal.com/magento2/extensions/enhanced-success-page.html) 377 | - [Enhanced Transactional Emails for Magento 2](https://www.magepal.com/magento2/extensions/enhanced-transactional-emails.html) 378 | - [Google Tag Manager](https://www.magepal.com/magento2/extensions/google-tag-manager.html) 379 | - [Reindex](https://www.magepal.com/magento2/extensions/reindex.html) 380 | - [Custom Shipping Method](https://www.magepal.com/magento2/extensions/custom-shipping-rates-for-magento-2.html) 381 | - [Preview Order Confirmation](https://www.magepal.com/magento2/extensions/preview-order-confirmation-page-for-magento-2.html) 382 | - [Guest to Customer](https://www.magepal.com/magento2/extensions/guest-to-customer.html) 383 | - [Admin Form Fields Manager](https://www.magepal.com/magento2/extensions/admin-form-fields-manager-for-magento-2.html) 384 | - [Customer Dashboard Links Manager](https://www.magepal.com/magento2/extensions/customer-dashboard-links-manager-for-magento-2.html) 385 | - [Lazy Loader](https://www.magepal.com/magento2/extensions/lazy-load.html) 386 | - [Order Confirmation Page Miscellaneous Scripts](https://www.magepal.com/magento2/extensions/order-confirmation-miscellaneous-scripts-for-magento-2.html) 387 | - [HTML Minifier for Magento2](https://www.magepal.com/magento2/extensions/html-minifier.html) 388 | - [Custom SMTP](https://www.magepal.com/magento2/extensions/custom-smtp.html) 389 | - [Catalog Hover Image for Magento](https://www.magepal.com/magento2/extensions/catalog-hover-image-for-magento.html) 390 | 391 | © MagePal LLC. | [www.magepal.com](https://www.magepal.com) 392 | --------------------------------------------------------------------------------