diff --git a/admin/admin-tab-integrate.php b/admin/admin-tab-integrate.php
index 185f1f5..bc1952a 100644
--- a/admin/admin-tab-integrate.php
+++ b/admin/admin-tab-integrate.php
@@ -36,6 +36,25 @@
'phase' => GTM4WP_PHASE_STABLE,
'plugintocheck' => 'woocommerce/woocommerce.php',
),
+ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART => array(
+ 'label' => esc_html__( 'Use WooCommerce Blocks add-to-cart', 'duracelltomi-google-tag-manager' ),
+ 'description' => sprintf(
+ gtm4wp_safe_admin_html(
+ // translators: 1: anchor element linking to WooCommerce Blocks DOM events docs. 2: closing anchor element.
+ __(
+ 'Enable this experimental feature to track WooCommerce Blocks cart interactions (Add to Cart + Options, mini-cart, and cart page).
+ This option uses the %1$sDOM events%2$s from WooCommerce Blocks to detect when products are added to or removed from the cart.
+ When enabled, the legacy add-to-cart tracking will be disabled.
+ This feature requires "Track e-commerce" to be enabled.',
+ 'duracelltomi-google-tag-manager'
+ )
+ ),
+ '',
+ ''
+ ),
+ 'phase' => GTM4WP_PHASE_EXPERIMENTAL,
+ 'plugintocheck' => 'woocommerce/woocommerce.php',
+ ),
GTM4WP_OPTION_INTEGRATE_WCPRODPERIMPRESSION => array(
'label' => esc_html__( 'Products per impression', 'duracelltomi-google-tag-manager' ),
'description' => gtm4wp_safe_admin_html(
diff --git a/admin/admin.php b/admin/admin.php
index 5b23997..112379f 100644
--- a/admin/admin.php
+++ b/admin/admin.php
@@ -652,8 +652,13 @@ function gtm4wp_sanitize_options( $options ) {
}
} elseif ( GTM4WP_OPTION_GTM_PLACEMENT === $optionname ) {
// GTM container ON/OFF + compat mode.
- $container_on_off = (bool) $options['container-on'];
- $container_compat = (int) $options['compat-mode'];
+ $container_on_off = isset( $options['container-on'] )
+ ? (bool) $options['container-on']
+ : ( GTM4WP_PLACEMENT_OFF !== (int) $optionvalue );
+
+ $container_compat = isset( $options['compat-mode'] )
+ ? (int) $options['compat-mode']
+ : (int) $optionvalue;
if ( ! $container_on_off ) {
$output[ $optionname ] = GTM4WP_PLACEMENT_OFF;
@@ -1298,6 +1303,19 @@ function gtm4wp_show_warning() {
);
echo '
';
}
+
+ if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART ] ) {
+ $blocks_health = gtm4wp_get_blocks_integration_health_state();
+
+ if ( ! $blocks_health['store_api_route'] || ! $blocks_health['gtm4wp_product_api'] ) {
+ echo '';
+ esc_html_e(
+ 'WooCommerce Blocks add-to-cart tracking is enabled but its required REST endpoints are unavailable. Please ensure WooCommerce Blocks assets and the GTM4WP REST endpoints are accessible.',
+ 'duracelltomi-google-tag-manager'
+ );
+ echo '
';
+ }
+ }
}
/**
@@ -1381,6 +1399,82 @@ function gtm4wp_show_upgrade_notification( $current_plugin_metadata, $new_plugin
add_action( 'admin_menu', 'gtm4wp_add_admin_page' );
add_action( 'admin_enqueue_scripts', 'gtm4wp_add_admin_js' );
add_action( 'admin_notices', 'gtm4wp_show_warning' );
+
+/**
+ * Returns health status for WooCommerce Blocks add-to-cart integration.
+ *
+ * @return array
+ */
+function gtm4wp_get_blocks_integration_health_state() {
+ $result = array(
+ 'store_api_route' => false,
+ 'gtm4wp_product_api' => false,
+ );
+
+ // First, try to exercise the endpoints directly so themes/hosts that lazy-load routes do not trigger false warnings.
+ $result['store_api_route'] = gtm4wp_rest_route_responds( '/wc/store/v1/cart', true );
+ $result['gtm4wp_product_api'] = gtm4wp_rest_route_responds( '/gtm4wp/v1/product/0', true );
+
+ if ( $result['store_api_route'] && $result['gtm4wp_product_api'] ) {
+ return $result;
+ }
+
+ if ( ! function_exists( 'rest_get_server' ) ) {
+ return $result;
+ }
+
+ $server = rest_get_server();
+
+ if ( ! $server ) {
+ return $result;
+ }
+
+ $routes = $server->get_routes();
+
+ foreach ( $routes as $route => $handler ) {
+ if ( ! $result['store_api_route'] && 0 === strpos( $route, '/wc/store/v1/cart' ) ) {
+ $result['store_api_route'] = true;
+ }
+
+ if ( ! $result['gtm4wp_product_api'] && 0 === strpos( $route, '/gtm4wp/v1/product' ) ) {
+ $result['gtm4wp_product_api'] = true;
+ }
+
+ if ( $result['store_api_route'] && $result['gtm4wp_product_api'] ) {
+ break;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Checks if a REST route responds without returning rest_no_route.
+ *
+ * @param string $route Route path to check.
+ * @param bool $allow_client_error Treat 4xx responses as success (route exists but request invalid).
+ * @return bool
+ */
+function gtm4wp_rest_route_responds( $route, $allow_client_error = false ) {
+ if ( ! class_exists( 'WP_REST_Request' ) || ! function_exists( 'rest_do_request' ) ) {
+ return false;
+ }
+
+ $request = new WP_REST_Request( 'GET', $route );
+ $response = rest_do_request( $request );
+
+ if ( is_wp_error( $response ) ) {
+ return 'rest_no_route' !== $response->get_error_code();
+ }
+
+ $status = (int) $response->get_status();
+
+ if ( $allow_client_error ) {
+ return $status >= 200 && $status < 500;
+ }
+
+ return $status >= 200 && $status < 400;
+}
add_action( 'admin_head', 'gtm4wp_admin_head' );
add_filter( 'plugin_action_links', 'gtm4wp_add_plugin_action_links', 10, 2 );
add_action( 'wp_ajax_gtm4wp_dismiss_notice', 'gtm4wp_dismiss_notice' );
diff --git a/common/readoptions.php b/common/readoptions.php
index e2cef86..9d66483 100644
--- a/common/readoptions.php
+++ b/common/readoptions.php
@@ -90,6 +90,7 @@
define( 'GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG', 'integrate-woocommerce-do-not-use-order-tracked-flag' );
define( 'GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL', 'integrate-woocommerce-clear-ecommerce-datalayer' );
define( 'GTM4WP_OPTION_INTEGRATE_WCDLMAXTIMEOUT', 'integrate-woocommerce-datalayer-max-timeout' );
+define( 'GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART', 'integrate-woocommerce-blocks-add-to-cart' );
define( 'GTM4WP_OPTION_INTEGRATE_WPECOMMERCE', 'integrate-wp-e-commerce' );
@@ -199,6 +200,7 @@
GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG => false,
GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL => false,
GTM4WP_OPTION_INTEGRATE_WCDLMAXTIMEOUT => 2000,
+ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART => false,
GTM4WP_OPTION_INTEGRATE_WPECOMMERCE => false,
diff --git a/dist/js/gtm4wp-woocommerce.js b/dist/js/gtm4wp-woocommerce.js
index 3fd8988..f6ae116 100644
--- a/dist/js/gtm4wp-woocommerce.js
+++ b/dist/js/gtm4wp-woocommerce.js
@@ -1 +1 @@
-"use strict";var gtm4wp_last_selected_product_variation;function gtm4wp_woocommerce_handle_cart_qty_change(){document.querySelectorAll(".product-quantity input.qty").forEach(function(t){var e=t.defaultValue,o=parseInt(t.value);if(e!=(o=isNaN(o)?e:o)){var t=t.closest(".cart_item"),t=t&&t.querySelector(".remove");if(t)return!(t=gtm4wp_read_json_from_node(t,"gtm4wp_product_data"))||void(ediv:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.widget-product-item,.woocommerce-grouped-product-list-item__label a")){if("undefined"==typeof google_tag_manager)return!0;c=t.target,_=c.closest(".products li:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.wc-block-grid__products li:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.products>div:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.widget-product-item,.woocommerce-grouped-product-list-item__label a");if(!_)return!0;var i,r=c.closest(".product,.wc-block-grid__product"),o=(r=(r=r||((r=c.closest(".products li"))||c.closest(".products>div")))||c.closest(".woocommerce-grouped-product-list-item__label"))?r.querySelector(".gtm4wp_productdata"):c,e=gtm4wp_read_json_from_node(o,"gtm4wp_product_data",["internal_id"]);if(!e)return!0;if(e.productlink!=_.getAttribute("href"))return!0;for(i in window.google_tag_manager)if("gtm-"==i.substring(0,4).toLowerCase()){window.gtm4wp_first_container_id=i;break}if(""===window.gtm4wp_first_container_id)return!0;var d=t.ctrlKey||t.metaKey,u="_blank"===_.target,p=t.defaultPrevented,m=(p||t.preventDefault(),(d||u)&&(window.productpage_window=window.open("about:blank","_blank")),e.productlink),r=(delete e.productlink,2e3);window.gtm4wp_datalayer_max_timeout&&(r=window.gtm4wp_datalayer_max_timeout),gtm4wp_push_ecommerce("select_item",[e],{currency:gtm4wp_currency},function(t){if(void 0!==t&&window.gtm4wp_first_container_id!=t)return!0;p||((u||d)&&productpage_window?productpage_window.location.href=m:document.location.href=m)},r)}},{capture:!0}),jQuery(document).on("found_variation",function(t,e){if(void 0!==e&&("interactive"!==document.readyState||!gtm4wp_view_item_fired_during_pageload)){t=t.target;if(!t)return!0;var o,t=t.querySelector("[name=gtm4wp_product_data]");if(!t)return!0;try{o=JSON.parse(t.value)}catch(t){return console&&console.error&&console.error(t.message),!0}o.price=gtm4wp_make_sure_is_float(o.price),o.item_group_id=o.id,o.id=e.variation_id,o.item_id=e.variation_id,o.sku=e.sku,gtm4wp_use_sku_instead&&e.sku&&""!==e.sku&&(o.id=e.sku,o.item_id=e.sku),o.price=gtm4wp_make_sure_is_float(e.display_price);var r,c=[];for(r in e.attributes)c.push(e.attributes[r]);o.item_variant=c.join(","),delete(gtm4wp_last_selected_product_variation=o).internal_id,gtm4wp_push_ecommerce("view_item",[o],{currency:gtm4wp_currency,value:o.price}),"interactive"===document.readyState&&(gtm4wp_view_item_fired_during_pageload=!0)}}),jQuery(".variations select").trigger("change"),jQuery(document).ajaxSuccess(function(t,e,o){void 0!==o&&-1div:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.widget-product-item,.woocommerce-grouped-product-list-item__label a")){if("undefined"==typeof google_tag_manager)return!0;r=t.target,a=r.closest(".products li:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.wc-block-grid__products li:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.products>div:not(.product-category) a:not(.add_to_cart_button):not(.quick-view-button),.widget-product-item,.woocommerce-grouped-product-list-item__label a");if(!a)return!0;var i,o=r.closest(".product,.wc-block-grid__product"),n=(o=(o=o||((o=r.closest(".products li"))||r.closest(".products>div")))||r.closest(".woocommerce-grouped-product-list-item__label"))?o.querySelector(".gtm4wp_productdata"):r,e=gtm4wp_read_json_from_node(n,"gtm4wp_product_data",["internal_id"]);if(!e)return!0;if(e.productlink!=a.getAttribute("href"))return!0;for(i in window.google_tag_manager)if("gtm-"==i.substring(0,4).toLowerCase()){window.gtm4wp_first_container_id=i;break}if(""===window.gtm4wp_first_container_id)return!0;var c=t.ctrlKey||t.metaKey,_="_blank"===a.target,s=t.defaultPrevented,p=(s||t.preventDefault(),(c||_)&&(window.productpage_window=window.open("about:blank","_blank")),e.productlink),o=(delete e.productlink,2e3);window.gtm4wp_datalayer_max_timeout&&(o=window.gtm4wp_datalayer_max_timeout),gtm4wp_push_ecommerce("select_item",[e],{currency:gtm4wp_currency},function(t){if(void 0!==t&&window.gtm4wp_first_container_id!=t)return!0;s||((_||c)&&productpage_window?productpage_window.location.href=p:document.location.href=p)},o)}},{capture:!0}),gtm4wp_blocks_integration_enabled||document.addEventListener("click",gtm4wp_classic_add_to_cart_click_handler,{capture:!0}),jQuery(document).on("found_variation",function(t,e){if(void 0!==e&&("interactive"!==document.readyState||!gtm4wp_view_item_fired_during_pageload)){t=t.target;if(!t)return!0;var r,t=t.querySelector("[name=gtm4wp_product_data]");if(!t)return!0;try{r=JSON.parse(t.value)}catch(t){return console&&console.error&&console.error(t.message),!0}r.price=gtm4wp_make_sure_is_float(r.price),r.item_group_id=r.id,r.id=e.variation_id,r.item_id=e.variation_id,r.sku=e.sku,gtm4wp_use_sku_instead&&e.sku&&""!==e.sku&&(r.id=e.sku,r.item_id=e.sku),r.price=gtm4wp_make_sure_is_float(e.display_price);var n,o=[];for(n in e.attributes)o.push(e.attributes[n]);r.item_variant=o.join(","),delete(gtm4wp_last_selected_product_variation=r).internal_id,gtm4wp_push_ecommerce("view_item",[r],{currency:gtm4wp_currency,value:r.price}),"interactive"===document.readyState&&(gtm4wp_view_item_fired_during_pageload=!0)}}),jQuery(".variations select").trigger("change"),jQuery(document).ajaxSuccess(function(t,e,r){void 0!==r&&-1get_sku() ) {
+ $parent_item_id = $parent_product->get_sku();
+ }
+
+ $_temp_productdata['item_group_sku'] = $parent_item_id;
+
+ if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSESKU ] && '' !== $parent_item_id ) {
+ $_temp_productdata['item_group_id'] = $parent_item_id;
+ } else {
+ $_temp_productdata['item_group_id'] = $parent_product_id;
+ }
}
if ( 1 === count( $product_cat_parts ) ) {
@@ -962,7 +981,8 @@ function gtm4wp_woocommerce_single_add_to_cart_tracking() {
global $product, $gtm4wp_options;
// exit early if there is nothing to do.
- if ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
+ if ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ]
+ || ! empty( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART ] ) ) {
return;
}
@@ -1376,8 +1396,27 @@ function gtm4wp_woocommerce_enqueue_scripts() {
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$in_footer = (bool) apply_filters( 'gtm4wp_' . GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE, true );
- wp_enqueue_script( 'gtm4wp-ecommerce-generic', $gtp4wp_script_path . 'gtm4wp-ecommerce-generic.js', array(), GTM4WP_VERSION, $in_footer );
- wp_enqueue_script( 'gtm4wp-woocommerce', $gtp4wp_script_path . 'gtm4wp-woocommerce.js', array( 'jquery' ), GTM4WP_VERSION, $in_footer );
+
+ $default_version = GTM4WP_VERSION;
+ $generic_script_ver = $default_version;
+ $woocommerce_script_ver = $default_version;
+
+ if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
+ $script_dir = trailingslashit( dirname( __DIR__ ) ) . 'js/';
+
+ $generic_script_path = $script_dir . 'gtm4wp-ecommerce-generic.js';
+ if ( file_exists( $generic_script_path ) ) {
+ $generic_script_ver = filemtime( $generic_script_path );
+ }
+
+ $woocommerce_script_path = $script_dir . 'gtm4wp-woocommerce.js';
+ if ( file_exists( $woocommerce_script_path ) ) {
+ $woocommerce_script_ver = filemtime( $woocommerce_script_path );
+ }
+ }
+
+ wp_enqueue_script( 'gtm4wp-ecommerce-generic', $gtp4wp_script_path . 'gtm4wp-ecommerce-generic.js', array(), $generic_script_ver, $in_footer );
+ wp_enqueue_script( 'gtm4wp-woocommerce', $gtp4wp_script_path . 'gtm4wp-woocommerce.js', array( 'jquery' ), $woocommerce_script_ver, $in_footer );
}
}
@@ -1507,6 +1546,107 @@ function gtm4wp_woocommerce_add_productdata_to_wc_block( $content, $data, $produ
add_action( 'wp_enqueue_scripts', 'gtm4wp_woocommerce_enqueue_scripts' );
add_filter( GTM4WP_WPFILTER_ADDGLOBALVARS_ARRAY, 'gtm4wp_woocommerce_add_global_vars' );
+add_action( 'rest_api_init', 'gtm4wp_register_wc_product_rest_route' );
+add_action( 'wp_ajax_gtm4wp_product_data', 'gtm4wp_ajax_get_product_data' );
+add_action( 'wp_ajax_nopriv_gtm4wp_product_data', 'gtm4wp_ajax_get_product_data' );
+
+/**
+ * Registers REST API endpoint to fetch processed product data.
+ *
+ * @return void
+ */
+function gtm4wp_register_wc_product_rest_route() {
+ register_rest_route(
+ 'gtm4wp/v1',
+ '/product/(?P\d+)',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => 'gtm4wp_rest_get_product_data',
+ 'permission_callback' => '__return_true',
+ )
+ );
+}
+
+/**
+ * REST API callback to retrieve processed product data.
+ *
+ * @param WP_REST_Request $request The REST request.
+ * @return WP_REST_Response|WP_Error
+ */
+function gtm4wp_rest_get_product_data( WP_REST_Request $request ) {
+ $product_id = absint( $request['id'] );
+
+ if ( $product_id <= 0 ) {
+ return new WP_Error( 'gtm4wp_invalid_product', __( 'Invalid product ID.', 'duracelltomi-google-tag-manager' ), array( 'status' => 400 ) );
+ }
+
+ $product = wc_get_product( $product_id );
+
+ if ( ! $product ) {
+ return new WP_Error( 'gtm4wp_product_not_found', __( 'Product not found.', 'duracelltomi-google-tag-manager' ), array( 'status' => 404 ) );
+ }
+
+ $product_data = gtm4wp_woocommerce_process_product( $product, array(), 'productdetail' );
+
+ if ( ! $product_data ) {
+ return new WP_Error( 'gtm4wp_product_unavailable', __( 'Unable to build product data.', 'duracelltomi-google-tag-manager' ), array( 'status' => 404 ) );
+ }
+
+ return rest_ensure_response(
+ array(
+ 'product' => $product_data,
+ )
+ );
+}
+
+/**
+ * AJAX fallback handler for product data when REST is not accessible.
+ *
+ * @return void
+ */
+function gtm4wp_ajax_get_product_data() {
+ check_ajax_referer( 'gtm4wp-product-data', 'nonce' );
+
+ $product_id = isset( $_REQUEST['product_id'] ) ? absint( $_REQUEST['product_id'] ) : 0;
+
+ if ( $product_id <= 0 ) {
+ wp_send_json_error(
+ array(
+ 'message' => __( 'Invalid product ID.', 'duracelltomi-google-tag-manager' ),
+ ),
+ 400
+ );
+ }
+
+ $product = wc_get_product( $product_id );
+
+ if ( ! $product ) {
+ wp_send_json_error(
+ array(
+ 'message' => __( 'Product not found.', 'duracelltomi-google-tag-manager' ),
+ ),
+ 404
+ );
+ }
+
+ $product_data = gtm4wp_woocommerce_process_product( $product, array(), 'ajax_fallback' );
+
+ if ( ! $product_data ) {
+ wp_send_json_error(
+ array(
+ 'message' => __( 'Unable to build product data.', 'duracelltomi-google-tag-manager' ),
+ ),
+ 500
+ );
+ }
+
+ wp_send_json_success(
+ array(
+ 'product' => $product_data,
+ )
+ );
+}
+
add_filter( 'woocommerce_blocks_product_grid_item_html', 'gtm4wp_woocommerce_add_productdata_to_wc_block', 10, 3 );
add_action( 'woocommerce_thankyou', 'gtm4wp_woocommerce_thankyou' );
diff --git a/js/gtm4wp-woocommerce.js b/js/gtm4wp-woocommerce.js
index 1643316..36641ec 100644
--- a/js/gtm4wp-woocommerce.js
+++ b/js/gtm4wp-woocommerce.js
@@ -4,6 +4,242 @@ window.gtm4wp_view_item_fired_during_pageload = false;
window.gtm4wp_checkout_step_fired = []; // step 1 will be the billing section which is reported during pageload, no need to handle here
window.gtm4wp_first_container_id = "";
+const gtm4wp_blocks_integration_enabled = ( typeof gtm4wp_blocks_add_to_cart !== 'undefined' && gtm4wp_blocks_add_to_cart );
+const gtm4wp_rest_root_url = ( () => {
+ if ( typeof gtm4wp_rest_root !== 'undefined' && gtm4wp_rest_root ) {
+ return gtm4wp_rest_root;
+ }
+
+ if ( window.wpApiSettings && window.wpApiSettings.root ) {
+ return window.wpApiSettings.root;
+ }
+
+ return window.location.origin.replace( /\/$/, '' ) + '/wp-json/';
+} )();
+const gtm4wp_rest_nonce_value = ( typeof gtm4wp_rest_nonce !== 'undefined' && gtm4wp_rest_nonce ) ?
+ gtm4wp_rest_nonce :
+ ( window.wpApiSettings && window.wpApiSettings.nonce ? window.wpApiSettings.nonce : null );
+const gtm4wp_blocks_ajax_endpoint = ( typeof gtm4wp_blocks_ajax_url !== 'undefined' && gtm4wp_blocks_ajax_url ) ?
+ gtm4wp_blocks_ajax_url :
+ ( typeof ajaxurl !== 'undefined' ? ajaxurl : '/wp-admin/admin-ajax.php' );
+const gtm4wp_blocks_product_nonce_value = ( typeof gtm4wp_blocks_product_nonce !== 'undefined' && gtm4wp_blocks_product_nonce ) ?
+ gtm4wp_blocks_product_nonce :
+ null;
+const gtm4wp_blocks_dedupe_window_value = ( () => {
+ const configured = ( typeof gtm4wp_blocks_dedupe_window !== 'undefined' ) ? parseInt( gtm4wp_blocks_dedupe_window, 10 ) : NaN;
+ return Number.isFinite( configured ) && configured > 0 ? configured : 800;
+} )();
+let gtm4wp_blocks_environment_warned = false;
+const gtm4wp_blocks_cart_storage_key = 'gtm4wp_wc_blocks_cart_state';
+const gtm4wp_blocks_cart_storage_version = 1;
+let gtm4wp_session_storage_supported = null;
+
+function gtm4wp_build_rest_url( path ) {
+ const sanitizedRoot = gtm4wp_rest_root_url.replace( /\/+$/, '' );
+ const sanitizedPath = String( path || '' ).replace( /^\/+/, '' );
+ return sanitizedRoot + '/' + sanitizedPath;
+}
+
+function gtm4wp_blocks_warn_once( message ) {
+ if ( gtm4wp_blocks_environment_warned ) {
+ return;
+ }
+
+ gtm4wp_blocks_environment_warned = true;
+
+ if ( window.console && window.console.warn ) {
+ window.console.warn( '[GTM4WP][WC Blocks]', message );
+ }
+}
+
+function gtm4wp_is_blocks_store_available() {
+ return !! ( window.wp && window.wp.data && window.wp.data.select && window.wp.data.subscribe );
+}
+
+function gtm4wp_delay( duration ) {
+ return new Promise( ( resolve ) => {
+ window.setTimeout( resolve, duration );
+ } );
+}
+
+function gtm4wp_is_session_storage_available() {
+ if ( null !== gtm4wp_session_storage_supported ) {
+ return gtm4wp_session_storage_supported;
+ }
+
+ try {
+ const test_key = '__gtm4wp_ss__';
+ window.sessionStorage.setItem( test_key, '1' );
+ window.sessionStorage.removeItem( test_key );
+ gtm4wp_session_storage_supported = true;
+ } catch ( e ) {
+ gtm4wp_session_storage_supported = false;
+ }
+
+ return gtm4wp_session_storage_supported;
+}
+
+function gtm4wp_normalize_key_value_pairs( source ) {
+ if ( ! source ) {
+ return '';
+ }
+
+ const entries = [];
+
+ if ( Array.isArray( source ) ) {
+ source.forEach( ( item ) => {
+ if ( item && 'object' === typeof item ) {
+ const name = item.name || item.attribute || item.key || '';
+ const value = ( 'value' in item ) ? item.value : ( item.value_html || item.label || '' );
+
+ if ( name || value ) {
+ entries.push( name + ':' + value );
+ }
+ }
+ } );
+ } else if ( 'object' === typeof source ) {
+ Object.keys( source )
+ .sort()
+ .forEach( ( key ) => {
+ entries.push( key + ':' + source[ key ] );
+ } );
+ }
+
+ return entries.join( '|' );
+}
+
+function gtm4wp_classic_add_to_cart_click_handler( e ) {
+ let event_target_element = e.target;
+
+ if ( !event_target_element ) {
+ return true;
+ }
+
+ // track add to cart events for simple products in product lists.
+ if ( event_target_element.closest( '.add_to_cart_button:not(.product_type_variable, .product_type_grouped, .single_add_to_cart_button)' ) ) {
+ const product_el = event_target_element.closest( '.product,.wc-block-grid__product' );
+
+ const productdata_el = product_el && product_el.querySelector( '.gtm4wp_productdata' );
+ if ( !productdata_el ) {
+ return true;
+ }
+
+ const productdata = gtm4wp_read_json_from_node( productdata_el, "gtm4wp_product_data" );
+ if ( !productdata ) {
+ return true;
+ }
+
+ if ( "variable" === productdata.product_type || "grouped" === productdata.product_type ) {
+ return true;
+ }
+
+ if ( productdata.productlink ) {
+ delete productdata.productlink;
+ }
+ delete productdata.product_type;
+ productdata.quantity = 1;
+
+ gtm4wp_push_ecommerce( 'add_to_cart', [ productdata ], {
+ 'currency': gtm4wp_currency,
+ 'value': productdata.price
+ });
+
+ return true;
+ }
+
+ // track add to cart events for products on product detail pages.
+ const add_to_cart_button = event_target_element.closest( '.single_add_to_cart_button' );
+ if ( !add_to_cart_button ) {
+ return true;
+ }
+
+ if ( add_to_cart_button.classList.contains( 'disabled' ) || add_to_cart_button.disabled ) {
+ // do not track clicks on disabled buttons.
+ return true;
+ }
+
+ const product_form = event_target_element.closest( 'form.cart' );
+ if ( !product_form ) {
+ return true;
+ }
+
+ let product_variant_id = product_form.querySelectorAll( '[name=variation_id]' );
+ let product_is_grouped = product_form.classList && product_form.classList.contains( 'grouped_form' );
+
+ if ( product_variant_id.length > 0 ) {
+ if ( gtm4wp_last_selected_product_variation ) {
+ const qty_el = product_form.querySelector( '[name=quantity]' );
+ gtm4wp_last_selected_product_variation.quantity = (qty_el && qty_el.value) || 1;
+
+ gtm4wp_push_ecommerce( 'add_to_cart', [ gtm4wp_last_selected_product_variation ], {
+ 'currency': gtm4wp_currency,
+ 'value': (gtm4wp_last_selected_product_variation.price * gtm4wp_last_selected_product_variation.quantity).toFixed(2)
+ });
+ }
+
+ return true;
+ }
+
+ if ( product_is_grouped ) {
+ const products_in_group = document.querySelectorAll( '.grouped_form .gtm4wp_productdata' );
+ let products = [];
+ let sum_value = 0;
+
+ products_in_group.forEach( function( product_data_el ) {
+ const productdata = gtm4wp_read_json_from_node(product_data_el, 'gtm4wp_product_data', ['productlink']);
+ if ( !productdata ) {
+ return true;
+ }
+
+ let product_qty = 0;
+ const product_qty_input = document.querySelectorAll( 'input[name=quantity\\[' + productdata.internal_id + '\\]]' );
+ if ( product_qty_input.length > 0 ) {
+ product_qty = (product_qty_input[0] && product_qty_input[0].value) || 1;
+ } else {
+ return true;
+ }
+
+ if ( 0 == product_qty ) {
+ return true;
+ }
+ productdata.quantity = product_qty;
+
+ delete productdata.internal_id;
+
+ products.push( productdata );
+ sum_value += productdata.price * productdata.quantity;
+ });
+
+ if ( 0 == products.length ) {
+ return true;
+ }
+
+ gtm4wp_push_ecommerce( 'add_to_cart', products, {
+ 'currency': gtm4wp_currency,
+ 'value': sum_value.toFixed(2)
+ });
+
+ return true;
+ }
+
+ const product_data_el = product_form.querySelector( '[name=gtm4wp_product_data]' );
+ if ( !product_data_el ) {
+ return true;
+ }
+
+ let productdata = gtm4wp_read_from_json( product_data_el.value );
+ productdata.quantity = product_form.querySelector( '[name=quantity]' ) && product_form.querySelector( '[name=quantity]' ).value;
+ if ( isNaN( productdata.quantity ) ) {
+ productdata.quantity = 1;
+ }
+
+ gtm4wp_push_ecommerce( 'add_to_cart', [ productdata ], {
+ 'currency': gtm4wp_currency,
+ 'value': productdata.price * productdata.quantity
+ });
+
+ return true;
+}
function gtm4wp_woocommerce_handle_cart_qty_change() {
document.querySelectorAll( '.product-quantity input.qty' ).forEach(function( qty_el ) {
@@ -211,119 +447,6 @@ function gtm4wp_woocommerce_process_pages() {
return true;
}
- // track add to cart events for simple products in product lists
- if ( event_target_element.closest( '.add_to_cart_button:not(.product_type_variable, .product_type_grouped, .single_add_to_cart_button)' ) ) {
- const product_el = event_target_element.closest( '.product,.wc-block-grid__product' );
-
- const productdata_el = product_el && product_el.querySelector( '.gtm4wp_productdata' );
- if ( !productdata_el ) {
- return true;
- }
-
- const productdata = gtm4wp_read_json_from_node( productdata_el, "gtm4wp_product_data" );
- if ( !productdata ) {
- return true;
- }
-
- if ( "variable" === productdata.product_type || "grouped" === productdata.product_type ) {
- return true;
- }
-
- if ( productdata.productlink ) {
- delete productdata.productlink;
- }
- delete productdata.product_type;
- productdata.quantity = 1;
-
- gtm4wp_push_ecommerce( 'add_to_cart', [ productdata ], {
- 'currency': gtm4wp_currency,
- 'value': productdata.price
- });
- }
-
- // track add to cart events for products on product detail pages
- const add_to_cart_button = event_target_element.closest( '.single_add_to_cart_button' );
- if ( add_to_cart_button ) {
- if (add_to_cart_button.classList.contains( 'disabled' ) || add_to_cart_button.disabled) {
- // do not track clicks on disabled buttons
- return true;
- }
-
- const product_form = event_target_element.closest( 'form.cart' );
- if ( !product_form ) {
- return true;
- }
-
- let product_variant_id = product_form.querySelectorAll( '[name=variation_id]' );
- let product_is_grouped = product_form.classList && product_form.classList.contains( 'grouped_form' );
-
- if ( product_variant_id.length > 0 ) {
- if ( gtm4wp_last_selected_product_variation ) {
- const qty_el = product_form.querySelector( '[name=quantity]' );
- gtm4wp_last_selected_product_variation.quantity = (qty_el && qty_el.value) || 1;
-
- gtm4wp_push_ecommerce( 'add_to_cart', [ gtm4wp_last_selected_product_variation ], {
- 'currency': gtm4wp_currency,
- 'value': (gtm4wp_last_selected_product_variation.price * gtm4wp_last_selected_product_variation.quantity).toFixed(2)
- });
- }
- } else if ( product_is_grouped ) {
- const products_in_group = document.querySelectorAll( '.grouped_form .gtm4wp_productdata' );
- let products = [];
- let sum_value = 0;
-
- products_in_group.forEach( function( product_data_el ) {
- const productdata = gtm4wp_read_json_from_node(product_data_el, 'gtm4wp_product_data', ['productlink']);
- if ( !productdata ) {
- return true;
- }
-
- let product_qty = 0;
- const product_qty_input = document.querySelectorAll( 'input[name=quantity\\[' + productdata.internal_id + '\\]]' );
- if ( product_qty_input.length > 0 ) {
- product_qty = (product_qty_input[0] && product_qty_input[0].value) || 1;
- } else {
- return true;
- }
-
- if ( 0 == product_qty ) {
- return true;
- }
- productdata.quantity = product_qty;
-
- delete productdata.internal_id;
-
- products.push( productdata );
- sum_value += productdata.price * productdata.quantity;
- });
-
- if ( 0 == products.length ) {
- return true;
- }
-
- gtm4wp_push_ecommerce( 'add_to_cart', products, {
- 'currency': gtm4wp_currency,
- 'value': sum_value.toFixed(2)
- });
- } else {
- const product_data_el = product_form.querySelector( '[name=gtm4wp_product_data]' );
- if ( !product_data_el ) {
- return true;
- }
-
- let productdata = gtm4wp_read_from_json( product_data_el.value );
- productdata.quantity = product_form.querySelector( '[name=quantity]' ) && product_form.querySelector( '[name=quantity]' ).value;
- if ( isNaN( productdata.quantity ) ) {
- productdata.quantity = 1;
- }
-
- gtm4wp_push_ecommerce( 'add_to_cart', [ productdata ], {
- 'currency': gtm4wp_currency,
- 'value': productdata.price * productdata.quantity
- });
- }
- }
-
// track remove links in mini cart widget and on cart page
if ( event_target_element.closest( '.mini_cart_item a.remove,.product-remove a.remove' ) ) {
const click_el = event_target_element;
@@ -491,6 +614,10 @@ function gtm4wp_woocommerce_process_pages() {
}
}, { capture: true } );
+ if ( !gtm4wp_blocks_integration_enabled ) {
+ document.addEventListener( 'click', gtm4wp_classic_add_to_cart_click_handler, { capture: true } );
+ }
+
// track variable products on their detail pages
// currently, we need to use jQuery here since WooCommerce is firing this event using jQuery
// that can not be catched using vanilla JS
@@ -691,3 +818,818 @@ if ( document.readyState !== "loading" ) {
document.addEventListener( "DOMContentLoaded", gtm4wp_woocommerce_page_loading_completed );
window.addEventListener( "load", gtm4wp_woocommerce_page_loading_completed );
}
+
+// WooCommerce Blocks add-to-cart tracking
+// Only enable if both the option is enabled AND ecommerce tracking is enabled
+if ( typeof gtm4wp_blocks_add_to_cart !== 'undefined' && gtm4wp_blocks_add_to_cart ) {
+ if ( ! window.wc ) {
+ gtm4wp_blocks_warn_once( 'WooCommerce Blocks scripts are not detected; block add_to_cart events might be unavailable.' );
+ }
+ // Store previous cart state to detect newly added/removed items
+ let gtm4wp_previous_cart_items = {};
+ const gtm4wp_previous_cart_snapshots = {};
+ const gtm4wp_product_api_cache = {};
+ let gtm4wp_last_cart_signature = null;
+ let gtm4wp_snapshot_in_progress = false;
+ let gtm4wp_dom_event_in_progress = false;
+ const gtm4wp_recent_deltas = {};
+ let gtm4wp_pending_cart_snapshot = null;
+ let gtm4wp_blocks_initialization = null;
+ let gtm4wp_blocks_initial_state_ready = false;
+ const gtm4wp_cart_fetch_ttl = 300;
+ let gtm4wp_cart_fetch_inflight = null;
+ let gtm4wp_cart_fetch_cache = null;
+ let gtm4wp_cart_fetch_cache_timestamp = 0;
+ function gtm4wp_bootstrap_blocks_state_complete( force_ready ) {
+ if ( force_ready || ! gtm4wp_blocks_initial_state_ready ) {
+ gtm4wp_blocks_initial_state_ready = true;
+ }
+ }
+
+ function gtm4wp_restore_cart_state_from_storage() {
+ if ( ! gtm4wp_is_session_storage_available() ) {
+ return false;
+ }
+
+ try {
+ const stored_value = window.sessionStorage.getItem( gtm4wp_blocks_cart_storage_key );
+
+ if ( ! stored_value ) {
+ return false;
+ }
+
+ const parsed = JSON.parse( stored_value );
+
+ if ( ! parsed || parsed.version !== gtm4wp_blocks_cart_storage_version || ! Array.isArray( parsed.items ) ) {
+ return false;
+ }
+
+ gtm4wp_previous_cart_items = {};
+ Object.keys( gtm4wp_previous_cart_snapshots ).forEach( ( snapshot_key ) => {
+ delete gtm4wp_previous_cart_snapshots[ snapshot_key ];
+ } );
+
+ parsed.items.forEach( ( entry ) => {
+ if ( ! entry || ! entry.key ) {
+ return;
+ }
+
+ const item_key = entry.key;
+ const quantity = parseInt( entry.quantity || ( entry.product && entry.product.quantity ) || 0 );
+
+ if ( isNaN( quantity ) || quantity < 0 ) {
+ return;
+ }
+
+ gtm4wp_previous_cart_items[ item_key ] = quantity;
+
+ if ( entry.product ) {
+ const snapshot = Object.assign( {}, entry.product );
+ snapshot.quantity = quantity;
+ gtm4wp_previous_cart_snapshots[ item_key ] = snapshot;
+ }
+ } );
+
+ gtm4wp_last_cart_signature = parsed.signature || null;
+
+ gtm4wp_bootstrap_blocks_state_complete( true );
+ return true;
+ } catch ( e ) {
+ return false;
+ }
+ }
+
+ function gtm4wp_persist_cart_state_to_storage() {
+ if ( ! gtm4wp_is_session_storage_available() ) {
+ return;
+ }
+
+ try {
+ const payload = {
+ version: gtm4wp_blocks_cart_storage_version,
+ signature: gtm4wp_last_cart_signature,
+ items: Object.keys( gtm4wp_previous_cart_items ).map( ( item_key ) => {
+ const quantity = gtm4wp_previous_cart_items[ item_key ];
+ const snapshot = gtm4wp_previous_cart_snapshots[ item_key ] || null;
+
+ return {
+ key: item_key,
+ quantity: quantity,
+ product: snapshot,
+ };
+ } ),
+ timestamp: Date.now(),
+ };
+
+ if ( payload.items.length > 0 || payload.signature ) {
+ window.sessionStorage.setItem( gtm4wp_blocks_cart_storage_key, JSON.stringify( payload ) );
+ } else {
+ window.sessionStorage.removeItem( gtm4wp_blocks_cart_storage_key );
+ }
+ } catch ( e ) {
+ // Suppress storage errors (private mode, quota, etc.)
+ }
+ }
+
+ async function gtm4wp_process_pending_snapshot_if_any() {
+ if ( ! gtm4wp_pending_cart_snapshot ) {
+ return;
+ }
+
+ const pending_snapshot = gtm4wp_pending_cart_snapshot;
+ gtm4wp_pending_cart_snapshot = null;
+ await gtm4wp_process_cart_snapshot( pending_snapshot );
+ }
+
+async function gtm4wp_mark_initial_state_ready() {
+ gtm4wp_bootstrap_blocks_state_complete( true );
+ await gtm4wp_process_pending_snapshot_if_any();
+ }
+
+ // Function to fetch cart data from WooCommerce Store API
+ function gtm4wp_fetch_cart_items() {
+ const now = Date.now();
+
+ if ( gtm4wp_cart_fetch_cache && ( now - gtm4wp_cart_fetch_cache_timestamp ) < gtm4wp_cart_fetch_ttl ) {
+ return Promise.resolve( gtm4wp_cart_fetch_cache );
+ }
+
+ if ( gtm4wp_cart_fetch_inflight ) {
+ return gtm4wp_cart_fetch_inflight;
+ }
+
+ // Try to get cart data from the Store API
+ const apiUrl = gtm4wp_build_rest_url( 'wc/store/v1/cart' );
+ const headers = {};
+
+ if ( gtm4wp_rest_nonce_value ) {
+ headers['X-WP-Nonce'] = gtm4wp_rest_nonce_value;
+ }
+
+ gtm4wp_cart_fetch_inflight = fetch( apiUrl, {
+ method: 'GET',
+ headers,
+ credentials: 'same-origin'
+ } )
+ .then( response => {
+ if ( !response.ok ) {
+ // If Store API fails, try to get from dataLayer if available
+ if ( window.wc && window.wc.store && window.wc.store.cart ) {
+ return window.wc.store.cart.getCartData();
+ }
+ throw new Error( 'Failed to fetch cart data' );
+ }
+ return response.json();
+ } )
+ .catch( error => {
+ console && console.error && console.error( 'GTM4WP: Error fetching cart:', error );
+ // Fallback: try to get from WooCommerce store if available
+ if ( window.wc && window.wc.store && window.wc.store.cart ) {
+ return window.wc.store.cart.getCartData();
+ }
+ return null;
+ } )
+ .then( ( data ) => {
+ gtm4wp_cart_fetch_cache = data;
+ gtm4wp_cart_fetch_cache_timestamp = Date.now();
+ return data;
+ } )
+ .finally( () => {
+ gtm4wp_cart_fetch_inflight = null;
+ } );
+
+ return gtm4wp_cart_fetch_inflight;
+ }
+
+ function gtm4wp_get_cart_item_key( cart_item ) {
+ const primary_product_id =
+ cart_item.product_id ||
+ ( cart_item.product && cart_item.product.id ) ||
+ ( cart_item.product && cart_item.product.product_id ) ||
+ cart_item.id ||
+ '';
+
+ const variation_id =
+ cart_item.variation_id ||
+ ( cart_item.variation && cart_item.variation.id ) ||
+ ( cart_item.variation && cart_item.variation.variation_id ) ||
+ '';
+
+ const attribute_source =
+ ( cart_item.variation && cart_item.variation.attributes ) ||
+ cart_item.attributes ||
+ ( cart_item.variation && cart_item.variation.attributes_data ) ||
+ null;
+
+ const customization_source = cart_item.item_data || cart_item.custom_data || null;
+
+ const attributes_signature = gtm4wp_normalize_key_value_pairs( attribute_source );
+ const customization_signature = gtm4wp_normalize_key_value_pairs( customization_source );
+
+ let signature = primary_product_id + ':' + variation_id + ':' + attributes_signature + ':' + customization_signature;
+
+ if ( 'function' === typeof window.gtm4wp_cart_item_signature_override ) {
+ try {
+ const override = window.gtm4wp_cart_item_signature_override( signature, cart_item );
+ if ( override ) {
+ signature = override;
+ }
+ } catch ( e ) {
+ // Ignore override errors
+ }
+ }
+
+ return signature;
+ }
+
+ function gtm4wp_should_register_delta( item_key, delta_qty, delta_type ) {
+ const absolute_delta = Math.abs( delta_qty );
+
+ if ( absolute_delta <= 0 ) {
+ return false;
+ }
+
+ const type = delta_type || ( delta_qty > 0 ? 'add' : 'remove' );
+ const dedupe_key = type + ':' + item_key + ':' + absolute_delta;
+ const now = Date.now();
+ const last_ts = gtm4wp_recent_deltas[ dedupe_key ] || 0;
+
+ if ( now - last_ts < gtm4wp_blocks_dedupe_window_value ) {
+ return false;
+ }
+
+ gtm4wp_recent_deltas[ dedupe_key ] = now;
+ return true;
+ }
+
+ function gtm4wp_store_cart_item_snapshot( item_key, product_data, quantity ) {
+ if ( ! item_key || ! product_data ) {
+ return;
+ }
+
+ const snapshot = Object.assign( {}, product_data );
+ snapshot.quantity = quantity;
+ gtm4wp_previous_cart_snapshots[ item_key ] = snapshot;
+ }
+
+ function gtm4wp_bootstrap_blocks_state() {
+ if ( ! gtm4wp_blocks_initialization ) {
+ gtm4wp_restore_cart_state_from_storage();
+
+ gtm4wp_blocks_initialization = gtm4wp_init_blocks_cart_tracking()
+ .catch( () => {} )
+ .finally( () => {
+ gtm4wp_mark_initial_state_ready();
+ } );
+ }
+
+ return gtm4wp_blocks_initialization;
+ }
+
+ async function gtm4wp_ensure_blocks_initialized() {
+ if ( ! gtm4wp_blocks_initialization ) {
+ return;
+ }
+
+ try {
+ await gtm4wp_blocks_initialization;
+ } catch ( e ) {
+ // Ignore initialization errors here; fallbacks will handle missing data.
+ }
+ }
+
+ async function gtm4wp_fetch_product_details_via_ajax( product_id ) {
+ if ( ! gtm4wp_blocks_ajax_endpoint || ! gtm4wp_blocks_product_nonce_value ) {
+ return null;
+ }
+
+ const formData = new FormData();
+ formData.append( 'action', 'gtm4wp_product_data' );
+ formData.append( 'product_id', product_id );
+ formData.append( 'nonce', gtm4wp_blocks_product_nonce_value );
+
+ try {
+ const response = await fetch( gtm4wp_blocks_ajax_endpoint, {
+ method: 'POST',
+ body: formData,
+ credentials: 'same-origin',
+ } );
+
+ if ( ! response.ok ) {
+ return null;
+ }
+
+ const data = await response.json();
+ const product_payload = ( data && data.success && data.data && data.data.product ) ? data.data.product : null;
+
+ if ( product_payload ) {
+ gtm4wp_product_api_cache[ product_id ] = product_payload;
+ }
+
+ return product_payload;
+ } catch ( ajaxError ) {
+ return null;
+ }
+ }
+
+ async function gtm4wp_fetch_product_details_from_api( product_id ) {
+ if ( ! product_id ) {
+ return null;
+ }
+
+ if ( gtm4wp_product_api_cache[ product_id ] ) {
+ return gtm4wp_product_api_cache[ product_id ];
+ }
+
+ try {
+ const response = await fetch( gtm4wp_build_rest_url( `gtm4wp/v1/product/${product_id}` ), {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: gtm4wp_rest_nonce_value ? { 'X-WP-Nonce': gtm4wp_rest_nonce_value } : undefined,
+ } );
+
+ if ( response.ok ) {
+ const data = await response.json();
+
+ if ( data && data.product ) {
+ gtm4wp_product_api_cache[ product_id ] = data.product;
+ return data.product;
+ }
+ } else if ( response.status !== 404 ) {
+ return await gtm4wp_fetch_product_details_via_ajax( product_id );
+ }
+ } catch ( error ) {
+ console && console.error && console.error( 'GTM4WP: Error fetching product detail', error );
+ return await gtm4wp_fetch_product_details_via_ajax( product_id );
+ }
+
+ return null;
+ }
+
+ async function gtm4wp_resolve_parent_identifier( parent_raw_id ) {
+ if ( ! parent_raw_id ) {
+ return null;
+ }
+
+ if ( gtm4wp_use_sku_instead ) {
+ const parent_details = await gtm4wp_fetch_product_details_from_api( parent_raw_id );
+
+ if ( parent_details && parent_details.item_id ) {
+ return parent_details.item_id;
+ }
+ }
+
+ return parent_raw_id;
+ }
+
+ // Function to convert WooCommerce cart item to GA4 product format
+ async function gtm4wp_convert_cart_item_to_product( cart_item ) {
+ // Handle different cart item structures from Store API
+ const product = cart_item.variation || cart_item.product || cart_item;
+
+ // Get product ID - use variation ID if available, otherwise product ID
+ let product_id = product.id || cart_item.id;
+ let item_id = product_id;
+
+ // Check if this is a variation
+ if ( cart_item.variation && cart_item.variation.id ) {
+ product_id = cart_item.variation.id;
+ item_id = cart_item.variation.id;
+ } else if ( cart_item.variation_id ) {
+ product_id = cart_item.variation_id;
+ item_id = cart_item.variation_id;
+ }
+
+ // Use SKU if configured
+ const sku = product.sku || cart_item.sku || '';
+ if ( gtm4wp_use_sku_instead && sku && ( '' !== sku ) ) {
+ item_id = sku;
+ }
+
+ // Get price - handle different price formats (cents vs dollars)
+ let price = 0;
+ if ( product.prices && product.prices.price ) {
+ price = parseFloat( product.prices.price ) / 100; // Convert from cents
+ } else if ( product.price ) {
+ price = parseFloat( product.price );
+ } else if ( cart_item.prices && cart_item.prices.price ) {
+ price = parseFloat( cart_item.prices.price ) / 100;
+ } else if ( cart_item.price ) {
+ price = parseFloat( cart_item.price );
+ }
+
+ // Build product data object matching the format used by gtm4wp_woocommerce_process_product
+ const product_type = product.type || cart_item.type || '';
+ const internal_id = product.id || cart_item.id || product_id;
+
+ const product_data = {
+ item_id: item_id,
+ item_name: product.name || cart_item.name || '',
+ price: gtm4wp_make_sure_is_float( price ),
+ quantity: cart_item.quantity || 1,
+ item_type: product_type || 'simple',
+ };
+
+ // Add SKU (fallback to product_id if no SKU)
+ product_data.sku = sku || product_id;
+
+ // Add item_group_id for variations (parent product ID)
+ if ( ( cart_item.variation && cart_item.variation.id ) || cart_item.variation_id ) {
+ const parent_raw_id = cart_item.id || product.id || product_id;
+ const resolved_parent_id = await gtm4wp_resolve_parent_identifier( parent_raw_id );
+
+ if ( resolved_parent_id && resolved_parent_id !== item_id ) {
+ product_data.item_group_id = resolved_parent_id;
+ }
+ }
+
+ // Add grouped parent ID if available.
+ if ( ! product_data.item_group_id ) {
+ const grouped_parent_raw_id =
+ ( cart_item.group_id )
+ || ( cart_item.parent && cart_item.parent.id )
+ || cart_item.parent_id
+ || product.parent_id
+ || ( product.parent && product.parent.id )
+ || ( cart_item.extensions && cart_item.extensions.grouped && cart_item.extensions.grouped.parent_id );
+
+ const resolved_group_parent_id = await gtm4wp_resolve_parent_identifier( grouped_parent_raw_id );
+
+ if ( resolved_group_parent_id && resolved_group_parent_id !== item_id ) {
+ product_data.item_group_id = resolved_group_parent_id;
+ }
+ }
+
+ const api_product_details = await gtm4wp_fetch_product_details_from_api( internal_id );
+ if ( api_product_details ) {
+ const mergeable_fields = [
+ 'item_category',
+ 'item_category2',
+ 'item_category3',
+ 'item_category4',
+ 'item_category5',
+ 'item_brand',
+ 'google_business_vertical',
+ 'stockstatus',
+ 'stocklevel',
+ 'item_group_id',
+ 'item_type',
+ 'item_variant',
+ 'item_group_sku',
+ ];
+
+ mergeable_fields.forEach( ( field ) => {
+ if ( ! product_data[field] && api_product_details[field] ) {
+ product_data[field] = api_product_details[field];
+ }
+ } );
+ }
+
+ // Add categories if available
+ const categories = product.categories || cart_item.categories || [];
+ if ( categories.length > 0 ) {
+ product_data.item_category = categories[0].name || categories[0] || '';
+ if ( categories.length > 1 ) {
+ product_data.item_category2 = categories[1].name || categories[1] || '';
+ }
+ if ( categories.length > 2 ) {
+ product_data.item_category3 = categories[2].name || categories[2] || '';
+ }
+ if ( categories.length > 3 ) {
+ product_data.item_category4 = categories[3].name || categories[3] || '';
+ }
+ if ( categories.length > 4 ) {
+ product_data.item_category5 = categories[4].name || categories[4] || '';
+ }
+ }
+
+ // Add brand if available
+ if ( product.brand || cart_item.brand ) {
+ product_data.item_brand = product.brand || cart_item.brand;
+ }
+
+ // Add variant attributes if available
+ if ( cart_item.variation && cart_item.variation.attributes ) {
+ const variant_attrs = [];
+ for ( const key in cart_item.variation.attributes ) {
+ if ( cart_item.variation.attributes.hasOwnProperty( key ) ) {
+ variant_attrs.push( cart_item.variation.attributes[key] );
+ }
+ }
+ if ( variant_attrs.length > 0 ) {
+ product_data.item_variant = variant_attrs.join( ',' );
+ }
+ } else if ( cart_item.variation && cart_item.variation.attributes ) {
+ // Alternative structure
+ const variant_attrs = Object.values( cart_item.variation.attributes );
+ if ( variant_attrs.length > 0 ) {
+ product_data.item_variant = variant_attrs.join( ',' );
+ }
+ }
+
+ return product_data;
+ }
+
+ async function gtm4wp_process_cart_snapshot( cart_data ) {
+ if ( ! cart_data ) {
+ return false;
+ }
+
+ if ( ! gtm4wp_blocks_initial_state_ready ) {
+ gtm4wp_pending_cart_snapshot = cart_data;
+ gtm4wp_bootstrap_blocks_state();
+ return false;
+ }
+
+ const cart_items = cart_data.items || cart_data.cartItems || [];
+ const cart_items_map = {};
+ const cart_signature_components = [];
+
+ cart_items.forEach( ( cart_item ) => {
+ const item_key = gtm4wp_get_cart_item_key( cart_item );
+
+ if ( ! item_key ) {
+ return;
+ }
+
+ let quantity_raw = cart_item.quantity;
+ if ( 'object' === typeof quantity_raw && null !== quantity_raw ) {
+ quantity_raw = quantity_raw.value || quantity_raw.count || quantity_raw.qty || 0;
+ }
+
+ const quantity = parseInt( quantity_raw || cart_item.quantity_total || cart_item.qty || 0 );
+
+ cart_items_map[ item_key ] = {
+ cart_item,
+ quantity,
+ };
+
+ cart_signature_components.push( item_key + ':' + quantity );
+ } );
+
+ const cart_signature = cart_signature_components.sort().join( '|' );
+
+ if ( cart_signature === gtm4wp_last_cart_signature ) {
+ return false;
+ }
+
+ const new_items = [];
+ const removed_items = [];
+ let total_value = 0;
+ let removed_value = 0;
+ const processed_keys = {};
+
+ const processPromises = Object.keys( cart_items_map ).map( async ( item_key ) => {
+ const entry = cart_items_map[ item_key ];
+ const current_qty = entry.quantity;
+ const previous_qty = gtm4wp_previous_cart_items[ item_key ] || 0;
+
+ if ( current_qty === previous_qty ) {
+ processed_keys[ item_key ] = true;
+ return;
+ }
+
+ const product_data = await gtm4wp_convert_cart_item_to_product( entry.cart_item );
+
+ if ( product_data ) {
+ gtm4wp_store_cart_item_snapshot( item_key, product_data, current_qty );
+
+ if ( current_qty > previous_qty ) {
+ const delta = current_qty - previous_qty;
+ const addition_payload = Object.assign( {}, product_data, { quantity: delta } );
+
+ if ( gtm4wp_should_register_delta( item_key, delta, 'add' ) ) {
+ new_items.push( addition_payload );
+ total_value += parseFloat( addition_payload.price || 0 ) * addition_payload.quantity;
+ }
+ } else if ( previous_qty > current_qty ) {
+ const delta = previous_qty - current_qty;
+ const removal_payload = Object.assign( {}, product_data, { quantity: delta } );
+
+ if ( gtm4wp_should_register_delta( item_key, delta, 'remove' ) ) {
+ removed_items.push( removal_payload );
+ removed_value += parseFloat( removal_payload.price || 0 ) * removal_payload.quantity;
+ }
+ }
+ }
+
+ gtm4wp_previous_cart_items[ item_key ] = current_qty;
+ processed_keys[ item_key ] = true;
+ } );
+
+ await Promise.all( processPromises );
+
+ const previous_keys = Object.keys( gtm4wp_previous_cart_items );
+
+ previous_keys.forEach( ( item_key ) => {
+ if ( processed_keys[ item_key ] || cart_items_map[ item_key ] ) {
+ return;
+ }
+
+ const previous_qty = gtm4wp_previous_cart_items[ item_key ] || 0;
+
+ if ( previous_qty <= 0 ) {
+ delete gtm4wp_previous_cart_items[ item_key ];
+ delete gtm4wp_previous_cart_snapshots[ item_key ];
+ return;
+ }
+
+ const snapshot = gtm4wp_previous_cart_snapshots[ item_key ];
+
+ if ( snapshot ) {
+ const removal_payload = Object.assign( {}, snapshot, { quantity: previous_qty } );
+
+ if ( gtm4wp_should_register_delta( item_key, previous_qty, 'remove' ) ) {
+ removed_items.push( removal_payload );
+ removed_value += parseFloat( removal_payload.price || 0 ) * removal_payload.quantity;
+ }
+ }
+
+ delete gtm4wp_previous_cart_items[ item_key ];
+ delete gtm4wp_previous_cart_snapshots[ item_key ];
+ } );
+
+ let changes_detected = false;
+
+ if ( new_items.length > 0 ) {
+ gtm4wp_push_ecommerce( 'add_to_cart', new_items, {
+ 'currency': gtm4wp_currency,
+ 'value': total_value.toFixed(2)
+ } );
+ changes_detected = true;
+ }
+
+ if ( removed_items.length > 0 ) {
+ gtm4wp_push_ecommerce( 'remove_from_cart', removed_items, {
+ 'currency': gtm4wp_currency,
+ 'value': removed_value.toFixed(2)
+ } );
+ changes_detected = true;
+ }
+
+ gtm4wp_last_cart_signature = cart_signature;
+ gtm4wp_persist_cart_state_to_storage();
+ return changes_detected;
+ }
+
+ // Function to process add-to-cart event
+ async function gtm4wp_process_blocks_add_to_cart( event ) {
+ if ( gtm4wp_dom_event_in_progress ) {
+ return;
+ }
+
+ gtm4wp_bootstrap_blocks_state();
+ await gtm4wp_ensure_blocks_initialized();
+
+ gtm4wp_dom_event_in_progress = true;
+
+ if ( gtm4wp_snapshot_in_progress ) {
+ gtm4wp_dom_event_in_progress = false;
+ return;
+ }
+
+ gtm4wp_snapshot_in_progress = true;
+
+ try {
+ const retrySchedule = [ 0, 250, 600 ];
+ let processedSnapshot = false;
+
+ for ( let retryIndex = 0; retryIndex < retrySchedule.length; retryIndex++ ) {
+ const delay = retrySchedule[ retryIndex ];
+
+ if ( delay > 0 ) {
+ await gtm4wp_delay( delay );
+ }
+
+ const cart_data = await gtm4wp_fetch_cart_items();
+ processedSnapshot = await gtm4wp_process_cart_snapshot( cart_data );
+
+ if ( processedSnapshot ) {
+ break;
+ }
+ }
+ } finally {
+ gtm4wp_snapshot_in_progress = false;
+ gtm4wp_dom_event_in_progress = false;
+ }
+ }
+
+ // Initialize: Fetch initial cart state when page loads
+ async function gtm4wp_init_blocks_cart_tracking() {
+ const cart_data = await gtm4wp_fetch_cart_items();
+
+ if ( ! cart_data ) {
+ await gtm4wp_mark_initial_state_ready();
+ return;
+ }
+
+ gtm4wp_previous_cart_items = {};
+ Object.keys( gtm4wp_previous_cart_snapshots ).forEach( ( snapshot_key ) => {
+ delete gtm4wp_previous_cart_snapshots[ snapshot_key ];
+ } );
+
+ const cart_items = cart_data.items || cart_data.cartItems || [];
+
+ await Promise.all( cart_items.map( async ( cart_item ) => {
+ const item_key = gtm4wp_get_cart_item_key( cart_item );
+
+ if ( ! item_key ) {
+ return;
+ }
+
+ const quantity = parseInt( cart_item.quantity || 0 );
+ gtm4wp_previous_cart_items[item_key] = quantity;
+
+ const product_data = await gtm4wp_convert_cart_item_to_product( cart_item );
+
+ if ( product_data ) {
+ gtm4wp_store_cart_item_snapshot( item_key, product_data, quantity );
+ }
+ } ) );
+
+ gtm4wp_last_cart_signature = cart_items
+ .map( ( cart_item ) => gtm4wp_get_cart_item_key( cart_item ) + ':' + parseInt( cart_item.quantity || 0 ) )
+ .sort()
+ .join( '|' );
+
+ await gtm4wp_mark_initial_state_ready();
+ gtm4wp_persist_cart_state_to_storage();
+ }
+
+ function gtm4wp_register_blocks_add_to_cart_listener( target ) {
+ if ( !target || target._gtm4wp_wc_blocks_listener_registered ) {
+ return;
+ }
+
+ target.addEventListener( 'wc-blocks_added_to_cart', function( event ) {
+ if ( event._gtm4wp_handled ) {
+ return;
+ }
+
+ event._gtm4wp_handled = true;
+
+ // Small delay to ensure cart is updated on the server
+ setTimeout( function() {
+ gtm4wp_process_blocks_add_to_cart( event );
+ }, 200 );
+ }, true );
+
+ target._gtm4wp_wc_blocks_listener_registered = true;
+ }
+
+ function gtm4wp_subscribe_to_cart_changes() {
+ if ( ! gtm4wp_is_blocks_store_available() ) {
+ gtm4wp_blocks_warn_once( 'WooCommerce Blocks data store unavailable; mini-cart add_to_cart tracking is disabled.' );
+ return;
+ }
+
+ const cartStoreHasData = () => {
+ const cartStore = wp.data.select( 'wc/store/cart' );
+ return cartStore && cartStore.getCartData ? cartStore.getCartData() : null;
+ };
+
+ wp.data.subscribe( async () => {
+ const cartData = cartStoreHasData();
+ if ( ! cartData ) {
+ return;
+ }
+
+ await gtm4wp_ensure_blocks_initialized();
+
+ if ( ! gtm4wp_blocks_initial_state_ready ) {
+ return;
+ }
+
+ const cart_items = cartData.items || cartData.cartItems || [];
+ const current_signature = cart_items
+ .map( ( cart_item ) => gtm4wp_get_cart_item_key( cart_item ) + ':' + parseInt( cart_item.quantity || 0 ) )
+ .sort()
+ .join( '|' );
+
+ if ( current_signature === gtm4wp_last_cart_signature || gtm4wp_snapshot_in_progress ) {
+ return;
+ }
+
+ if ( gtm4wp_blocks_initial_state_ready && ! gtm4wp_dom_event_in_progress ) {
+ await gtm4wp_process_cart_snapshot( cartData );
+ } else {
+ gtm4wp_bootstrap_blocks_state_complete( true );
+ gtm4wp_last_cart_signature = current_signature;
+ }
+ } );
+ }
+
+ function gtm4wp_start_blocks_tracking() {
+ gtm4wp_bootstrap_blocks_state();
+ gtm4wp_register_blocks_add_to_cart_listener( document );
+ gtm4wp_register_blocks_add_to_cart_listener( document.body );
+ gtm4wp_subscribe_to_cart_changes();
+ }
+
+ // Listen for WooCommerce Blocks add-to-cart DOM event
+ if ( document.readyState === 'loading' ) {
+ document.addEventListener( 'DOMContentLoaded', gtm4wp_start_blocks_tracking );
+ } else {
+ gtm4wp_start_blocks_tracking();
+ }
+}
diff --git a/readme.txt b/readme.txt
index 823d5a2..983d036 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,7 +5,7 @@ Tags: google tag manager, tag manager, gtm, google ads, google analytics
Requires at least: 3.4.0
Requires PHP: 7.4
Tested up to: 6.8
-Stable tag: 1.22.2
+Stable tag: 1.22.3
License: GPLv3
License URI: http://www.gnu.org/licenses/gpl.html
@@ -224,6 +224,10 @@ to report micro conversions and/or to serve ads only to visitors who spend more
== Changelog ==
+= 1.22.3 =
+
+* Added: new WooCommerce Add to Cart block trackign for adds and removes from cart
+
= 1.22.2 =
* Fixed: purchase event was not fired when is_order_received_page() WooCommerce tag was not supported by the template and the fallback method had to activate.
@@ -352,6 +356,10 @@ If you are on GA360 and still collecting ecommerce data, you need to update your
== Upgrade Notice ==
+= 1.22.3 =
+
+Minor release with improved WooCommerce Blocks health checks and admin option sanitizing.
+
= 1.22.2 =
Bugfix release