get_id();
$product_type = $product->get_type();
$remarketing_id = $product_id;
$product_sku = $product->get_sku();
if ( 'variation' === $product_type ) {
$parent_product_id = $product->get_parent_id();
$product_cat = gtm4wp_get_product_category( $parent_product_id, $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSEFULLCATEGORYPATH ] );
} else {
$product_cat = gtm4wp_get_product_category( $product_id, $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSEFULLCATEGORYPATH ] );
}
$product_cat_parts = explode( '/', $product_cat );
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSESKU ] && ( '' !== $product_sku ) ) {
$remarketing_id = $product_sku;
}
$_temp_productdata = array(
'internal_id' => $product_id,
'item_id' => $remarketing_id,
'item_name' => $product->get_title(),
'sku' => $product_sku ? $product_sku : $product_id,
'price' => round( (float) wc_get_price_to_display( $product ), 2 ), // Unfortunately this does not force a .00 postfix for integers.
'stocklevel' => $product->get_stock_quantity(),
'stockstatus' => $product->get_stock_status(),
'google_business_vertical' => $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ],
);
if ( 'variation' === $product_type ) {
$_temp_productdata['item_group_id'] = $parent_product_id;
}
if ( 1 === count( $product_cat_parts ) ) {
$_temp_productdata['item_category'] = $product_cat_parts[0];
} elseif ( count( $product_cat_parts ) > 1 ) {
$_temp_productdata['item_category'] = $product_cat_parts[0];
$max_category_levels = min( 5, count( $product_cat_parts ) );
for ( $i = 1; $i < $max_category_levels; $i++ ) {
$_temp_productdata[ 'item_category' . ( $i + 1 ) ] = $product_cat_parts[ $i ];
}
}
$_temp_productdata[ gtm4wp_get_gads_product_id_variable_name( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ] ) ] = gtm4wp_prefix_productid( $_temp_productdata['item_id'], $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMPRODIDPREFIX ] );
if ( '' !== $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECBRANDTAXONOMY ] ) {
if ( isset( $parent_product_id ) && ( 0 !== $parent_product_id ) ) {
$product_id_to_query = $parent_product_id;
} else {
$product_id_to_query = $product_id;
}
$_temp_productdata['item_brand'] = gtm4wp_get_product_term( $product_id_to_query, $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECBRANDTAXONOMY ] );
}
if ( 'variation' === $product_type ) {
$_temp_productdata['item_variant'] = implode( ',', $product->get_variation_attributes() );
}
$_temp_productdata = array_merge( $_temp_productdata, $additional_product_attributes );
/**
* Filters the ecommerce array before using it for tracking.
* Can be used to add custom dimensions and metrics on your own or to later existing product attributes based on your own logic.
*
* Called before outputting any of the following ecommerce action.
* The action can be identified using the attributes_used_for parameter of the filter.
*
* purchase: order received page
* cart: cart page
* checkout: checkout page
* productdetail: product detail page
* readdedtocart: user clicked on the “Undo” link on the cart page after removing an item
* addtocartsingle: product added to cart
* widgetproduct: product shown in a sidebar widget
* productlist: product shown in a product list (category page or special product list like ‘New products’)
* groupedproductlist: product shown on a product detail page of a grouped product
*
* @param array $_temp_productdata An associative array containing all GA4 product attributes as well as any custom attribute
* @param string $attributes_used_for The name of the ecommerce action where this product will be used
*/
return apply_filters( GTM4WP_WPFILTER_EEC_PRODUCT_ARRAY, $_temp_productdata, $attributes_used_for );
}
/**
* Takes a WooCommerce order and returns an associative array that can be used
* for enhanced ecommerce tracking and Google Ads dynamic remarketing (legacy version).
*
* @param WC_Order $order The order that needs to be processed.
* @return array An array with an array of product data.
*/
function gtm4wp_woocommerce_process_order_items( $order ) {
$order_data = array();
if ( ! $order ) {
return $order_data;
}
if ( ! ( $order instanceof WC_Order ) ) {
return $order_data;
}
$order_items = $order->get_items();
if ( $order_items ) {
foreach ( $order_items as $order_item ) {
/**
* This filter allows 3rd party code to exclude specific products from reporting.
*
* @param bool true Constant value telling 3rd party code that the order item will be included in reporting if not changed by the filter.
* @param WC_Order_Item $order_item The order item object retrived from WooCommerce.
*
* return bool If the filter returns false, the order item will be omitted from processing.
*/
if ( ! apply_filters( GTM4WP_WPFILTER_EEC_ORDER_ITEM, true, $order_item ) ) {
continue;
}
$product = $order_item->get_product();
$inc_tax = ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) );
$product_price = round( (float) $order->get_item_total( $order_item, $inc_tax ), 2 );
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'quantity' => $order_item->get_quantity(),
'price' => $product_price,
),
'purchase'
);
unset( $eec_product_array['internal_id'] );
if ( $eec_product_array ) {
$order_data[] = $eec_product_array;
}
}
}
// No need to apply a filter here since all products in the array have been already filtered in gtm4wp_woocommerce_process_product().
return $order_data;
}
/**
* Returns an associative array that can be used in the data layer to output the raw order data.
*
* @param WC_Order $order The WooCommerce order object.
* @param array $order_items An array including product data generated with gtm4wp_woocommerce_process_product().
* @return array
*/
function gtm4wp_woocommerce_get_raw_order_datalayer( $order, $order_items ) {
$order_data = array();
if ( ! ( $order instanceof WC_Order ) ) {
return $order_data;
}
if ( ! is_array( $order_items ) ) {
return $order_data;
}
$billing_email_hash = gtm4wp_normalize_and_hash_email_address( 'sha256', $order->get_billing_email() );
$billing_first_hash = gtm4wp_normalize_and_hash( 'sha256', $order->get_billing_first_name(), false );
$billing_last_hash = gtm4wp_normalize_and_hash( 'sha256', $order->get_billing_last_name(), false );
$billing_phone_hash = gtm4wp_normalize_and_hash( 'sha256', $order->get_billing_phone(), true );
$order_data = array(
'attributes' => array(
'date' => $order->get_date_created()->date( 'c' ),
'order_number' => $order->get_order_number(),
'order_key' => $order->get_order_key(),
'payment_method' => esc_js( $order->get_payment_method() ),
'payment_method_title' => esc_js( $order->get_payment_method_title() ),
'shipping_method' => esc_js( $order->get_shipping_method() ),
'status' => esc_js( $order->get_status() ),
'coupons' => implode( ', ', $order->get_coupon_codes() ),
),
'totals' => array(
'currency' => esc_js( $order->get_currency() ),
'discount_total' => esc_js( $order->get_discount_total() ),
'discount_tax' => esc_js( $order->get_discount_tax() ),
'shipping_total' => esc_js( $order->get_shipping_total() ),
'shipping_tax' => esc_js( $order->get_shipping_tax() ),
'cart_tax' => esc_js( $order->get_cart_tax() ),
'total' => esc_js( $order->get_total() ),
'total_tax' => esc_js( $order->get_total_tax() ),
'total_discount' => esc_js( $order->get_total_discount() ),
'subtotal' => esc_js( $order->get_subtotal() ),
'tax_totals' => $order->get_tax_totals(),
),
'customer' => array(
'id' => $order->get_customer_id(),
'billing' => array(
'first_name' => esc_js( $order->get_billing_first_name() ),
'first_name_hash' => esc_js( $billing_first_hash ),
'last_name' => esc_js( $order->get_billing_last_name() ),
'last_name_hash' => esc_js( $billing_last_hash ),
'company' => esc_js( $order->get_billing_company() ),
'address_1' => esc_js( $order->get_billing_address_1() ),
'address_2' => esc_js( $order->get_billing_address_2() ),
'city' => esc_js( $order->get_billing_city() ),
'state' => esc_js( $order->get_billing_state() ),
'postcode' => esc_js( $order->get_billing_postcode() ),
'country' => esc_js( $order->get_billing_country() ),
'email' => esc_js( $order->get_billing_email() ),
'emailhash' => esc_js( $billing_email_hash ), // deprecated.
'email_hash' => esc_js( $billing_email_hash ),
'phone' => esc_js( $order->get_billing_phone() ),
'phone_hash' => esc_js( $billing_phone_hash ),
),
'shipping' => array(
'first_name' => esc_js( $order->get_shipping_first_name() ),
'last_name' => esc_js( $order->get_shipping_last_name() ),
'company' => esc_js( $order->get_shipping_company() ),
'address_1' => esc_js( $order->get_shipping_address_1() ),
'address_2' => esc_js( $order->get_shipping_address_2() ),
'city' => esc_js( $order->get_shipping_city() ),
'state' => esc_js( $order->get_shipping_state() ),
'postcode' => esc_js( $order->get_shipping_postcode() ),
'country' => esc_js( $order->get_shipping_country() ),
),
),
'items' => $order_items,
);
/**
* Filters the orderData array before using it for tracking.
* Can be used to add custom order or even product data into the data layer.
*
* @param array $order_data An associative array containing all data (head data and products) about the currently placed order.
* @param WC_Order $order The WooCommerce order object.
*/
return apply_filters( GTM4WP_WPFILTER_EEC_ORDER_DATA, $order_data, $order );
}
/**
* Takes a WooCommerce order and order items and generates the standard/classic and
* enhanced ecommerce version of the purchase data layer codes for Universal Analytics.
*
* @param WC_Order $order The WooCommerce order that needs to be transformed into an enhanced ecommerce data layer.
* @param array $order_items The array returned by gtm4wp_woocommerce_process_order_items(). It not set, then function will call gtm4wp_woocommerce_process_order_items().
* @return array The data layer content as an associative array that can be passed to json_encode() to product a JavaScript object used by GTM.
*/
function gtm4wp_woocommerce_get_purchase_datalayer( $order, $order_items = null ) {
global $gtm4wp_options;
$data_layer = array();
if ( $order instanceof WC_Order ) {
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEXCLUDETAX ] ) {
$order_revenue = (float) ( $order->get_total() - $order->get_total_tax() );
} else {
$order_revenue = (float) $order->get_total();
}
$order_shipping_cost = (float) $order->get_shipping_total();
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEXCLUDESHIPPING ] ) {
$order_revenue -= $order_shipping_cost;
}
$order_currency = $order->get_currency();
$data_layer['event'] = 'purchase';
$data_layer['ecommerce'] = array(
'currency' => $order_currency,
'transaction_id' => $order->get_order_number(),
'affiliation' => '',
'value' => $order_revenue,
'tax' => (float) $order->get_total_tax(),
'shipping' => (float) ( $order->get_shipping_total() ),
'coupon' => implode( ', ', $order->get_coupon_codes() ),
);
if ( isset( $order_items ) ) {
$_order_items = $order_items;
} else {
$_order_items = gtm4wp_woocommerce_process_order_items( $order );
}
if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$data_layer['ecommerce']['items'] = $_order_items;
}
}
/**
* Filters the ecommerce purchase data layer content.
* Can be used to add custom data to the data layer when the purhcase ecommerce action is included.
*
* @param array $data_layer An associative array containing the full data layer including purchase header attributes.
* @param WC_Order $order The WooCommerce order that needs to be transformed into an enhanced ecommerce data layer.
*/
return apply_filters( GTM4WP_WPFILTER_ECC_PURCHASE_DATALAYER, $data_layer, $order );
}
/**
* Function executed when the main GTM4WP data layer generation happens.
* Hooks into gtm4wp_compile_datalayer.
*
* @param array $data_layer An array of key-value pairs that will be converted into a JavaScript object on the frontend for GTM.
* @return array Extended data layer content with WooCommerce data added.
*/
function gtm4wp_woocommerce_datalayer_filter_items( $data_layer ) {
global $gtm4wp_options;
if ( array_key_exists( 'HTTP_X_REQUESTED_WITH', $_SERVER ) ) {
return $data_layer;
}
$woo = WC();
// Customer data will be present on every pageview if feature is enabled.
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCCUSTOMERDATA ] ) {
if ( $woo->customer instanceof WC_Customer ) {
// we need to use this instead of $woo->customer as this will load proper total order number and value from the database instead of the session.
$woo_customer = new WC_Customer( $woo->customer->get_id() );
$data_layer['customerTotalOrders'] = $woo_customer->get_order_count();
$data_layer['customerTotalOrderValue'] = $woo_customer->get_total_spent();
$data_layer['customerFirstName'] = $woo_customer->get_first_name();
$data_layer['customerLastName'] = $woo_customer->get_last_name();
$data_layer['customerBillingFirstName'] = $woo_customer->get_billing_first_name();
$data_layer['customerBillingLastName'] = $woo_customer->get_billing_last_name();
$data_layer['customerBillingCompany'] = $woo_customer->get_billing_company();
$data_layer['customerBillingAddress1'] = $woo_customer->get_billing_address_1();
$data_layer['customerBillingAddress2'] = $woo_customer->get_billing_address_2();
$data_layer['customerBillingCity'] = $woo_customer->get_billing_city();
$data_layer['customerBillingState'] = $woo_customer->get_billing_state();
$data_layer['customerBillingPostcode'] = $woo_customer->get_billing_postcode();
$data_layer['customerBillingCountry'] = $woo_customer->get_billing_country();
$data_layer['customerBillingEmail'] = $woo_customer->get_billing_email();
$data_layer['customerBillingEmailHash'] = gtm4wp_normalize_and_hash_email_address( 'sha256', $woo_customer->get_billing_email() );
$data_layer['customerBillingPhone'] = $woo_customer->get_billing_phone();
$data_layer['customerShippingFirstName'] = $woo_customer->get_shipping_first_name();
$data_layer['customerShippingLastName'] = $woo_customer->get_shipping_last_name();
$data_layer['customerShippingCompany'] = $woo_customer->get_shipping_company();
$data_layer['customerShippingAddress1'] = $woo_customer->get_shipping_address_1();
$data_layer['customerShippingAddress2'] = $woo_customer->get_shipping_address_2();
$data_layer['customerShippingCity'] = $woo_customer->get_shipping_city();
$data_layer['customerShippingState'] = $woo_customer->get_shipping_state();
$data_layer['customerShippingPostcode'] = $woo_customer->get_shipping_postcode();
$data_layer['customerShippingCountry'] = $woo_customer->get_shipping_country();
}
}
// Cart content will be present on every pageview if feature is enabled.
if (
$gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEINCLUDECARTINDL ] &&
isset( $woo ) &&
isset( $woo->cart )
) {
$current_cart = $woo->cart;
$data_layer['cartContent'] = array(
'totals' => array(
'applied_coupons' => $current_cart->get_applied_coupons(),
'discount_total' => $current_cart->get_discount_total(),
'subtotal' => $current_cart->get_subtotal(),
'total' => $current_cart->get_cart_contents_total(),
),
'items' => array(),
);
foreach ( $current_cart->get_cart() as $cart_item_id => $cart_item_data ) {
/**
* Applying WooCommerce's own woocommerce_cart_item_product filter here is essential in order to hide everything
* from tracking codes that is not visible to the user as well.
*/
$product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id );
/**
* This filter allows 3rd party code to exclude specific products from reporting.
*
* @param bool true Constant value telling 3rd party code that the order item will be included in reporting if not changed by the filter.
* @param array $cart_item_data Associative array generated by WooCommerce returned by the WC()->cart->get_cart() function call.
*
* return bool If the filter returns false, the cart item will be omitted from processing.
*/
if (
! apply_filters( GTM4WP_WPFILTER_EEC_CART_ITEM, true, $cart_item_data )
|| ! apply_filters( 'woocommerce_widget_cart_item_visible', true, $cart_item_data, $cart_item_id )
) {
continue;
}
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'quantity' => $cart_item_data['quantity'],
),
'cart'
);
unset( $eec_product_array['internal_id'] );
$data_layer['cartContent']['items'][] = $eec_product_array;
}
}
// Product detail view data layer content.
if ( is_product() ) {
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$postid = get_the_ID();
$product = wc_get_product( $postid );
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(),
'productdetail'
);
$data_layer['productRatingCounts'] = $product->get_rating_counts();
$data_layer['productAverageRating'] = (float) $product->get_average_rating();
$data_layer['productReviewCount'] = (int) $product->get_review_count();
$data_layer['productType'] = $product->get_type();
switch ( $data_layer['productType'] ) {
case 'variable':
$data_layer['productIsVariable'] = 1;
if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCVIEWITEMONPARENT ] ) {
$gtm4wp_currency = get_woocommerce_currency();
unset( $eec_product_array['internal_id'] );
gtm4wp_datalayer_push(
'view_item',
array(
'ecommerce' => array(
'currency' => $gtm4wp_currency,
'value' => $eec_product_array['price'],
'items' => array(
$eec_product_array,
),
),
)
);
}
break;
case 'grouped':
$data_layer['productIsVariable'] = 0;
break;
default:
$data_layer['productIsVariable'] = 0;
$gtm4wp_currency = get_woocommerce_currency();
unset( $eec_product_array['internal_id'] );
gtm4wp_datalayer_push(
'view_item',
array(
'ecommerce' => array(
'currency' => $gtm4wp_currency,
'value' => $eec_product_array['price'],
'items' => array(
$eec_product_array,
),
),
)
);
}
}
} elseif ( is_cart() ) {
// Cart page data layer content.
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$gtm4wp_cart_products = array();
$gtm4wp_cart_total = 0;
$gtm4wp_currency = get_woocommerce_currency();
foreach ( $woo->cart->get_cart() as $cart_item_id => $cart_item_data ) {
/**
* Applying WooCommerce's own woocommerce_cart_item_product filter here is essential in order to hide everything
* from tracking codes that is not visible to the user as well.
*/
$product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id );
/**
* This filter allows 3rd party code to exclude specific products from reporting.
*
* @param bool true Constant value telling 3rd party code that the order item will be included in reporting if not changed by the filter.
* @param array $cart_item_data Associative array generated by WooCommerce returned by the WC()->cart->get_cart() function call.
*
* return bool If the filter returns false, the cart item will be omitted from processing.
*/
if ( ! apply_filters( GTM4WP_WPFILTER_EEC_CART_ITEM, true, $cart_item_data ) ) {
continue;
}
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'quantity' => $cart_item_data['quantity'],
),
'cart'
);
unset( $eec_product_array['internal_id'] );
$gtm4wp_cart_products[] = $eec_product_array;
$gtm4wp_cart_total += $eec_product_array['price'] * $eec_product_array['quantity'];
}
gtm4wp_datalayer_push(
'view_cart',
array(
'ecommerce' => array(
'currency' => $gtm4wp_currency,
'value' => $gtm4wp_cart_total,
'items' => $gtm4wp_cart_products,
),
)
);
}
} elseif ( is_checkout() ) {
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$gtm4wp_checkout_products = array();
$gtm4wp_checkout_total = 0;
$gtm4wp_currency = get_woocommerce_currency();
foreach ( $woo->cart->get_cart() as $cart_item_id => $cart_item_data ) {
/**
* Applying WooCommerce's own woocommerce_cart_item_product filter here is essential in order to hide everything
* from tracking codes that is not visible to the user as well.
*/
$product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id );
/**
* This filter allows 3rd party code to exclude specific products from reporting.
*
* @param bool true Constant value telling 3rd party code that the order item will be included in reporting if not changed by the filter.
* @param array $cart_item_data Associative array generated by WooCommerce returned by the WC()->cart->get_cart() function call.
*
* return bool If the filter returns false, the cart item will be omitted from processing.
*/
if ( ! apply_filters( GTM4WP_WPFILTER_EEC_CART_ITEM, true, $cart_item_data ) ) {
continue;
}
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'quantity' => $cart_item_data['quantity'],
),
'checkout'
);
unset( $eec_product_array['internal_id'] );
$gtm4wp_checkout_products[] = $eec_product_array;
$gtm4wp_checkout_total += $eec_product_array['quantity'] * $eec_product_array['price'];
} // end foreach cart item
gtm4wp_datalayer_push(
'begin_checkout',
array(
'ecommerce' => array(
'currency' => $gtm4wp_currency,
'value' => $gtm4wp_checkout_total,
'items' => $gtm4wp_checkout_products,
),
)
);
wc_enqueue_js(
'
window.gtm4wp_checkout_products = ' . wp_json_encode( $gtm4wp_checkout_products ) . ';
window.gtm4wp_checkout_value = ' . (float) $gtm4wp_checkout_total . ';'
);
}
}
// Handle add_to_cart event when product was readded after removing from the cart.
if ( $woo && $woo->session ) {
$cart_readded_hash = $woo->session->get( 'gtm4wp_product_readded_to_cart' );
if ( isset( $cart_readded_hash ) ) {
$cart_item = $woo->cart->get_cart_item( $cart_readded_hash );
if ( ! empty( $cart_item ) ) {
$product = $cart_item['data'];
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'quantity' => $cart_item['quantity'],
),
'readdedtocart'
);
$gtm4wp_currency = get_woocommerce_currency();
unset( $eec_product_array['internal_id'] );
gtm4wp_datalayer_push(
'add_to_cart',
array(
'ecommerce' => array(
'currency' => $gtm4wp_currency,
'value' => $eec_product_array['price'] * $eec_product_array['quantity'],
'items' => array( $eec_product_array ),
),
)
);
}
$woo->session->set( 'gtm4wp_product_readded_to_cart', null );
}
}
gtm4wp_fire_additional_datalayer_pushes();
return apply_filters( GTM4WP_WPFILTER_EEC_DATALAYER_PAGELOAD, $data_layer );
}
/**
* Executed during woocommerce_thankyou.
* This is a fallback function to output purchase data layer on customized order received pages where
* the is_order_received_page() template tag returns false for some reason.
*
* @param int $order_id The ID of the order placed by the user just recently.
* @return void
*/
function gtm4wp_woocommerce_thankyou( $order_id ) {
global $gtm4wp_options, $gtm4wp_datalayer_name;
if ( $order_id > 0 ) {
$order = wc_get_order( $order_id );
}
$data_layer = array();
if ( isset( $order ) && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERMAXAGE ] ) {
$now = new DateTime( 'now', $order->get_date_created()->getTimezone() );
if ( $order->is_paid() && $order->get_date_paid() ) {
$diff = $now->diff( $order->get_date_paid() );
$minutes = ( $diff->days * 24 * 60 ) + ( $diff->h * 60 ) + $diff->i;
} else {
$diff = $now->diff( $order->get_date_created() );
$minutes = ( $diff->days * 24 * 60 ) + ( $diff->h * 60 ) + $diff->i;
}
if ( $minutes > $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERMAXAGE ] ) {
unset( $order );
}
}
$order_items = null;
// Raw order data will be outputted regardless of whether the purhcase has been already tracked previously, since this data is not meant to track using GA.
if ( isset( $order ) && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERDATA ] ) {
$order_items = gtm4wp_woocommerce_process_order_items( $order );
$data_layer['orderData'] = gtm4wp_woocommerce_get_raw_order_datalayer( $order, $order_items );
}
$do_not_flag_tracked_order = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG ] );
if ( isset( $order ) && ( 1 === (int) $order->get_meta( '_ga_tracked', true ) ) && ! $do_not_flag_tracked_order ) {
unset( $order );
}
if ( isset( $_COOKIE['gtm4wp_orderid_tracked'] ) ) {
$tracked_order_id = filter_var( wp_unslash( $_COOKIE['gtm4wp_orderid_tracked'] ), FILTER_VALIDATE_INT );
if ( $tracked_order_id && ( $tracked_order_id === $order_id ) && ! $do_not_flag_tracked_order ) {
unset( $order );
}
}
if ( isset( $order ) && ( 'failed' === $order->get_status() ) ) {
// do not track order where payment failed.
unset( $order );
}
if ( isset( $order ) ) {
/**
* Variable for Google Smart Shopping campaign new customer reporting.
*
* @see https://support.google.com/google-ads/answer/9917012?hl=en-AU#zippy=%2Cinstall-with-google-tag-manager
*/
$data_layer['new_customer'] = \Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore::is_returning_customer( $order ) === false;
$purchase_data_layer = gtm4wp_woocommerce_get_purchase_datalayer( $order, $order_items );
$data_layer = array_merge(
$data_layer,
$purchase_data_layer
);
$script_tag = '
' . gtm4wp_generate_script_opening_tag() . '
// Check whether this order has been already tracked in this browser.
// Read order id already tracked from cookies or local storage.
let gtm4wp_orderid_tracked = "";
if ( !window.localStorage ) {
let gtm4wp_cookie = "; " + document.cookie;
let gtm4wp_cookie_parts = gtm4wp_cookie.split( "; gtm4wp_orderid_tracked=" );
if ( gtm4wp_cookie_parts.length == 2 ) {
gtm4wp_orderid_tracked = gtm4wp_cookie_parts.pop().split(";").shift();
}
} else {
gtm4wp_orderid_tracked = window.localStorage.getItem( "gtm4wp_orderid_tracked" );
}
// Check whether this order has been already tracked before in this browser.
let gtm4wp_order_already_tracked = false;
if ( gtm4wp_orderid_tracked && ( ' . esc_js( $order->get_order_number() ) . ' == gtm4wp_orderid_tracked ) ) {
gtm4wp_order_already_tracked = true;
}
// only push purchase action if not tracked already.
if ( !gtm4wp_order_already_tracked ) {
window.' . esc_js( $gtm4wp_datalayer_name ) . ' = window.' . esc_js( $gtm4wp_datalayer_name ) . ' || [];
window.' . esc_js( $gtm4wp_datalayer_name ) . '.push(' . wp_json_encode( $data_layer ) . ');
}
// Store order ID to prevent tracking this purchase again.
if ( !window.localStorage ) {
var gtm4wp_orderid_cookie_expire = new Date();
gtm4wp_orderid_cookie_expire.setTime( gtm4wp_orderid_cookie_expire.getTime() + (365*24*60*60*1000) );
var gtm4wp_orderid_cookie_expires_part = "expires=" + gtm4wp_orderid_cookie_expire.toUTCString();
document.cookie = "gtm4wp_orderid_tracked=" + ' . esc_js( $order->get_order_number() ) . ' + ";" + gtm4wp_orderid_cookie_expires_part + ";path=/";
} else {
window.localStorage.setItem( "gtm4wp_orderid_tracked", ' . esc_js( $order->get_order_number() ) . ' );
}
';
echo htmlspecialchars_decode( //phpcs:ignore
wp_kses(
$script_tag,
gtm4wp_get_sanitize_script_block_rules()
)
);
if ( ! $do_not_flag_tracked_order ) {
$order->update_meta_data( '_ga_tracked', 1 );
$order->save();
}
}
}
/**
* Function executed with the woocommerce_after_add_to_cart_button hook.
*
* @return void
*/
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 ] ) {
return;
}
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(),
'addtocartsingle'
);
echo '' . "\n";
}
/**
* Ecommerce product array with the product that is currently shown in the cart.
*
* @var array
*/
$GLOBALS['gtm4wp_cart_item_proddata'] = '';
/**
* Executed during woocommerce_cart_item_product for each product in the cart.
* Stores the ecommerce product data into a global variable
* to be processed when the cart item is rendered.
*
* @see https://woocommerce.github.io/code-reference/files/woocommerce-templates-cart-cart.html#source-view.41
*
* @param WC_Product $product A WooCommerce product that is shown in the cart.
* @param string $cart_item Not used by this hook.
* @param string $cart_id Not used by this hook.
* @return array Ecommerce product data in an associative array.
*/
function gtm4wp_woocommerce_cart_item_product_filter( $product, $cart_item = '', $cart_id = '' ) {
global $gtm4wp_cart_item_proddata;
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'productlink' => apply_filters( 'the_permalink', get_permalink(), 0 ),
),
'cart'
);
$gtm4wp_cart_item_proddata = $eec_product_array;
return $product;
}
/**
* Executed during woocommerce_cart_item_remove_link.
* Adds additional product data into the remove product link of the cart table to be able to track
* ecommerce remove_from_cart action with product data.
*
* @global gtm4wp_cart_item_proddata The previously stored product array in gtm4wp_woocommerce_cart_item_product_filter.
*
* @param string $remove_from_cart_link The HTML code of the remove from cart link element.
* @return string The updated remove product from cart link with product data added in data attributes.
*/
function gtm4wp_woocommerce_cart_item_remove_link_filter( $remove_from_cart_link ) {
global $gtm4wp_cart_item_proddata;
if ( ! isset( $gtm4wp_cart_item_proddata ) ) {
return $remove_from_cart_link;
}
if ( ! is_array( $gtm4wp_cart_item_proddata ) ) {
return $remove_from_cart_link;
}
if ( ! isset( $gtm4wp_cart_item_proddata['item_variant'] ) ) {
$gtm4wp_cart_item_proddata['item_variant'] = '';
}
if ( ! isset( $gtm4wp_cart_item_proddata['item_brand'] ) ) {
$gtm4wp_cart_item_proddata['item_brand'] = '';
}
$cartlink_with_data = sprintf(
'data-gtm4wp_product_data="%s" href="',
esc_attr( wp_json_encode( $gtm4wp_cart_item_proddata ) )
);
$gtm4wp_cart_item_proddata = '';
return gtm4wp_str_replace_first( 'href="', $cartlink_with_data, $remove_from_cart_link );
}
/**
* Executed during loop_end.
* Resets the product impression list name after a specific product list ended rendering.
*
* @return void
*/
function gtm4wp_woocommerce_reset_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = '';
}
/**
* Executed during woocommerce_related_products_args.
* Sets the currently rendered product list impression name to Related Products.
*
* @param array $arg Not used by this hook.
* @return array
*/
function gtm4wp_woocommerce_add_related_to_loop( $arg ) {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Related Products', 'duracelltomi-google-tag-manager' );
return $arg;
}
/**
* Executed during woocommerce_cross_sells_columns.
* Sets the currently rendered product list impression name to Cross-Sell Products.
*
* @param array $arg Not used by this hook.
* @return array
*/
function gtm4wp_woocommerce_add_cross_sell_to_loop( $arg ) {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Cross-Sell Products', 'duracelltomi-google-tag-manager' );
return $arg;
}
/**
* Executed during woocommerce_upsells_columns.
* Sets the currently rendered product list impression name to Upsell Products.
*
* @param array $arg Not used by this hook.
* @return array
*/
function gtm4wp_woocommerce_add_upsells_to_loop( $arg ) {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Upsell Products', 'duracelltomi-google-tag-manager' );
return $arg;
}
/**
* Executed during widget_title.
* This hook is used for any custom (classic) product list widget with custom title.
* The widget title will be used to report a custom product list name into Google Analytics.
* This function also resets the $gtm4wp_product_counter global variable to report the first
* product in the widget in the proper position.
*
* @param string $widget_title The title of the widget being rendered.
* @return string The updated widget title which is not changed by this function.
*/
function gtm4wp_woocommerce_widget_title_filter( $widget_title ) {
global $gtm4wp_product_counter, $gtm4wp_last_widget_title;
$gtm4wp_product_counter = 1;
$gtm4wp_last_widget_title = $widget_title . __( ' (widget)', 'duracelltomi-google-tag-manager' );
return $widget_title;
}
/**
* Executed during woocommerce_shortcode_before_recent_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_recent_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Recent Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_shortcode_before_sale_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_sale_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Sale Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_shortcode_before_best_selling_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_best_selling_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Best Selling Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_shortcode_before_top_rated_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_top_rated_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Top Rated Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_shortcode_before_featured_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_featured_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Featured Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_shortcode_before_related_products_loop.
* Sets the product list title for product list impression reporting.
*
* @return void
*/
function gtm4wp_woocommerce_before_related_products_loop() {
global $woocommerce_loop;
$woocommerce_loop['listtype'] = __( 'Related Products', 'duracelltomi-google-tag-manager' );
}
/**
* Executed during woocommerce_before_template_part.
* Starts output buffering in order to be able to add product data attributes to the link element
* of a product list (classic) widget.
*
* @param string $template_name The template part that is being rendered.
* @return void
*/
function gtm4wp_woocommerce_before_template_part( $template_name ) {
ob_start();
}
/**
* Executed during woocommerce_after_template_part.
* Stops output buffering and gets the generated content since woocommerce_before_template_part.
* Adds data attributes into the product link to be able to track product list impression and
* click actions with Google Tag Manager.
*
* @param string $template_name The template part that is being rendered. This functions looks for content-widget-product.php.
* @return void
*/
function gtm4wp_woocommerce_after_template_part( $template_name ) {
global $product, $gtm4wp_product_counter, $gtm4wp_last_widget_title;
$productitem = ob_get_contents();
ob_end_clean();
if ( 'content-widget-product.php' === $template_name ) {
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'productlink' => apply_filters( 'the_permalink', get_permalink(), 0 ),
'item_list_name' => $gtm4wp_last_widget_title,
'index' => $gtm4wp_product_counter,
),
'widgetproduct'
);
if ( ! isset( $eec_product_array['item_brand'] ) ) {
$eec_product_array['item_brand'] = '';
}
$productlink_with_data = sprintf(
'data-gtm4wp_product_data="%s" href="',
esc_attr( wp_json_encode( $eec_product_array ) )
);
$gtm4wp_product_counter++;
$productitem = str_replace( 'href="', $productlink_with_data, $productitem );
}
/*
$productitem is initialized as the template itself outputs a product item.
Therefore I can not pass this to wp_kses() as it can include eventually any HTML.
This filter function only adds additional attributes to the link element that points
to a product detail page. Attribute values are escaped above.
*/
echo $productitem; // phpcs:ignore
}
/**
* Generates a element that can be used as a hidden addition to the DOM to be able to report
* product list impressions and clicks on list pages like product category or tag pages.
*
* @param WC_Product $product A WooCommerce product object.
* @param string $listtype The name of the product list where the product is currently shown.
* @param string $itemix The index of the product in the product list. The first product should have the index no. 1.
* @param string $permalink The link where the click should land when a users clicks on this product element.
* @return string A hidden element that includes all product data needed for enhanced ecommerce reporting in product lists.
*/
function gtm4wp_woocommerce_get_product_list_item_extra_tag( $product, $listtype, $itemix, $permalink ) {
if ( ! isset( $product ) ) {
return;
}
if ( ! ( $product instanceof WC_Product ) ) {
return false;
}
if ( is_search() ) {
$list_name = __( 'Search Results', 'duracelltomi-google-tag-manager' );
} elseif ( '' !== $listtype ) {
$list_name = $listtype;
} else {
$list_name = __( 'General Product List', 'duracelltomi-google-tag-manager' );
}
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$posts_per_page = get_query_var( 'posts_per_page' );
if ( $posts_per_page < 1 ) {
$posts_per_page = 1;
}
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'productlink' => $permalink,
'item_list_name' => $list_name,
'index' => (int) $itemix + ( $posts_per_page * ( $paged - 1 ) ),
'product_type' => $product->get_type(),
),
'productlist'
);
if ( ! isset( $eec_product_array['item_brand'] ) ) {
$eec_product_array['item_brand'] = '';
}
return sprintf(
'',
esc_attr( wp_json_encode( $eec_product_array ) )
);
}
/**
* Executed during woocommerce_after_shop_loop_item.
* Shows a hidden element with product data to report enhanced ecommerce
* product impression and click actions in product lists.
*
* @return void
*/
function gtm4wp_woocommerce_after_shop_loop_item() {
global $product, $woocommerce_loop;
$listtype = '';
if ( isset( $woocommerce_loop['listtype'] ) && ( '' !== $woocommerce_loop['listtype'] ) ) {
$listtype = $woocommerce_loop['listtype'];
}
$itemix = '';
if ( isset( $woocommerce_loop['loop'] ) && ( '' !== $woocommerce_loop['loop'] ) ) {
$itemix = $woocommerce_loop['loop'];
}
// no need to escape here as everthing is handled within the function call with esc_attr() and esc_url().
echo gtm4wp_woocommerce_get_product_list_item_extra_tag( //phpcs:ignore
$product,
$listtype,
$itemix,
apply_filters(
'the_permalink',
get_permalink(),
0
)
);
}
/**
* Executed during woocommerce_cart_item_restored.
* When the user restores the just removed cart item, this function stores the cart item key to
* be able to generate an add_to_cart event after restoration completes.
*
* @param string $cart_item_key A unique cart item key.
* @return void
*/
function gtm4wp_woocommerce_cart_item_restored( $cart_item_key ) {
$woo = WC();
if ( $woo && $woo->session ) {
$woo->session->set( 'gtm4wp_product_readded_to_cart', $cart_item_key );
}
}
/**
* Executed during wp_enqueue_scripts.
* Loads ecommerce frontend JavaScript codes to track on site events and interactions.
*
* @return void
*/
function gtm4wp_woocommerce_enqueue_scripts() {
global $gtm4wp_options, $gtp4wp_script_path;
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 );
}
}
/**
* Executed during wc_quick_view_before_single_product.
* This function makes GTM4WP compatible with the WooCommerce Quick View plugin.
* It allows GTM4WP to fire product detail action when quick view is opened.
*
* @return void
*/
function gtm4wp_wc_quick_view_before_single_product() {
global $gtm4wp_options;
$data_layer = array(
'event' => 'view_item',
);
if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$postid = get_the_ID();
$product = wc_get_product( $postid );
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(),
'productdetail'
);
$data_layer['productRatingCounts'] = $product->get_rating_counts();
$data_layer['productAverageRating'] = (float) $product->get_average_rating();
$data_layer['productReviewCount'] = (int) $product->get_review_count();
$data_layer['productType'] = $product->get_type();
switch ( $data_layer['productType'] ) {
case 'variable':
$data_layer['productIsVariable'] = 1;
break;
case 'grouped':
$data_layer['productIsVariable'] = 0;
break;
default:
$data_layer['productIsVariable'] = 0;
$gtm4wp_currency = get_woocommerce_currency();
$data_layer['ecommerce'] = array(
'currency' => $gtm4wp_currency,
'value' => $eec_product_array['price'],
'item' => $eec_product_array,
);
}
}
echo '
';
}
/**
* Executed during woocommerce_grouped_product_list_column_label.
* Adds product list impression info into every product listed on a grouped product detail page to
* track product list impression and click interactions for individual products in the grouped product.
*
* @param string $labelvalue Not used by this function, returns the value without modifying it.
* @param WC_Product $product The WooCommerce product object being shown.
* @return string The string that has been passed to the $labelvalue parameter without any modification.
*/
function gtm4wp_woocommerce_grouped_product_list_column_label( $labelvalue, $product ) {
global $gtm4wp_grouped_product_ix;
if ( ! isset( $product ) ) {
return $labelvalue;
}
$list_name = __( 'Grouped Product Detail Page', 'duracelltomi-google-tag-manager' );
$eec_product_array = gtm4wp_woocommerce_process_product(
$product,
array(
'productlink' => $product->get_permalink(),
'item_list_name' => $list_name,
'index' => $gtm4wp_grouped_product_ix,
),
'groupedproductlist'
);
$gtm4wp_grouped_product_ix++;
if ( ! isset( $eec_product_array['item_brand'] ) ) {
$eec_product_array['item_brand'] = '';
}
$labelvalue .=
sprintf(
'',
esc_attr( wp_json_encode( $eec_product_array ) )
);
return $labelvalue;
}
/**
* Executed during woocommerce_blocks_product_grid_item_html.
* Adds product list impression data into a product list that has been generated using the block templates
* provided by WooCommerce. This allows proper tracking ot WooCommerce Blocks with product list
* impression and click actions.
*
* @param string $content Product grid item HTML.
* @param object $data Product data passed to the template.
* @param WC_Product $product Product object.
* @return string The product grid item HTML with added hidden element for ecommerce tracking.
*/
function gtm4wp_woocommerce_add_productdata_to_wc_block( $content, $data, $product ) {
$product_data_tag = gtm4wp_woocommerce_get_product_list_item_extra_tag( $product, '', 0, $data->permalink );
return preg_replace( '//i', '$0' . $product_data_tag, $content );
}
add_filter( GTM4WP_WPFILTER_COMPILE_DATALAYER, 'gtm4wp_woocommerce_datalayer_filter_items' );
add_filter( 'loop_end', 'gtm4wp_woocommerce_reset_loop' );
add_action( 'woocommerce_after_shop_loop_item', 'gtm4wp_woocommerce_after_shop_loop_item' );
add_action( 'woocommerce_after_add_to_cart_button', 'gtm4wp_woocommerce_single_add_to_cart_tracking' );
add_action( 'wp_enqueue_scripts', 'gtm4wp_woocommerce_enqueue_scripts' );
add_filter( GTM4WP_WPFILTER_ADDGLOBALVARS_ARRAY, 'gtm4wp_woocommerce_add_global_vars' );
add_filter( 'woocommerce_blocks_product_grid_item_html', 'gtm4wp_woocommerce_add_productdata_to_wc_block', 10, 3 );
add_action( 'woocommerce_thankyou', 'gtm4wp_woocommerce_thankyou' );
if ( true === $GLOBALS['gtm4wp_options'][ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
add_action( 'woocommerce_before_template_part', 'gtm4wp_woocommerce_before_template_part' );
add_action( 'woocommerce_after_template_part', 'gtm4wp_woocommerce_after_template_part' );
add_filter( 'widget_title', 'gtm4wp_woocommerce_widget_title_filter' );
add_action( 'wc_quick_view_before_single_product', 'gtm4wp_wc_quick_view_before_single_product' );
add_filter( 'woocommerce_grouped_product_list_column_label', 'gtm4wp_woocommerce_grouped_product_list_column_label', 10, 2 );
add_filter( 'woocommerce_cart_item_product', 'gtm4wp_woocommerce_cart_item_product_filter' );
add_filter( 'woocommerce_cart_item_remove_link', 'gtm4wp_woocommerce_cart_item_remove_link_filter' );
add_action( 'woocommerce_cart_item_restored', 'gtm4wp_woocommerce_cart_item_restored' );
add_filter( 'woocommerce_related_products_args', 'gtm4wp_woocommerce_add_related_to_loop' );
add_filter( 'woocommerce_related_products_columns', 'gtm4wp_woocommerce_add_related_to_loop' );
add_filter( 'woocommerce_cross_sells_columns', 'gtm4wp_woocommerce_add_cross_sell_to_loop' );
add_filter( 'woocommerce_upsells_columns', 'gtm4wp_woocommerce_add_upsells_to_loop' );
add_action( 'woocommerce_shortcode_before_recent_products_loop', 'gtm4wp_woocommerce_before_recent_products_loop' );
add_action( 'woocommerce_shortcode_before_sale_products_loop', 'gtm4wp_woocommerce_before_sale_products_loop' );
add_action( 'woocommerce_shortcode_before_best_selling_products_loop', 'gtm4wp_woocommerce_before_best_selling_products_loop' );
add_action( 'woocommerce_shortcode_before_top_rated_products_loop', 'gtm4wp_woocommerce_before_top_rated_products_loop' );
add_action( 'woocommerce_shortcode_before_featured_products_loop', 'gtm4wp_woocommerce_before_featured_products_loop' );
add_action( 'woocommerce_shortcode_before_related_products_loop', 'gtm4wp_woocommerce_before_related_products_loop' );
}