version, '3.7', '>=' ); } else { $GLOBALS['gtm4wp_is_woocommerce3_7'] = false; } $GLOBALS['gtm4wp_grouped_product_ix'] = 1; $GLOBALS['gtm4wp_woocommerce_purchase_data_pushed'] = false; /** * Convert special unicode quotation and dash characters to normal version. * * @see https://snippets.webaware.com.au/ramblings/php-really-doesnt-unicode/ * * @param string $fancy Input string with special unicode quotes and dash characters. * @return string All kind of quotes and dash characters replaced with normal version. */ function gtm4wp_untexturize( $fancy ) { $fixes = array( json_decode( '"\u201C"' ) => '"', // left double quotation mark. json_decode( '"\u201D"' ) => '"', // right double quotation mark. json_decode( '"\u2018"' ) => "'", // left single quotation mark. json_decode( '"\u2019"' ) => "'", // right single quotation mark. json_decode( '"\u2032"' ) => "'", // prime (minutes, feet). json_decode( '"\u2033"' ) => '"', // double prime (seconds, inches). json_decode( '"\u2013"' ) => '-', // en dash. json_decode( '"\u2014"' ) => '--', // em dash. ); $normal = strtr( $fancy, $fixes ); return $normal; } /** * Takes a product ID and returns a string that has a prefix appended. * The prefix can be set on the GTM4WP options page under Integration->WooCommerce. * * This is needed in cases where the generated feed has IDs with some sort of constant prefix and * tracking needs to align with this ID in order for dynamic remarketing to work properly. * * @param int|string $product_id A product ID that has to be prefixed. * @return string. The product ID with the prefix strings. */ function gtm4wp_prefix_productid( $product_id ) { global $gtm4wp_options; if ( '' !== $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMPRODIDPREFIX ] ) { return $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMPRODIDPREFIX ] . $product_id; } else { return $product_id; } } /** * Replace only the first occurrence of the search string with the replacement string. * * @see https://stackoverflow.com/questions/1252693/using-str-replace-so-that-it-only-acts-on-the-first-match * * TODO: replace regexp usage. * * @param string $search The value being searched for, otherwise known as the needle. Must be a string. * @param string $replace The replacement value that replaces found search values. Must be a string. * @param string $subject The string being searched and replaced on, otherwise known as the haystack. * @return string This function returns a string with the replaced values. */ function gtm4wp_str_replace_first( $search, $replace, $subject ) { $search = '/' . preg_quote( $search, '/' ) . '/'; return preg_replace( $search, $replace, $subject, 1 ); } /** * Given a WooCommerce category ID, this function returns the full path to this category separated with the / character. * * @param int $category_id The ID of the WooCommerce category that needs to be scanned for parents. * @return string The category path. An example outout can be: Home / Clothing / Toddlers. */ function gtm4wp_get_product_category_hierarchy( $category_id ) { $cat_hierarchy = ''; $category_parent_list = get_term_parents_list( $category_id, 'product_cat', array( 'format' => 'name', 'separator' => '/', 'link' => false, 'inclusive' => true, ) ); if ( is_string( $category_parent_list ) ) { $cat_hierarchy = trim( $category_parent_list, '/' ); } return $cat_hierarchy; } /** * Given a WooCommerce product ID, this function will return the first assigned category of the product. * Currently, it does not take into account the "primary category" option of various SEO plugins. * * @param int $product_id A WooCommerce product ID whose first assigned category has to be returned. * @param boolean $fullpath Set this to true of you need to query the full path including parent categories. Defaults to false. * @return string The first category name of the product. Incluldes the name of parent categories if the $fullpath parameter is set to true. */ function gtm4wp_get_product_category( $product_id, $fullpath = false ) { $product_category = ''; $wp_category_taxonomy = 'product_cat'; $primary_category_id = false; $category_data = false; if ( function_exists( 'yoast_get_primary_term_id' ) ) { $primary_category_id = yoast_get_primary_term_id( $wp_category_taxonomy, $product_id ); } elseif ( function_exists( 'rank_math' ) ) { $rank_math_data = get_post_meta( $product_id, 'rank_math_primary_' . $wp_category_taxonomy, true ); if ( ! empty( $rank_math_data ) && intval( $rank_math_data ) ) { $primary_category_id = $rank_math_data; } } if ( false === $primary_category_id ) { $product_categories = wp_get_post_terms( $product_id, $wp_category_taxonomy, array( 'orderby' => 'parent', 'order' => 'ASC', ) ); if ( ( is_array( $product_categories ) ) && ( count( $product_categories ) > 0 ) ) { $category_data = array_pop( $product_categories ); } } else { $category_data = get_term( $primary_category_id, $wp_category_taxonomy ); } if ( false !== $category_data ) { if ( $fullpath ) { $product_category = gtm4wp_get_product_category_hierarchy( $category_data->term_id ); } else { $product_category = $category_data->name; } } return $product_category; } /** * Given a WooCommerce product ID, this function returns the assigned value of a custom taxonomy like the brand name. * * @see https://developer.wordpress.org/reference/functions/wp_get_post_terms/ * * @param int $product_id A WooCommerce product ID whose taxonomy assosiation needs to be queried. * @param string $taxonomy The taxonomy slug for which to retrieve terms. * @return string Returns the first assigned taxonomy value to the given WooCommerce product ID. */ function gtm4wp_woocommerce_getproductterm( $product_id, $taxonomy ) { $gtm4wp_product_terms = wp_get_post_terms( $product_id, $taxonomy, array( 'orderby' => 'parent', 'order' => 'ASC', ) ); if ( is_array( $gtm4wp_product_terms ) && ( count( $gtm4wp_product_terms ) > 0 ) ) { return $gtm4wp_product_terms[0]->name; } return ''; } /** * Given a WP_Product instane, this function returns an array of product attributes in the format of * Google Analytics enhanced ecommerce product data. * * @see https://developers.google.com/analytics/devguides/collection/ua/gtm/enhanced-ecommerce * * @param WP_Product $product An instance of WP_Product that needs to be transformed into an enhanced ecommerce product object. * @param array $additional_product_attributes Any key-value pair that needs to be added into the enhanced ecommerce product object. * @param string $attributes_used_for The placement ID of the product that is passed to the apply_filters hook so that 3rd party code can be notified where this product data is being used. * @return array The enhanced ecommerce product object of the WooCommerce product. */ function gtm4wp_process_product( $product, $additional_product_attributes, $attributes_used_for ) { global $gtm4wp_options; if ( ! $product ) { return false; } if ( ! ( $product instanceof WC_Product ) ) { return false; } $product_id = $product->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 ] ); } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSESKU ] && ( '' !== $product_sku ) ) { $remarketing_id = $product_sku; } $_temp_productdata = array( 'id' => $remarketing_id, 'internal_id' => $product_id, 'name' => $product->get_title(), 'sku' => $product_sku ? $product_sku : $product_id, 'category' => $product_cat, '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(), ); 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['brand'] = gtm4wp_woocommerce_getproductterm( $product_id_to_query, $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECBRANDTAXONOMY ] ); } if ( 'variation' === $product_type ) { $_temp_productdata['variant'] = implode( ',', $product->get_variation_attributes() ); } $_temp_productdata = array_merge( $_temp_productdata, $additional_product_attributes ); return apply_filters( GTM4WP_WPFILTER_EEC_PRODUCT_ARRAY, $_temp_productdata, $attributes_used_for ); } /** * Given a Google Business vertical ID, this function returns the name of the "ID" field in tagging Google Ads dynamic remarketing. * This "id" in most cases, but sometimes "destination". * * @param string $vertical_id The Google Business vertical ID (like retail, flights, etc.). * @return string The name of the "ID" field for tagging. */ function gtm4wp_get_gads_product_id_variable_name( $vertical_id ) { global $gtm4wp_business_verticals_ids; if ( array_key_exists( $vertical_id, $gtm4wp_business_verticals_ids ) ) { return $gtm4wp_business_verticals_ids[ $vertical_id ]; } else { return 'id'; } } /** * Takes a GA3 style enhanced ecommerce product object and transforms it into a GA4 product object. * * @param array $productdata WooCommerce product data in GA3 enhanced ecommerce product object format. * @return array WooCommerce product data in GA4 enhanced ecommerce product object format. */ function gtm4wp_map_eec_to_ga4( $productdata ) { global $gtm4wp_options; if ( ! is_array( $productdata ) ) { return; } $category_path = array_key_exists( 'category', $productdata ) ? $productdata['category'] : ''; $category_parts = explode( '/', $category_path ); // Default, required parameters. $ga4_product = array( 'item_id' => array_key_exists( 'id', $productdata ) ? (string) $productdata['id'] : '', 'item_name' => array_key_exists( 'name', $productdata ) ? $productdata['name'] : '', 'item_brand' => array_key_exists( 'brand', $productdata ) ? $productdata['brand'] : '', 'price' => array_key_exists( 'price', $productdata ) ? $productdata['price'] : '', ); // Category, also handle category path. if ( 1 === count( $category_parts ) ) { $ga4_product['item_category'] = $category_parts[0]; } elseif ( count( $category_parts ) > 1 ) { $ga4_product['item_category'] = $category_parts[0]; $num_category_parts = min( 5, count( $category_parts ) ); for ( $i = 1; $i < $num_category_parts; $i++ ) { $ga4_product[ 'item_category' . (string) ( $i + 1 ) ] = $category_parts[ $i ]; } } // Optional parameters which should not be included in the array if not set. if ( array_key_exists( 'variant', $productdata ) ) { $ga4_product['item_variant'] = $productdata['variant']; } if ( array_key_exists( 'listname', $productdata ) ) { $ga4_product['item_list_name'] = $productdata['listname']; } if ( array_key_exists( 'listposition', $productdata ) ) { $ga4_product['index'] = $productdata['listposition']; } if ( array_key_exists( 'quantity', $productdata ) ) { $ga4_product['quantity'] = $productdata['quantity']; } if ( array_key_exists( 'coupon', $productdata ) ) { $ga4_product['coupon'] = $productdata['coupon']; } $ga4_product['google_business_vertical'] = $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ]; $ga4_product[ gtm4wp_get_gads_product_id_variable_name( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ] ) ] = gtm4wp_prefix_productid( $ga4_product['item_id'] ); return $ga4_product; } /** * 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 associative array with the keys: * products - enhanced ecommerce (GA3) products * sumprice - total order value based on item data * product_ids - array of product IDs to be used in ecomm_prodid. */ function gtm4wp_process_order_items( $order ) { global $gtm4wp_options; $return_data = array( 'products' => array(), 'sumprice' => 0, 'product_ids' => array(), ); if ( ! $order ) { return $return_data; } if ( ! ( $order instanceof WC_Order ) ) { return $return_data; } $order_items = $order->get_items(); if ( $order_items ) { foreach ( $order_items as $item ) { if ( ! apply_filters( GTM4WP_WPFILTER_EEC_ORDER_ITEM, true, $item ) ) { continue; } $product = $item->get_product(); $inc_tax = ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ); $product_price = round( (float) $order->get_item_total( $item, $inc_tax ), 2 ); $eec_product_array = gtm4wp_process_product( $product, array( 'quantity' => $item->get_quantity(), 'price' => $product_price, ), 'purchase' ); if ( $eec_product_array ) { $return_data['products'][] = $eec_product_array; $return_data['sumprice'] += $product_price * $eec_product_array['quantity']; $return_data['product_ids'][] = gtm4wp_prefix_productid( $eec_product_array['id'] ); } } } return $return_data; } /** * Function to be called on the gtm4wp_add_global_vars hook to output WooCommerce related global JavaScript variables. * * @param array $return The already added variables as key-value pairs in an associative array. * @return array The $return parameter with added global JavaScript variables as key-value pairs. */ function gtm4wp_woocommerce_addglobalvars( $return ) { global $gtm4wp_options; if ( function_exists( 'WC' ) && WC()->cart ) { $gtm4wp_needs_shipping_address = (bool) WC()->cart->needs_shipping_address(); } else { $gtm4wp_needs_shipping_address = false; } $return['gtm4wp_use_sku_instead'] = (int) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSESKU ] ); $return['gtm4wp_id_prefix'] = gtm4wp_prefix_productid( '' ); $return['gtm4wp_remarketing'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ); $return['gtm4wp_eec'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ); $return['gtm4wp_classicec'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKCLASSICEC ] ); $return['gtm4wp_currency'] = get_woocommerce_currency(); $return['gtm4wp_product_per_impression'] = (int) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCPRODPERIMPRESSION ] ); $return['gtm4wp_needs_shipping_address'] = (bool) $gtm4wp_needs_shipping_address; $return['gtm4wp_business_vertical'] = esc_js( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ] ); $return['gtm4wp_business_vertical_id'] = gtm4wp_get_gads_product_id_variable_name( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBUSINESSVERTICAL ] ); $return['gtm4wp_clear_ecommerce'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL ] ); return $return; } /** * 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_process_order_items(). It not set, then function will call gtm4wp_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_get_purchase_datalayer( $order, $order_items ) { global $gtm4wp_options, $gtm4wp_is_woocommerce3_7; $data_layer = array(); if ( $order instanceof WC_Order ) { $woo = WC(); /** * 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; 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(); if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKCLASSICEC ] ) { $data_layer['event'] = 'gtm4wp.orderCompleted'; $data_layer['transactionId'] = $order->get_order_number(); $data_layer['transactionAffiliation'] = ''; $data_layer['transactionTotal'] = $order_revenue; $data_layer['transactionShipping'] = $order_shipping_cost; $data_layer['transactionTax'] = (float) $order->get_total_tax(); $data_layer['transactionCurrency'] = $order_currency; } if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $data_layer['event'] = 'gtm4wp.orderCompletedEEC'; $data_layer['ecommerce'] = array( 'currencyCode' => $order_currency, 'purchase' => array( 'actionField' => array( 'id' => $order->get_order_number(), 'affiliation' => '', 'revenue' => $order_revenue, 'tax' => (float) $order->get_total_tax(), 'shipping' => (float) ( $order->get_shipping_total() ), 'coupon' => implode( ', ', ( $gtm4wp_is_woocommerce3_7 ? $order->get_coupon_codes() : $order->get_used_coupons() ) ), ), ), ); } if ( isset( $order_items ) ) { $_order_items = $order_items; } else { $_order_items = gtm4wp_process_order_items( $order ); } if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKCLASSICEC ] ) { $data_layer['transactionProducts'] = $_order_items['products']; } if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $data_layer['ecommerce']['purchase']['products'] = $_order_items['products']; } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = $_order_items['product_ids']; $data_layer['ecomm_pagetype'] = 'purchase'; $data_layer['ecomm_totalvalue'] = (float) $_order_items['sumprice']; } } return apply_filters( 'gtm4wp_purchase_datalayer', $data_layer ); } /** * 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, $wp_query, $gtm4wp_datalayer_name, $gtm4wp_product_counter, $gtm4wp_is_woocommerce3_7; if ( array_key_exists( 'HTTP_X_REQUESTED_WITH', $_SERVER ) ) { return $data_layer; } $woo = WC(); 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'] = hash( '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(); } } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEINCLUDECARTINDL ] && version_compare( $woo->version, '3.2', '>=' ) && 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 ) { $product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id ); 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_process_product( $product, array( 'quantity' => $cart_item_data['quantity'], ), 'cart' ); $data_layer['cartContent']['items'][] = $eec_product_array; } } if ( is_product_category() || is_product_tag() || is_front_page() || is_shop() ) { $ecomm_pagetype = 'category'; if ( is_front_page() ) { $ecomm_pagetype = 'home'; } elseif ( is_search() ) { $ecomm_pagetype = 'searchresults'; } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = array(); $data_layer['ecomm_pagetype'] = $ecomm_pagetype; $data_layer['ecomm_totalvalue'] = 0; } } elseif ( is_product() ) { if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] || ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) ) { $postid = get_the_ID(); $product = wc_get_product( $postid ); $eec_product_array = gtm4wp_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; $data_layer['ecomm_prodid'] = gtm4wp_prefix_productid( $eec_product_array['id'] ); $data_layer['ecomm_pagetype'] = 'product'; $data_layer['ecomm_totalvalue'] = $eec_product_array['price']; break; case 'grouped': $data_layer['productIsVariable'] = 0; break; default: $data_layer['productIsVariable'] = 0; if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = gtm4wp_prefix_productid( $eec_product_array['id'] ); $data_layer['ecomm_pagetype'] = 'product'; $data_layer['ecomm_totalvalue'] = $eec_product_array['price']; } if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $currency_code = get_woocommerce_currency(); $data_layer['event'] = 'gtm4wp.changeDetailViewEEC'; $data_layer['ecommerce'] = array( 'currencyCode' => $currency_code, 'detail' => array( 'products' => array( $eec_product_array, ), ), ); } } } } elseif ( is_cart() ) { if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] || $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECCARTASFIRSTSTEP ] || $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $gtm4wp_cart_products = array(); $gtm4wp_cart_products_remarketing = array(); $gtm4wp_currency = get_woocommerce_currency(); foreach ( $woo->cart->get_cart() as $cart_item_id => $cart_item_data ) { $product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id ); if ( ! apply_filters( GTM4WP_WPFILTER_EEC_CART_ITEM, true, $cart_item_data ) ) { continue; } $eec_product_array = gtm4wp_process_product( $product, array( 'quantity' => $cart_item_data['quantity'], ), 'cart' ); $gtm4wp_cart_products[] = $eec_product_array; $gtm4wp_cart_products_remarketing[] = gtm4wp_prefix_productid( $eec_product_array['id'] ); } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = $gtm4wp_cart_products_remarketing; $data_layer['ecomm_pagetype'] = 'cart'; if ( ! $woo->cart->prices_include_tax ) { $cart_total = $woo->cart->cart_contents_total; } else { $cart_total = $woo->cart->cart_contents_total + $woo->cart->tax_total; } $data_layer['ecomm_totalvalue'] = (float) $cart_total; } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECCARTASFIRSTSTEP ] ) { $data_layer['event'] = 'gtm4wp.checkoutStepEEC'; $data_layer['ecommerce'] = array( 'currencyCode' => $gtm4wp_currency, 'checkout' => array( 'actionField' => array( 'step' => 1, ), 'products' => $gtm4wp_cart_products, ), ); } else { // add only ga4 products to populate view_cart event. $data_layer['ecommerce'] = array( 'cart' => $gtm4wp_cart_products, ); } } } } elseif ( is_order_received_page() ) { $do_not_flag_tracked_order = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG ] ); // Supressing 'Processing form data without nonce verification.' message as there is no nonce accesible in this case. $order_id = filter_var( wp_unslash( isset( $_GET['order'] ) ? $_GET['order'] : '' ), FILTER_VALIDATE_INT ); // phpcs:ignore if ( ! $order_id & isset( $GLOBALS['wp']->query_vars['order-received'] ) ) { $order_id = $GLOBALS['wp']->query_vars['order-received']; } $order_id = absint( $order_id ); $order_id_filtered = apply_filters( 'woocommerce_thankyou_order_id', $order_id ); if ( '' !== $order_id_filtered ) { $order_id = $order_id_filtered; } // Supressing 'Processing form data without nonce verification.' message as there is no nonce accesible in this case. $order_key = isset( $_GET['key'] ) ? wc_clean( sanitize_text_field( wp_unslash( $_GET['key'] ) ) ) : ''; // phpcs:ignore $order_key = apply_filters( 'woocommerce_thankyou_order_key', $order_key ); if ( $order_id > 0 ) { $order = wc_get_order( $order_id ); if ( $order instanceof WC_Order ) { $this_order_key = $order->get_order_key(); if ( $this_order_key !== $order_key ) { unset( $order ); } } else { unset( $order ); } } /* From this point if for any reason purchase data is not pushed that is because for a specific reason. In any other case woocommerce_thankyou hook will be the fallback if is_order_received_page does not work. */ $GLOBALS['gtm4wp_woocommerce_purchase_data_pushed'] = true; if ( isset( $order ) && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERMAXAGE ] ) { if ( $order->is_paid() && $order->get_date_paid() ) { $now = new DateTime( 'now', $order->get_date_paid()->getTimezone() ); $diff = $now->diff( $order->get_date_paid() ); $minutes = ( $diff->days * 24 * 60 ) + ( $diff->h * 60 ) + $diff->i; } else { $now = new DateTime( 'now', $order->get_date_created()->getTimezone() ); $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; if ( isset( $order ) && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERDATA ] ) { $order_items = gtm4wp_process_order_items( $order ); $data_layer['orderData'] = 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( ', ', ( $gtm4wp_is_woocommerce3_7 ? $order->get_coupon_codes() : $order->get_used_coupons() ) ), ), '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() ), 'last_name' => esc_js( $order->get_billing_last_name() ), '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( hash( 'sha256', $order->get_billing_email() ) ), 'phone' => esc_js( $order->get_billing_phone() ), ), '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['products'], ); } 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 ) ) { $data_layer = array_merge( $data_layer, gtm4wp_get_purchase_datalayer( $order, $order_items ) ); if ( ! $do_not_flag_tracked_order ) { $order->update_meta_data( '_ga_tracked', 1 ); $order->save(); } } } elseif ( is_checkout() ) { if ( ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) || ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) ) { $gtm4wp_checkout_products = array(); $gtm4wp_checkout_products_remarketing = array(); $gtm4wp_totalvalue = 0; foreach ( $woo->cart->get_cart() as $cart_item_id => $cart_item_data ) { $product = apply_filters( 'woocommerce_cart_item_product', $cart_item_data['data'], $cart_item_data, $cart_item_id ); if ( ! apply_filters( GTM4WP_WPFILTER_EEC_CART_ITEM, true, $cart_item_data ) ) { continue; } $eec_product_array = gtm4wp_process_product( $product, array( 'quantity' => $cart_item_data['quantity'], ), 'cart' ); $gtm4wp_checkout_products[] = $eec_product_array; $gtm4wp_checkout_products_remarketing[] = gtm4wp_prefix_productid( $eec_product_array['id'] ); $gtm4wp_totalvalue += $eec_product_array['quantity'] * $eec_product_array['price']; } // end foreach cart item if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = $gtm4wp_checkout_products_remarketing; $data_layer['ecomm_pagetype'] = 'cart'; $data_layer['ecomm_totalvalue'] = (float) $gtm4wp_totalvalue; } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $currency_code = get_woocommerce_currency(); $ga4_products = array(); $sum_value = 0; foreach ( $gtm4wp_checkout_products as $oneproduct ) { $ga4_products[] = gtm4wp_map_eec_to_ga4( $oneproduct ); $sum_value += $oneproduct['price'] * $oneproduct['quantity']; } $data_layer['event'] = 'gtm4wp.checkoutStepEEC'; $data_layer['ecommerce'] = array( 'currencyCode' => $currency_code, 'checkout' => array( 'actionField' => array( 'step' => 1 + (int) $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECCARTASFIRSTSTEP ], ), 'products' => $gtm4wp_checkout_products, ), ); wc_enqueue_js( ' window.gtm4wp_checkout_products = ' . wp_json_encode( $gtm4wp_checkout_products ) . '; window.gtm4wp_checkout_products_ga4 = ' . wp_json_encode( $ga4_products ) . '; window.gtm4wp_checkout_value = ' . (float) $sum_value . '; window.gtm4wp_checkout_step_offset = ' . (int) $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCEECCARTASFIRSTSTEP ] . ';' ); } } } else { if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_pagetype'] = 'other'; } } if ( function_exists( 'WC' ) && WC()->session ) { $cart_readded_hash = WC()->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_process_product( $product, array( 'quantity' => $cart_item['quantity'], ), 'readdedtocart' ); $currency_code = get_woocommerce_currency(); $data_layer['event'] = 'gtm4wp.addProductToCartEEC'; $data_layer['ecommerce'] = array( 'currencyCode' => $currency_code, 'add' => array( 'products' => array( $eec_product_array, ), ), ); } WC()->session->set( 'gtm4wp_product_readded_to_cart', null ); } } return apply_filters( 'gtm4wp_datalayer_on_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 this flag is set to true, it means that the puchase event was fired when capturing the is_order_received_page template tag therefore no need to handle this here twice */ if ( $GLOBALS['gtm4wp_woocommerce_purchase_data_pushed'] ) { return; } if ( $order_id > 0 ) { $order = wc_get_order( $order_id ); } if ( isset( $order ) && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCORDERMAXAGE ] ) { $now = new DateTime(); 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 ); } } $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 ) ) { $data_layer = gtm4wp_get_purchase_datalayer( $order, null ); $has_html5_support = current_theme_supports( 'html5' ); $add_cookiebot_ignore = $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_COOKIEBOT ]; echo ' '; 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_datalayer_name, $gtm4wp_options; // exit early if there is nothing to do. if ( ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKCLASSICEC ] ) && ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) ) { return; } $eec_product_array = gtm4wp_process_product( $product, array(), 'addtocartsingle' ); foreach ( $eec_product_array as $eec_product_array_key => $eec_product_array_value ) { echo '' . "\n"; } } /** * Universal Analytics enhanced 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 Universal Analytics enhanced 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 Enhanced ecommerce product data in an associative array. */ function gtm4wp_woocommerce_cart_item_product_filter( $product, $cart_item = '', $cart_id = '' ) { global $gtm4wp_options; $eec_product_array = gtm4wp_process_product( $product, array( 'productlink' => apply_filters( 'the_permalink', get_permalink(), 0 ), ), 'cart' ); $GLOBALS['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 * enhanced 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 ) { if ( ! isset( $GLOBALS['gtm4wp_cart_item_proddata'] ) ) { return $remove_from_cart_link; } if ( ! is_array( $GLOBALS['gtm4wp_cart_item_proddata'] ) ) { return $remove_from_cart_link; } if ( ! isset( $GLOBALS['gtm4wp_cart_item_proddata']['variant'] ) ) { $GLOBALS['gtm4wp_cart_item_proddata']['variant'] = ''; } if ( ! isset( $GLOBALS['gtm4wp_cart_item_proddata']['brand'] ) ) { $GLOBALS['gtm4wp_cart_item_proddata']['brand'] = ''; } $cartlink_with_data = sprintf( 'data-gtm4wp_product_id="%s" data-gtm4wp_product_name="%s" data-gtm4wp_product_price="%s" data-gtm4wp_product_cat="%s" data-gtm4wp_product_url="%s" data-gtm4wp_product_variant="%s" data-gtm4wp_product_stocklevel="%s" data-gtm4wp_product_brand="%s" href="', esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['id'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['name'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['price'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['category'] ), esc_url( $GLOBALS['gtm4wp_cart_item_proddata']['productlink'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['variant'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['stocklevel'] ), esc_attr( $GLOBALS['gtm4wp_cart_item_proddata']['brand'] ) ); $GLOBALS['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 gtp4wp_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 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, $gtm4wp_options; $productitem = ob_get_contents(); ob_end_clean(); if ( 'content-widget-product.php' === $template_name ) { $eec_product_array = gtm4wp_process_product( $product, array( 'productlink' => apply_filters( 'the_permalink', get_permalink(), 0 ), 'listname' => $gtm4wp_last_widget_title, 'listposition' => $gtm4wp_product_counter, ), 'widgetproduct' ); if ( ! isset( $eec_product_array['brand'] ) ) { $eec_product_array['brand'] = ''; } $productlink_with_data = sprintf( 'data-gtm4wp_product_id="%s" data-gtm4wp_product_internal_id="%s" data-gtm4wp_product_name="%s" data-gtm4wp_product_price="%s" data-gtm4wp_product_cat="%s" data-gtm4wp_product_url="%s" data-gtm4wp_productlist_name="%s" data-gtm4wp_product_listposition="%s" data-gtm4wp_product_stocklevel="%s" data-gtm4wp_product_brand="%s" href="', esc_attr( $eec_product_array['id'] ), esc_attr( $eec_product_array['internal_id'] ), esc_attr( $eec_product_array['name'] ), esc_attr( $eec_product_array['price'] ), esc_attr( $eec_product_array['category'] ), esc_url( $eec_product_array['productlink'] ), esc_attr( $eec_product_array['listname'] ), esc_attr( $eec_product_array['listposition'] ), esc_attr( $eec_product_array['stocklevel'] ), esc_attr( $eec_product_array['brand'] ) ); $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 } /** * 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_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_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_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_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_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_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_before_related_products_loop() { global $woocommerce_loop; $woocommerce_loop['listtype'] = __( 'Related Products', 'duracelltomi-google-tag-manager' ); } /** * 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 ) { global $wp_query, $gtm4wp_options; if ( ! isset( $product ) ) { return; } if ( ! ( $product instanceof WC_Product ) ) { return false; } $product_id = $product->get_id(); $product_cat = ''; if ( is_product_category() ) { global $wp_query; $cat_obj = $wp_query->get_queried_object(); if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSEFULLCATEGORYPATH ] ) { $product_cat = gtm4wp_get_product_category_hierarchy( $cat_obj->term_id ); } else { $product_cat = $cat_obj->name; } } else { $product_cat = gtm4wp_get_product_category( $product_id, $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSEFULLCATEGORYPATH ] ); } 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_process_product( $product, array( 'productlink' => $permalink, 'listname' => $list_name, 'listposition' => (int) $itemix + ( $posts_per_page * ( $paged - 1 ) ), ), 'productlist' ); if ( ! isset( $eec_product_array['brand'] ) ) { $eec_product_array['brand'] = ''; } return sprintf( '', esc_attr( $eec_product_array['id'] ), esc_attr( $eec_product_array['internal_id'] ), esc_attr( $eec_product_array['name'] ), esc_attr( $eec_product_array['price'] ), esc_attr( $eec_product_array['category'] ), esc_url( $eec_product_array['productlink'] ), esc_attr( $eec_product_array['listposition'] ), esc_attr( $eec_product_array['listname'] ), esc_attr( $eec_product_array['stocklevel'] ), esc_attr( $eec_product_array['brand'] ) ); } /** * 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 ) { if ( function_exists( 'WC' ) && WC()->session ) { WC()->session->set( 'gtm4wp_product_readded_to_cart', $cart_item_key ); } } /** * Executes during wp_enqueue_scripts. * Loads classic/standard and enhanced 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_WCTRACKCLASSICEC ] ) { $in_footer = (bool) apply_filters( 'gtm4wp_' . GTM4WP_OPTION_INTEGRATE_WCTRACKCLASSICEC, false ); wp_enqueue_script( 'gtm4wp-woocommerce-classic', $gtp4wp_script_path . 'gtm4wp-woocommerce-classic.js', array( 'jquery' ), GTM4WP_VERSION, $in_footer ); } if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $in_footer = (bool) apply_filters( 'gtm4wp_' . GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC, false ); wp_enqueue_script( 'gtm4wp-woocommerce-enhanced', $gtp4wp_script_path . 'gtm4wp-woocommerce-enhanced.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, $gtm4wp_datalayer_name; $data_layer = array( 'event' => 'gtm4wp.changeDetailViewEEC', ); if ( ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) || ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) ) { $postid = get_the_ID(); $product = wc_get_product( $postid ); $eec_product_array = gtm4wp_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; $data_layer['ecomm_prodid'] = gtm4wp_prefix_productid( $eec_product_array['id'] ); $data_layer['ecomm_pagetype'] = 'product'; $data_layer['ecomm_totalvalue'] = $eec_product_array['price']; break; case 'grouped': $data_layer['productIsVariable'] = 0; break; default: $data_layer['productIsVariable'] = 0; if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCREMARKETING ] ) { $data_layer['ecomm_prodid'] = gtm4wp_prefix_productid( $eec_product_array['id'] ); $data_layer['ecomm_pagetype'] = 'product'; $data_layer['ecomm_totalvalue'] = $eec_product_array['price']; } if ( true === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { $currency_code = get_woocommerce_currency(); $data_layer['ecommerce'] = array( 'currencyCode' => $currency_code, 'detail' => array( 'products' => array( $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_options, $gtm4wp_grouped_product_ix; if ( ! isset( $product ) ) { return $labelvalue; } $list_name = __( 'Grouped Product Detail Page', 'duracelltomi-google-tag-manager' ); $eec_product_array = gtm4wp_process_product( $product, array( 'productlink' => $product->get_permalink(), 'listname' => $list_name, 'listposition' => $gtm4wp_grouped_product_ix, ), 'groupedproductlist' ); $gtm4wp_grouped_product_ix++; if ( ! isset( $eec_product_array['brand'] ) ) { $eec_product_array['brand'] = ''; } $labelvalue .= sprintf( '', esc_attr( $eec_product_array['id'] ), esc_attr( $eec_product_array['internal_id'] ), esc_attr( $eec_product_array['sku'] ), esc_attr( $eec_product_array['name'] ), esc_attr( $eec_product_array['price'] ), esc_attr( $eec_product_array['category'] ), esc_url( $eec_product_array['productlink'] ), esc_attr( $eec_product_array['listposition'] ), esc_attr( $eec_product_array['listname'] ), esc_attr( $eec_product_array['stocklevel'] ), esc_attr( $eec_product_array['brand'] ) ); 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_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 ); } // do not add filter if someone enabled WooCommerce integration without an activated WooCommerce plugin. if ( function_exists( 'WC' ) ) { add_filter( GTM4WP_WPFILTER_COMPILE_DATALAYER, 'gtm4wp_woocommerce_datalayer_filter_items' ); add_filter( 'loop_end', 'gtp4wp_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_addglobalvars' ); add_filter( 'woocommerce_blocks_product_grid_item_html', 'gtm4wp_add_productdata_to_wc_block', 10, 3 ); add_action( 'woocommerce_thankyou', 'gtm4wp_woocommerce_thankyou' ); if ( true === $GLOBALS['gtm4wp_options'][ GTM4WP_OPTION_INTEGRATE_WCTRACKENHANCEDEC ] ) { 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_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_before_recent_products_loop' ); add_action( 'woocommerce_shortcode_before_sale_products_loop', 'gtm4wp_before_sale_products_loop' ); add_action( 'woocommerce_shortcode_before_best_selling_products_loop', 'gtm4wp_before_best_selling_products_loop' ); add_action( 'woocommerce_shortcode_before_top_rated_products_loop', 'gtm4wp_before_top_rated_products_loop' ); add_action( 'woocommerce_shortcode_before_featured_products_loop', 'gtm4wp_before_featured_products_loop' ); add_action( 'woocommerce_shortcode_before_related_products_loop', 'gtm4wp_before_related_products_loop' ); } }