From 8ebae1d1b5d775e53e2ed3329e85992c5ea60bee Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Tue, 3 Oct 2023 11:53:47 +0100 Subject: [PATCH 001/126] Add settings field --- includes/class-wc-google-analytics.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 749d2850..33df188c 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -189,6 +189,17 @@ public function init_form_fields() { } $this->form_fields = array( + 'ga_product_identifier' => array( + 'title' => __( 'Product Identification', 'woocommerce-google-analytics-integration' ), + 'description' => __( 'Specify how your products will be identified to Google Analytics. Changing this setting may cause issues with historical data if a product was previously identified using a different structure.', 'woocommerce-google-analytics-integration' ), + 'type' => 'select', + 'options' => array( + 'product_id' => __( 'Product ID', 'woocommerce-google-analytics-integration' ), + 'product_sku' => __( 'Product SKU with prefixed (#) ID as fallback', 'woocommerce-google-analytics-integration' ) + ), + // If the option is not set then the product SKU is used as default for existing installations + 'default' => get_option( 'woocommerce_ga_product_identifier', 'product_sku' ) + ), 'ga_id' => array( 'title' => __( 'Google Analytics Tracking ID', 'woocommerce-google-analytics-integration' ), 'description' => __( 'Log into your Google Analytics account to find your ID. e.g. GT-XXXXX or G-XXXXX', 'woocommerce-google-analytics-integration' ), From 26d7c1d138189bc4c6e9547cb8e1ccb6d1cec37e Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Tue, 3 Oct 2023 12:58:19 +0100 Subject: [PATCH 002/126] Add option defaults --- includes/class-wc-google-analytics.php | 43 +++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 33df188c..c3648bd2 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -147,30 +147,31 @@ public function __construct() { */ public function init_options() { $options = array( - 'ga_id', - 'ga_set_domain_name', - 'ga_gtag_enabled', - 'ga_standard_tracking_enabled', - 'ga_support_display_advertising', - 'ga_support_enhanced_link_attribution', - 'ga_use_universal_analytics', - 'ga_anonymize_enabled', - 'ga_404_tracking_enabled', - 'ga_ecommerce_tracking_enabled', - 'ga_enhanced_ecommerce_tracking_enabled', - 'ga_enhanced_remove_from_cart_enabled', - 'ga_enhanced_product_impression_enabled', - 'ga_enhanced_product_click_enabled', - 'ga_enhanced_checkout_process_enabled', - 'ga_enhanced_product_detail_view_enabled', - 'ga_event_tracking_enabled', - 'ga_linker_cross_domains', - 'ga_linker_allow_incoming_enabled', + 'ga_product_identifier' => 'product_sku', + 'ga_id' => null, + 'ga_set_domain_name' => null, + 'ga_gtag_enabled' => null, + 'ga_standard_tracking_enabled' => null, + 'ga_support_display_advertising' => null, + 'ga_support_enhanced_link_attribution' => null, + 'ga_use_universal_analytics' => null, + 'ga_anonymize_enabled' => null, + 'ga_404_tracking_enabled' => null, + 'ga_ecommerce_tracking_enabled' => null, + 'ga_enhanced_ecommerce_tracking_enabled' => null, + 'ga_enhanced_remove_from_cart_enabled' => null, + 'ga_enhanced_product_impression_enabled' => null, + 'ga_enhanced_product_click_enabled' => null, + 'ga_enhanced_checkout_process_enabled' => null, + 'ga_enhanced_product_detail_view_enabled' => null, + 'ga_event_tracking_enabled' => null, + 'ga_linker_cross_domains' => null, + 'ga_linker_allow_incoming_enabled' => null, ); $constructor = array(); - foreach ( $options as $option ) { - $constructor[ $option ] = $this->$option = $this->get_option( $option ); + foreach ( $options as $option => $default ) { + $constructor[ $option ] = $this->$option = $this->get_option( $option, $default ); } return $constructor; From 1a99cc60595876801025b4fd09b9eabe5872412d Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Tue, 3 Oct 2023 14:40:52 +0100 Subject: [PATCH 003/126] Restructure get_product_identifier --- includes/class-wc-abstract-google-analytics-js.php | 14 ++++++++++---- includes/class-wc-google-analytics.php | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index c78fe467..f0b1733e 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -114,11 +114,17 @@ abstract protected function add_transaction_enhanced( $order ); * @return string */ public static function get_product_identifier( $product ) { - if ( ! empty( $product->get_sku() ) ) { - return esc_js( $product->get_sku() ); - } else { - return esc_js( '#' . ( $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id() ) ); + $identifier = $product->get_id(); + + if ( 'product_sku' === self::get( 'ga_product_identifier' ) ) { + if ( ! empty( $product->get_sku() ) ) { + $identifier = $product->get_sku(); + } else { + $identifier = '#' . ( $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id() ); + } } + + return apply_filters( 'woocommerce_ga_product_identifier', $identifier, $product ); } /** diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index c3648bd2..803c483c 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -199,7 +199,7 @@ public function init_form_fields() { 'product_sku' => __( 'Product SKU with prefixed (#) ID as fallback', 'woocommerce-google-analytics-integration' ) ), // If the option is not set then the product SKU is used as default for existing installations - 'default' => get_option( 'woocommerce_ga_product_identifier', 'product_sku' ) + 'default' => 'product_sku' ), 'ga_id' => array( 'title' => __( 'Google Analytics Tracking ID', 'woocommerce-google-analytics-integration' ), From f68cb042a7b6949eb22320a120109dda77fc7f23 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Tue, 3 Oct 2023 14:41:05 +0100 Subject: [PATCH 004/126] Add unit tests --- tests/unit-tests/WCGoogleGtagJS.php | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 47d06a19..232d873b 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -116,4 +116,39 @@ public function test_integration_script_is_enqueued_for_variation() { $this->assertEquals( true, wp_script_is( $gtag->script_handle . '-ga-integration', 'enqueued' ), 'the script is enqueued' ); } + /** + * Test the get_product_identifier method to verify: + * + * 1. Product SKU is returned if the `ga_product_identifier` option is set to `product_sku`. + * 2. Prefixed (#) product ID is returned if the `ga_product_identifier` option is set to `product_sku` and the product SKU is empty. + * 3. Product ID is returned if the `ga_product_identifier` option is set to `product_id`. + * 4. The filter `woocommerce_ga_product_identifier` can be used to modify the value. + * + * @return void + */ + public function test_get_product_identifier() { + $mock_sku = $this->getMockBuilder( WC_Google_Gtag_JS::class ) + ->setMethods( array( '__construct' ) ) + ->setConstructorArgs( array( array( 'ga_product_identifier' => 'product_sku' ) ) ) + ->getMock(); + + $this->assertEquals( $this->get_product()->get_sku(), $mock_sku::get_product_identifier( $this->get_product() ) ); + + $this->get_product()->set_sku( '' ); + $this->assertEquals( '#' . $this->get_product()->get_id(), $mock_sku::get_product_identifier( $this->get_product() ) ); + + $mock_id = $this->getMockBuilder( WC_Google_Gtag_JS::class ) + ->setMethods( array( '__construct' ) ) + ->setConstructorArgs( array( array( 'ga_product_identifier' => 'product_id' ) ) ) + ->getMock(); + + $this->assertEquals( $this->get_product()->get_id(), $mock_id::get_product_identifier( $this->get_product() ) ); + + add_filter( 'woocommerce_ga_product_identifier', function( $product ) { + return 'filtered'; + } ); + + $this->assertEquals( 'filtered', $mock_id::get_product_identifier( $this->get_product() ) ); + } + } From 0ddd6f87cc61b9e5b5f4b21e9ba5f36f9f992d34 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 3 Oct 2023 18:07:26 +0100 Subject: [PATCH 005/126] Add default option value --- includes/class-wc-abstract-google-analytics-js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index f0b1733e..53b7eeca 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -35,7 +35,7 @@ abstract public static function get_instance( $options = array() ); * @return string Value of the option */ protected static function get( $option ) { - return self::$options[ $option ]; + return self::$options[ $option ] ?? null; } /** From 09fb50c3069226b0aa3684ebc9438b96540abcc1 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 3 Oct 2023 18:45:21 +0100 Subject: [PATCH 006/126] Set default options for new installations --- woocommerce-google-analytics-integration.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/woocommerce-google-analytics-integration.php b/woocommerce-google-analytics-integration.php index ede6eabb..49429ab3 100644 --- a/woocommerce-google-analytics-integration.php +++ b/woocommerce-google-analytics-integration.php @@ -30,6 +30,7 @@ __FILE__, function () { WC_Google_Analytics_Integration::get_instance()->maybe_show_ga_pro_notices(); + WC_Google_Analytics_Integration::get_instance()->maybe_set_defaults(); } ); @@ -208,6 +209,23 @@ public function maybe_show_ga_pro_notices() { update_option( 'woocommerce_google_analytics_pro_notice_shown', true ); } + /** + * Set default options during activation if no settings exist + * + * @since x.x.x + * + * @return void + */ + public function maybe_set_defaults() { + $settings_key = 'woocommerce_google_analytics_settings'; + + if ( false === get_option( $settings_key, false ) ) { + update_option( $settings_key, array( + 'ga_product_identifier' => 'product_id' + ) ); + } + } + /** * Get the path to something in the plugin dir. * From 08e026891abbdf6f2974ba72ddfa5965561aee90 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 3 Oct 2023 18:45:32 +0100 Subject: [PATCH 007/126] Fix CS --- includes/class-wc-google-analytics.php | 4 ++-- tests/unit-tests/WCGoogleGtagJS.php | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 803c483c..8d746732 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -196,10 +196,10 @@ public function init_form_fields() { 'type' => 'select', 'options' => array( 'product_id' => __( 'Product ID', 'woocommerce-google-analytics-integration' ), - 'product_sku' => __( 'Product SKU with prefixed (#) ID as fallback', 'woocommerce-google-analytics-integration' ) + 'product_sku' => __( 'Product SKU with prefixed (#) ID as fallback', 'woocommerce-google-analytics-integration' ), ), // If the option is not set then the product SKU is used as default for existing installations - 'default' => 'product_sku' + 'default' => 'product_sku', ), 'ga_id' => array( 'title' => __( 'Google Analytics Tracking ID', 'woocommerce-google-analytics-integration' ), diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 232d873b..75e66550 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -144,9 +144,12 @@ public function test_get_product_identifier() { $this->assertEquals( $this->get_product()->get_id(), $mock_id::get_product_identifier( $this->get_product() ) ); - add_filter( 'woocommerce_ga_product_identifier', function( $product ) { - return 'filtered'; - } ); + add_filter( + 'woocommerce_ga_product_identifier', + function( $product ) { + return 'filtered'; + } + ); $this->assertEquals( 'filtered', $mock_id::get_product_identifier( $this->get_product() ) ); } From daf592becf1017b8d66ffd84d38cc24ef6053562 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 4 Oct 2023 09:43:23 +0100 Subject: [PATCH 008/126] Fix CS --- woocommerce-google-analytics-integration.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/woocommerce-google-analytics-integration.php b/woocommerce-google-analytics-integration.php index 49429ab3..337e0b70 100644 --- a/woocommerce-google-analytics-integration.php +++ b/woocommerce-google-analytics-integration.php @@ -211,7 +211,7 @@ public function maybe_show_ga_pro_notices() { /** * Set default options during activation if no settings exist - * + * * @since x.x.x * * @return void @@ -220,9 +220,12 @@ public function maybe_set_defaults() { $settings_key = 'woocommerce_google_analytics_settings'; if ( false === get_option( $settings_key, false ) ) { - update_option( $settings_key, array( - 'ga_product_identifier' => 'product_id' - ) ); + update_option( + $settings_key, + array( + 'ga_product_identifier' => 'product_id', + ) + ); } } From eea89a64d21d3a2cf78bc3eeb3b10535381b2d52 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 4 Oct 2023 16:56:06 +0100 Subject: [PATCH 009/126] Update product object structure --- assets/js/src/utils.js | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/assets/js/src/utils.js b/assets/js/src/utils.js index af74dfda..8a68e102 100644 --- a/assets/js/src/utils.js +++ b/assets/js/src/utils.js @@ -11,10 +11,10 @@ import { addAction, removeAction } from '@wordpress/hooks'; */ export const getProductFieldObject = ( product, quantity ) => { return { - id: getProductId( product ), - name: product.name, + item_id: getProductId( product ), + item_name: product.name, quantity, - category: getProductCategory( product ), + ...getProductCategories( product ), price: formatPrice( product.prices.price, product.prices.currency_minor_unit @@ -33,10 +33,10 @@ export const getProductFieldObject = ( product, quantity ) => { */ export const getProductImpressionObject = ( product, listName ) => { return { - id: getProductId( product ), - name: product.name, - list_name: listName, - category: getProductCategory( product ), + item_id: getProductId( product ), + item_name: product.name, + item_list_name: listName, + ...getProductCategories( product ), price: formatPrice( product.prices.price, product.prices.currency_minor_unit @@ -86,8 +86,34 @@ const getProductId = ( product ) => { * * @return {string} - The name of the first category of the product or an empty string if the product has no categories. */ -const getProductCategory = ( product ) => { +const getProductCategories = ( product ) => { return 'categories' in product && product.categories.length - ? product.categories[ 0 ].name - : ''; + ? getCategoryObject( product.categories ) + : {}; +}; + +/** + * Returns an object containing up to 5 categories for the product. + * + * @param {Object} categories - An array of product categories + * + * @return {Object} - An categories object + */ +const getCategoryObject = ( categories ) => { + return Object.fromEntries( + categories.slice( 0, 5 ).map( ( category, index ) => { + return [ formatCategoryKey( index ), category.name ]; + } ) + ); +}; + +/** + * Returns the correctly formatted key for the category object. + * + * @param {number} index Index of the current category + * + * @return {string} - A formatted key for the category object + */ +const formatCategoryKey = ( index ) => { + return 'item_category' + ( index > 0 ? index + 1 : '' ); }; From ed8af327178c5a0f9fe503bb54ac856af352fb03 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 4 Oct 2023 16:57:13 +0100 Subject: [PATCH 010/126] Update view_item_list event --- assets/js/src/tracking.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 63329121..d78c441c 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -24,14 +24,14 @@ export const trackListProducts = ( { listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), } ) => { trackEvent( 'view_item_list', { - event_category: 'engagement', - event_label: __( + item_list_id: 'engagement', + item_list_name: __( 'Viewing products', 'woocommerce-google-analytics-integration' ), items: products.map( ( product, index ) => ( { ...getProductImpressionObject( product, listName ), - list_position: index + 1, + index: index + 1, } ) ), } ); }; From 6641bf607dd062e748d210793e0fb9c773044056 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 4 Oct 2023 16:57:40 +0100 Subject: [PATCH 011/126] Update add_to_cart event --- assets/js/src/tracking.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index d78c441c..3c7babda 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -45,11 +45,6 @@ export const trackListProducts = ( { */ export const trackAddToCart = ( { product, quantity = 1 } ) => { trackEvent( 'add_to_cart', { - event_category: 'ecommerce', - event_label: __( - 'Add to Cart', - 'woocommerce-google-analytics-integration' - ), items: [ getProductFieldObject( product, quantity ) ], } ); }; From a663b1213aebbab554c094dc472547c038bc5caf Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 4 Oct 2023 17:06:52 +0100 Subject: [PATCH 012/126] Update remove_from_cart event --- assets/js/src/tracking.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 3c7babda..4f97e719 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -58,11 +58,6 @@ export const trackAddToCart = ( { product, quantity = 1 } ) => { */ export const trackRemoveCartItem = ( { product, quantity = 1 } ) => { trackEvent( 'remove_from_cart', { - event_category: 'ecommerce', - event_label: __( - 'Remove Cart Item', - 'woocommerce-google-analytics-integration' - ), items: [ getProductFieldObject( product, quantity ) ], } ); }; From 80dde61ac467cd9924bf49b5bcd2bc023110cc91 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 16:37:12 +0100 Subject: [PATCH 013/126] Add begin_checkout event --- assets/js/src/actions.js | 4 +++- assets/js/src/tracking.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 502e4473..73aa3df1 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -2,6 +2,7 @@ import { __ } from '@wordpress/i18n'; import { NAMESPACE, ACTION_PREFIX } from './constants'; import { + trackBeginCheckout, trackListProducts, trackAddToCart, trackChangeCartItemQuantity, @@ -30,8 +31,9 @@ import { addUniqueAction } from './utils'; addUniqueAction( `${ ACTION_PREFIX }-checkout-render-checkout-form`, NAMESPACE, - ( { ...storeCart } ) => trackCheckoutStep( 0 )( storeCart ) + trackBeginCheckout ); + addUniqueAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE, diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 4f97e719..54297ae4 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -80,6 +80,23 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { } ); }; +/** + * Track begin_checkout event + * + * @param { storeCart: Object } param The cart object received from the store. + */ +export const trackBeginCheckout = ( { storeCart } ) => { + trackEvent( 'begin_checkout', { + currency: storeCart.totals.currency_code, + value: formatPrice( + storeCart.totals.total_price, + storeCart.totals.currency_minor_unit + ), + coupon: storeCart.coupons[ 0 ]?.code || '', + items: storeCart.items.map( getProductFieldObject ), + }); +}; + /** * Track a begin_checkout and checkout_progress event * Notice calling this will set the current checkout step as the step provided in the parameter. From 92bafdd3b9e366d22bde5ce6cfce225cecba4194 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 16:43:04 +0100 Subject: [PATCH 014/126] Remove email, phone, and billing address events --- assets/js/src/actions.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 73aa3df1..7178b50d 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -1,5 +1,5 @@ import { __ } from '@wordpress/i18n'; - +import { removeAction } from '@wordpress/hooks'; import { NAMESPACE, ACTION_PREFIX } from './constants'; import { trackBeginCheckout, @@ -34,28 +34,15 @@ addUniqueAction( trackBeginCheckout ); -addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-email-address`, - NAMESPACE, - ( { ...storeCart } ) => trackCheckoutStep( 1 )( storeCart ) -); addUniqueAction( `${ ACTION_PREFIX }-checkout-set-shipping-address`, NAMESPACE, ( { ...storeCart } ) => trackCheckoutStep( 2 )( storeCart ) ); -addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-billing-address`, - NAMESPACE, - ( { ...storeCart } ) => trackCheckoutStep( 3 )( storeCart ) -); -addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-phone-number`, - NAMESPACE, - ( { step, ...storeCart } ) => { - trackCheckoutStep( step === 'shipping' ? 2 : 3 )( storeCart ); - } -); + +removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); /** * Choose a shipping rate From bd25a7ed425832dca5639b7e1156f4a96d7034df Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 17:59:45 +0100 Subject: [PATCH 015/126] Add add_shipping_info event --- assets/js/src/actions.js | 3 ++- assets/js/src/tracking.js | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 7178b50d..e5f07eb6 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -3,6 +3,7 @@ import { removeAction } from '@wordpress/hooks'; import { NAMESPACE, ACTION_PREFIX } from './constants'; import { trackBeginCheckout, + trackShippingInfo, trackListProducts, trackAddToCart, trackChangeCartItemQuantity, @@ -37,7 +38,7 @@ addUniqueAction( addUniqueAction( `${ ACTION_PREFIX }-checkout-set-shipping-address`, NAMESPACE, - ( { ...storeCart } ) => trackCheckoutStep( 2 )( storeCart ) + trackShippingInfo ); removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 54297ae4..3d1416d2 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -83,7 +83,7 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { /** * Track begin_checkout event * - * @param { storeCart: Object } param The cart object received from the store. + * @param { storeCart: Object } param The cart object */ export const trackBeginCheckout = ( { storeCart } ) => { trackEvent( 'begin_checkout', { @@ -97,6 +97,23 @@ export const trackBeginCheckout = ( { storeCart } ) => { }); }; +/** + * Track add_shipping_info event + * + * @param { storeCart: Object } param The cart object + */ +export const trackShippingInfo = ( { storeCart } ) => { + trackEvent( 'add_shipping_info', { + currency: storeCart.totals.currency_code, + value: formatPrice( + storeCart.totals.total_price, + storeCart.totals.currency_minor_unit + ), + coupon: storeCart.coupons[ 0 ]?.code || '', + items: storeCart.items.map( getProductFieldObject ), + }); +}; + /** * Track a begin_checkout and checkout_progress event * Notice calling this will set the current checkout step as the step provided in the parameter. From b6ec761abe575dd432f078fc9b8bf1e7a5477f10 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:00:26 +0100 Subject: [PATCH 016/126] Remove trackCheckoutStep --- assets/js/src/actions.js | 1 - assets/js/src/tracking.js | 28 ---------------------------- 2 files changed, 29 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index e5f07eb6..6274040d 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -8,7 +8,6 @@ import { trackAddToCart, trackChangeCartItemQuantity, trackRemoveCartItem, - trackCheckoutStep, trackCheckoutOption, trackEvent, trackSelectContent, diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 3d1416d2..99d45daf 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -114,34 +114,6 @@ export const trackShippingInfo = ( { storeCart } ) => { }); }; -/** - * Track a begin_checkout and checkout_progress event - * Notice calling this will set the current checkout step as the step provided in the parameter. - * - * @param {number} step The checkout step for to track - * @return {(function( { storeCart: Object } ): void)} A callable receiving the cart to track the checkout event. - */ -export const trackCheckoutStep = - ( step ) => - ( { storeCart } ) => { - if ( currentStep === step ) { - return; - } - - trackEvent( step === 0 ? 'begin_checkout' : 'checkout_progress', { - items: storeCart.cartItems.map( getProductFieldObject ), - coupon: storeCart.cartCoupons[ 0 ]?.code || '', - currency: storeCart.cartTotals.currency_code, - value: formatPrice( - storeCart.cartTotals.total_price, - storeCart.cartTotals.currency_minor_unit - ), - checkout_step: step, - } ); - - currentStep = step; - }; - /** * Track a set_checkout_option event * Notice calling this will set the current checkout step as the step provided in the parameter. From bd73c66824505103563b5fd86cd6c7f8b22ba310 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:28:11 +0100 Subject: [PATCH 017/126] Track add_shipping_info on checkout submit --- assets/js/src/actions.js | 53 ++++++++++++++++----------------------- assets/js/src/tracking.js | 3 ++- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 6274040d..8f3e9ecf 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -3,7 +3,8 @@ import { removeAction } from '@wordpress/hooks'; import { NAMESPACE, ACTION_PREFIX } from './constants'; import { trackBeginCheckout, - trackShippingInfo, + trackShippingTier, + trackPaymentMethod, trackListProducts, trackAddToCart, trackChangeCartItemQuantity, @@ -18,15 +19,10 @@ import { import { addUniqueAction } from './utils'; /** - * Track customer progress through steps of the checkout. Triggers the event when the step changes: - * 1 - Contact information - * 2 - Shipping address - * 3 - Billing address - * 4 - Shipping options - * 5 - Payment options + * Track begin_checkout * - * @summary Track checkout progress with begin_checkout and checkout_progress - * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#1_measure_checkout_steps + * @summary Track the customer has started the checkout process + * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#begin_checkout */ addUniqueAction( `${ ACTION_PREFIX }-checkout-render-checkout-form`, @@ -34,34 +30,26 @@ addUniqueAction( trackBeginCheckout ); +/** + * Track add_shipping_info + * + * @summary Track the selected shipping tier when the checkout form is submitted + * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info + */ addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-shipping-address`, + `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE, - trackShippingInfo + trackShippingTier ); +/** + * The following actions were previously tracked using]checkout_progress + * in UA but there is no comparable event in GA4. + */ removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); -/** - * Choose a shipping rate - * - * @summary Track the shipping rate being set using set_checkout_option - * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options - */ -addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-selected-shipping-rate`, - NAMESPACE, - ( { shippingRateId } ) => { - trackCheckoutOption( { - step: 4, - option: __( 'Shipping Method', 'woo-gutenberg-products-block' ), - value: shippingRateId, - } )(); - } -); - /** * Choose a payment method * @@ -138,9 +126,10 @@ addUniqueAction( * @summary Track the add_payment_info event * @see https://developers.google.com/gtagjs/reference/ga4-events#add_payment_info */ -addUniqueAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE, () => { - trackEvent( 'add_payment_info' ); -} ); +// addUniqueAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE, ( c ) => { +// console.log( c ); +// trackEvent( 'add_payment_info' ); +// } ); /** * Product View Link Clicked diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 99d45daf..87db2f14 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -102,7 +102,7 @@ export const trackBeginCheckout = ( { storeCart } ) => { * * @param { storeCart: Object } param The cart object */ -export const trackShippingInfo = ( { storeCart } ) => { +export const trackShippingTier = ( storeCart ) => { trackEvent( 'add_shipping_info', { currency: storeCart.totals.currency_code, value: formatPrice( @@ -110,6 +110,7 @@ export const trackShippingInfo = ( { storeCart } ) => { storeCart.totals.currency_minor_unit ), coupon: storeCart.coupons[ 0 ]?.code || '', + shipping_tier: storeCart.shippingRates[ 0 ]?.shipping_rates?.find( rate => rate.selected )?.name || '', items: storeCart.items.map( getProductFieldObject ), }); }; From 1dabbe19a6ff5a178c4c79c0f1ab23e5e5b82b13 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:31:57 +0100 Subject: [PATCH 018/126] Remove trackCheckoutOption --- assets/js/src/actions.js | 21 --------------------- assets/js/src/tracking.js | 23 ----------------------- 2 files changed, 44 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 8f3e9ecf..0b2faab4 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -4,13 +4,10 @@ import { NAMESPACE, ACTION_PREFIX } from './constants'; import { trackBeginCheckout, trackShippingTier, - trackPaymentMethod, trackListProducts, trackAddToCart, trackChangeCartItemQuantity, trackRemoveCartItem, - trackCheckoutOption, - trackEvent, trackSelectContent, trackSearch, trackViewItem, @@ -50,24 +47,6 @@ removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); -/** - * Choose a payment method - * - * @summary Track the payment method being set using set_checkout_option - * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options - */ -addUniqueAction( - `${ ACTION_PREFIX }-checkout-set-active-payment-method`, - NAMESPACE, - ( { paymentMethodSlug } ) => { - trackCheckoutOption( { - step: 5, - option: __( 'Payment Method', 'woo-gutenberg-products-block' ), - value: paymentMethodSlug, - } )(); - } -); - /** * Product List View * diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 87db2f14..bad56986 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -115,29 +115,6 @@ export const trackShippingTier = ( storeCart ) => { }); }; -/** - * Track a set_checkout_option event - * Notice calling this will set the current checkout step as the step provided in the parameter. - * - * @param {Object} params The params from the option. - * @param {number} params.step The step to track - * @param {string} params.option The option to set in checkout - * @param {string} params.value The value for the option - * - * @return {(function() : void)} A callable to track the checkout event. - */ -export const trackCheckoutOption = - ( { step, option, value } ) => - () => { - trackEvent( 'set_checkout_option', { - checkout_step: step, - checkout_option: option, - value, - } ); - - currentStep = step; - }; - /** * Tracks select_content event. * From 0f614c91e07ba2eeed8a4cab7c5d63edccbb6231 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:32:27 +0100 Subject: [PATCH 019/126] Remove add_payment_info event --- assets/js/src/actions.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 0b2faab4..24b9bdeb 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -96,20 +96,6 @@ addUniqueAction( trackRemoveCartItem ); -/** - * Add Payment Information - * - * This event signifies a user has submitted their payment information. Note, this is used to indicate checkout - * submission, not `purchase` which is triggered on the thanks page. - * - * @summary Track the add_payment_info event - * @see https://developers.google.com/gtagjs/reference/ga4-events#add_payment_info - */ -// addUniqueAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE, ( c ) => { -// console.log( c ); -// trackEvent( 'add_payment_info' ); -// } ); - /** * Product View Link Clicked * From 9aef6f53025f32a0fcde9701b8e024194de85652 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:42:41 +0100 Subject: [PATCH 020/126] Update select_content event --- assets/js/src/tracking.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index bad56986..ef74d8b6 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -120,15 +120,11 @@ export const trackShippingTier = ( storeCart ) => { * * @param {Object} params The function params * @param {Object} params.product The product to track - * @param {string} params.listName The name of the list in which the item was presented to the user. */ -export const trackSelectContent = ( { - product, - listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), -} ) => { +export const trackSelectContent = ( { product } ) => { trackEvent( 'select_content', { content_type: 'product', - items: [ getProductImpressionObject( product, listName ) ], + content_id: getProductId( product ), } ); }; From 65fcc7af785b25feffd72eb7bae0556f5eb5beb0 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:44:20 +0100 Subject: [PATCH 021/126] Remove @wordpress/i18n import --- assets/js/src/actions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js index 24b9bdeb..94017c33 100644 --- a/assets/js/src/actions.js +++ b/assets/js/src/actions.js @@ -1,4 +1,3 @@ -import { __ } from '@wordpress/i18n'; import { removeAction } from '@wordpress/hooks'; import { NAMESPACE, ACTION_PREFIX } from './constants'; import { From 662a963f2957f4976c79513fa7b700669eed38d5 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:44:42 +0100 Subject: [PATCH 022/126] Remove currentStep --- assets/js/src/tracking.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index ef74d8b6..70ec7d62 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -5,13 +5,6 @@ import { formatPrice, } from './utils'; -/** - * Variable holding the current checkout step. It will be modified by trackCheckoutOption and trackCheckoutStep methods. - * - * @type {number} - */ -let currentStep = -1; - /** * Tracks view_item_list event * From 848c44680f4a7476ea65a60ef15207593574a03f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 5 Oct 2023 18:57:42 +0100 Subject: [PATCH 023/126] Export getProductId --- assets/js/src/tracking.js | 22 ++++++++++++++-------- assets/js/src/utils.js | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 70ec7d62..2051354a 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -2,6 +2,7 @@ import { __ } from '@wordpress/i18n'; import { getProductFieldObject, getProductImpressionObject, + getProductId, formatPrice, } from './utils'; @@ -75,8 +76,9 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { /** * Track begin_checkout event - * - * @param { storeCart: Object } param The cart object + * + * @param {Object} params The function params + * @param {Object} params.storeCart The cart object */ export const trackBeginCheckout = ( { storeCart } ) => { trackEvent( 'begin_checkout', { @@ -87,15 +89,16 @@ export const trackBeginCheckout = ( { storeCart } ) => { ), coupon: storeCart.coupons[ 0 ]?.code || '', items: storeCart.items.map( getProductFieldObject ), - }); + } ); }; /** * Track add_shipping_info event - * - * @param { storeCart: Object } param The cart object + * + * @param {Object} params The function params + * @param {Object} params.storeCart The cart object */ -export const trackShippingTier = ( storeCart ) => { +export const trackShippingTier = ( { storeCart } ) => { trackEvent( 'add_shipping_info', { currency: storeCart.totals.currency_code, value: formatPrice( @@ -103,9 +106,12 @@ export const trackShippingTier = ( storeCart ) => { storeCart.totals.currency_minor_unit ), coupon: storeCart.coupons[ 0 ]?.code || '', - shipping_tier: storeCart.shippingRates[ 0 ]?.shipping_rates?.find( rate => rate.selected )?.name || '', + shipping_tier: + storeCart.shippingRates[ 0 ]?.shipping_rates?.find( + ( rate ) => rate.selected + )?.name || '', items: storeCart.items.map( getProductFieldObject ), - }); + } ); }; /** diff --git a/assets/js/src/utils.js b/assets/js/src/utils.js index 8a68e102..e84bfea8 100644 --- a/assets/js/src/utils.js +++ b/assets/js/src/utils.js @@ -75,7 +75,7 @@ export const addUniqueAction = ( hookName, namespace, callback ) => { * * @return {string} - The product ID */ -const getProductId = ( product ) => { +export const getProductId = ( product ) => { return product.sku ? product.sku : '#' + product.id; }; From 4371430c82ceed0c0c83a55496e323bc24760e89 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 9 Oct 2023 12:51:49 +0100 Subject: [PATCH 024/126] Update comment --- includes/class-wc-abstract-google-analytics-js.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 53b7eeca..b8472bb4 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -31,8 +31,9 @@ abstract public static function get_instance( $options = array() ); /** * Return one of our options * - * @param string $option Key/name for the option - * @return string Value of the option + * @param string $option Key/name for the option + * + * @return string|null Value of the option or null if not found */ protected static function get( $option ) { return self::$options[ $option ] ?? null; From 9840d7f53823fdd68c7bf31b2ec494b5ef153b7a Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 25 Oct 2023 16:28:03 +0100 Subject: [PATCH 025/126] Check data exists before tracking view_item_list --- assets/js/src/tracking.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 2051354a..ee826367 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -17,17 +17,19 @@ export const trackListProducts = ( { products, listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), } ) => { - trackEvent( 'view_item_list', { - item_list_id: 'engagement', - item_list_name: __( - 'Viewing products', - 'woocommerce-google-analytics-integration' - ), - items: products.map( ( product, index ) => ( { - ...getProductImpressionObject( product, listName ), - index: index + 1, - } ) ), - } ); + if ( products.length > 0 ) { + trackEvent( 'view_item_list', { + item_list_id: 'engagement', + item_list_name: __( + 'Viewing products', + 'woocommerce-google-analytics-integration' + ), + items: products.map( ( product, index ) => ( { + ...getProductImpressionObject( product, listName ), + index: index + 1, + } ) ), + } ); + } }; /** From e61521798cb657d1fbd9763ae9db0d72315a279d Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 26 Oct 2023 09:27:16 +0100 Subject: [PATCH 026/126] Check product for quantity --- assets/js/src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/src/utils.js b/assets/js/src/utils.js index e84bfea8..a343b788 100644 --- a/assets/js/src/utils.js +++ b/assets/js/src/utils.js @@ -13,7 +13,7 @@ export const getProductFieldObject = ( product, quantity ) => { return { item_id: getProductId( product ), item_name: product.name, - quantity, + quantity: product.quantity ?? quantity, ...getProductCategories( product ), price: formatPrice( product.prices.price, From c566ea7f34636c460ffc8bc1aeb22a03fcff9b73 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 07:25:52 +0100 Subject: [PATCH 027/126] Remove tracking from class-wc-google-analytics.php --- includes/class-wc-google-analytics.php | 197 ------------------------- 1 file changed, 197 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 8d746732..187d1b76 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -126,16 +126,6 @@ public function __construct() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_tracking_code' ), 9 ); add_filter( 'script_loader_tag', array( $this, 'async_script_loader_tags' ), 10, 3 ); - // Event tracking code - add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) ); - add_action( 'wp_footer', array( $this, 'loop_add_to_cart' ) ); - add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) ); - add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) ); - add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 ); - add_filter( 'woocommerce_loop_add_to_cart_link', array( $this, 'track_product' ), 10, 2 ); - add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) ); - add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) ); - // utm_nooverride parameter for Google AdWords add_filter( 'woocommerce_get_return_url', array( $this, 'utm_nooverride' ) ); } @@ -536,193 +526,6 @@ private function disable_tracking( $type ) { return is_admin() || current_user_can( 'manage_options' ) || ( ! $this->ga_id ) || 'no' === $type || apply_filters( 'woocommerce_ga_disable_tracking', false, $type ); } - /** - * Google Analytics event tracking for single product add to cart - */ - public function add_to_cart() { - if ( $this->disable_tracking( $this->ga_event_tracking_enabled ) ) { - return; - } - if ( ! is_single() ) { - return; - } - - global $product; - - if ( 'yes' === $this->ga_gtag_enabled ) { - $this->get_tracking_instance()->add_to_cart( $product ); - return; - } - - // Add single quotes to allow jQuery to be substituted into _trackEvent parameters - $parameters = array(); - $parameters['category'] = "'" . __( 'Products', 'woocommerce-google-analytics-integration' ) . "'"; - $parameters['action'] = "'" . __( 'Add to Cart', 'woocommerce-google-analytics-integration' ) . "'"; - $parameters['label'] = "'" . esc_js( $product->get_sku() ? __( 'ID:', 'woocommerce-google-analytics-integration' ) . ' ' . $product->get_sku() : '#' . $product->get_id() ) . "'"; - - if ( ! $this->disable_tracking( $this->ga_enhanced_ecommerce_tracking_enabled ) ) { - - $item = '{'; - - if ( $product->is_type( 'variable' ) ) { - $item .= "'id': google_analytics_integration_product_data[ $('input[name=\"variation_id\"]').val() ] !== undefined ? google_analytics_integration_product_data[ $('input[name=\"variation_id\"]').val() ].id : false,"; - $item .= "'variant': google_analytics_integration_product_data[ $('input[name=\"variation_id\"]').val() ] !== undefined ? google_analytics_integration_product_data[ $('input[name=\"variation_id\"]').val() ].variant : false,"; - } else { - $item .= "'id': '" . $this->get_tracking_instance()->get_product_identifier( $product ) . "',"; - } - - $item .= "'name': '" . esc_js( $product->get_title() ) . "',"; - $item .= "'category': " . $this->get_tracking_instance()->product_get_category_line( $product ); - $item .= "'quantity': $( 'input.qty' ).val() ? $( 'input.qty' ).val() : '1'"; - $item .= '}'; - - $parameters['item'] = $item; - - $code = $this->get_tracking_instance()->tracker_var() . "( 'ec:addProduct', {$item} );"; - $parameters['enhanced'] = $code; - } - - $this->get_tracking_instance()->event_tracking_code( $parameters, '.single_add_to_cart_button' ); - } - - /** - * Enhanced Analytics event tracking for removing a product from the cart - */ - public function remove_from_cart() { - if ( ! $this->enhanced_ecommerce_enabled( $this->ga_enhanced_remove_from_cart_enabled ) ) { - return; - } - - $this->get_tracking_instance()->remove_from_cart(); - } - - /** - * Adds the product ID and SKU to the remove product link if not present - * - * @param string $url - * @param string $key - * @return string - */ - public function remove_from_cart_attributes( $url, $key ) { - if ( strpos( $url, 'data-product_id' ) !== false ) { - return $url; - } - - if ( ! is_object( WC()->cart ) ) { - return $url; - } - - $item = WC()->cart->get_cart_item( $key ); - $product = $item['data']; - - if ( ! is_object( $product ) ) { - return $url; - } - - $url = str_replace( 'href=', 'data-product_id="' . esc_attr( $product->get_id() ) . '" data-product_sku="' . esc_attr( $product->get_sku() ) . '" href=', $url ); - return $url; - } - - /** - * Google Analytics event tracking for loop add to cart - */ - public function loop_add_to_cart() { - if ( $this->disable_tracking( $this->ga_event_tracking_enabled ) || 'yes' === $this->ga_gtag_enabled ) { - return; - } - - // Add single quotes to allow jQuery to be substituted into _trackEvent parameters - $parameters = array(); - $parameters['category'] = "'" . __( 'Products', 'woocommerce-google-analytics-integration' ) . "'"; - $parameters['action'] = "'" . __( 'Add to Cart', 'woocommerce-google-analytics-integration' ) . "'"; - $parameters['label'] = "($(this).data('product_sku')) ? ($(this).data('product_sku')) : ('#' + $(this).data('product_id'))"; // Product SKU or ID - - if ( ! $this->disable_tracking( $this->ga_enhanced_ecommerce_tracking_enabled ) ) { - $item = '{'; - $item .= "'id': ($(this).data('product_sku')) ? ($(this).data('product_sku')) : ('#' + $(this).data('product_id')),"; - $item .= "'quantity': $(this).data('quantity')"; - $item .= '}'; - $parameters['item'] = $item; - - $code = $this->get_tracking_instance()->tracker_var() . "( 'ec:addProduct', " . $item . ' );'; - $parameters['enhanced'] = $code; - } - - $this->get_tracking_instance()->event_tracking_code( $parameters, '.add_to_cart_button:not(.product_type_variable, .product_type_grouped)' ); - } - - /** - * Determine if the conditions are met for enhanced ecommerce interactions to be displayed. - * Currently checks if Global Tags OR Universal Analytics are enabled, plus Enhanced eCommerce. - * - * @param array $extra_checks Any extra option values that should be 'yes' to proceed - * @return bool Whether enhanced ecommerce transactions can be displayed. - */ - protected function enhanced_ecommerce_enabled( $extra_checks = [] ) { - if ( ! is_array( $extra_checks ) ) { - $extra_checks = [ $extra_checks ]; - } - - // False if gtag and UA are disabled. - if ( $this->disable_tracking( $this->ga_use_universal_analytics ) && $this->disable_tracking( $this->ga_gtag_enabled ) ) { - return false; - } - - // False if gtag or UA is enabled, but enhanced ecommerce is disabled. - if ( $this->disable_tracking( $this->ga_enhanced_ecommerce_tracking_enabled ) ) { - return false; - } - - // False if any specified interaction-level checks are disabled. - foreach ( $extra_checks as $option_value ) { - if ( $this->disable_tracking( $option_value ) ) { - return false; - } - } - - return true; - } - - /** - * Measure a product click and impression from a Product list - * - * @param string $link The Add To Cart Link - * @param WC_Product $product The Product - */ - public function track_product( $link, $product ) { - if ( $this->enhanced_ecommerce_enabled( $this->ga_enhanced_product_click_enabled ) ) { - $this->get_tracking_instance()->listing_impression( $product ); - $this->get_tracking_instance()->listing_click( $product ); - } - - return $link; - } - - /** - * Measure a product detail view - */ - public function product_detail() { - if ( ! $this->enhanced_ecommerce_enabled( $this->ga_enhanced_product_detail_view_enabled ) ) { - return; - } - - global $product; - $this->get_tracking_instance()->product_detail( $product ); - } - - /** - * Tracks when the checkout form is loaded - * - * @param mixed $checkout (unused) - */ - public function checkout_process( $checkout ) { - if ( ! $this->enhanced_ecommerce_enabled( $this->ga_enhanced_checkout_process_enabled ) ) { - return; - } - - $this->get_tracking_instance()->checkout_process( WC()->cart->get_cart() ); - } - /** * Add the utm_nooverride parameter to any return urls. This makes sure Google Adwords doesn't mistake the offsite gateway as the referrer. * From be97de4ce70c00b10619f8a5a608ebee0d340f30 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 07:32:11 +0100 Subject: [PATCH 028/126] Remove UA settings --- includes/class-wc-google-analytics.php | 77 +------------------------- 1 file changed, 2 insertions(+), 75 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 187d1b76..8822df64 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -20,15 +20,6 @@ class WC_Google_Analytics extends WC_Integration { /** @var string $ga_id Google Analytics Tracking ID */ public $ga_id; - /** @var string $ga_set_domain_name Domain Name (legacy setting) */ - public $ga_set_domain_name; - - /** @var string $ga_use_universal_analytics Use Legacy Universal Analytics (yes|no) */ - public $ga_use_universal_analytics; - - /** @var string $ga_gtag_enable Is GA4 enabled (yes|no) */ - public $ga_gtag_enabled; - /** @var string $ga_standard_tracking_enabled Is standard tracking enabled (yes|no) */ public $ga_standard_tracking_enabled; @@ -47,9 +38,6 @@ class WC_Google_Analytics extends WC_Integration { /** @var string $ga_ecommerce_tracking_enabled Purchase transactions (yes|no) */ public $ga_ecommerce_tracking_enabled; - /** @var string $ga_enhanced_ecommerce_tracking_enabled Enhanced ecommerce tracking (yes|no) */ - public $ga_enhanced_ecommerce_tracking_enabled; - /** @var string $ga_enhanced_remove_from_cart_enabled Track remove from cart events (yes|no) */ public $ga_enhanced_remove_from_cart_enabled; @@ -86,11 +74,7 @@ class WC_Google_Analytics extends WC_Integration { * @return WC_Abstract_Google_Analytics_JS */ protected function get_tracking_instance( $options = array() ) { - if ( 'yes' === $this->ga_gtag_enabled ) { - return WC_Google_Gtag_JS::get_instance( $options ); - } - - return WC_Google_Analytics_JS::get_instance( $options ); + return WC_Google_Gtag_JS::get_instance( $options ); } /** @@ -139,16 +123,11 @@ public function init_options() { $options = array( 'ga_product_identifier' => 'product_sku', 'ga_id' => null, - 'ga_set_domain_name' => null, - 'ga_gtag_enabled' => null, 'ga_standard_tracking_enabled' => null, 'ga_support_display_advertising' => null, 'ga_support_enhanced_link_attribution' => null, - 'ga_use_universal_analytics' => null, 'ga_anonymize_enabled' => null, 'ga_404_tracking_enabled' => null, - 'ga_ecommerce_tracking_enabled' => null, - 'ga_enhanced_ecommerce_tracking_enabled' => null, 'ga_enhanced_remove_from_cart_enabled' => null, 'ga_enhanced_product_impression_enabled' => null, 'ga_enhanced_product_click_enabled' => null, @@ -171,14 +150,6 @@ public function init_options() { * Tells WooCommerce which settings to display under the "integration" tab */ public function init_form_fields() { - // backwards_compatibility - if ( get_option( 'woocommerce_ga_use_universal_analytics' ) ) { - $ua_default_value = get_option( 'woocommerce_ga_use_universal_analytics' ); - } else { - // don't enable for extension updates, only default to enabled on new installs - $ua_default_value = get_option( $this->get_option_key() ) ? 'no' : 'yes'; - } - $this->form_fields = array( 'ga_product_identifier' => array( 'title' => __( 'Product Identification', 'woocommerce-google-analytics-integration' ), @@ -198,34 +169,6 @@ public function init_form_fields() { 'placeholder' => 'GT-XXXXX', 'default' => get_option( 'woocommerce_ga_id' ), // Backwards compat ), - 'ga_set_domain_name' => array( - 'title' => __( 'Set Domain Name', 'woocommerce-google-analytics-integration' ), - /* translators: Read more link */ - 'description' => sprintf( __( '(Optional) Sets the _setDomainName variable. %1$sSee here for more information%2$s.', 'woocommerce-google-analytics-integration' ), '', '' ), - 'type' => 'text', - 'default' => '', - 'class' => 'legacy-setting', - ), - - 'ga_gtag_enabled' => array( - 'title' => __( 'Tracking Options', 'woocommerce-google-analytics-integration' ), - 'label' => __( 'Use Global Site Tag', 'woocommerce-google-analytics-integration' ), - /* translators: Read more link */ - 'description' => sprintf( __( 'The Global Site Tag provides streamlined tagging across Google’s site measurement, conversion tracking, and remarketing products. This must be enabled to use a Google Analytics 4 Measurement ID (e.g., G-XXXXX or GT-XXXXX). %1$sSee here for more information%2$s.', 'woocommerce-google-analytics-integration' ), '', '' ), - 'type' => 'checkbox', - 'checkboxgroup' => '', - 'default' => get_option( $this->get_option_key() ) ? 'no' : 'yes', // don't enable on updates, only default on new installs - ), - - 'ga_use_universal_analytics' => array( - 'label' => __( 'Enable Universal Analytics', 'woocommerce-google-analytics-integration' ), - /* translators: Read more start and end links */ - 'description' => sprintf( __( 'Uses Universal Analytics instead of Classic Google Analytics. If you have not previously used Google Analytics on this site, check this box. Otherwise, %1$sfollow step 1 of the Universal Analytics upgrade guide.%2$s Enabling this setting will take care of step 2. %3$sRead more about Universal Analytics%4$s. Universal Analytics or Global Site Tag must be enabled to enable enhanced eCommerce.', 'woocommerce-google-analytics-integration' ), '', '', '', '' ), - 'type' => 'checkbox', - 'checkboxgroup' => '', - 'default' => $ua_default_value, - 'class' => 'legacy-setting', - ), 'ga_standard_tracking_enabled' => array( 'label' => __( 'Enable Standard Tracking', 'woocommerce-google-analytics-integration' ), 'description' => __( 'This tracks session data such as demographics, system, etc. You don\'t need to enable this if you are using a 3rd party Google analytics plugin.', 'woocommerce-google-analytics-integration' ), @@ -293,19 +236,6 @@ public function init_form_fields() { 'checkboxgroup' => '', 'default' => 'no', ), - 'ga_enhanced_ecommerce_tracking_enabled' => array( - 'title' => __( 'Enhanced eCommerce', 'woocommerce-google-analytics-integration' ), - 'label' => __( 'Enable Enhanced eCommerce ', 'woocommerce-google-analytics-integration' ), - /* translators: Read more link */ - 'description' => sprintf( __( 'Enhanced eCommerce allows you to measure more user interactions with your store, including: product impressions, product detail views, starting the checkout process, adding cart items, and removing cart items. Universal Analytics or Global Site Tag must be enabled for Enhanced eCommerce to work. If using Universal Analytics, turn on Enhanced eCommerce in your Google Analytics dashboard before enabling this setting. %1$sSee here for more information%2$s.', 'woocommerce-google-analytics-integration' ), '', '' ), - 'type' => 'checkbox', - 'checkboxgroup' => '', - 'default' => 'no', - 'class' => 'legacy-setting', - ), - - // Enhanced eCommerce Sub-Settings - 'ga_enhanced_remove_from_cart_enabled' => array( 'label' => __( 'Remove from Cart Events', 'woocommerce-google-analytics-integration' ), 'type' => 'checkbox', @@ -373,13 +303,10 @@ public function track_options( $data ) { 'standard_tracking_enabled' => $this->ga_standard_tracking_enabled, 'support_display_advertising' => $this->ga_support_display_advertising, 'support_enhanced_link_attribution' => $this->ga_support_enhanced_link_attribution, - 'use_universal_analytics' => $this->ga_use_universal_analytics, 'anonymize_enabled' => $this->ga_anonymize_enabled, 'ga_404_tracking_enabled' => $this->ga_404_tracking_enabled, 'ecommerce_tracking_enabled' => $this->ga_ecommerce_tracking_enabled, 'event_tracking_enabled' => $this->ga_event_tracking_enabled, - 'gtag_enabled' => $this->ga_gtag_enabled, - 'set_domain_name' => empty( $this->ga_set_domain_name ) ? 'no' : 'yes', 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, 'enhanced_ecommerce_tracking_enabled' => $this->ga_enhanced_ecommerce_tracking_enabled, 'linker_allow_incoming_enabled' => empty( $this->ga_linker_allow_incoming_enabled ) ? 'no' : 'yes', @@ -459,7 +386,7 @@ public function enqueue_tracking_code() { } // Check if is order received page and stop when the products and not tracked - if ( is_order_received_page() && 'yes' === $this->ga_ecommerce_tracking_enabled ) { + if ( is_order_received_page() ) { $order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0; $order = wc_get_order( $order_id ); if ( $order && ! (bool) $order->get_meta( '_ga_tracked' ) ) { From f1e830de2167e5c1495d9c3373fe7891588961ba Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 07:33:16 +0100 Subject: [PATCH 029/126] Remove class-wc-google-analytics-js.php --- includes/class-wc-google-analytics-js.php | 543 ---------------------- includes/class-wc-google-analytics.php | 1 - 2 files changed, 544 deletions(-) delete mode 100644 includes/class-wc-google-analytics-js.php diff --git a/includes/class-wc-google-analytics-js.php b/includes/class-wc-google-analytics-js.php deleted file mode 100644 index 5012bdb6..00000000 --- a/includes/class-wc-google-analytics-js.php +++ /dev/null @@ -1,543 +0,0 @@ -get_order_currency() : $order->get_currency() ) . "']"; - } - - $code .= ');'; - - self::load_analytics_code_in_header( apply_filters( 'woocommerce_ga_classic_snippet_output', $code ) ); - } - - /** - * Enqueues JavaScript to build the addImpression object - * - * @param WC_Product $product - */ - public static function listing_impression( $product ) { - if ( is_search() ) { - $list = 'Search Results'; - } else { - $list = 'Product List'; - } - - wc_enqueue_js( - self::tracker_var() . "( 'ec:addImpression', { - 'id': '" . esc_js( $product->get_id() ) . "', - 'name': '" . esc_js( $product->get_title() ) . "', - 'category': " . self::product_get_category_line( $product ) . " - 'list': '" . esc_js( $list ) . "' - } ); - " - ); - } - - /** - * Enqueues JavaScript to build an addProduct and click object - * - * @param WC_Product $product - */ - public static function listing_click( $product ) { - if ( is_search() ) { - $list = 'Search Results'; - } else { - $list = 'Product List'; - } - - wc_enqueue_js( - " - $( '.product.post-" . esc_js( $product->get_id() ) . ' a , .product.post-' . esc_js( $product->get_id() ) . " button' ).on('click', function() { - if ( false === $(this).hasClass( 'product_type_variable' ) && false === $(this).hasClass( 'product_type_grouped' ) ) { - " . self::tracker_var() . "( 'ec:addProduct', { - 'id': '" . esc_js( $product->get_id() ) . "', - 'name': '" . esc_js( $product->get_title() ) . "', - 'category': " . self::product_get_category_line( $product ) . ' - }); - } - ' . self::tracker_var() . "( 'ec:setAction', 'click', { list: '" . esc_js( $list ) . "' }); - " . self::tracker_var() . "( 'send', 'event', 'UX', 'click', ' " . esc_js( $list ) . "' ); - }); - " - ); - } - - /** - * Loads in the footer - * - * @see wp_footer - */ - public static function classic_analytics_footer() { - if ( 'yes' === self::get( 'ga_support_display_advertising' ) ) { - $ga_url = "('https:' == document.location.protocol ? 'https://' : 'http://') + 'stats.g.doubleclick.net/dc.js'"; - } else { - $ga_url = "('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'"; - } - - $code = "(function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = " . $ga_url . "; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })();"; - - wc_enqueue_js( $code ); - } - - /** - * Enqueues JavaScript to send the pageview last thing (needed for things like addImpression) - */ - public static function load_page_view_footer() { - if ( apply_filters( 'wc_google_analytics_send_pageview', true ) ) { - wc_enqueue_js( self::tracker_var() . "( 'send', 'pageview' ); " ); - } - } - - /** - * This was created to fix public facing api typo in a filter name - * and inform about the deprecation. - * - * @param boolean $send_pageview - */ - public static function universal_analytics_footer_filter( $send_pageview ) { - return apply_filters_deprecated( 'wc_goole_analytics_send_pageview', array( $send_pageview ), '1.4.20', 'wc_google_analytics_send_pageview' ); - } - - /** - * Loads the universal analytics code - * - * @param string $logged_in 'yes' if the user is logged in, no if not (this is a string so we can pass it to GA) - */ - protected static function load_analytics_universal( $logged_in ) { - $domainname = self::get( 'ga_set_domain_name' ); - - if ( ! empty( $domainname ) ) { - $set_domain_name = esc_js( self::get( 'ga_set_domain_name' ) ); - } else { - $set_domain_name = 'auto'; - } - - $support_display_advertising = ''; - if ( 'yes' === self::get( 'ga_support_display_advertising' ) ) { - $support_display_advertising = self::tracker_var() . "( 'require', 'displayfeatures' );"; - } - - $support_enhanced_link_attribution = ''; - if ( 'yes' === self::get( 'ga_support_enhanced_link_attribution' ) ) { - $support_enhanced_link_attribution = self::tracker_var() . "( 'require', 'linkid' );"; - } - - $anonymize_enabled = ''; - if ( 'yes' === self::get( 'ga_anonymize_enabled' ) ) { - $anonymize_enabled = self::tracker_var() . "( 'set', 'anonymizeIp', true );"; - } - - $track_404_enabled = ''; - if ( 'yes' === self::get( 'ga_404_tracking_enabled' ) && is_404() ) { - // See https://developers.google.com/analytics/devguides/collection/analyticsjs/events for reference - $track_404_enabled = self::tracker_var() . "( 'send', 'event', 'Error', '404 Not Found', 'page: ' + document.location.pathname + document.location.search + ' referrer: ' + document.referrer );"; - } - - $src = apply_filters( 'woocommerce_google_analytics_script_src', '//www.google-analytics.com/analytics.js' ); - - $ga_snippet_head = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script', '{$src}','" . self::tracker_var() . "');"; - - $ga_id = self::get( 'ga_id' ); - - if ( 'yes' === self::get( 'ga_linker_allow_incoming_enabled' ) ) { - $ga_snippet_create = self::tracker_var() . "( 'create', '" . esc_js( $ga_id ) . "', '" . $set_domain_name . "', { allowLinker: true });"; - } else { - $ga_snippet_create = self::tracker_var() . "( 'create', '" . esc_js( $ga_id ) . "', '" . $set_domain_name . "' );"; - } - - if ( ! empty( self::DEVELOPER_ID ) ) { - $ga_snippet_developer_id = "(window.gaDevIds=window.gaDevIds||[]).push('" . self::DEVELOPER_ID . "');"; - } else { - $ga_snippet_developer_id = ''; - } - - $ga_snippet_require = $support_display_advertising . - $support_enhanced_link_attribution . - $anonymize_enabled . - $track_404_enabled . ' - ' . self::tracker_var() . "( 'set', 'dimension1', '" . $logged_in . "' );\n"; - - if ( 'yes' === self::get( 'ga_enhanced_ecommerce_tracking_enabled' ) ) { - $ga_snippet_require .= self::tracker_var() . "( 'require', 'ec' );"; - } else { - $ga_snippet_require .= self::tracker_var() . "( 'require', 'ecommerce', 'ecommerce.js');"; - } - - $ga_cross_domains = ! empty( self::get( 'ga_linker_cross_domains' ) ) ? array_map( 'esc_js', explode( ',', self::get( 'ga_linker_cross_domains' ) ) ) : false; - - if ( $ga_cross_domains ) { - $ga_snippet_require .= self::tracker_var() . "( 'require', 'linker' );"; - $ga_snippet_require .= self::tracker_var() . "( 'linker:autoLink', " . wp_json_encode( $ga_cross_domains ) . ');'; - } - - $ga_snippet_head = apply_filters( 'woocommerce_ga_snippet_head', $ga_snippet_head ); - $ga_snippet_create = apply_filters( 'woocommerce_ga_snippet_create', $ga_snippet_create, $ga_id ); - $ga_snippet_developer_id = apply_filters( 'woocommerce_ga_snippet_developer_id', $ga_snippet_developer_id ); - $ga_snippet_require = apply_filters( 'woocommerce_ga_snippet_require', $ga_snippet_require ); - - $code = $ga_snippet_head . $ga_snippet_create . $ga_snippet_developer_id . $ga_snippet_require; - - self::load_analytics_code_in_header( apply_filters( 'woocommerce_ga_snippet_output', $code ) ); - } - - /** - * Generate code used to pass transaction data to Google Analytics. - * - * @param WC_Order $order WC_Order Object. - */ - public function add_transaction( $order ) { - if ( 'yes' === self::get( 'ga_use_universal_analytics' ) ) { - if ( 'yes' === self::get( 'ga_enhanced_ecommerce_tracking_enabled' ) ) { - $transaction_code = self::add_transaction_enhanced( $order ); - } else { - $transaction_code = self::add_transaction_universal( $order ); - } - } else { - $transaction_code = self::add_transaction_classic( $order ); - } - - // Check localStorage to avoid duplicate transactions if page is reloaded without hitting server. - $code = " - var ga_orders = []; - try { - ga_orders = localStorage.getItem( 'ga_orders' ); - ga_orders = ga_orders ? JSON.parse( ga_orders ) : []; - } catch {} - if ( -1 === ga_orders.indexOf( '" . esc_js( $order->get_order_number() ) . "' ) ) { - " . $transaction_code . " - try { - ga_orders.push( '" . esc_js( $order->get_order_number() ) . "' ); - localStorage.setItem( 'ga_orders', JSON.stringify( ga_orders ) ); - } catch {} - }"; - - wc_enqueue_js( $code ); - } - - /** - * Transaction tracking for ga.js (classic) - * - * @param WC_Order $order WC_Order Object - * @return string Add Transaction Code - */ - protected function add_transaction_classic( $order ) { - $code = "_gaq.push(['_addTrans', - '" . esc_js( $order->get_order_number() ) . "', // order ID - required - '" . esc_js( get_bloginfo( 'name' ) ) . "', // affiliation or store name - '" . esc_js( $order->get_total() ) . "', // total - required - '" . esc_js( $order->get_total_tax() ) . "', // tax - '" . esc_js( $order->get_total_shipping() ) . "', // shipping - '" . esc_js( version_compare( WC_VERSION, '3.0', '<' ) ? $order->billing_city : $order->get_billing_city() ) . "', // city - '" . esc_js( version_compare( WC_VERSION, '3.0', '<' ) ? $order->billing_state : $order->get_billing_state() ) . "', // state or province - '" . esc_js( version_compare( WC_VERSION, '3.0', '<' ) ? $order->billing_country : $order->get_billing_country() ) . "' // country - ]);"; - - // Order items - if ( $order->get_items() ) { - foreach ( $order->get_items() as $item ) { - $code .= self::add_item_classic( $order, $item ); - } - } - - $code .= "_gaq.push(['_trackTrans']);"; - return $code; - } - - /** - * Generate Universal Analytics Enhanced Ecommerce transaction tracking code - * - * @param WC_Order $order - * @return string - */ - protected function add_transaction_enhanced( $order ) { - $code = self::tracker_var() . "( 'set', '&cu', '" . esc_js( version_compare( WC_VERSION, '3.0', '<' ) ? $order->get_order_currency() : $order->get_currency() ) . "' );"; - - // Order items - if ( $order->get_items() ) { - foreach ( $order->get_items() as $item ) { - $code .= self::add_item_enhanced( $order, $item ); - } - } - - $code .= self::tracker_var() . "( 'ec:setAction', 'purchase', { - 'id': '" . esc_js( $order->get_order_number() ) . "', - 'affiliation': '" . esc_js( get_bloginfo( 'name' ) ) . "', - 'revenue': '" . esc_js( $order->get_total() ) . "', - 'tax': '" . esc_js( $order->get_total_tax() ) . "', - 'shipping': '" . esc_js( $order->get_total_shipping() ) . "' - } );"; - - return $code; - } - - /** - * Add Item (Classic) - * - * @param WC_Order $order WC_Order Object - * @param array $item The item to add to a transaction/order - * @return string - */ - protected function add_item_classic( $order, $item ) { - $_product = version_compare( WC_VERSION, '3.0', '<' ) ? $order->get_product_from_item( $item ) : $item->get_product(); - - $code = "_gaq.push(['_addItem',"; - $code .= "'" . esc_js( $order->get_order_number() ) . "',"; - $code .= "'" . esc_js( $_product->get_sku() ? $_product->get_sku() : $_product->get_id() ) . "',"; - $code .= "'" . esc_js( $item['name'] ) . "',"; - $code .= self::product_get_category_line( $_product ); - $code .= "'" . esc_js( $order->get_item_total( $item ) ) . "',"; - $code .= "'" . esc_js( $item['qty'] ) . "'"; - $code .= ']);'; - - return $code; - } - - /** - * Add Item (Enhanced, Universal) - * - * @param WC_Order $order WC_Order Object - * @param WC_Order_Item $item The item to add to a transaction/order - * @return string - */ - protected function add_item_enhanced( $order, $item ) { - $_product = version_compare( WC_VERSION, '3.0', '<' ) ? $order->get_product_from_item( $item ) : $item->get_product(); - $variant = self::product_get_variant_line( $_product ); - - $code = self::tracker_var() . "( 'ec:addProduct', {"; - $code .= "'id': '" . esc_js( $_product->get_sku() ? $_product->get_sku() : $_product->get_id() ) . "',"; - $code .= "'name': '" . esc_js( $item['name'] ) . "',"; - $code .= "'category': " . self::product_get_category_line( $_product ); - - if ( '' !== $variant ) { - $code .= "'variant': " . $variant; - } - - $code .= "'price': '" . esc_js( $order->get_item_total( $item ) ) . "',"; - $code .= "'quantity': '" . esc_js( $item['qty'] ) . "'"; - $code .= '});'; - - return $code; - } - - /** - * Output JavaScript to track an enhanced ecommerce remove from cart action - */ - public function remove_from_cart() { - echo( " - - " ); - } - - /** - * Enqueue JavaScript to track a product detail view - * - * @param WC_Product $product - */ - public function product_detail( $product ) { - if ( empty( $product ) ) { - return; - } - - wc_enqueue_js( - self::tracker_var() . "( 'ec:addProduct', { - 'id': '" . esc_js( $product->get_sku() ? $product->get_sku() : ( '#' . $product->get_id() ) ) . "', - 'name': '" . esc_js( $product->get_title() ) . "', - 'category': " . self::product_get_category_line( $product ) . " - 'price': '" . esc_js( $product->get_price() ) . "', - } ); - - " . self::tracker_var() . "( 'ec:setAction', 'detail' );" - ); - } - - /** - * Enqueue JS to track when the checkout process is started - * - * @param array $cart items/contents of the cart - */ - public function checkout_process( $cart ) { - $code = ''; - - foreach ( $cart as $cart_item_key => $cart_item ) { - $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); - $variant = self::product_get_variant_line( $product ); - $code .= self::tracker_var() . "( 'ec:addProduct', { - 'id': '" . esc_js( $product->get_sku() ? $product->get_sku() : ( '#' . $product->get_id() ) ) . "', - 'name': '" . esc_js( $product->get_title() ) . "', - 'category': " . self::product_get_category_line( $product ); - - if ( '' !== $variant ) { - $code .= "'variant': " . $variant; - } - - $code .= "'price': '" . esc_js( $product->get_price() ) . "', - 'quantity': '" . esc_js( $cart_item['quantity'] ) . "' - } );"; - } - - $code .= self::tracker_var() . "( 'ec:setAction','checkout' );"; - wc_enqueue_js( $code ); - } - - /** - * Enqueue JavaScript for Add to cart tracking - * - * @param array $parameters associative array of _trackEvent parameters - * @param string $selector jQuery selector for binding click event - */ - public function event_tracking_code( $parameters, $selector ) { - $parameters = apply_filters( 'woocommerce_ga_event_tracking_parameters', $parameters ); - - if ( 'yes' === self::get( 'ga_use_universal_analytics' ) ) { - if ( 'yes' === self::get( 'ga_enhanced_ecommerce_tracking_enabled' ) ) { - wc_enqueue_js( - " - $( '" . $selector . "' ).on( 'click', function() { - " . $parameters['enhanced'] . ' - ' . self::tracker_var() . "( 'ec:setAction', 'add' ); - " . self::tracker_var() . "( 'send', 'event', 'UX', 'click', 'add to cart' ); - }); - " - ); - return; - } else { - $track_event = self::tracker_var() . "('send', 'event', %s, %s, %s);"; - } - } else { - $track_event = "_gaq.push(['_trackEvent', %s, %s, %s]);"; - } - - wc_enqueue_js( - " - $( '" . $selector . "' ).on( 'click', function() { - " . sprintf( $track_event, $parameters['category'], $parameters['action'], $parameters['label'] ) . ' - }); - ' - ); - } - - /** - * Loads a code using the google-analytics handler in the head. - * - * @param string $code The code to add attached to the google-analytics handler - */ - protected static function load_analytics_code_in_header( $code ) { - wp_register_script( 'google-analytics', '', array( 'google-analytics-opt-out' ), null, false ); - wp_add_inline_script( 'google-analytics', $code ); - wp_enqueue_script( 'google-analytics' ); - } - -} diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 8822df64..0d66f400 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -93,7 +93,6 @@ public function __construct() { // Contains snippets/JS tracking code include_once 'class-wc-abstract-google-analytics-js.php'; - include_once 'class-wc-google-analytics-js.php'; include_once 'class-wc-google-gtag-js.php'; $this->get_tracking_instance( $constructor ); From 036f1eb1ff82be9def15b4e1da926ff29e34ea56 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 07:36:18 +0100 Subject: [PATCH 030/126] Remove admin-ga-settings.js --- assets/js/src/admin-ga-settings.js | 44 -------------------------- includes/class-wc-google-analytics.php | 29 ----------------- webpack.config.js | 5 --- 3 files changed, 78 deletions(-) delete mode 100644 assets/js/src/admin-ga-settings.js diff --git a/assets/js/src/admin-ga-settings.js b/assets/js/src/admin-ga-settings.js deleted file mode 100644 index 41fc7d55..00000000 --- a/assets/js/src/admin-ga-settings.js +++ /dev/null @@ -1,44 +0,0 @@ -jQuery( document ).ready( function ( $ ) { - const ecCheckbox = $( - '#woocommerce_google_analytics_ga_enhanced_ecommerce_tracking_enabled' - ); - const uaCheckbox = $( - '#woocommerce_google_analytics_ga_use_universal_analytics' - ); - const gtagCheckbox = $( '#woocommerce_google_analytics_ga_gtag_enabled' ); - - updateToggles(); - - ecCheckbox.change( updateToggles ); - uaCheckbox.change( updateToggles ); - gtagCheckbox.change( updateToggles ); - - function updateToggles() { - const isEnhancedEcommerce = ecCheckbox.is( ':checked' ); - const isUniversalAnalytics = uaCheckbox.is( ':checked' ); - const isGtag = gtagCheckbox.is( ':checked' ); - - // Legacy: gtag NO - toggleCheckboxRow( $( '.legacy-setting' ), ! isGtag ); - - // Enhanced settings: Enhanced YES + universal YES or gtag YES - toggleCheckboxRow( - $( '.enhanced-setting' ), - isEnhancedEcommerce && ( isUniversalAnalytics || isGtag ) - ); - - // Enhanced toggle: universal YES or gtag YES - toggleCheckboxRow( ecCheckbox, isUniversalAnalytics || isGtag ); - - // Universal toggle: gtag NO - toggleCheckboxRow( uaCheckbox, ! isGtag ); - } - - function toggleCheckboxRow( checkbox, isVisible ) { - if ( isVisible ) { - checkbox.closest( 'tr' ).show(); - } else { - checkbox.closest( 'tr' ).hide(); - } - } -} ); diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 0d66f400..73074788 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -102,7 +102,6 @@ public function __construct() { add_filter( 'woocommerce_tracker_data', array( $this, 'track_options' ) ); add_action( 'woocommerce_update_options_integration_google_analytics', array( $this, 'process_admin_options' ) ); add_action( 'woocommerce_update_options_integration_google_analytics', array( $this, 'show_options_info' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_assets' ) ); add_action( 'admin_init', array( $this, 'privacy_policy' ) ); // Tracking code @@ -323,34 +322,6 @@ public function track_options( $data ) { return $data; } - /** - * Enqueue the admin JavaScript - */ - public function load_admin_assets() { - $screen = get_current_screen(); - if ( 'woocommerce_page_wc-settings' !== $screen->id ) { - return; - } - - // phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( empty( $_GET['tab'] ) ) { - return; - } - - // phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( 'integration' !== $_GET['tab'] ) { - return; - } - - wp_enqueue_script( - 'wc-google-analytics-admin-enhanced-settings', - Plugin::get_instance()->get_js_asset_url( 'admin-ga-settings.js' ), - Plugin::get_instance()->get_js_asset_dependencies( 'admin-ga-settings', [ 'jquery' ] ), - Plugin::get_instance()->get_js_asset_version( 'admin-ga-settings' ), - true - ); - } - /** * Add suggested privacy policy content * diff --git a/webpack.config.js b/webpack.config.js index 33baae69..2c96cf2a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,11 +5,6 @@ const webpackConfig = { ...defaultConfig, entry: { actions: path.resolve( process.cwd(), 'assets/js/src', 'actions.js' ), - 'admin-ga-settings': path.resolve( - process.cwd(), - 'assets/js/src', - 'admin-ga-settings.js' - ), 'ga-integration': path.resolve( process.cwd(), 'assets/js/src', From 3188a5534e6e7535f3fea2ee3f4d3af02b3ede7f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 08:18:08 +0100 Subject: [PATCH 031/126] Remove unused alias --- includes/class-wc-google-analytics.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 73074788..f3775753 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -4,7 +4,6 @@ exit; // Exit if accessed directly } -use WC_Google_Analytics_Integration as Plugin; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists; /** From 1c5414268866d04989ef7affbb176d28b8ebc569 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 08:22:00 +0100 Subject: [PATCH 032/126] Remove UA tracking from abstract class --- .../class-wc-abstract-google-analytics-js.php | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index b8472bb4..9ba43abd 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -93,11 +93,7 @@ abstract public static function load_analytics( $order = false ); * @param WC_Order $order WC_Order Object */ public function add_transaction( $order ) { - if ( 'yes' === self::get( 'ga_enhanced_ecommerce_tracking_enabled' ) || 'yes' === self::get( 'ga_gtag_enabled' ) ) { - wc_enqueue_js( static::add_transaction_enhanced( $order ) ); - } else { - wc_enqueue_js( self::add_transaction_universal( $order ) ); - } + wc_enqueue_js( static::add_transaction_enhanced( $order ) ); } /** @@ -128,55 +124,6 @@ public static function get_product_identifier( $product ) { return apply_filters( 'woocommerce_ga_product_identifier', $identifier, $product ); } - /** - * Generate Universal Analytics add item tracking code - * - * @param WC_Order $order WC_Order Object - * @param WC_Order_Item $item The item to add to a transaction/order - * @return string - */ - protected function add_item_universal( $order, $item ) { - $_product = version_compare( WC_VERSION, '3.0', '<' ) ? $order->get_product_from_item( $item ) : $item->get_product(); - - $code = "ga('ecommerce:addItem', {"; - $code .= "'id': '" . esc_js( $order->get_order_number() ) . "',"; - $code .= "'name': '" . esc_js( $item['name'] ) . "',"; - $code .= "'sku': '" . esc_js( $_product->get_sku() ? $_product->get_sku() : $_product->get_id() ) . "',"; - $code .= "'category': " . self::product_get_category_line( $_product ); - $code .= "'price': '" . esc_js( $order->get_item_total( $item ) ) . "',"; - $code .= "'quantity': '" . esc_js( $item['qty'] ) . "'"; - $code .= '});'; - - return $code; - } - - /** - * Generate Universal Analytics transaction tracking code - * - * @param WC_Order $order WC_Order object - * @return string Add Transaction tracking code - */ - protected function add_transaction_universal( $order ) { - $code = "ga('ecommerce:addTransaction', { - 'id': '" . esc_js( $order->get_order_number() ) . "', // Transaction ID. Required - 'affiliation': '" . esc_js( get_bloginfo( 'name' ) ) . "', // Affiliation or store name - 'revenue': '" . esc_js( $order->get_total() ) . "', // Grand Total - 'shipping': '" . esc_js( $order->get_total_shipping() ) . "', // Shipping - 'tax': '" . esc_js( $order->get_total_tax() ) . "', // Tax - 'currency': '" . esc_js( version_compare( WC_VERSION, '3.0', '<' ) ? $order->get_order_currency() : $order->get_currency() ) . "' // Currency - });"; - - // Order items - if ( $order->get_items() ) { - foreach ( $order->get_items() as $item ) { - $code .= self::add_item_universal( $order, $item ); - } - } - - $code .= "ga('ecommerce:send');"; - return $code; - } - /** * Returns a 'category' JSON line based on $product * From 2ee2fad2b5f52f6fbd1567ee20a5730a4caeb039 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 10:35:59 +0100 Subject: [PATCH 033/126] Move tracking to class-wc-google-gtag-js.php --- .../class-wc-abstract-google-analytics-js.php | 8 +- includes/class-wc-google-gtag-js.php | 89 ++++++++++++++++--- 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 9ba43abd..58689dd5 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -173,17 +173,13 @@ abstract public function remove_from_cart(); /** * Enqueue JavaScript to track a product detail view - * - * @param WC_Product $product */ - abstract public function product_detail( $product ); + abstract public function product_detail(); /** * Enqueue JS to track when the checkout process is started - * - * @param array $cart items/contents of the cart */ - abstract public function checkout_process( $cart ); + abstract public function checkout_process(); /** * Enqueue JavaScript for Add to cart tracking diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index fc4606de..0757155f 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -41,6 +41,15 @@ public function __construct( $options = array() ) { // Setup frontend scripts add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) ); add_action( 'woocommerce_before_single_product', array( $this, 'setup_frontend_scripts' ) ); + + // Event tracking code + add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) ); + add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) ); + add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) ); + add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 ); + add_filter( 'woocommerce_loop_add_to_cart_link', array( $this, 'track_product' ), 10, 2 ); + add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) ); + add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) ); } /** @@ -234,10 +243,14 @@ public static function listing_click( $product ) { /** * Output Javascript to track add_to_cart event on single product page - * - * @param WC_Product $product The product currently being viewed */ - public static function add_to_cart( WC_Product $product ) { + public static function add_to_cart() { + if ( 'yes' !== self::get( 'ga_event_tracking_enabled' ) || ! is_single() ) { + return; + } + + global $product; + $items = array( 'id' => self::get_product_identifier( $product ), 'name' => $product->get_title(), @@ -263,7 +276,7 @@ public static function add_to_cart( WC_Product $product ) { } $event_code .= self::get_event_code( - 'add_to_cart', + 'add_to_cart_x', '{"items": [item_data]}', false ); @@ -382,6 +395,10 @@ public function add_item( $order, $item ) { * Output JavaScript to track an enhanced ecommerce remove from cart action */ public function remove_from_cart() { + if ( 'yes' !== self::get( 'ga_enhanced_remove_from_cart_enabled' ) ) { + return; + } + $event_code = self::get_event_code( 'remove_from_cart', '{"items": [{ @@ -401,16 +418,62 @@ public function remove_from_cart() { ); } + /** - * Enqueue JavaScript to track a product detail view + * Adds the product ID and SKU to the remove product link if not present * - * @param WC_Product $product + * @param string $url + * @param string $key + * @return string */ - public function product_detail( $product ) { - if ( empty( $product ) ) { + public function remove_from_cart_attributes( $url, $key ) { + if ( strpos( $url, 'data-product_id' ) !== false ) { + return $url; + } + + if ( ! is_object( WC()->cart ) ) { + return $url; + } + + $item = WC()->cart->get_cart_item( $key ); + $product = $item['data']; + + if ( ! is_object( $product ) ) { + return $url; + } + + $url = str_replace( 'href=', 'data-product_id="' . esc_attr( $product->get_id() ) . '" data-product_sku="' . esc_attr( $product->get_sku() ) . '" href=', $url ); + return $url; + } + + /** + * Measure a product click and impression from a Product list + * + * @param string $link The Add To Cart Link + * @param WC_Product $product The Product + */ + public function track_product( $link, $product ) { + if ( 'yes' === self::get( 'ga_enhanced_product_impression_enabled' ) ) { + self::listing_impression( $product ); + } + + if ( 'yes' === self::get( 'ga_enhanced_product_click_enabled' ) ) { + self::listing_click( $product ); + } + + return $link; + } + + /** + * Enqueue JavaScript to track a product detail view + */ + public function product_detail() { + if ( 'yes' !== self::get( 'ga_enhanced_product_detail_view_enabled' ) ) { return; } + global $product; + $event_code = self::get_event_code( 'view_item', array( @@ -430,10 +493,14 @@ public function product_detail( $product ) { /** * Enqueue JS to track when the checkout process is started - * - * @param array $cart items/contents of the cart */ - public function checkout_process( $cart ) { + public function checkout_process() { + if ( 'yes' !== self::get( 'ga_enhanced_checkout_process_enabled' ) ) { + return; + } + + $cart = WC()->cart->get_cart(); + $items = array(); foreach ( $cart as $cart_item_key => $cart_item ) { $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); From 2ce30f6c02feba99452eaa155f4db8116e2caba2 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 12:25:23 +0100 Subject: [PATCH 034/126] Fix unit tests --- tests/unit-tests/CheckoutProcess.php | 8 +++++++- tests/unit-tests/ProductDetail.php | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/CheckoutProcess.php b/tests/unit-tests/CheckoutProcess.php index 63c0f957..55ad729a 100644 --- a/tests/unit-tests/CheckoutProcess.php +++ b/tests/unit-tests/CheckoutProcess.php @@ -25,7 +25,13 @@ public function test_begin_checkout_event() { $cart = WC()->cart; $add_to = $cart->add_to_cart( $product->get_id() ); - ( new WC_Google_Gtag_JS() )->checkout_process( $cart->get_cart() ); + + $mock = $this->getMockBuilder( WC_Google_Gtag_JS::class ) + ->setMethods( array( '__construct' ) ) + ->setConstructorArgs( array( array( 'ga_enhanced_checkout_process_enabled' => 'yes' ) ) ) + ->getMock(); + + $mock->checkout_process( $cart->get_cart() ); // Confirm woocommerce_gtag_event_data is called by checkout_process(). $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for begin_checkout (checkout_process()) event' ); diff --git a/tests/unit-tests/ProductDetail.php b/tests/unit-tests/ProductDetail.php index f73723aa..e6b34e2d 100644 --- a/tests/unit-tests/ProductDetail.php +++ b/tests/unit-tests/ProductDetail.php @@ -19,9 +19,15 @@ class ProductDetail extends EventsDataTest { * @return void */ public function test_view_item_event() { + global $product; $product = $this->get_product(); - ( new WC_Google_Gtag_JS() )->product_detail( $product ); + $mock = $this->getMockBuilder( WC_Google_Gtag_JS::class ) + ->setMethods( array( '__construct' ) ) + ->setConstructorArgs( array( array( 'ga_enhanced_product_detail_view_enabled' => 'yes' ) ) ) + ->getMock(); + + $mock->product_detail(); // Confirm woocommerce_gtag_event_data is called by product_detail(). $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for view_item (product_detail()) event' ); From acbe1a4e27d7b311a53d19fe7027ff91fda4fe63 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 27 Oct 2023 17:02:11 +0100 Subject: [PATCH 035/126] Add notice if property ID starts with "UA" --- includes/class-wc-google-analytics.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index f3775753..53001265 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -90,6 +90,8 @@ public function __construct() { $this->init_settings(); $constructor = $this->init_options(); + add_action( 'admin_notices', array( $this, 'universal_analytics_upgrade_notice' ) ); + // Contains snippets/JS tracking code include_once 'class-wc-abstract-google-analytics-js.php'; include_once 'class-wc-google-gtag-js.php'; @@ -111,6 +113,26 @@ public function __construct() { add_filter( 'woocommerce_get_return_url', array( $this, 'utm_nooverride' ) ); } + /** + * Conditionally display an error notice to the merchant if the stored property ID starts with "UA" + * + * @return void + */ + public function universal_analytics_upgrade_notice () { + if ( 'ua' === substr( strtolower( $this->get_option( 'ga_id' ) ), 0, 2 ) ) { + printf( + '

%2$s

', + 'notice notice-error', + sprintf( + /* translators: 1) URL for Google documentation on upgrading from UA to GA4 2) URL to WooCommerce Google Analytics settings page */ + __( 'Your website is configured to use Universal Analytics which Google retired in July of 2023. Update your account using the setup assistant and then update your WooCommerce settings.', 'woocommerce-google-analytics-integration' ), + 'https://support.google.com/analytics/answer/9744165?sjid=9632005471070882766-EU#zippy=%2Cin-this-article', + '/wp-admin/admin.php?page=wc-settings&tab=integration§ion=google_analytics' + ) + ); + } + } + /** * Loads all of our options for this plugin (stored as properties as well) * From 6c818bc2fa7a8f6befbf40dd49e0ea93d04444fd Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 30 Oct 2023 03:57:28 +0000 Subject: [PATCH 036/126] CS Fixes --- includes/class-wc-google-analytics.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 53001265..bd3053f5 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -118,14 +118,14 @@ public function __construct() { * * @return void */ - public function universal_analytics_upgrade_notice () { + public function universal_analytics_upgrade_notice() { if ( 'ua' === substr( strtolower( $this->get_option( 'ga_id' ) ), 0, 2 ) ) { - printf( + echo sprintf( '

%2$s

', 'notice notice-error', sprintf( /* translators: 1) URL for Google documentation on upgrading from UA to GA4 2) URL to WooCommerce Google Analytics settings page */ - __( 'Your website is configured to use Universal Analytics which Google retired in July of 2023. Update your account using the setup assistant and then update your WooCommerce settings.', 'woocommerce-google-analytics-integration' ), + __( 'Your website is configured to use Universal Analytics which Google retired in July of 2023. Update your account using the setup assistant and then update your WooCommerce settings.', 'woocommerce-google-analytics-integration' ), // phpcs:ignore WordPress.Security.EscapeOutput 'https://support.google.com/analytics/answer/9744165?sjid=9632005471070882766-EU#zippy=%2Cin-this-article', '/wp-admin/admin.php?page=wc-settings&tab=integration§ion=google_analytics' ) From fe0ced46d17313614dab64865d24643e8b6f8a6f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 30 Oct 2023 04:07:13 +0000 Subject: [PATCH 037/126] Fix event name --- includes/class-wc-google-gtag-js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 0757155f..64c2d7b4 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -276,7 +276,7 @@ public static function add_to_cart() { } $event_code .= self::get_event_code( - 'add_to_cart_x', + 'add_to_cart', '{"items": [item_data]}', false ); From 2b48a17c2c3d7c780819cb3a5ef4a8478679e189 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 30 Oct 2023 05:20:15 +0000 Subject: [PATCH 038/126] Re-order settings fields --- includes/class-wc-google-analytics.php | 37 +++++++++++--------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index f3775753..ebdd3370 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -205,7 +205,15 @@ public function init_form_fields() { 'checkboxgroup' => '', 'default' => 'yes', ), + 'ga_linker_allow_incoming_enabled' => array( + 'label' => __( 'Accept Incoming Linker Parameters', 'woocommerce-google-analytics-integration' ), + 'description' => __( 'Enabling this option will allow incoming linker parameters from other websites.', 'woocommerce-google-analytics-integration' ), + 'type' => 'checkbox', + 'checkboxgroup' => '', + 'default' => 'no', + ), 'ga_ecommerce_tracking_enabled' => array( + 'title' => __( 'Event Tracking', 'woocommerce-google-analytics-integration' ), 'label' => __( 'Purchase Transactions', 'woocommerce-google-analytics-integration' ), 'description' => __( 'This requires a payment gateway that redirects to the thank you/order received page after payment. Orders paid with gateways which do not do this will not be tracked.', 'woocommerce-google-analytics-integration' ), 'type' => 'checkbox', @@ -218,27 +226,11 @@ public function init_form_fields() { 'checkboxgroup' => '', 'default' => 'yes', ), - 'ga_linker_cross_domains' => array( - 'title' => __( 'Cross Domain Tracking', 'woocommerce-google-analytics-integration' ), - /* translators: Read more link */ - 'description' => sprintf( __( 'Add a comma separated list of domains for automatic linking. %1$sRead more about Cross Domain Measurement%2$s', 'woocommerce-google-analytics-integration' ), '', '' ), - 'type' => 'text', - 'placeholder' => 'example.com, example.net', - 'default' => '', - ), - 'ga_linker_allow_incoming_enabled' => array( - 'label' => __( 'Accept Incoming Linker Parameters', 'woocommerce-google-analytics-integration' ), - 'description' => __( 'Enabling this option will allow incoming linker parameters from other websites.', 'woocommerce-google-analytics-integration' ), - 'type' => 'checkbox', - 'checkboxgroup' => '', - 'default' => 'no', - ), 'ga_enhanced_remove_from_cart_enabled' => array( 'label' => __( 'Remove from Cart Events', 'woocommerce-google-analytics-integration' ), 'type' => 'checkbox', 'checkboxgroup' => '', 'default' => 'yes', - 'class' => 'enhanced-setting', ), 'ga_enhanced_product_impression_enabled' => array( @@ -246,7 +238,6 @@ public function init_form_fields() { 'type' => 'checkbox', 'checkboxgroup' => '', 'default' => 'yes', - 'class' => 'enhanced-setting', ), 'ga_enhanced_product_click_enabled' => array( @@ -254,7 +245,6 @@ public function init_form_fields() { 'type' => 'checkbox', 'checkboxgroup' => '', 'default' => 'yes', - 'class' => 'enhanced-setting', ), 'ga_enhanced_product_detail_view_enabled' => array( @@ -262,7 +252,6 @@ public function init_form_fields() { 'type' => 'checkbox', 'checkboxgroup' => '', 'default' => 'yes', - 'class' => 'enhanced-setting', ), 'ga_enhanced_checkout_process_enabled' => array( @@ -270,7 +259,14 @@ public function init_form_fields() { 'type' => 'checkbox', 'checkboxgroup' => '', 'default' => 'yes', - 'class' => 'enhanced-setting', + ), + 'ga_linker_cross_domains' => array( + 'title' => __( 'Cross Domain Tracking', 'woocommerce-google-analytics-integration' ), + /* translators: Read more link */ + 'description' => sprintf( __( 'Add a comma separated list of domains for automatic linking. %1$sRead more about Cross Domain Measurement%2$s', 'woocommerce-google-analytics-integration' ), '', '' ), + 'type' => 'text', + 'placeholder' => 'example.com, example.net', + 'default' => '', ), ); } @@ -305,7 +301,6 @@ public function track_options( $data ) { 'ecommerce_tracking_enabled' => $this->ga_ecommerce_tracking_enabled, 'event_tracking_enabled' => $this->ga_event_tracking_enabled, 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, - 'enhanced_ecommerce_tracking_enabled' => $this->ga_enhanced_ecommerce_tracking_enabled, 'linker_allow_incoming_enabled' => empty( $this->ga_linker_allow_incoming_enabled ) ? 'no' : 'yes', 'linker_cross_domains' => $this->ga_linker_cross_domains, ); From a7e9ac44b9c864bec435ef331415bceb81871c15 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 30 Oct 2023 05:21:45 +0000 Subject: [PATCH 039/126] Remove event_tracking_code --- .../class-wc-abstract-google-analytics-js.php | 8 ---- includes/class-wc-google-gtag-js.php | 43 ------------------- 2 files changed, 51 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 58689dd5..00aae30f 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -180,12 +180,4 @@ abstract public function product_detail(); * Enqueue JS to track when the checkout process is started */ abstract public function checkout_process(); - - /** - * Enqueue JavaScript for Add to cart tracking - * - * @param array $parameters associative array of _trackEvent parameters - * @param string $selector jQuery selector for binding click event - */ - abstract public function event_tracking_code( $parameters, $selector ); } diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 64c2d7b4..7303c86d 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -531,47 +531,4 @@ public function checkout_process() { wc_enqueue_js( $event_code ); } - /** - * @deprecated 1.6.0 - * - * Enqueue JavaScript for Add to cart tracking - * - * @param array $parameters Associative array of _trackEvent parameters - * @param string $selector jQuery selector for binding click event - */ - public function event_tracking_code( $parameters, $selector ) { - wc_deprecated_function( 'event_tracking_code', '1.6.0', 'get_event_code' ); - - // Called with invalid 'Add to Cart' action, update to sync with Default Google Analytics Event 'add_to_cart' - $parameters['action'] = '\'add_to_cart\''; - $parameters['category'] = '\'ecommerce\''; - - $parameters = apply_filters( 'woocommerce_gtag_event_tracking_parameters', $parameters ); - - if ( 'yes' === self::get( 'ga_enhanced_ecommerce_tracking_enabled' ) ) { - $track_event = sprintf( - self::tracker_var() . "( 'event', %s, { 'event_category': %s, 'event_label': %s, 'items': [ %s ] } );", - $parameters['action'], - $parameters['category'], - $parameters['label'], - $parameters['item'] - ); - } else { - $track_event = sprintf( - self::tracker_var() . "( 'event', %s, { 'event_category': %s, 'event_label': %s } );", - $parameters['action'], - $parameters['category'], - $parameters['label'] - ); - } - - wc_enqueue_js( - " - $( '" . $selector . "' ).on( 'click', function() { - " . $track_event . ' - }); - ' - ); - } - } From 8082ac281b4169ce18449d4a928c1db88508a49e Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 30 Oct 2023 05:23:24 +0000 Subject: [PATCH 040/126] Fix alignment --- includes/class-wc-google-analytics.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index ebdd3370..72965e3b 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -293,16 +293,16 @@ public function show_options_info() { */ public function track_options( $data ) { $data['wc-google-analytics'] = array( - 'standard_tracking_enabled' => $this->ga_standard_tracking_enabled, - 'support_display_advertising' => $this->ga_support_display_advertising, - 'support_enhanced_link_attribution' => $this->ga_support_enhanced_link_attribution, - 'anonymize_enabled' => $this->ga_anonymize_enabled, - 'ga_404_tracking_enabled' => $this->ga_404_tracking_enabled, - 'ecommerce_tracking_enabled' => $this->ga_ecommerce_tracking_enabled, - 'event_tracking_enabled' => $this->ga_event_tracking_enabled, - 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, - 'linker_allow_incoming_enabled' => empty( $this->ga_linker_allow_incoming_enabled ) ? 'no' : 'yes', - 'linker_cross_domains' => $this->ga_linker_cross_domains, + 'standard_tracking_enabled' => $this->ga_standard_tracking_enabled, + 'support_display_advertising' => $this->ga_support_display_advertising, + 'support_enhanced_link_attribution' => $this->ga_support_enhanced_link_attribution, + 'anonymize_enabled' => $this->ga_anonymize_enabled, + 'ga_404_tracking_enabled' => $this->ga_404_tracking_enabled, + 'ecommerce_tracking_enabled' => $this->ga_ecommerce_tracking_enabled, + 'event_tracking_enabled' => $this->ga_event_tracking_enabled, + 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, + 'linker_allow_incoming_enabled' => empty( $this->ga_linker_allow_incoming_enabled ) ? 'no' : 'yes', + 'linker_cross_domains' => $this->ga_linker_cross_domains, ); // ID prefix, blank, or X for unknown From 1f0b6fc4a75f5a98127e482614363253c689a0a2 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 31 Oct 2023 08:59:33 +0000 Subject: [PATCH 041/126] Escape notice text and update external URL --- includes/class-wc-google-analytics.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index bd3053f5..53ebd2ed 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -125,9 +125,11 @@ public function universal_analytics_upgrade_notice() { 'notice notice-error', sprintf( /* translators: 1) URL for Google documentation on upgrading from UA to GA4 2) URL to WooCommerce Google Analytics settings page */ - __( 'Your website is configured to use Universal Analytics which Google retired in July of 2023. Update your account using the setup assistant and then update your WooCommerce settings.', 'woocommerce-google-analytics-integration' ), // phpcs:ignore WordPress.Security.EscapeOutput - 'https://support.google.com/analytics/answer/9744165?sjid=9632005471070882766-EU#zippy=%2Cin-this-article', - '/wp-admin/admin.php?page=wc-settings&tab=integration§ion=google_analytics' + esc_html__( 'Your website is configured to use Universal Analytics which Google retired in July of 2023. Update your account using the %1$ssetup assistant%2$s and then update your %3$sWooCommerce settings%4$s.', 'woocommerce-google-analytics-integration' ), + '', + '', + '', + '' ) ); } From 4a06f525ce405c4bdc579d22dcda8c85c8964666 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 1 Nov 2023 08:55:00 +0000 Subject: [PATCH 042/126] Update formatPrice to return a number --- assets/js/src/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/src/utils.js b/assets/js/src/utils.js index a343b788..527571b2 100644 --- a/assets/js/src/utils.js +++ b/assets/js/src/utils.js @@ -50,10 +50,10 @@ export const getProductImpressionObject = ( product, listName ) => { * @param {string} price - The price to parse * @param {number} [currencyMinorUnit=2] - The number decimals to show in the currency * - * @return {string} - The price of the product formatted + * @return {number} - The price of the product formatted */ export const formatPrice = ( price, currencyMinorUnit = 2 ) => { - return ( parseInt( price, 10 ) / 10 ** currencyMinorUnit ).toString(); + return parseInt( price, 10 ) / 10 ** currencyMinorUnit; }; /** From f6041b9e9f20e790c6c99a08408afc204444302e Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 1 Nov 2023 16:31:51 +0000 Subject: [PATCH 043/126] Conditionally include coupon --- assets/js/src/tracking.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index ee826367..9db3a713 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -89,8 +89,8 @@ export const trackBeginCheckout = ( { storeCart } ) => { storeCart.totals.total_price, storeCart.totals.currency_minor_unit ), - coupon: storeCart.coupons[ 0 ]?.code || '', items: storeCart.items.map( getProductFieldObject ), + ...( storeCart.coupons[ 0 ]?.code ? { coupon: storeCart.coupons[ 0 ]?.code } : {} ), } ); }; @@ -107,12 +107,12 @@ export const trackShippingTier = ( { storeCart } ) => { storeCart.totals.total_price, storeCart.totals.currency_minor_unit ), - coupon: storeCart.coupons[ 0 ]?.code || '', shipping_tier: storeCart.shippingRates[ 0 ]?.shipping_rates?.find( ( rate ) => rate.selected )?.name || '', items: storeCart.items.map( getProductFieldObject ), + ...( storeCart.coupons[ 0 ]?.code ? { coupon: storeCart.coupons[ 0 ]?.code } : {} ), } ); }; From 813b7ee6d48ed343c8bba96f3d12b2e48382787a Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 1 Nov 2023 17:18:46 +0000 Subject: [PATCH 044/126] Add cart coupon utility --- assets/js/src/tracking.js | 5 +++-- assets/js/src/utils.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js index 9db3a713..d4c6a1d9 100644 --- a/assets/js/src/tracking.js +++ b/assets/js/src/tracking.js @@ -4,6 +4,7 @@ import { getProductImpressionObject, getProductId, formatPrice, + getCartCoupon, } from './utils'; /** @@ -89,8 +90,8 @@ export const trackBeginCheckout = ( { storeCart } ) => { storeCart.totals.total_price, storeCart.totals.currency_minor_unit ), + ...getCartCoupon( storeCart ), items: storeCart.items.map( getProductFieldObject ), - ...( storeCart.coupons[ 0 ]?.code ? { coupon: storeCart.coupons[ 0 ]?.code } : {} ), } ); }; @@ -111,8 +112,8 @@ export const trackShippingTier = ( { storeCart } ) => { storeCart.shippingRates[ 0 ]?.shipping_rates?.find( ( rate ) => rate.selected )?.name || '', + ...getCartCoupon( storeCart ), items: storeCart.items.map( getProductFieldObject ), - ...( storeCart.coupons[ 0 ]?.code ? { coupon: storeCart.coupons[ 0 ]?.code } : {} ), } ); }; diff --git a/assets/js/src/utils.js b/assets/js/src/utils.js index 527571b2..4a3ec624 100644 --- a/assets/js/src/utils.js +++ b/assets/js/src/utils.js @@ -79,6 +79,21 @@ export const getProductId = ( product ) => { return product.sku ? product.sku : '#' + product.id; }; +/** + * Returns an Object containing the cart coupon if one has been applied + * + * @param {Object} storeCart - The cart to check for coupons + * + * @return {Object} - Either an empty Object or one containing the coupon + */ +export const getCartCoupon = ( storeCart ) => { + return storeCart.coupons[ 0 ]?.code + ? { + coupon: storeCart.coupons[ 0 ]?.code, + } + : {}; +}; + /** * Returns the name of the first category of a product, or an empty string if the product has no categories. * From 0e945df0618d74d68a778ad9377abb88332a7d23 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 8 Nov 2023 12:14:15 +0000 Subject: [PATCH 045/126] Remove inline JS tracking --- assets/js/src/ga-integration.js | 14 - includes/class-wc-google-gtag-js.php | 435 +-------------------------- 2 files changed, 8 insertions(+), 441 deletions(-) delete mode 100644 assets/js/src/ga-integration.js diff --git a/assets/js/src/ga-integration.js b/assets/js/src/ga-integration.js deleted file mode 100644 index 39078bef..00000000 --- a/assets/js/src/ga-integration.js +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line camelcase -window.google_analytics_integration_product_data = {}; - -jQuery( document ).ready( function ( $ ) { - $( document ).on( - 'found_variation', - 'form.cart', - function ( e, variation ) { - window.google_analytics_integration_product_data[ - variation.variation_id - ] = variation.google_analytics_integration; - } - ); -} ); diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 7303c86d..99d3d5d6 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -16,20 +16,6 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var string $script_handle Handle for the front end JavaScript file */ public $script_handle = 'woocommerce-google-analytics-integration'; - /** - * Get the class instance - * - * @param array $options Options - * @return WC_Abstract_Google_Analytics_JS - */ - public static function get_instance( $options = array() ) { - if ( null === self::$instance ) { - self::$instance = new self( $options ); - } - - return self::$instance; - } - /** * Constructor * Takes our options from the parent class so we can later use them in the JS snippets @@ -40,47 +26,14 @@ public function __construct( $options = array() ) { self::$options = $options; // Setup frontend scripts add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) ); - add_action( 'woocommerce_before_single_product', array( $this, 'setup_frontend_scripts' ) ); - - // Event tracking code - add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) ); - add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) ); - add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) ); - add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 ); - add_filter( 'woocommerce_loop_add_to_cart_link', array( $this, 'track_product' ), 10, 2 ); - add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) ); - add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) ); - } - - /** - * Enqueue the frontend scripts and make formatted variant data available via filter - * - * @return void - */ - public function setup_frontend_scripts() { - global $product; - - if ( $product instanceof WC_Product_Variable ) { - // Filter variation data to include formatted strings required for add_to_cart event - add_filter( 'woocommerce_available_variation', array( $this, 'variant_data' ), 10, 3 ); - // Add default inline product data for add to cart tracking - wp_enqueue_script( $this->script_handle . '-ga-integration' ); - } } /** * Register front end JavaScript */ public function register_scripts() { - wp_register_script( - $this->script_handle . '-ga-integration', - Plugin::get_instance()->get_js_asset_url( 'ga-integration.js' ), - Plugin::get_instance()->get_js_asset_dependencies( 'ga-integration', [ 'jquery' ] ), - Plugin::get_instance()->get_js_asset_version( 'ga-integration' ), - true - ); wp_enqueue_script( - $this->script_handle . '-actions', + $this->script_handle, Plugin::get_instance()->get_js_asset_url( 'actions.js' ), Plugin::get_instance()->get_js_asset_dependencies( 'actions' ), Plugin::get_instance()->get_js_asset_version( 'actions' ), @@ -97,197 +50,6 @@ public static function tracker_var() { return apply_filters( 'woocommerce_gtag_tracker_variable', 'gtag' ); } - /** - * Add formatted id and variant to variable product data - * - * @param array $data Data accessible via `found_variation` trigger - * @param WC_Product_Variable $product - * @param WC_Product_Variation $variation - * @return array - */ - public function variant_data( $data, $product, $variation ) { - $data['google_analytics_integration'] = array( - 'id' => self::get_product_identifier( $variation ), - 'variant' => substr( self::product_get_variant_line( $variation ), 1, -2 ), - ); - - return $data; - } - - /** - * Returns Javascript string for Google Analytics events - * - * @param string $event The type of event - * @param array|string $data Event data to be sent. If $data is an array then it will be filtered, escaped, and encoded - * @return string - */ - public static function get_event_code( string $event, $data ): string { - return sprintf( "%s('event', '%s', %s)", self::tracker_var(), esc_js( $event ), ( is_array( $data ) ? self::format_event_data( $data ) : $data ) ); - } - - /** - * Escape and encode event data - * - * @param array $data Event data to processed and formatted - * @return string - */ - public static function format_event_data( array $data ): string { - $data = apply_filters( 'woocommerce_gtag_event_data', $data ); - - // Recursively walk through $data array and escape all values that will be used in JS. - array_walk_recursive( - $data, - function( &$value, $key ) { - $value = esc_js( $value ); - } - ); - - return wp_json_encode( $data ); - } - - /** - * Returns a list of category names the product is atttributed to - * - * @param WC_Product $product Product to generate category line for - * @return string - */ - public static function product_get_category_line( $product ) { - $category_names = array(); - $categories = get_the_terms( $product->get_id(), 'product_cat' ); - - $variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : false; - if ( is_array( $variation_data ) && ! empty( $variation_data ) ) { - $categories = get_the_terms( $product->get_parent_id(), 'product_cat' ); - } - - if ( false !== $categories && ! is_wp_error( $categories ) ) { - foreach ( $categories as $category ) { - $category_names[] = $category->name; - } - } - - return join( '/', $category_names ); - } - - /** - * Return list name for event - * - * @return string - */ - public static function get_list_name(): string { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended - return isset( $_GET['s'] ) ? __( 'Search Results', 'woocommerce-google-analytics-integration' ) : __( 'Product List', 'woocommerce-google-analytics-integration' ); - } - - /** - * Enqueues JavaScript to build the view_item_list event - * - * @param WC_Product $product - */ - public static function listing_impression( $product ) { - $event_code = self::get_event_code( - 'view_item_list', - array( - 'items' => array( - array( - 'id' => self::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => self::product_get_category_line( $product ), - 'list' => self::get_list_name(), - ), - ), - ) - ); - - wc_enqueue_js( $event_code ); - } - - /** - * Enqueues JavaScript for select_content and add_to_cart events for the product archive - * - * @param WC_Product $product - */ - public static function listing_click( $product ) { - $item = array( - 'id' => self::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => self::product_get_category_line( $product ), - 'quantity' => 1, - ); - - $select_content_event_code = self::get_event_code( - 'select_content', - array( - 'items' => array( $item ), - ) - ); - - $add_to_cart_event_code = self::get_event_code( - 'add_to_cart', - array( - 'items' => array( $item ), - ) - ); - - wc_enqueue_js( - " - $( '.product.post-" . esc_js( $product->get_id() ) . ' a , .product.post-' . esc_js( $product->get_id() ) . " button' ).on('click', function() { - if ( false === $(this).hasClass( 'product_type_variable' ) && false === $(this).hasClass( 'product_type_grouped' ) ) { - $add_to_cart_event_code - } else { - $select_content_event_code - } - });" - ); - } - - /** - * Output Javascript to track add_to_cart event on single product page - */ - public static function add_to_cart() { - if ( 'yes' !== self::get( 'ga_event_tracking_enabled' ) || ! is_single() ) { - return; - } - - global $product; - - $items = array( - 'id' => self::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => self::product_get_category_line( $product ), - 'quantity' => 1, - ); - - // Set item data as Javascript variable so that quantity, variant, and ID can be updated before sending the event - $event_code = ' - const item_data = ' . self::format_event_data( $items ) . '; - item_data.quantity = $("input.qty").val() ? $("input.qty").val() : 1;'; - - if ( $product->is_type( 'variable' ) ) { - // Check the global google_analytics_integration_product_data Javascript variable contains data - // for the current variation selection and if it does update the item_data to be sent for this event - $event_code .= " - const selected_variation = google_analytics_integration_product_data[ $('input[name=\"variation_id\"]').val() ]; - if ( selected_variation !== undefined ) { - item_data.id = selected_variation.id; - item_data.variant = selected_variation.variant; - } - "; - } - - $event_code .= self::get_event_code( - 'add_to_cart', - '{"items": [item_data]}', - false - ); - - wc_enqueue_js( - "$( '.single_add_to_cart_button' ).on('click', function() { - $event_code - });" - ); - } - /** * Loads the standard Gtag code * @@ -338,197 +100,16 @@ function ' . self::tracker_var() . '(){dataLayer.push(arguments);} } /** - * Generate Gtag transaction tracking code - * - * @param WC_Order $order - * @return string - */ - public function add_transaction_enhanced( $order ) { - $event_items = array(); - $order_items = $order->get_items(); - if ( ! empty( $order_items ) ) { - foreach ( $order_items as $item ) { - $event_items[] = self::add_item( $order, $item ); - } - } - - return self::get_event_code( - 'purchase', - array( - 'transaction_id' => $order->get_order_number(), - 'affiliation' => get_bloginfo( 'name' ), - 'value' => $order->get_total(), - 'tax' => $order->get_total_tax(), - 'shipping' => $order->get_total_shipping(), - 'currency' => $order->get_currency(), - 'items' => $event_items, - ) - ); - } - - /** - * Add Item - * - * @param WC_Order $order WC_Order Object - * @param WC_Order_Item $item The item to add to a transaction/order - */ - public function add_item( $order, $item ) { - $product = $item->get_product(); - $variant = self::product_get_variant_line( $product ); - - $event_item = array( - 'id' => self::get_product_identifier( $product ), - 'name' => $item['name'], - 'category' => self::product_get_category_line( $product ), - 'price' => $order->get_item_total( $item ), - 'quantity' => $item['qty'], - ); - - if ( '' !== $variant ) { - $event_item['variant'] = $variant; - } - - return $event_item; - } - - /** - * Output JavaScript to track an enhanced ecommerce remove from cart action - */ - public function remove_from_cart() { - if ( 'yes' !== self::get( 'ga_enhanced_remove_from_cart_enabled' ) ) { - return; - } - - $event_code = self::get_event_code( - 'remove_from_cart', - '{"items": [{ - "id": $(this).data("product_sku") ? $(this).data("product_sku") : "#" + $(this).data("product_id"), - "quantity": $(this).parent().parent().find(".qty").val() ? $(this).parent().parent().find(".qty").val() : "1" - }]}' - ); - - // To track all the consecutive removals, - // we listen for clicks on `.woocommerce` container(s), - // as `.woocommerce-cart-form` and its items are re-rendered on each removal. - wc_enqueue_js( - "const selector = '.woocommerce-cart-form__cart-item .remove'; - $( '.woocommerce' ).off('click', selector).on( 'click', selector, function() { - $event_code - });" - ); - } - - - /** - * Adds the product ID and SKU to the remove product link if not present - * - * @param string $url - * @param string $key - * @return string - */ - public function remove_from_cart_attributes( $url, $key ) { - if ( strpos( $url, 'data-product_id' ) !== false ) { - return $url; - } - - if ( ! is_object( WC()->cart ) ) { - return $url; - } - - $item = WC()->cart->get_cart_item( $key ); - $product = $item['data']; - - if ( ! is_object( $product ) ) { - return $url; - } - - $url = str_replace( 'href=', 'data-product_id="' . esc_attr( $product->get_id() ) . '" data-product_sku="' . esc_attr( $product->get_sku() ) . '" href=', $url ); - return $url; - } - - /** - * Measure a product click and impression from a Product list + * Get the class instance * - * @param string $link The Add To Cart Link - * @param WC_Product $product The Product - */ - public function track_product( $link, $product ) { - if ( 'yes' === self::get( 'ga_enhanced_product_impression_enabled' ) ) { - self::listing_impression( $product ); - } - - if ( 'yes' === self::get( 'ga_enhanced_product_click_enabled' ) ) { - self::listing_click( $product ); - } - - return $link; - } - - /** - * Enqueue JavaScript to track a product detail view - */ - public function product_detail() { - if ( 'yes' !== self::get( 'ga_enhanced_product_detail_view_enabled' ) ) { - return; - } - - global $product; - - $event_code = self::get_event_code( - 'view_item', - array( - 'items' => array( - array( - 'id' => self::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => self::product_get_category_line( $product ), - 'price' => $product->get_price(), - ), - ), - ) - ); - - wc_enqueue_js( $event_code ); - } - - /** - * Enqueue JS to track when the checkout process is started + * @param array $options Options + * @return WC_Abstract_Google_Analytics_JS */ - public function checkout_process() { - if ( 'yes' !== self::get( 'ga_enhanced_checkout_process_enabled' ) ) { - return; - } - - $cart = WC()->cart->get_cart(); - - $items = array(); - foreach ( $cart as $cart_item_key => $cart_item ) { - $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); - - $item_data = array( - 'id' => self::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => self::product_get_category_line( $product ), - 'price' => $product->get_price(), - 'quantity' => $cart_item['quantity'], - ); - - $variant = self::product_get_variant_line( $product ); - if ( '' !== $variant ) { - $item_data['variant'] = $variant; - } - - $items[] = $item_data; + public static function get_instance( $options = array() ) { + if ( null === self::$instance ) { + self::$instance = new self( $options ); } - $event_code = self::get_event_code( - 'begin_checkout', - array( - 'items' => $items, - ) - ); - - wc_enqueue_js( $event_code ); + return self::$instance; } - } From 5eee69ec38981bf2a9f91b63d204a505dd5dac0f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 8 Nov 2023 14:26:24 +0000 Subject: [PATCH 046/126] Remove abstract methods --- .../class-wc-abstract-google-analytics-js.php | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 00aae30f..020fdad1 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -66,44 +66,6 @@ function gaOptout() { wp_enqueue_script( 'google-analytics-opt-out' ); } - /** - * Enqueues JavaScript to build the addImpression object - * - * @param WC_Product $product - */ - abstract public static function listing_impression( $product ); - - /** - * Enqueues JavaScript to build an addProduct and click object - * - * @param WC_Product $product - */ - abstract public static function listing_click( $product ); - - /** - * Loads the correct Google Gtag code (classic or universal) - * - * @param boolean|WC_Order $order Classic analytics needs order data to set the currency correctly - */ - abstract public static function load_analytics( $order = false ); - - /** - * Generate code used to pass transaction data to Google Analytics. - * - * @param WC_Order $order WC_Order Object - */ - public function add_transaction( $order ) { - wc_enqueue_js( static::add_transaction_enhanced( $order ) ); - } - - /** - * Generate Enhanced eCommerce transaction tracking code - * - * @param WC_Order $order WC_Order object - * @return string Add Transaction Code - */ - abstract protected function add_transaction_enhanced( $order ); - /** * Get item identifier from product data * @@ -165,19 +127,4 @@ public static function product_get_variant_line( $_product ) { return $out; } - - /** - * Echo JavaScript to track an enhanced ecommerce remove from cart action - */ - abstract public function remove_from_cart(); - - /** - * Enqueue JavaScript to track a product detail view - */ - abstract public function product_detail(); - - /** - * Enqueue JS to track when the checkout process is started - */ - abstract public function checkout_process(); } From 273ef9263924426fef94f7b753f299690d49e1b1 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Wed, 8 Nov 2023 14:28:22 +0000 Subject: [PATCH 047/126] Add inline script data --- includes/class-wc-google-gtag-js.php | 106 +++++++++++++++------------ 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 99d3d5d6..23e72cd8 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -16,6 +16,9 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var string $script_handle Handle for the front end JavaScript file */ public $script_handle = 'woocommerce-google-analytics-integration'; + /** @var string $script_data Data required for frontend event tracking */ + private $script_data = array(); + /** * Constructor * Takes our options from the parent class so we can later use them in the JS snippets @@ -24,21 +27,54 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { */ public function __construct( $options = array() ) { self::$options = $options; + + $this->load_analytics_config(); + // Setup frontend scripts add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) ); } /** - * Register front end JavaScript + * Register front end scripts and inline script data + * + * @return void */ public function register_scripts() { + wp_enqueue_script( + 'google-tag-manager', + 'https://www.googletagmanager.com/gtag/js?id=' . self::get( 'ga_id' ), + array(), + null, + false + ); + wp_enqueue_script( $this->script_handle, Plugin::get_instance()->get_js_asset_url( 'actions.js' ), - Plugin::get_instance()->get_js_asset_dependencies( 'actions' ), + array( + ...Plugin::get_instance()->get_js_asset_dependencies( 'actions' ), + 'google-tag-manager', + ), Plugin::get_instance()->get_js_asset_version( 'actions' ), true ); + + wp_add_inline_script( + $this->script_handle, + sprintf( + 'const wcgaiData = %s;', + $this->get_script_data() + ) + ); + } + + /** + * Return a JSON encoded string of all script data for the current page load + * + * @return string + */ + public function get_script_data(): string { + return wp_json_encode( $this->script_data ); } /** @@ -46,57 +82,33 @@ public function register_scripts() { * * @return string */ - public static function tracker_var() { + public static function tracker_var(): string { return apply_filters( 'woocommerce_gtag_tracker_variable', 'gtag' ); } /** - * Loads the standard Gtag code + * Add Google Analytics configuration data to the script data * - * @param WC_Order $order WC_Order Object (not used in this implementation, but mandatory in the abstract class) + * @return void */ - public static function load_analytics( $order = false ) { - $logged_in = is_user_logged_in() ? 'yes' : 'no'; - - $track_404_enabled = ''; - if ( 'yes' === self::get( 'ga_404_tracking_enabled' ) && is_404() ) { - // See https://developers.google.com/analytics/devguides/collection/gtagjs/events for reference - $track_404_enabled = self::tracker_var() . "( 'event', '404_not_found', { 'event_category':'error', 'event_label':'page: ' + document.location.pathname + document.location.search + ' referrer: ' + document.referrer });"; - } - - $gtag_developer_id = ''; - if ( ! empty( self::DEVELOPER_ID ) ) { - $gtag_developer_id = self::tracker_var() . "('set', 'developer_id." . self::DEVELOPER_ID . "', true);"; - } - - $gtag_id = self::get( 'ga_id' ); - $gtag_cross_domains = ! empty( self::get( 'ga_linker_cross_domains' ) ) ? array_map( 'esc_js', explode( ',', self::get( 'ga_linker_cross_domains' ) ) ) : array(); - $gtag_snippet = ' - window.dataLayer = window.dataLayer || []; - function ' . self::tracker_var() . '(){dataLayer.push(arguments);} - ' . self::tracker_var() . "('js', new Date()); - $gtag_developer_id - - " . self::tracker_var() . "('config', '" . esc_js( $gtag_id ) . "', { - 'allow_google_signals': " . ( 'yes' === self::get( 'ga_support_display_advertising' ) ? 'true' : 'false' ) . ", - 'link_attribution': " . ( 'yes' === self::get( 'ga_support_enhanced_link_attribution' ) ? 'true' : 'false' ) . ", - 'anonymize_ip': " . ( 'yes' === self::get( 'ga_anonymize_enabled' ) ? 'true' : 'false' ) . ", - 'linker':{ - 'domains': " . wp_json_encode( $gtag_cross_domains ) . ", - 'allow_incoming': " . ( 'yes' === self::get( 'ga_linker_allow_incoming_enabled' ) ? 'true' : 'false' ) . ", - }, - 'custom_map': { - 'dimension1': 'logged_in' - }, - 'logged_in': '$logged_in' - } ); - - $track_404_enabled - "; - - wp_register_script( 'google-tag-manager', 'https://www.googletagmanager.com/gtag/js?id=' . esc_js( $gtag_id ), array( 'google-analytics-opt-out' ), null, false ); - wp_add_inline_script( 'google-tag-manager', apply_filters( 'woocommerce_gtag_snippet', $gtag_snippet ) ); - wp_enqueue_script( 'google-tag-manager' ); + public function load_analytics_config() { + $this->script_data['config'] = array( + 'developer_id' => self::DEVELOPER_ID, + 'gtag_id' => self::get( 'ga_id' ), + 'tracker_var' => self::tracker_var(), + 'track_404' => 'yes' === self::get( 'ga_404_tracking_enabled' ), + 'allow_google_signals' => 'yes' === self::get( 'ga_support_display_advertising' ), + 'link_attribution' => 'yes' === self::get( 'ga_support_enhanced_link_attribution' ), + 'anonymize_ip' => 'yes' === self::get( 'ga_anonymize_enabled' ), + 'logged_in' => is_user_logged_in(), + 'linker' => array( + 'domains' => ! empty( self::get( 'ga_linker_cross_domains' ) ) ? array_map( 'esc_js', explode( ',', self::get( 'ga_linker_cross_domains' ) ) ) : array(), + 'allow_incoming' => 'yes' === self::get( 'ga_linker_allow_incoming_enabled' ), + ), + 'custom_map' => array( + 'dimension1' => 'logged_in', + ), + ); } /** From 01facf0ff194facf5b72d2a55e9a1bba3201462f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 9 Nov 2023 14:01:27 +0000 Subject: [PATCH 048/126] Format and attach event data in abstract class --- .../class-wc-abstract-google-analytics-js.php | 145 ++++++++++++------ includes/class-wc-google-analytics.php | 3 +- 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 020fdad1..b275ab29 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -21,17 +21,50 @@ abstract class WC_Abstract_Google_Analytics_JS { public const DEVELOPER_ID = 'dOGY3NW'; /** - * Get the class instance + * Constructor + * To be called from child classes to setup event data * - * @param array $options Options - * @return WC_Abstract_Google_Analytics_JS + * @return void */ - abstract public static function get_instance( $options = array() ); + public function __construct() { + $this->attach_event_data(); + } + + /** + * Hook into various parts of WooCommerce and set the relevant + * script data that the frontend tracking script will use. + * + * @return void + */ + public function attach_event_data() { + add_action( + 'woocommerce_before_checkout_form', + function() { + $this->set_script_data( 'cart', $this->get_formatted_cart() ); + } + ); + + add_action( + 'woocommerce_before_single_product', + function() { + global $product; + $this->set_script_data( 'single', $this->get_formatted_product( $product ) ); + } + ); + + add_action( + 'woocommerce_shop_loop_item_title', + function() { + global $product; + $this->set_script_data( 'products', $this->get_formatted_product( $product ), $product->get_id() ); + } + ); + } /** * Return one of our options * - * @param string $option Key/name for the option + * @param string $option Key/name for the option. * * @return string|null Value of the option or null if not found */ @@ -39,13 +72,6 @@ protected static function get( $option ) { return self::$options[ $option ] ?? null; } - /** - * Returns the tracker variable this integration should use - * - * @return string - */ - abstract public static function tracker_var(); - /** * Generic GA snippet for opt out */ @@ -69,7 +95,7 @@ function gaOptout() { /** * Get item identifier from product data * - * @param WC_Product $product WC_Product Object + * @param WC_Product $product WC_Product Object. * @return string */ public static function get_product_identifier( $product ) { @@ -87,44 +113,77 @@ public static function get_product_identifier( $product ) { } /** - * Returns a 'category' JSON line based on $product + * Returns an array of cart data in the required format * - * @param WC_Product $_product Product to pull info for - * @return string Line of JSON + * @return array */ - public static function product_get_category_line( $_product ) { - $out = []; - $variation_data = $_product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $_product->get_id() ) : false; - $categories = get_the_terms( $_product->get_id(), 'product_cat' ); - - if ( is_array( $variation_data ) && ! empty( $variation_data ) ) { - $parent_product = wc_get_product( $_product->get_parent_id() ); - $categories = get_the_terms( $parent_product->get_id(), 'product_cat' ); - } - - if ( $categories ) { - foreach ( $categories as $category ) { - $out[] = $category->name; - } - } + public function get_formatted_cart(): array { + return array( + 'items' => array_map( + function( $item ) { + return array( + 'id' => $this->get_product_identifier( $item['data'] ), + 'name' => $item['data']->get_name(), + 'quantity' => $item['quantity'], + 'prices' => array( + 'price' => $item['data']->get_price(), + 'currency_minor_unit' => wc_get_price_decimals(), + ), + ); + }, + WC()->cart->get_cart() + ), + 'coupons' => WC()->cart->get_coupons(), + 'totals' => array( + 'currency_code' => get_woocommerce_currency(), + 'total_price' => WC()->cart->get_total( 'edit' ), + 'currency_minor_unit' => wc_get_price_decimals(), + ), + ); + } - return "'" . esc_js( join( '/', $out ) ) . "',"; + /** + * Returns an array of product data in the required format + * + * @param WC_Product $product The product to format. + * + * @return array + */ + public function get_formatted_product( WC_Product $product ): array { + return array( + 'item_id' => $this->get_product_identifier( $product ), + 'item_name' => $product->get_name(), + 'categories' => wc_get_product_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ), + 'prices' => array( + 'price' => $product->get_price(), + 'currency_minor_unit' => wc_get_price_decimals(), + ), + ); } /** - * Returns a 'variant' JSON line based on $product + * Returns the tracker variable this integration should use * - * @param WC_Product $_product Product to pull info for - * @return string Line of JSON + * @return string */ - public static function product_get_variant_line( $_product ) { - $out = ''; - $variation_data = $_product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $_product->get_id() ) : false; + abstract public static function tracker_var(): string; - if ( is_array( $variation_data ) && ! empty( $variation_data ) ) { - $out = "'" . esc_js( wc_get_formatted_variation( $variation_data, true ) ) . "',"; - } + /** + * Add an event to the script data + * + * @param string $type The type of event being added. + * @param string $data Event to add. + * @param mixed $key Key to use for the data. + * + * @return void + */ + abstract public function set_script_data( string $type, $data, $key = false ); - return $out; - } + /** + * Get the class instance + * + * @param array $options Options + * @return WC_Abstract_Google_Analytics_JS + */ + abstract public static function get_instance( $options = array() ): WC_Abstract_Google_Analytics_JS; } diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index ec092d39..5bf84bf7 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -369,6 +369,8 @@ public function enqueue_tracking_code() { global $wp; $display_ecommerce_tracking = false; + $this->get_tracking_instance()->load_opt_out(); + if ( $this->disable_tracking( 'all' ) ) { return; } @@ -397,7 +399,6 @@ public function enqueue_tracking_code() { * Generate Standard Google Analytics tracking */ protected function enqueue_standard_tracking_code() { - $this->get_tracking_instance()->load_opt_out(); $this->get_tracking_instance()->load_analytics(); } From 18f6ba1dce13f5c6f7231d86cd65fa50ad43a5c1 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 10 Nov 2023 16:47:01 +0000 Subject: [PATCH 049/126] Map classic hooks to Blocks and inline event data --- includes/class-wc-google-gtag-js.php | 63 +++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 23e72cd8..5b5390f9 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -19,6 +19,18 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var string $script_data Data required for frontend event tracking */ private $script_data = array(); + /** @var array $mappings A map of Blocks Actions to classic WooCommerce hooks to use for events */ + private $mappings = array( + 'checkout-render-checkout-form' => 'woocommerce_before_checkout_form', + 'checkout-submit' => 'woocommerce_thankyou', + 'product-list-render' => 'woocommerce_shop_loop', + 'cart-add-item' => 'woocommerce_add_to_cart', + 'cart-set-item-quantity' => 'woocommerce_after_cart_item_quantity_update', + 'cart-remove-item' => 'woocommerce_cart_item_removed', + 'product-view-link' => 'woocommerce_after_single_product', + 'product-render' => 'woocommerce_after_single_product', + ); + /** * Constructor * Takes our options from the parent class so we can later use them in the JS snippets @@ -26,12 +38,15 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { * @param array $options Options */ public function __construct( $options = array() ) { + parent::__construct(); self::$options = $options; $this->load_analytics_config(); + $this->map_actions(); // Setup frontend scripts add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) ); + add_action( 'wp_footer', array( $this, 'inline_script_data' ) ); } /** @@ -58,16 +73,60 @@ public function register_scripts() { Plugin::get_instance()->get_js_asset_version( 'actions' ), true ); + } + /** + * Add inline script data to the front end + * + * @return void + */ + public function inline_script_data() { wp_add_inline_script( $this->script_handle, sprintf( 'const wcgaiData = %s;', $this->get_script_data() - ) + ), + 'before' + ); + } + + /** + * Hook into WooCommerce and add corresponding Blocks Actions to our event data + * + * @return void + */ + public function map_actions() { + array_walk( + $this->mappings, + function( $hook, $block_action ) { + add_action( + $hook, + function() use ( $block_action ) { + $this->set_script_data( 'events', $block_action ); + } + ); + } ); } + /** + * Add an event to the script data + * + * @param string $type The type of event this data is related to. + * @param mixed $data The event data to add. + * @param mixed $key If not false then the $data will be added as a new array item with this key. + * + * @return void + */ + public function set_script_data( string $type, $data, $key = false ) { + if ( ! $key ) { + $this->script_data[ $type ] = $data; + } else { + $this->script_data[ $type ][ $key ] = $data; + } + } + /** * Return a JSON encoded string of all script data for the current page load * @@ -117,7 +176,7 @@ public function load_analytics_config() { * @param array $options Options * @return WC_Abstract_Google_Analytics_JS */ - public static function get_instance( $options = array() ) { + public static function get_instance( $options = array() ): WC_Abstract_Google_Analytics_JS { if ( null === self::$instance ) { self::$instance = new self( $options ); } From 4d333053b77ab6696c68d4dc852cbf2933d6056f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 10:12:34 +0000 Subject: [PATCH 050/126] Update wcgaiData single product name --- includes/class-wc-abstract-google-analytics-js.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index b275ab29..115814d2 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -36,7 +36,7 @@ public function __construct() { * * @return void */ - public function attach_event_data() { + public function attach_event_data(): void { add_action( 'woocommerce_before_checkout_form', function() { @@ -48,7 +48,7 @@ function() { 'woocommerce_before_single_product', function() { global $product; - $this->set_script_data( 'single', $this->get_formatted_product( $product ) ); + $this->set_script_data( 'product', $this->get_formatted_product( $product ) ); } ); @@ -68,14 +68,14 @@ function() { * * @return string|null Value of the option or null if not found */ - protected static function get( $option ) { + protected static function get( $option ): ?string { return self::$options[ $option ] ?? null; } /** * Generic GA snippet for opt out */ - public static function load_opt_out() { + public static function load_opt_out(): void { $code = " var gaProperty = '" . esc_js( self::get( 'ga_id' ) ) . "'; var disableStr = 'ga-disable-' + gaProperty; @@ -98,7 +98,7 @@ function gaOptout() { * @param WC_Product $product WC_Product Object. * @return string */ - public static function get_product_identifier( $product ) { + public static function get_product_identifier( $product ): string { $identifier = $product->get_id(); if ( 'product_sku' === self::get( 'ga_product_identifier' ) ) { @@ -152,7 +152,7 @@ function( $item ) { public function get_formatted_product( WC_Product $product ): array { return array( 'item_id' => $this->get_product_identifier( $product ), - 'item_name' => $product->get_name(), + 'name' => $product->get_name(), 'categories' => wc_get_product_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ), 'prices' => array( 'price' => $product->get_price(), From 6a563e26cfbba51ecb7989210e1a2e7dabebb469 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 10:16:01 +0000 Subject: [PATCH 051/126] Remove key for wcgaiData products --- includes/class-wc-abstract-google-analytics-js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 115814d2..f33af751 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -56,7 +56,7 @@ function() { 'woocommerce_shop_loop_item_title', function() { global $product; - $this->set_script_data( 'products', $this->get_formatted_product( $product ), $product->get_id() ); + $this->set_script_data( 'products', $this->get_formatted_product( $product ) ); } ); } From 3506c88183aa4294172cfb4f595421c032b73357 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 11:38:38 +0000 Subject: [PATCH 052/126] Fix array keys --- .../class-wc-abstract-google-analytics-js.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index f33af751..851bb874 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -95,10 +95,11 @@ function gaOptout() { /** * Get item identifier from product data * - * @param WC_Product $product WC_Product Object. + * @param WC_Product $product WC_Product Object. + * * @return string */ - public static function get_product_identifier( $product ): string { + public static function get_product_identifier( WC_Product $product ): string { $identifier = $product->get_id(); if ( 'product_sku' === self::get( 'ga_product_identifier' ) ) { @@ -151,9 +152,9 @@ function( $item ) { */ public function get_formatted_product( WC_Product $product ): array { return array( - 'item_id' => $this->get_product_identifier( $product ), - 'name' => $product->get_name(), - 'categories' => wc_get_product_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ), + 'id' => $this->get_product_identifier( $product ), + 'name' => $product->get_name(), + 'categories' => wc_get_product_terms( $product->get_id(), 'product_cat', array( 'number' => 5 ) ), 'prices' => array( 'price' => $product->get_price(), 'currency_minor_unit' => wc_get_price_decimals(), @@ -171,13 +172,13 @@ abstract public static function tracker_var(): string; /** * Add an event to the script data * - * @param string $type The type of event being added. - * @param string $data Event to add. - * @param mixed $key Key to use for the data. + * @param string $type The type of event this data is related to. + * @param string|array $data The event data to add. + * @param string $key If not null then the $data will be added as a new array item with this key. * * @return void */ - abstract public function set_script_data( string $type, $data, $key = false ); + abstract public function set_script_data( string $type, string|array $data, ?string $key = null ): void; /** * Get the class instance From 738f3ec22e5b559ddbed53e8f63705ae38ba0178 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 11:40:57 +0000 Subject: [PATCH 053/126] Update event map and script data --- includes/class-wc-google-gtag-js.php | 51 +++++++++++++++------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 5b5390f9..21c1862d 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -19,16 +19,16 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var string $script_data Data required for frontend event tracking */ private $script_data = array(); - /** @var array $mappings A map of Blocks Actions to classic WooCommerce hooks to use for events */ + /** @var array $mappings A map of the GA4 events and the classic WooCommerce hooks that trigger them */ private $mappings = array( - 'checkout-render-checkout-form' => 'woocommerce_before_checkout_form', - 'checkout-submit' => 'woocommerce_thankyou', - 'product-list-render' => 'woocommerce_shop_loop', - 'cart-add-item' => 'woocommerce_add_to_cart', - 'cart-set-item-quantity' => 'woocommerce_after_cart_item_quantity_update', - 'cart-remove-item' => 'woocommerce_cart_item_removed', - 'product-view-link' => 'woocommerce_after_single_product', - 'product-render' => 'woocommerce_after_single_product', + 'begin_checkout' => 'woocommerce_before_checkout_form', + 'add_shipping_info' => 'woocommerce_thankyou', + 'view_item_list' => 'woocommerce_shop_loop', + 'add_to_cart' => 'woocommerce_add_to_cart', + // 'cart-set-item-quantity' => 'woocommerce_after_cart_item_quantity_update', + 'remove_from_cart' => 'woocommerce_cart_item_removed', + 'view_item' => 'woocommerce_after_single_product', + 'select_content' => 'woocommerce_after_single_product', ); /** @@ -54,7 +54,7 @@ public function __construct( $options = array() ) { * * @return void */ - public function register_scripts() { + public function register_scripts(): void { wp_enqueue_script( 'google-tag-manager', 'https://www.googletagmanager.com/gtag/js?id=' . self::get( 'ga_id' ), @@ -80,7 +80,7 @@ public function register_scripts() { * * @return void */ - public function inline_script_data() { + public function inline_script_data(): void { wp_add_inline_script( $this->script_handle, sprintf( @@ -96,14 +96,14 @@ public function inline_script_data() { * * @return void */ - public function map_actions() { + public function map_actions(): void { array_walk( $this->mappings, - function( $hook, $block_action ) { + function( $hook, $gtag_event ) { add_action( $hook, - function() use ( $block_action ) { - $this->set_script_data( 'events', $block_action ); + function() use ( $gtag_event ) { + $this->set_script_data( 'events', $gtag_event, true ); } ); } @@ -113,18 +113,23 @@ function() use ( $block_action ) { /** * Add an event to the script data * - * @param string $type The type of event this data is related to. - * @param mixed $data The event data to add. - * @param mixed $key If not false then the $data will be added as a new array item with this key. + * @param string $type The type of event this data is related to. + * @param string|array $data The event data to add. + * @param string $key If not null then the $data will be added as a new array item with this key. * * @return void */ - public function set_script_data( string $type, $data, $key = false ) { - if ( ! $key ) { - $this->script_data[ $type ] = $data; - } else { + public function set_script_data( string $type, string|array $data, ?string $key = null ): void { + if ( ! isset( $this->script_data[ $type ] ) ) { + $this->script_data[ $type ] = array(); + } + + if ( ! is_null( $key ) ) { $this->script_data[ $type ][ $key ] = $data; + } else { + $this->script_data[ $type ][] = $data; } + } /** @@ -150,7 +155,7 @@ public static function tracker_var(): string { * * @return void */ - public function load_analytics_config() { + public function load_analytics_config(): void { $this->script_data['config'] = array( 'developer_id' => self::DEVELOPER_ID, 'gtag_id' => self::get( 'ga_id' ), From d5a659e96896af6d7d288f136f8c2d3323fd3256 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 11:49:03 +0000 Subject: [PATCH 054/126] Add a tracker utility --- assets/js/src/tracker.js | 99 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 assets/js/src/tracker.js diff --git a/assets/js/src/tracker.js b/assets/js/src/tracker.js new file mode 100644 index 00000000..67750118 --- /dev/null +++ b/assets/js/src/tracker.js @@ -0,0 +1,99 @@ +/** + * A tracking utility for initializing a GA4, managing accepted + * events, attaching data to those events, and tracking them. + * + * @param {Object} config Configuration settings for the tracker. + * + * @return {Object} An object containing methods: init, setupEvents, event{ attach, get, track }. + */ +export const tracker = ( ( config ) => { + const events = []; + + return { + /** + * Initializes the tracker and dataLayer if not already done. + */ + init: () => { + if ( window[ config.tracker_var ] ) { + // Tracker already initialized. Do nothing. + return; + } + + window.dataLayer = window.dataLayer || []; + window[ config.tracker_var ] = function () { + window.dataLayer.push( arguments ); + }; + window[ config.tracker_var ]( 'js', new Date() ); + window[ config.tracker_var ]( + 'set', + `developer_id.${ config.developer_id }`, + true + ); + window[ config.tracker_var ]( 'config', config.gtag_id, { + allow_google_signals: config.allow_google_signals, + link_attribution: config.link_attribution, + anonymize_ip: config.anonymize_ip, + logged_in: config.logged_in, + linker: config.linker, + custom_map: config.custom_map, + } ); + }, + /** + * Each event in the array is added to a global `events` object using its `name` as the key. + * + * @param {Object[]} eventsArray An array of event objects. + */ + setupEvents: ( eventsArray ) => { + eventsArray.forEach( + ( event ) => ( events[ event.name ] = event ) + ); + }, + /** + * An object to control events which exist in the global `events` object. + * + * @param {string} event - The name of the event. + * + * @return {Object} An object containing methods to attach, get, and track the event. + */ + event: ( event ) => { + return { + /** + * Attach data to an event and track it. + * + * @param {*} data - Data to be used for the event. + */ + attach: ( data ) => { + const eventObject = tracker.event( event ).get(); + tracker + .event( eventObject.name ) + .track( eventObject.callback( data ) ); + }, + + /** + * Retrieves the event object, or returns false if not found. + * + * @return {Object|boolean} The event object or false if the event does not exist. + */ + get: () => { + return events[ event ] ?? false; + }, + + /** + * Tracks the event with associated data. + * + * @param {*} data - Data to be tracked with the event. + */ + track: ( data ) => { + window[ config.tracker_var ]( + 'event', + tracker.event( event ).get().name, + data + ); + }, + }; + }, + }; + // eslint-disable-next-line no-undef +} )( wcgaiData.config ?? {} ); + +tracker.init(); From 977f49a11f096cfe3cea2833c3181ffba4a82903 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 12:30:11 +0000 Subject: [PATCH 055/126] Update utils --- assets/js/src/{utils.js => utils/index.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename assets/js/src/{utils.js => utils/index.js} (100%) diff --git a/assets/js/src/utils.js b/assets/js/src/utils/index.js similarity index 100% rename from assets/js/src/utils.js rename to assets/js/src/utils/index.js From e24a1842a9edf2eb0af321f215dbdf6b5539f210 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 13:20:01 +0000 Subject: [PATCH 056/126] Move actions to data formatting functions --- assets/js/src/tracker/data-formatting.js | 193 +++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 assets/js/src/tracker/data-formatting.js diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js new file mode 100644 index 00000000..8aa9dcb9 --- /dev/null +++ b/assets/js/src/tracker/data-formatting.js @@ -0,0 +1,193 @@ +import { __ } from '@wordpress/i18n'; +import { + getProductFieldObject, + getProductImpressionObject, + getProductId, + formatPrice, + getCartCoupon, +} from '../utils'; + +/** + * Tracks view_item_list event + * + * @param {Object} params The function params + * @param {Array} params.products The products to track + * @param {string} [params.listName] The name of the list in which the item was presented to the user. + */ +export const trackListProducts = ( { + products, + listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), +} ) => { + if ( products.length === 0 ) { + return false; + } + + return { + item_list_id: 'engagement', + item_list_name: __( + 'Viewing products', + 'woocommerce-google-analytics-integration' + ), + items: products.map( ( product, index ) => ( { + ...getProductImpressionObject( product, listName ), + index: index + 1, + } ) ), + }; +}; + +/** + * Tracks add_to_cart event + * + * @param {Object} params The function params + * @param {Array} params.product The product to track + * @param {number} [params.quantity=1] The quantity of that product in the cart. + */ +export const trackAddToCart = ( { product, quantity = 1 } ) => { + trackEvent( 'add_to_cart', { + items: [ getProductFieldObject( product, quantity ) ], + } ); +}; + +/** + * Tracks remove_from_cart event + * + * @param {Object} params The function params + * @param {Array} params.product The product to track + * @param {number} [params.quantity=1] The quantity of that product in the cart. + */ +export const trackRemoveCartItem = ( { product, quantity = 1 } ) => { + trackEvent( 'remove_from_cart', { + items: [ getProductFieldObject( product, quantity ) ], + } ); +}; + +/** + * Tracks change_cart_quantity event + * + * @param {Object} params The function params + * @param {Array} params.product The product to track + * @param {number} [params.quantity=1] The quantity of that product in the cart. + */ +export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { + trackEvent( 'change_cart_quantity', { + event_category: 'ecommerce', + event_label: __( + 'Change Cart Item Quantity', + 'woocommerce-google-analytics-integration' + ), + items: [ getProductFieldObject( product, quantity ) ], + } ); +}; + +/** + * Track begin_checkout event + * + * @param {Object} params The function params + * @param {Object} params.storeCart The cart object + */ +export const trackBeginCheckout = ( { storeCart } ) => { + return { + currency: storeCart.totals.currency_code, + value: formatPrice( + storeCart.totals.total_price, + storeCart.totals.currency_minor_unit + ), + ...getCartCoupon( storeCart ), + items: storeCart.items.map( getProductFieldObject ), + }; +}; + +/** + * Track add_shipping_info event + * + * @param {Object} params The function params + * @param {Object} params.storeCart The cart object + */ +export const trackShippingTier = ( { storeCart } ) => { + trackEvent( 'add_shipping_info', { + currency: storeCart.totals.currency_code, + value: formatPrice( + storeCart.totals.total_price, + storeCart.totals.currency_minor_unit + ), + shipping_tier: + storeCart.shippingRates[ 0 ]?.shipping_rates?.find( + ( rate ) => rate.selected + )?.name || '', + ...getCartCoupon( storeCart ), + items: storeCart.items.map( getProductFieldObject ), + } ); +}; + +/** + * Tracks select_content event. + * + * @param {Object} params The function params + * @param {Object} params.product The product to track + */ +export const trackSelectContent = ( { product } ) => { + trackEvent( 'select_content', { + content_type: 'product', + content_id: getProductId( product ), + } ); +}; + +/** + * Tracks search event. + * + * @param {Object} params The function params + * @param {string} params.searchTerm The search term to track + */ +export const trackSearch = ( { searchTerm } ) => { + trackEvent( 'search', { + search_term: searchTerm, + } ); +}; + +/** + * Tracks view_item event + * + * @param {Object} params The function params + * @param {Object} params.product The product to track + * @param {string} [params.listName] The name of the list in which the item was presented to the user. + */ +export const trackViewItem = ( { + product, + listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), +} ) => { + if ( product ) { + return { + items: [ getProductImpressionObject( product, listName ) ], + }; + } +}; + +/** + * Track exception event + * + * @param {Object} params The function params + * @param {string} params.status The status of the exception. It should be "error" for tracking it. + * @param {string} params.content The exception description + */ +export const trackException = ( { status, content } ) => { + if ( status === 'error' ) { + trackEvent( 'exception', { + description: content, + fatal: false, + } ); + } +}; + +/** + * Track an event using the global gtag function. + * + * @param {string} eventName - Name of the event to track + * @param {Object} [eventParams] - Props to send within the event + */ +export const trackEvent = ( eventName, eventParams ) => { + if ( typeof gtag !== 'function' ) { + throw new Error( 'Function gtag not implemented.' ); + } + + window.gtag( 'event', eventName, eventParams ); +}; From bb6ecc7312b65382d38709763513150b08dbcdab Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 13:20:15 +0000 Subject: [PATCH 057/126] Add tracker utility --- assets/js/src/tracker/index.js | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 assets/js/src/tracker/index.js diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js new file mode 100644 index 00000000..0fd4a1e5 --- /dev/null +++ b/assets/js/src/tracker/index.js @@ -0,0 +1,104 @@ +import { config } from '../config'; + +/** + * A tracking utility for initializing a GA4, managing accepted + * events, attaching data to those events, and tracking them. + * + * @param {Object} config Configuration settings for the tracker. + * + * @return {Object} An object containing methods: init, setupEvents, event{ attach, get, track }. + */ +export const tracker = ( () => { + const events = []; + + return { + /** + * Initializes the tracker and dataLayer if not already done. + */ + init: () => { + if ( window[ config.tracker_var ] ) { + // Tracker already initialized. Do nothing. + return; + } + + window.dataLayer = window.dataLayer || []; + window[ config.tracker_var ] = function () { + window.dataLayer.push( arguments ); + }; + window[ config.tracker_var ]( 'js', new Date() ); + window[ config.tracker_var ]( + 'set', + `developer_id.${ config.developer_id }`, + true + ); + window[ config.tracker_var ]( 'config', config.gtag_id, { + allow_google_signals: config.allow_google_signals, + link_attribution: config.link_attribution, + anonymize_ip: config.anonymize_ip, + logged_in: config.logged_in, + linker: config.linker, + custom_map: config.custom_map, + } ); + }, + /** + * Each event in the array is added to a global `events` object using its `name` as the key. + * + * @param {Object[]} eventsArray An array of event objects. + */ + setupEvents: ( eventsArray ) => { + eventsArray.forEach( + ( event ) => ( events[ event.name ] = event ) + ); + }, + /** + * An object to control events which exist in the global `events` object. + * + * @param {string} event - The name of the event. + * + * @return {Object} An object containing methods to attach, get, and track the event. + */ + event: ( event ) => { + return { + /** + * Attach data to an event and track it. + * + * @param {*} data - Data to be used for the event. + */ + attach: ( data ) => { + const eventObject = tracker.event( event ).get(); + const eventData = eventObject.callback( data ); + + // The experimental hooks from Blocks are sometimes triggered + // without data so we'll only track events with data. + if ( eventData ) { + tracker.event( eventObject.name ).track( eventData ); + } + }, + + /** + * Retrieves the event object, or returns false if not found. + * + * @return {Object|boolean} The event object or false if the event does not exist. + */ + get: () => { + return events[ event ] ?? false; + }, + + /** + * Tracks the event with associated data. + * + * @param {*} data - Data to be tracked with the event. + */ + track: ( data ) => { + window[ config.tracker_var ]( + 'event', + tracker.event( event ).get().name, + data + ); + }, + }; + }, + }; +} )(); + +tracker.init(); From bd5f4cf5f3afedd3b506f84ecc17c1de2b49d20f Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 13:20:40 +0000 Subject: [PATCH 058/126] Add blocks and classic integrations --- assets/js/src/actions.js | 140 -------------------------- assets/js/src/integrations/blocks.js | 27 +++++ assets/js/src/integrations/classic.js | 29 ++++++ 3 files changed, 56 insertions(+), 140 deletions(-) delete mode 100644 assets/js/src/actions.js create mode 100644 assets/js/src/integrations/blocks.js create mode 100644 assets/js/src/integrations/classic.js diff --git a/assets/js/src/actions.js b/assets/js/src/actions.js deleted file mode 100644 index 94017c33..00000000 --- a/assets/js/src/actions.js +++ /dev/null @@ -1,140 +0,0 @@ -import { removeAction } from '@wordpress/hooks'; -import { NAMESPACE, ACTION_PREFIX } from './constants'; -import { - trackBeginCheckout, - trackShippingTier, - trackListProducts, - trackAddToCart, - trackChangeCartItemQuantity, - trackRemoveCartItem, - trackSelectContent, - trackSearch, - trackViewItem, - trackException, -} from './tracking'; -import { addUniqueAction } from './utils'; - -/** - * Track begin_checkout - * - * @summary Track the customer has started the checkout process - * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#begin_checkout - */ -addUniqueAction( - `${ ACTION_PREFIX }-checkout-render-checkout-form`, - NAMESPACE, - trackBeginCheckout -); - -/** - * Track add_shipping_info - * - * @summary Track the selected shipping tier when the checkout form is submitted - * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info - */ -addUniqueAction( - `${ ACTION_PREFIX }-checkout-submit`, - NAMESPACE, - trackShippingTier -); - -/** - * The following actions were previously tracked using]checkout_progress - * in UA but there is no comparable event in GA4. - */ -removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); - -/** - * Product List View - * - * @summary Track the view_item_list event - * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item_list - */ -addUniqueAction( - `${ ACTION_PREFIX }-product-list-render`, - NAMESPACE, - trackListProducts -); - -/** - * Add to cart. - * - * This event signifies that an item was added to a cart for purchase. - * - * @summary Track the add_to_cart event - * @see https://developers.google.com/gtagjs/reference/ga4-events#add_to_cart - */ -addUniqueAction( - `${ ACTION_PREFIX }-cart-add-item`, - NAMESPACE, - trackAddToCart -); - -/** - * Change cart item quantities - * - * @summary Custom change_cart_quantity event. - */ -addUniqueAction( - `${ ACTION_PREFIX }-cart-set-item-quantity`, - NAMESPACE, - trackChangeCartItemQuantity -); - -/** - * Remove item from the cart - * - * @summary Track the remove_from_cart event - * @see https://developers.google.com/gtagjs/reference/ga4-events#remove_from_cart - */ -addUniqueAction( - `${ ACTION_PREFIX }-cart-remove-item`, - NAMESPACE, - trackRemoveCartItem -); - -/** - * Product View Link Clicked - * - * @summary Track the select_content event - * @see https://developers.google.com/gtagjs/reference/ga4-events#select_content - */ -addUniqueAction( - `${ ACTION_PREFIX }-product-view-link`, - NAMESPACE, - trackSelectContent -); - -/** - * Product Search - * - * @summary Track the search event - * @see https://developers.google.com/gtagjs/reference/ga4-events#search - */ -addUniqueAction( `${ ACTION_PREFIX }-product-search`, NAMESPACE, trackSearch ); - -/** - * Single Product View - * - * @summary Track the view_item event - * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item - */ -addUniqueAction( - `${ ACTION_PREFIX }-product-render`, - NAMESPACE, - trackViewItem -); - -/** - * Track notices as Exception events. - * - * @summary Track the exception event - * @see https://developers.google.com/analytics/devguides/collection/gtagjs/exceptions - */ -addUniqueAction( - `${ ACTION_PREFIX }-store-notice-create`, - NAMESPACE, - trackException -); diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js new file mode 100644 index 00000000..0ae2040d --- /dev/null +++ b/assets/js/src/integrations/blocks.js @@ -0,0 +1,27 @@ +import { removeAction } from '@wordpress/hooks'; +import { addUniqueAction } from '../utils'; +import { tracker } from '../tracker'; +import { ACTION_PREFIX, NAMESPACE } from '../constants'; + +addUniqueAction( + `${ ACTION_PREFIX }-product-list-render`, + NAMESPACE, + tracker.event( 'view_item_list' ).attach +); + +/** + * Temporarily remove all actions for demo purposes. + */ + +removeAction( `${ ACTION_PREFIX }-checkout-render-checkout-form`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-cart-add-item`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-cart-set-item-quantity`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-cart-remove-item`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-product-view-link`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-product-search`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-product-render`, NAMESPACE ); +removeAction( `${ ACTION_PREFIX }-store-notice-create`, NAMESPACE ); diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js new file mode 100644 index 00000000..f8909477 --- /dev/null +++ b/assets/js/src/integrations/classic.js @@ -0,0 +1,29 @@ +import { tracker } from '../tracker'; +import { events, cart, products, product } from '../config.js'; + +/** + * The Google Analytics integration for classic WooCommerce pages + * triggers events using three different methods. + * + * 1. Automatically attach events listed in the global `wcgaiData.events` object. + * 2. Listen for custom jQuery events triggered by core WooCommerce. + * 3. Listen for various actions (i.e clicks) on specific elements. + */ + +export const trackClassicIntegration = () => { + const eventData = { + storeCart: cart, + products, + product, + }; + + Object.values( events ?? {} ).forEach( ( event ) => { + switch ( event ) { + // If the queued event name matches an event that has been registered with the + // tracker then automatically attach the event using the default data structures. + case tracker.event( event ).get()?.name: + tracker.event( event ).attach( eventData ); + break; + } + } ); +}; From e14d27fcc6fd7c43716cff62349c1539d8237b50 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 13:20:49 +0000 Subject: [PATCH 059/126] Add config file --- assets/js/src/config.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 assets/js/src/config.js diff --git a/assets/js/src/config.js b/assets/js/src/config.js new file mode 100644 index 00000000..caf36612 --- /dev/null +++ b/assets/js/src/config.js @@ -0,0 +1,6 @@ +/* global wcgaiData */ +export const config = wcgaiData.config ?? {}; +export const events = wcgaiData.events ?? {}; +export const cart = wcgaiData.cart ?? {}; +export const products = wcgaiData.products ?? {}; +export const product = wcgaiData.product ?? {}; From 3a36b96ce89d6bb20f7234e82ced3c9c932a9470 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Fri, 17 Nov 2023 13:21:11 +0000 Subject: [PATCH 060/126] Add new entry point --- assets/js/src/index.js | 90 +++++++++++++ assets/js/src/tracker.js | 99 -------------- assets/js/src/tracking.js | 191 --------------------------- includes/class-wc-google-gtag-js.php | 6 +- tests/unit-tests/WCGoogleGtagJS.php | 12 +- webpack.config.js | 7 +- 6 files changed, 100 insertions(+), 305 deletions(-) create mode 100644 assets/js/src/index.js delete mode 100644 assets/js/src/tracker.js delete mode 100644 assets/js/src/tracking.js diff --git a/assets/js/src/index.js b/assets/js/src/index.js new file mode 100644 index 00000000..b5a8f663 --- /dev/null +++ b/assets/js/src/index.js @@ -0,0 +1,90 @@ +import { tracker } from './tracker'; + +import { + trackBeginCheckout, + trackShippingTier, + trackListProducts, + trackAddToCart, + // trackChangeCartItemQuantity, + trackRemoveCartItem, + trackSelectContent, + trackSearch, + trackViewItem, + trackException, +} from './tracker/data-formatting'; + +/** + * Register all Google Analytics 4 events that can be tracked + */ +tracker.setupEvents( [ + { + /** + * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#begin_checkout + */ + name: 'begin_checkout', + callback: trackBeginCheckout, + }, + { + /** + * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info + */ + name: 'add_shipping_info', + callback: trackShippingTier, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item_list + */ + name: 'view_item_list', + callback: trackListProducts, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#add_to_cart + */ + name: 'add_to_cart', + callback: trackAddToCart, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#remove_from_cart + */ + name: 'remove_from_cart', + callback: trackRemoveCartItem, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#select_content + */ + name: 'select_content', + callback: trackSelectContent, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#search + */ + name: 'search', + callback: trackSearch, + }, + { + /** + * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item + */ + name: 'view_item', + callback: trackViewItem, + }, + { + /** + * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#exception + */ + name: 'exception', + callback: trackException, + }, +] ); + +// Initialize tracking for classic WooCommerce pages +import { trackClassicIntegration } from './integrations/classic'; +trackClassicIntegration(); + +// Initialize tracking for Block based WooCommerce pages +import './integrations/blocks'; diff --git a/assets/js/src/tracker.js b/assets/js/src/tracker.js deleted file mode 100644 index 67750118..00000000 --- a/assets/js/src/tracker.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * A tracking utility for initializing a GA4, managing accepted - * events, attaching data to those events, and tracking them. - * - * @param {Object} config Configuration settings for the tracker. - * - * @return {Object} An object containing methods: init, setupEvents, event{ attach, get, track }. - */ -export const tracker = ( ( config ) => { - const events = []; - - return { - /** - * Initializes the tracker and dataLayer if not already done. - */ - init: () => { - if ( window[ config.tracker_var ] ) { - // Tracker already initialized. Do nothing. - return; - } - - window.dataLayer = window.dataLayer || []; - window[ config.tracker_var ] = function () { - window.dataLayer.push( arguments ); - }; - window[ config.tracker_var ]( 'js', new Date() ); - window[ config.tracker_var ]( - 'set', - `developer_id.${ config.developer_id }`, - true - ); - window[ config.tracker_var ]( 'config', config.gtag_id, { - allow_google_signals: config.allow_google_signals, - link_attribution: config.link_attribution, - anonymize_ip: config.anonymize_ip, - logged_in: config.logged_in, - linker: config.linker, - custom_map: config.custom_map, - } ); - }, - /** - * Each event in the array is added to a global `events` object using its `name` as the key. - * - * @param {Object[]} eventsArray An array of event objects. - */ - setupEvents: ( eventsArray ) => { - eventsArray.forEach( - ( event ) => ( events[ event.name ] = event ) - ); - }, - /** - * An object to control events which exist in the global `events` object. - * - * @param {string} event - The name of the event. - * - * @return {Object} An object containing methods to attach, get, and track the event. - */ - event: ( event ) => { - return { - /** - * Attach data to an event and track it. - * - * @param {*} data - Data to be used for the event. - */ - attach: ( data ) => { - const eventObject = tracker.event( event ).get(); - tracker - .event( eventObject.name ) - .track( eventObject.callback( data ) ); - }, - - /** - * Retrieves the event object, or returns false if not found. - * - * @return {Object|boolean} The event object or false if the event does not exist. - */ - get: () => { - return events[ event ] ?? false; - }, - - /** - * Tracks the event with associated data. - * - * @param {*} data - Data to be tracked with the event. - */ - track: ( data ) => { - window[ config.tracker_var ]( - 'event', - tracker.event( event ).get().name, - data - ); - }, - }; - }, - }; - // eslint-disable-next-line no-undef -} )( wcgaiData.config ?? {} ); - -tracker.init(); diff --git a/assets/js/src/tracking.js b/assets/js/src/tracking.js deleted file mode 100644 index d4c6a1d9..00000000 --- a/assets/js/src/tracking.js +++ /dev/null @@ -1,191 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { - getProductFieldObject, - getProductImpressionObject, - getProductId, - formatPrice, - getCartCoupon, -} from './utils'; - -/** - * Tracks view_item_list event - * - * @param {Object} params The function params - * @param {Array} params.products The products to track - * @param {string} [params.listName] The name of the list in which the item was presented to the user. - */ -export const trackListProducts = ( { - products, - listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), -} ) => { - if ( products.length > 0 ) { - trackEvent( 'view_item_list', { - item_list_id: 'engagement', - item_list_name: __( - 'Viewing products', - 'woocommerce-google-analytics-integration' - ), - items: products.map( ( product, index ) => ( { - ...getProductImpressionObject( product, listName ), - index: index + 1, - } ) ), - } ); - } -}; - -/** - * Tracks add_to_cart event - * - * @param {Object} params The function params - * @param {Array} params.product The product to track - * @param {number} [params.quantity=1] The quantity of that product in the cart. - */ -export const trackAddToCart = ( { product, quantity = 1 } ) => { - trackEvent( 'add_to_cart', { - items: [ getProductFieldObject( product, quantity ) ], - } ); -}; - -/** - * Tracks remove_from_cart event - * - * @param {Object} params The function params - * @param {Array} params.product The product to track - * @param {number} [params.quantity=1] The quantity of that product in the cart. - */ -export const trackRemoveCartItem = ( { product, quantity = 1 } ) => { - trackEvent( 'remove_from_cart', { - items: [ getProductFieldObject( product, quantity ) ], - } ); -}; - -/** - * Tracks change_cart_quantity event - * - * @param {Object} params The function params - * @param {Array} params.product The product to track - * @param {number} [params.quantity=1] The quantity of that product in the cart. - */ -export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { - trackEvent( 'change_cart_quantity', { - event_category: 'ecommerce', - event_label: __( - 'Change Cart Item Quantity', - 'woocommerce-google-analytics-integration' - ), - items: [ getProductFieldObject( product, quantity ) ], - } ); -}; - -/** - * Track begin_checkout event - * - * @param {Object} params The function params - * @param {Object} params.storeCart The cart object - */ -export const trackBeginCheckout = ( { storeCart } ) => { - trackEvent( 'begin_checkout', { - currency: storeCart.totals.currency_code, - value: formatPrice( - storeCart.totals.total_price, - storeCart.totals.currency_minor_unit - ), - ...getCartCoupon( storeCart ), - items: storeCart.items.map( getProductFieldObject ), - } ); -}; - -/** - * Track add_shipping_info event - * - * @param {Object} params The function params - * @param {Object} params.storeCart The cart object - */ -export const trackShippingTier = ( { storeCart } ) => { - trackEvent( 'add_shipping_info', { - currency: storeCart.totals.currency_code, - value: formatPrice( - storeCart.totals.total_price, - storeCart.totals.currency_minor_unit - ), - shipping_tier: - storeCart.shippingRates[ 0 ]?.shipping_rates?.find( - ( rate ) => rate.selected - )?.name || '', - ...getCartCoupon( storeCart ), - items: storeCart.items.map( getProductFieldObject ), - } ); -}; - -/** - * Tracks select_content event. - * - * @param {Object} params The function params - * @param {Object} params.product The product to track - */ -export const trackSelectContent = ( { product } ) => { - trackEvent( 'select_content', { - content_type: 'product', - content_id: getProductId( product ), - } ); -}; - -/** - * Tracks search event. - * - * @param {Object} params The function params - * @param {string} params.searchTerm The search term to track - */ -export const trackSearch = ( { searchTerm } ) => { - trackEvent( 'search', { - search_term: searchTerm, - } ); -}; - -/** - * Tracks view_item event - * - * @param {Object} params The function params - * @param {Object} params.product The product to track - * @param {string} [params.listName] The name of the list in which the item was presented to the user. - */ -export const trackViewItem = ( { - product, - listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), -} ) => { - if ( product ) { - trackEvent( 'view_item', { - items: [ getProductImpressionObject( product, listName ) ], - } ); - } -}; - -/** - * Track exception event - * - * @param {Object} params The function params - * @param {string} params.status The status of the exception. It should be "error" for tracking it. - * @param {string} params.content The exception description - */ -export const trackException = ( { status, content } ) => { - if ( status === 'error' ) { - trackEvent( 'exception', { - description: content, - fatal: false, - } ); - } -}; - -/** - * Track an event using the global gtag function. - * - * @param {string} eventName - Name of the event to track - * @param {Object} [eventParams] - Props to send within the event - */ -export const trackEvent = ( eventName, eventParams ) => { - if ( typeof gtag !== 'function' ) { - throw new Error( 'Function gtag not implemented.' ); - } - - window.gtag( 'event', eventName, eventParams ); -}; diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 21c1862d..0b91450c 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -65,12 +65,12 @@ public function register_scripts(): void { wp_enqueue_script( $this->script_handle, - Plugin::get_instance()->get_js_asset_url( 'actions.js' ), + Plugin::get_instance()->get_js_asset_url( 'main.js' ), array( - ...Plugin::get_instance()->get_js_asset_dependencies( 'actions' ), + ...Plugin::get_instance()->get_js_asset_dependencies( 'main' ), 'google-tag-manager', ), - Plugin::get_instance()->get_js_asset_version( 'actions' ), + Plugin::get_instance()->get_js_asset_version( 'main' ), true ); } diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 75e66550..83ddfe8b 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -18,8 +18,8 @@ class WCGoogleGtagJS extends EventsDataTest { * Check that `WC_Google_Gtag_JS` registers * the `assets/js/ga-integration.js` script * as `woocommerce-google-analytics-integration-ga-integration` (`->script_handle . '-ga-integration'`) - * and enqueues the `assets/js/actions.js` script - * as `woocommerce-google-analytics-integration--actions` (`->script_handle . '--actions'`) + * and enqueues the `assets/js/build/main.js` script + * as `woocommerce-google-analytics-integration--main` (`->script_handle . '--main'`) * on `wp_enqueue_scripts` action. * * @return void @@ -40,11 +40,11 @@ public function test_scripts_are_registered() { $registered_url = wp_scripts()->registered[ $integration_handle ]->src; $this->assertStringContainsString( 'assets/js/build/ga-integration.js', $registered_url, 'The script does not point to the correct URL' ); - // Assert assert `-actions` is enqueued with the correct name. - $actions_handle = $gtag->script_handle . '-actions'; - $this->assertEquals( true, wp_script_is( $actions_handle, 'enqueued' ), '`…-actions` script was not enqueued' ); + // Assert assert `-main` is enqueued with the correct name. + $actions_handle = $gtag->script_handle . '-main'; + $this->assertEquals( true, wp_script_is( $actions_handle, 'enqueued' ), '`…-main` script was not enqueued' ); $registered_url = wp_scripts()->registered[ $actions_handle ]->src; - $this->assertStringContainsString( 'assets/js/build/actions.js', $registered_url, 'The script does not point to the correct URL' ); + $this->assertStringContainsString( 'assets/js/build/main.js', $registered_url, 'The script does not point to the correct URL' ); } /** diff --git a/webpack.config.js b/webpack.config.js index 2c96cf2a..5dde7f32 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,12 +4,7 @@ const path = require( 'path' ); const webpackConfig = { ...defaultConfig, entry: { - actions: path.resolve( process.cwd(), 'assets/js/src', 'actions.js' ), - 'ga-integration': path.resolve( - process.cwd(), - 'assets/js/src', - 'ga-integration.js' - ), + main: path.resolve( process.cwd(), 'assets/js/src', 'index.js' ), }, output: { ...defaultConfig.output, From a2349aabf372d96e2fd24712560da9846c25ccbc Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 20 Nov 2023 08:11:33 +0000 Subject: [PATCH 061/126] Remove enqueue_standard_tracking_code --- includes/class-wc-google-analytics.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 5bf84bf7..eb0b8e99 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -384,22 +384,6 @@ public function enqueue_tracking_code() { $this->enqueue_ecommerce_tracking_code( $order_id ); } } - - if ( is_woocommerce() || is_cart() || ( is_checkout() && ! $display_ecommerce_tracking ) ) { - $display_ecommerce_tracking = true; - $this->enqueue_standard_tracking_code(); - } - - if ( ! $display_ecommerce_tracking && 'yes' === $this->ga_standard_tracking_enabled ) { - $this->enqueue_standard_tracking_code(); - } - } - - /** - * Generate Standard Google Analytics tracking - */ - protected function enqueue_standard_tracking_code() { - $this->get_tracking_instance()->load_analytics(); } /** From 82d2ce2ed3e74384491c7e71612295380e0eca49 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Mon, 20 Nov 2023 08:11:42 +0000 Subject: [PATCH 062/126] Update method signature --- includes/class-wc-abstract-google-analytics-js.php | 2 +- includes/class-wc-google-gtag-js.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 851bb874..18d296f0 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -178,7 +178,7 @@ abstract public static function tracker_var(): string; * * @return void */ - abstract public function set_script_data( string $type, string|array $data, ?string $key = null ): void; + abstract public function set_script_data( string $type, $data, ?string $key = null ): void; /** * Get the class instance diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 0b91450c..a2f68ef9 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -119,7 +119,7 @@ function() use ( $gtag_event ) { * * @return void */ - public function set_script_data( string $type, string|array $data, ?string $key = null ): void { + public function set_script_data( string $type, $data, ?string $key = null ): void { if ( ! isset( $this->script_data[ $type ] ) ) { $this->script_data[ $type ] = array(); } From 5ebea9c1ae948ca7c776993510bea3fc849bab34 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 14:30:55 +0000 Subject: [PATCH 063/126] Remove event map --- assets/js/src/index.js | 84 ------------------------------------------ 1 file changed, 84 deletions(-) diff --git a/assets/js/src/index.js b/assets/js/src/index.js index b5a8f663..72d2e091 100644 --- a/assets/js/src/index.js +++ b/assets/js/src/index.js @@ -1,87 +1,3 @@ -import { tracker } from './tracker'; - -import { - trackBeginCheckout, - trackShippingTier, - trackListProducts, - trackAddToCart, - // trackChangeCartItemQuantity, - trackRemoveCartItem, - trackSelectContent, - trackSearch, - trackViewItem, - trackException, -} from './tracker/data-formatting'; - -/** - * Register all Google Analytics 4 events that can be tracked - */ -tracker.setupEvents( [ - { - /** - * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#begin_checkout - */ - name: 'begin_checkout', - callback: trackBeginCheckout, - }, - { - /** - * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info - */ - name: 'add_shipping_info', - callback: trackShippingTier, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item_list - */ - name: 'view_item_list', - callback: trackListProducts, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#add_to_cart - */ - name: 'add_to_cart', - callback: trackAddToCart, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#remove_from_cart - */ - name: 'remove_from_cart', - callback: trackRemoveCartItem, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#select_content - */ - name: 'select_content', - callback: trackSelectContent, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#search - */ - name: 'search', - callback: trackSearch, - }, - { - /** - * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item - */ - name: 'view_item', - callback: trackViewItem, - }, - { - /** - * @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#exception - */ - name: 'exception', - callback: trackException, - }, -] ); - // Initialize tracking for classic WooCommerce pages import { trackClassicIntegration } from './integrations/classic'; trackClassicIntegration(); From d135e41181af4f22758a8395cbcaeccfffc3de90 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 14:31:52 +0000 Subject: [PATCH 064/126] Rename data formatters --- assets/js/src/tracker/data-formatting.js | 68 ++++++++++++------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js index 8aa9dcb9..aeaf831b 100644 --- a/assets/js/src/tracker/data-formatting.js +++ b/assets/js/src/tracker/data-formatting.js @@ -8,13 +8,13 @@ import { } from '../utils'; /** - * Tracks view_item_list event + * Formats data for the view_item_list event * * @param {Object} params The function params * @param {Array} params.products The products to track * @param {string} [params.listName] The name of the list in which the item was presented to the user. */ -export const trackListProducts = ( { +export const view_item_list = ( { products, listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), } ) => { @@ -36,29 +36,29 @@ export const trackListProducts = ( { }; /** - * Tracks add_to_cart event + * Formats data for the add_to_cart event * * @param {Object} params The function params * @param {Array} params.product The product to track * @param {number} [params.quantity=1] The quantity of that product in the cart. */ -export const trackAddToCart = ( { product, quantity = 1 } ) => { - trackEvent( 'add_to_cart', { +export const add_to_cart = ( { product, quantity = 1 } ) => { + return { items: [ getProductFieldObject( product, quantity ) ], - } ); + }; }; /** - * Tracks remove_from_cart event + * Formats data for the remove_from_cart event * * @param {Object} params The function params * @param {Array} params.product The product to track * @param {number} [params.quantity=1] The quantity of that product in the cart. */ -export const trackRemoveCartItem = ( { product, quantity = 1 } ) => { - trackEvent( 'remove_from_cart', { +export const remove_from_cart = ( { product, quantity = 1 } ) => { + return { items: [ getProductFieldObject( product, quantity ) ], - } ); + }; }; /** @@ -80,12 +80,12 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { }; /** - * Track begin_checkout event + * Formats data for the begin_checkout event * * @param {Object} params The function params * @param {Object} params.storeCart The cart object */ -export const trackBeginCheckout = ( { storeCart } ) => { +export const begin_checkout = ( { storeCart } ) => { return { currency: storeCart.totals.currency_code, value: formatPrice( @@ -98,13 +98,13 @@ export const trackBeginCheckout = ( { storeCart } ) => { }; /** - * Track add_shipping_info event + * Formats data for the add_shipping_info event * * @param {Object} params The function params * @param {Object} params.storeCart The cart object */ -export const trackShippingTier = ( { storeCart } ) => { - trackEvent( 'add_shipping_info', { +export const add_shipping_info = ( { storeCart } ) => { + return { currency: storeCart.totals.currency_code, value: formatPrice( storeCart.totals.total_price, @@ -116,54 +116,56 @@ export const trackShippingTier = ( { storeCart } ) => { )?.name || '', ...getCartCoupon( storeCart ), items: storeCart.items.map( getProductFieldObject ), - } ); + }; }; /** - * Tracks select_content event. + * Formats data for the select_content event. * * @param {Object} params The function params * @param {Object} params.product The product to track */ -export const trackSelectContent = ( { product } ) => { - trackEvent( 'select_content', { +export const select_content = ( { product } ) => { + return { content_type: 'product', content_id: getProductId( product ), - } ); + }; }; /** - * Tracks search event. + * Formats data for the search event. * * @param {Object} params The function params * @param {string} params.searchTerm The search term to track */ -export const trackSearch = ( { searchTerm } ) => { - trackEvent( 'search', { +export const search = ( { searchTerm } ) => { + return { search_term: searchTerm, - } ); + }; }; /** - * Tracks view_item event + * Formats data for the view_item event * * @param {Object} params The function params * @param {Object} params.product The product to track * @param {string} [params.listName] The name of the list in which the item was presented to the user. */ -export const trackViewItem = ( { +export const view_item = ( { product, listName = __( 'Product List', 'woocommerce-google-analytics-integration' ), } ) => { - if ( product ) { - return { - items: [ getProductImpressionObject( product, listName ) ], - }; + if ( ! product ) { + return false; } + + return { + items: [ getProductImpressionObject( product, listName ) ], + }; }; /** - * Track exception event + * Formats data for the exception event * * @param {Object} params The function params * @param {string} params.status The status of the exception. It should be "error" for tracking it. @@ -171,10 +173,10 @@ export const trackViewItem = ( { */ export const trackException = ( { status, content } ) => { if ( status === 'error' ) { - trackEvent( 'exception', { + return { description: content, fatal: false, - } ); + }; } }; From 1fb333604193a0416eab0f087af5f7670b5962c9 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 14:32:25 +0000 Subject: [PATCH 065/126] Restructure Tracker --- assets/js/src/tracker/index.js | 166 ++++++++++++++------------------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 0fd4a1e5..545c6dcb 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -1,104 +1,82 @@ import { config } from '../config'; +import * as formatters from './data-formatting'; + +let instance; /** - * A tracking utility for initializing a GA4, managing accepted - * events, attaching data to those events, and tracking them. - * - * @param {Object} config Configuration settings for the tracker. + * A tracking utility for initializing a GA4 and tracking accepted events. * - * @return {Object} An object containing methods: init, setupEvents, event{ attach, get, track }. + * @class */ -export const tracker = ( () => { - const events = []; - - return { - /** - * Initializes the tracker and dataLayer if not already done. - */ - init: () => { - if ( window[ config.tracker_var ] ) { - // Tracker already initialized. Do nothing. - return; - } +class Tracker { + /** + * Constructs a new instance of the Tracker class. + * + * @throws {Error} If an instance of the Tracker already exists. + */ + constructor() { + if( instance ) { + throw new Error( 'Cannot instantiate more than one Tracker' ); + } + instance = this; + instance.init(); + } - window.dataLayer = window.dataLayer || []; - window[ config.tracker_var ] = function () { - window.dataLayer.push( arguments ); - }; - window[ config.tracker_var ]( 'js', new Date() ); - window[ config.tracker_var ]( - 'set', - `developer_id.${ config.developer_id }`, - true - ); - window[ config.tracker_var ]( 'config', config.gtag_id, { - allow_google_signals: config.allow_google_signals, - link_attribution: config.link_attribution, - anonymize_ip: config.anonymize_ip, - logged_in: config.logged_in, - linker: config.linker, - custom_map: config.custom_map, - } ); - }, - /** - * Each event in the array is added to a global `events` object using its `name` as the key. - * - * @param {Object[]} eventsArray An array of event objects. - */ - setupEvents: ( eventsArray ) => { - eventsArray.forEach( - ( event ) => ( events[ event.name ] = event ) - ); - }, - /** - * An object to control events which exist in the global `events` object. - * - * @param {string} event - The name of the event. - * - * @return {Object} An object containing methods to attach, get, and track the event. - */ - event: ( event ) => { - return { - /** - * Attach data to an event and track it. - * - * @param {*} data - Data to be used for the event. - */ - attach: ( data ) => { - const eventObject = tracker.event( event ).get(); - const eventData = eventObject.callback( data ); + /** + * Initializes the tracker and dataLayer if not already done. + */ + init() { + if ( window[ config.tracker_var ] ) { + // Tracker already initialized. Do nothing. + return; + } - // The experimental hooks from Blocks are sometimes triggered - // without data so we'll only track events with data. - if ( eventData ) { - tracker.event( eventObject.name ).track( eventData ); - } - }, + window.dataLayer = window.dataLayer || []; + + function gtag() { + window.dataLayer.push( arguments ); + } + + window[ config.tracker_var ] = gtag; - /** - * Retrieves the event object, or returns false if not found. - * - * @return {Object|boolean} The event object or false if the event does not exist. - */ - get: () => { - return events[ event ] ?? false; - }, + gtag( 'js', new Date() ); + gtag( + 'set', + `developer_id.${ config.developer_id }`, + true + ); + gtag( 'config', config.gtag_id, { + allow_google_signals: config.allow_google_signals, + link_attribution: config.link_attribution, + anonymize_ip: config.anonymize_ip, + logged_in: config.logged_in, + linker: config.linker, + custom_map: config.custom_map, + } ); + } - /** - * Tracks the event with associated data. - * - * @param {*} data - Data to be tracked with the event. - */ - track: ( data ) => { - window[ config.tracker_var ]( - 'event', - tracker.event( event ).get().name, - data - ); - }, - }; - }, - }; -} )(); + /** + * Creates and returns an event handler object for a specified event name. + * + * @param {string} name The name of the event. + * @returns {{handler: function(*): void}} An object with a `handler` method for processing and tracking the event. + * @throws {Error} If the event name is not supported. + */ + event( name ) { + if( ! formatters[ name ] ) { + throw new Error( `Event ${ name } is not supported.` ); + } + + return { + handler: ( data ) => { + window[ config.tracker_var ]( + 'event', + name, + formatters[ name ]( data ) + ); + }, + }; + } +} -tracker.init(); +export const tracker = Object.freeze( new Tracker() ); From d114b28d91f25115583408b37413f95d76cbbbfb Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 14:32:55 +0000 Subject: [PATCH 066/126] Update tracking --- assets/js/src/integrations/blocks.js | 2 +- assets/js/src/integrations/classic.js | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 0ae2040d..2d2f2aad 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -6,7 +6,7 @@ import { ACTION_PREFIX, NAMESPACE } from '../constants'; addUniqueAction( `${ ACTION_PREFIX }-product-list-render`, NAMESPACE, - tracker.event( 'view_item_list' ).attach + tracker.event( 'view_item_list' ).handler ); /** diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index f8909477..e66818df 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -17,13 +17,7 @@ export const trackClassicIntegration = () => { product, }; - Object.values( events ?? {} ).forEach( ( event ) => { - switch ( event ) { - // If the queued event name matches an event that has been registered with the - // tracker then automatically attach the event using the default data structures. - case tracker.event( event ).get()?.name: - tracker.event( event ).attach( eventData ); - break; - } + Object.values( events ?? {} ).forEach( ( eventName ) => { + tracker.event( eventName ).handler( eventData ); } ); }; From d7d9add7d3743ac6da33c40c7603f1fb661935cb Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 14:33:30 +0000 Subject: [PATCH 067/126] Reduce categories data to name only --- includes/class-wc-abstract-google-analytics-js.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 18d296f0..23424775 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -154,7 +154,10 @@ public function get_formatted_product( WC_Product $product ): array { return array( 'id' => $this->get_product_identifier( $product ), 'name' => $product->get_name(), - 'categories' => wc_get_product_terms( $product->get_id(), 'product_cat', array( 'number' => 5 ) ), + 'categories' => array_map( + fn( $category ) => array( 'name' => $category->name ), + wc_get_product_terms( $product->get_id(), 'product_cat', array( 'number' => 5 ) ) + ), 'prices' => array( 'price' => $product->get_price(), 'currency_minor_unit' => wc_get_price_decimals(), From 4d65dfc7aae06dfc5cec036664991ffdfb0cdd60 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Thu, 7 Dec 2023 15:03:13 +0000 Subject: [PATCH 068/126] Update event => WC hook map --- includes/class-wc-google-gtag-js.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index a2f68ef9..75af7074 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -21,14 +21,13 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var array $mappings A map of the GA4 events and the classic WooCommerce hooks that trigger them */ private $mappings = array( - 'begin_checkout' => 'woocommerce_before_checkout_form', - 'add_shipping_info' => 'woocommerce_thankyou', - 'view_item_list' => 'woocommerce_shop_loop', - 'add_to_cart' => 'woocommerce_add_to_cart', - // 'cart-set-item-quantity' => 'woocommerce_after_cart_item_quantity_update', - 'remove_from_cart' => 'woocommerce_cart_item_removed', - 'view_item' => 'woocommerce_after_single_product', - 'select_content' => 'woocommerce_after_single_product', + 'begin_checkout' => 'woocommerce_before_checkout_form', + 'add_shipping_info' => 'woocommerce_thankyou', + 'view_item_list' => 'woocommerce_before_shop_loop_item', + 'add_to_cart' => 'woocommerce_add_to_cart', + 'remove_from_cart' => 'woocommerce_cart_item_removed', + 'view_item' => 'woocommerce_after_single_product', + 'select_content' => 'woocommerce_after_single_product', ); /** @@ -103,7 +102,7 @@ function( $hook, $gtag_event ) { add_action( $hook, function() use ( $gtag_event ) { - $this->set_script_data( 'events', $gtag_event, true ); + $this->set_script_data( 'events', $gtag_event, $gtag_event ); } ); } @@ -123,7 +122,7 @@ public function set_script_data( string $type, $data, ?string $key = null ): voi if ( ! isset( $this->script_data[ $type ] ) ) { $this->script_data[ $type ] = array(); } - + if ( ! is_null( $key ) ) { $this->script_data[ $type ][ $key ] = $data; } else { From 08ce30eafe435feb312b4b031e8bff42e3e2f158 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Sun, 10 Dec 2023 16:56:15 +0000 Subject: [PATCH 069/126] Update Tracker --- assets/js/src/tracker/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 545c6dcb..e6c745e8 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -66,7 +66,7 @@ class Tracker { if( ! formatters[ name ] ) { throw new Error( `Event ${ name } is not supported.` ); } - + return { handler: ( data ) => { window[ config.tracker_var ]( From 326c59e53932b1ceb7e8aaf3b29de77fa9c02cb1 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Sun, 10 Dec 2023 16:59:46 +0000 Subject: [PATCH 070/126] Add utility to find a single product by its ID --- assets/js/src/utils/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 4a3ec624..634137d8 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -1,4 +1,5 @@ import { addAction, removeAction } from '@wordpress/hooks'; +import { products } from '../config.js'; /** * Formats data into the productFieldObject shape. @@ -132,3 +133,13 @@ const getCategoryObject = ( categories ) => { const formatCategoryKey = ( index ) => { return 'item_category' + ( index > 0 ? index + 1 : '' ); }; + +/** + * Searches through the global wcgaiData.products object to find a single product by its ID + * + * @param {*number} id The ID of the product to search for + * @returns {Object|undefined} The product object or undefined if not found + */ +export const getProductFromID = ( id ) => { + return products.find( ( { product_id } ) => product_id === id ); +} From 58a6fc2ad55873d56d88ad48730837a69afcd5d7 Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Sun, 10 Dec 2023 17:05:34 +0000 Subject: [PATCH 071/126] Allow for single entry for data type in script data --- includes/class-wc-abstract-google-analytics-js.php | 3 ++- includes/class-wc-google-gtag-js.php | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 23424775..d19998f3 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -48,7 +48,7 @@ function() { 'woocommerce_before_single_product', function() { global $product; - $this->set_script_data( 'product', $this->get_formatted_product( $product ) ); + $this->set_script_data( 'product', $this->get_formatted_product( $product ), null, true ); } ); @@ -152,6 +152,7 @@ function( $item ) { */ public function get_formatted_product( WC_Product $product ): array { return array( + 'product_id' => $product->get_id(), 'id' => $this->get_product_identifier( $product ), 'name' => $product->get_name(), 'categories' => array_map( diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 75af7074..e08ccea6 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -115,15 +115,18 @@ function() use ( $gtag_event ) { * @param string $type The type of event this data is related to. * @param string|array $data The event data to add. * @param string $key If not null then the $data will be added as a new array item with this key. + * @param bool $single If true then no other data will be added for this type. * * @return void */ - public function set_script_data( string $type, $data, ?string $key = null ): void { + public function set_script_data( string $type, $data, ?string $key = null, bool $single = false ): void { if ( ! isset( $this->script_data[ $type ] ) ) { $this->script_data[ $type ] = array(); } - if ( ! is_null( $key ) ) { + if ( $single ) { + $this->script_data[ $type ] = $data; + } elseif ( ! is_null( $key ) ) { $this->script_data[ $type ][ $key ] = $data; } else { $this->script_data[ $type ][] = $data; From 89900a18908cdb622c6600d872077fe72f300f3e Mon Sep 17 00:00:00 2001 From: Martyn Jones Date: Sun, 10 Dec 2023 17:06:00 +0000 Subject: [PATCH 072/126] Track add to cart events --- assets/js/src/integrations/blocks.js | 14 ++++++++++++-- assets/js/src/integrations/classic.js | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 2d2f2aad..2681f618 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -9,6 +9,18 @@ addUniqueAction( tracker.event( 'view_item_list' ).handler ); +addUniqueAction( + `${ ACTION_PREFIX }-product-render`, + NAMESPACE, + tracker.event( 'view_item' ).handler +); + +addUniqueAction( + `${ ACTION_PREFIX }-cart-add-item`, + NAMESPACE, + tracker.event( 'add_to_cart' ).handler +); + /** * Temporarily remove all actions for demo purposes. */ @@ -18,10 +30,8 @@ removeAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-cart-add-item`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-cart-set-item-quantity`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-cart-remove-item`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-product-view-link`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-product-search`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-product-render`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-store-notice-create`, NAMESPACE ); diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index e66818df..07ce88d5 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -1,12 +1,13 @@ import { tracker } from '../tracker'; +import { getProductFromID } from '../utils'; import { events, cart, products, product } from '../config.js'; /** * The Google Analytics integration for classic WooCommerce pages * triggers events using three different methods. * - * 1. Automatically attach events listed in the global `wcgaiData.events` object. - * 2. Listen for custom jQuery events triggered by core WooCommerce. + * 1. Automatically handle events listed in the global `wcgaiData.events` object. + * 2. Listen for custom events from WooCommerce core. * 3. Listen for various actions (i.e clicks) on specific elements. */ @@ -20,4 +21,20 @@ export const trackClassicIntegration = () => { Object.values( events ?? {} ).forEach( ( eventName ) => { tracker.event( eventName ).handler( eventData ); } ); + + /** + * Track the custom add to cart event dispatched by WooCommerce Core + * + * @param {Event} e - The event object + * @param {Object} fragments - An object containing fragments of the updated cart. + * @param {string} cartHash - A string representing the hash of the cart after the update. + * @param {HTMLElement[]} button - An array of HTML elements representing the add to cart button. + */ + document.body.onadded_to_cart = ( e, fragments, cartHash, button ) => { + tracker.event( 'add_to_cart' ).handler( { + product: getProductFromID( + parseInt( button[ 0 ].dataset.product_id ) + ), + } ); + }; }; From cabeb455dfa6587b6cdacc1310f60d6066439940 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 18 Jan 2024 17:13:59 +0000 Subject: [PATCH 073/126] Fix JS formatting issues --- assets/js/src/tracker/data-formatting.js | 4 ++++ assets/js/src/tracker/index.js | 19 ++++++++----------- assets/js/src/utils/index.js | 7 ++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js index aeaf831b..fad5d973 100644 --- a/assets/js/src/tracker/data-formatting.js +++ b/assets/js/src/tracker/data-formatting.js @@ -7,6 +7,8 @@ import { getCartCoupon, } from '../utils'; +/* eslint-disable camelcase */ + /** * Formats data for the view_item_list event * @@ -164,6 +166,8 @@ export const view_item = ( { }; }; +/* eslint-enable camelcase */ + /** * Formats data for the exception event * diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index e6c745e8..4154c2bc 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -11,11 +11,11 @@ let instance; class Tracker { /** * Constructs a new instance of the Tracker class. - * + * * @throws {Error} If an instance of the Tracker already exists. */ constructor() { - if( instance ) { + if ( instance ) { throw new Error( 'Cannot instantiate more than one Tracker' ); } instance = this; @@ -32,19 +32,15 @@ class Tracker { } window.dataLayer = window.dataLayer || []; - + function gtag() { window.dataLayer.push( arguments ); } - + window[ config.tracker_var ] = gtag; gtag( 'js', new Date() ); - gtag( - 'set', - `developer_id.${ config.developer_id }`, - true - ); + gtag( 'set', `developer_id.${ config.developer_id }`, true ); gtag( 'config', config.gtag_id, { allow_google_signals: config.allow_google_signals, link_attribution: config.link_attribution, @@ -59,11 +55,12 @@ class Tracker { * Creates and returns an event handler object for a specified event name. * * @param {string} name The name of the event. - * @returns {{handler: function(*): void}} An object with a `handler` method for processing and tracking the event. + * @return {{handler: function(*): void}} An object with a `handler` method for processing and tracking the event. * @throws {Error} If the event name is not supported. */ event( name ) { - if( ! formatters[ name ] ) { + /* eslint import/namespace: [ 'error', { allowComputed: true } ] */ + if ( ! formatters[ name ] ) { throw new Error( `Event ${ name } is not supported.` ); } diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 634137d8..f13fbcd0 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -137,9 +137,10 @@ const formatCategoryKey = ( index ) => { /** * Searches through the global wcgaiData.products object to find a single product by its ID * - * @param {*number} id The ID of the product to search for - * @returns {Object|undefined} The product object or undefined if not found + * @param {number} id The ID of the product to search for + * @return {Object|undefined} The product object or undefined if not found */ export const getProductFromID = ( id ) => { + /* eslint-disable-next-line camelcase */ return products.find( ( { product_id } ) => product_id === id ); -} +}; From 87d82f6fbc2ea754fdc9c8c75be3ea3dba651ba8 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 18 Jan 2024 17:36:59 +0000 Subject: [PATCH 074/126] Remove unused PHP tests --- composer.json | 2 +- composer.lock | 369 ++++++++++++++------ includes/class-wc-google-gtag-js.php | 2 +- tests/unit-tests/AddTransactionEnhanced.php | 54 --- tests/unit-tests/CheckoutProcess.php | 66 ---- tests/unit-tests/ListingClick.php | 45 --- tests/unit-tests/ListingImpression.php | 45 --- tests/unit-tests/ProductDetail.php | 50 --- tests/unit-tests/WCGoogleGtagJS.php | 89 +---- 9 files changed, 269 insertions(+), 453 deletions(-) delete mode 100644 tests/unit-tests/AddTransactionEnhanced.php delete mode 100644 tests/unit-tests/CheckoutProcess.php delete mode 100644 tests/unit-tests/ListingClick.php delete mode 100644 tests/unit-tests/ListingImpression.php delete mode 100644 tests/unit-tests/ProductDetail.php diff --git a/composer.json b/composer.json index 4dcc5b04..d0e791b0 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "wp-coding-standards/wpcs": "^2.3", "exussum12/coverage-checker": "^1.0", "phpunit/phpunit": "^9.5", - "yoast/phpunit-polyfills": "^1.0" + "yoast/phpunit-polyfills": "^1.1.0" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index ae214bee..f168238b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ace93dcb2663aefb52cf3f91ebc4e52c", + "content-hash": "0d84c93324af4fd84a5436cfe068bd4c", "packages": [], "packages-dev": [ { @@ -76,34 +76,38 @@ "stylecheck", "tests" ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, "time": "2022-02-04T12:51:07+00:00" }, { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -128,6 +132,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -142,7 +150,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "exussum12/coverage-checker", @@ -188,20 +196,24 @@ } ], "description": "Allows checking the code coverage of a single pull request", + "support": { + "issues": "https://github.com/exussum12/coverageChecker/issues", + "source": "https://github.com/exussum12/coverageChecker/tree/1.0.2" + }, "time": "2022-01-19T14:42:51+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -237,26 +249,30 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -295,7 +311,11 @@ "parser", "php" ], - "time": "2022-05-31T20:59:12+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + }, + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -351,6 +371,10 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, "time": "2021-07-20T11:28:43+00:00" }, { @@ -398,27 +422,31 @@ } ], "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.23", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -433,8 +461,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -465,13 +493,18 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-12-28T12:41:10+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -521,6 +554,10 @@ "filesystem", "iterator" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -580,6 +617,10 @@ "keywords": [ "process" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -635,6 +676,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -690,6 +735,10 @@ "keywords": [ "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -700,20 +749,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.27", + "version": "9.6.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -724,7 +773,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -742,8 +791,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -751,7 +800,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -780,6 +829,11 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + }, "funding": [ { "url": "https://phpunit.de/sponsors.html", @@ -794,7 +848,7 @@ "type": "tidelift" } ], - "time": "2022-12-09T07:31:23+00:00" + "time": "2023-12-01T16:55:19+00:00" }, { "name": "sebastian/cli-parser", @@ -840,6 +894,10 @@ ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -892,6 +950,10 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -943,6 +1005,10 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1013,6 +1079,10 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1023,20 +1093,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1066,26 +1136,30 @@ ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1128,26 +1202,30 @@ "unidiff", "unified diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -1187,13 +1265,17 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -1260,6 +1342,10 @@ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1270,16 +1356,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -1320,30 +1406,34 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1373,13 +1463,17 @@ ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -1426,6 +1520,10 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1477,6 +1575,10 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1487,16 +1589,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -1535,14 +1637,18 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -1587,6 +1693,10 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1597,16 +1707,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -1639,13 +1749,17 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -1688,6 +1802,10 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1698,16 +1816,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.8.1", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", "shasum": "" }, "require": { @@ -1717,11 +1835,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -1736,29 +1854,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], - "time": "2022-06-18T07:21:10+00:00" + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-01-11T20:47:48+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -1785,13 +1932,17 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + }, "funding": [ { "url": "https://github.com/theseer", "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "wp-coding-standards/wpcs", @@ -1837,20 +1988,25 @@ "standards", "wordpress" ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, "time": "2020-05-13T23:57:56+00:00" }, { "name": "yoast/phpunit-polyfills", - "version": "1.0.4", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c" + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", - "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/224e4a1329c03d8bad520e3fc4ec980034a4b212", + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212", "shasum": "" }, "require": { @@ -1858,13 +2014,12 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.2.1" + "yoast/yoastcs": "^2.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev", - "dev-develop": "1.x-dev" + "dev-main": "2.x-dev" } }, "autoload": { @@ -1894,7 +2049,11 @@ "polyfill", "testing" ], - "time": "2022-11-16T09:07:52+00:00" + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2023-08-19T14:25:08+00:00" } ], "aliases": [], @@ -1904,5 +2063,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.1.0" } diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index e08ccea6..91ece4a1 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -123,7 +123,7 @@ public function set_script_data( string $type, $data, ?string $key = null, bool if ( ! isset( $this->script_data[ $type ] ) ) { $this->script_data[ $type ] = array(); } - + if ( $single ) { $this->script_data[ $type ] = $data; } elseif ( ! is_null( $key ) ) { diff --git a/tests/unit-tests/AddTransactionEnhanced.php b/tests/unit-tests/AddTransactionEnhanced.php deleted file mode 100644 index 539a1f2e..00000000 --- a/tests/unit-tests/AddTransactionEnhanced.php +++ /dev/null @@ -1,54 +0,0 @@ -get_order(); - - $gtag = new WC_Google_Gtag_JS(); - $gtag->add_transaction_enhanced( $order ); - - // Confirm woocommerce_gtag_event_data is called by add_transaction_enhanced(). - $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for purchase (add_transaction_enhanced()) event' ); - - $order_items = $order->get_items(); - $event_items = array(); - - if ( ! empty( $order_items ) ) { - foreach ( $order_items as $item ) { - $event_items[] = $gtag->add_item( $order, $item ); - } - } - - // The expected data structure for this event. - $expected_data = array( - 'transaction_id' => $order->get_order_number(), - 'affiliation' => get_bloginfo( 'name' ), - 'value' => $order->get_total(), - 'tax' => $order->get_total_tax(), - 'shipping' => $order->get_total_shipping(), - 'currency' => $order->get_currency(), - 'items' => $event_items, - ); - - // Confirm data structure matches what's expected. - $this->assertEquals( $expected_data, $this->get_event_data(), 'Event data does not match expected data structure for purchase (add_transaction_enhanced()) event' ); - } - -} diff --git a/tests/unit-tests/CheckoutProcess.php b/tests/unit-tests/CheckoutProcess.php deleted file mode 100644 index 55ad729a..00000000 --- a/tests/unit-tests/CheckoutProcess.php +++ /dev/null @@ -1,66 +0,0 @@ -get_customer()->get_id() ); - - $product = $this->get_product(); - $cart = WC()->cart; - - $add_to = $cart->add_to_cart( $product->get_id() ); - - $mock = $this->getMockBuilder( WC_Google_Gtag_JS::class ) - ->setMethods( array( '__construct' ) ) - ->setConstructorArgs( array( array( 'ga_enhanced_checkout_process_enabled' => 'yes' ) ) ) - ->getMock(); - - $mock->checkout_process( $cart->get_cart() ); - - // Confirm woocommerce_gtag_event_data is called by checkout_process(). - $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for begin_checkout (checkout_process()) event' ); - - $expected_data = array( - 'items' => array(), - ); - - foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) { - $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); - - $item_data = array( - 'id' => WC_Google_Gtag_JS::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => WC_Google_Gtag_JS::product_get_category_line( $product ), - 'price' => $product->get_price(), - 'quantity' => $cart_item['quantity'], - ); - - $variant = WC_Google_Gtag_JS::product_get_variant_line( $product ); - if ( '' !== $variant ) { - $item_data['variant'] = $variant; - } - - $expected_data['items'][] = $item_data; - } - - // Confirm data structure matches what's expected. - $this->assertEquals( $expected_data, $this->get_event_data(), 'Event data does not match expected data structure for begin_checkout (checkout_process()) event' ); - } - -} diff --git a/tests/unit-tests/ListingClick.php b/tests/unit-tests/ListingClick.php deleted file mode 100644 index b0b43764..00000000 --- a/tests/unit-tests/ListingClick.php +++ /dev/null @@ -1,45 +0,0 @@ -get_product(); - - ( new WC_Google_Gtag_JS() )->listing_click( $product ); - - // Code is generated for two events in listing_click() so we would expect the filter is called twice. - $this->assertEquals( 2, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for select_content and add_to_cart (listing_click()) events' ); - - // The expected data structure for this event. - $expected_data = array( - 'items' => array( - array( - 'id' => WC_Google_Gtag_JS::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => WC_Google_Gtag_JS::product_get_category_line( $product ), - 'quantity' => 1, - ), - ), - ); - - // Confirm data structure matches what's expected. - $this->assertEquals( $expected_data, $this->get_event_data(), 'Event data does not match expected data structure for select_content and add_to_cart (listing_click()) events' ); - } - -} diff --git a/tests/unit-tests/ListingImpression.php b/tests/unit-tests/ListingImpression.php deleted file mode 100644 index 1213be3f..00000000 --- a/tests/unit-tests/ListingImpression.php +++ /dev/null @@ -1,45 +0,0 @@ -get_product(); - - ( new WC_Google_Gtag_JS() )->listing_impression( $product ); - - // Confirm woocommerce_gtag_event_data is called by listing_impression(). - $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for view_item_list (listing_impression()) event' ); - - // The expected data structure for this event. - $expected_data = array( - 'items' => array( - array( - 'id' => WC_Google_Gtag_JS::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => WC_Google_Gtag_JS::product_get_category_line( $product ), - 'list' => WC_Google_Gtag_JS::get_list_name(), - ), - ), - ); - - // Confirm data structure matches what's expected. - $this->assertEquals( $expected_data, $this->get_event_data(), 'Event data does not match expected data structure for view_item_list (listing_impression()) event' ); - } - -} diff --git a/tests/unit-tests/ProductDetail.php b/tests/unit-tests/ProductDetail.php deleted file mode 100644 index e6b34e2d..00000000 --- a/tests/unit-tests/ProductDetail.php +++ /dev/null @@ -1,50 +0,0 @@ -get_product(); - - $mock = $this->getMockBuilder( WC_Google_Gtag_JS::class ) - ->setMethods( array( '__construct' ) ) - ->setConstructorArgs( array( array( 'ga_enhanced_product_detail_view_enabled' => 'yes' ) ) ) - ->getMock(); - - $mock->product_detail(); - - // Confirm woocommerce_gtag_event_data is called by product_detail(). - $this->assertEquals( 1, $this->get_event_data_filter_call_count(), 'woocommerce_gtag_event_data filter was not called for view_item (product_detail()) event' ); - - // The expected data structure for this event. - $expected_data = array( - 'items' => array( - array( - 'id' => WC_Google_Gtag_JS::get_product_identifier( $product ), - 'name' => $product->get_title(), - 'category' => WC_Google_Gtag_JS::product_get_category_line( $product ), - 'price' => $product->get_price(), - ), - ), - ); - - // Confirm data structure matches what's expected. - $this->assertEquals( $expected_data, $this->get_event_data(), 'Event data does not match expected data structure for view_item (product_detail()) event' ); - } -} diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 83ddfe8b..c88ad459 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -15,12 +15,7 @@ class WCGoogleGtagJS extends EventsDataTest { /** - * Check that `WC_Google_Gtag_JS` registers - * the `assets/js/ga-integration.js` script - * as `woocommerce-google-analytics-integration-ga-integration` (`->script_handle . '-ga-integration'`) - * and enqueues the `assets/js/build/main.js` script - * as `woocommerce-google-analytics-integration--main` (`->script_handle . '--main'`) - * on `wp_enqueue_scripts` action. + * Check that `WC_Google_Gtag_JS` registers and enqueues the `assets/js/build/main.js` script * * @return void */ @@ -33,89 +28,11 @@ public function test_scripts_are_registered() { // Assert the handle property. $this->assertEquals( 'woocommerce-google-analytics-integration', $gtag->script_handle, '`WC_Google_Gtag_JS->script_handle` is not equal `woocommerce-google-analytics-integration`' ); - // Assert assert `-ga-intregration` is registered with the correct name, but not yet enqueued. - $integration_handle = $gtag->script_handle . '-ga-integration'; - $this->assertEquals( true, wp_script_is( $integration_handle, 'registered' ), '`…-ga-integration` script was not registered' ); - $this->assertEquals( false, wp_script_is( $integration_handle, 'enqueued' ), 'the script is enqueued too early' ); - $registered_url = wp_scripts()->registered[ $integration_handle ]->src; - $this->assertStringContainsString( 'assets/js/build/ga-integration.js', $registered_url, 'The script does not point to the correct URL' ); - - // Assert assert `-main` is enqueued with the correct name. - $actions_handle = $gtag->script_handle . '-main'; - $this->assertEquals( true, wp_script_is( $actions_handle, 'enqueued' ), '`…-main` script was not enqueued' ); - $registered_url = wp_scripts()->registered[ $actions_handle ]->src; + $this->assertEquals( true, wp_script_is( $gtag->script_handle, 'enqueued' ), '`…-main` script was not enqueued' ); + $registered_url = wp_scripts()->registered[ $gtag->script_handle ]->src; $this->assertStringContainsString( 'assets/js/build/main.js', $registered_url, 'The script does not point to the correct URL' ); } - /** - * Check that `WC_Google_Gtag_JS` does not enqueue - * the `…-ga-integration` script - * on `woocommerce_before_single_product` action, for a simple product. - * - * @return void - */ - public function test_integration_script_is_not_enqueued_for_simple() { - global $product; - $product = WC_Helper_Product::create_simple_product(); - - $gtag = new WC_Google_Gtag_JS(); - - // Mimic WC action. - do_action( 'wp_enqueue_scripts' ); - ob_start(); // Silence output. - do_action( 'woocommerce_before_single_product' ); - ob_get_clean(); - - // Assert the handle is not enqueued. - $this->assertEquals( false, wp_script_is( $gtag->script_handle . '-ga-integration', 'enqueued' ), 'the script is enqueued' ); - } - - /** - * Check that `WC_Google_Gtag_JS` does not enqueue - * the `…-ga-integration` script - * on `woocommerce_before_single_product` action, for a bool as a `global $product`. - * - * @return void - */ - public function test_integration_script_is_not_enqueued_for_bool() { - global $product; - $product = false; - - $gtag = new WC_Google_Gtag_JS(); - - // Mimic WC action. - do_action( 'wp_enqueue_scripts' ); - ob_start(); // Silence output. - do_action( 'woocommerce_before_single_product' ); - ob_get_clean(); - - // Assert the handle is not enqueued. - $this->assertEquals( false, wp_script_is( $gtag->script_handle . '-ga-integration', 'enqueued' ), 'the script is enqueued' ); - } - - /** - * Check that `WC_Google_Gtag_JS` enqueue - * the `…-ga-integration` script - * on `woocommerce_before_single_product` action, for a variable product. - * - * @return void - */ - public function test_integration_script_is_enqueued_for_variation() { - global $product; - $product = WC_Helper_Product::create_variation_product(); - - $gtag = new WC_Google_Gtag_JS(); - - // Mimic WC action. - do_action( 'wp_enqueue_scripts' ); - ob_start(); // Silence output. - do_action( 'woocommerce_before_single_product' ); - ob_get_clean(); - - // Assert the handle is enqueued. - $this->assertEquals( true, wp_script_is( $gtag->script_handle . '-ga-integration', 'enqueued' ), 'the script is enqueued' ); - } - /** * Test the get_product_identifier method to verify: * From e4dd675f6d09aec06ad3fef05c66ec6ee95e06a6 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 18 Jan 2024 18:01:38 +0000 Subject: [PATCH 075/126] Update composer lock file --- composer.lock | 512 +++++++++++++++++++++++++------------------------- 1 file changed, 259 insertions(+), 253 deletions(-) diff --git a/composer.lock b/composer.lock index f168238b..ea67d554 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0d84c93324af4fd84a5436cfe068bd4c", + "content-hash": "336ac555fb93138aa749dc593e197f4d", "packages": [], "packages-dev": [ { @@ -76,38 +76,34 @@ "stylecheck", "tests" ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, "time": "2022-02-04T12:51:07+00:00" }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -132,10 +128,6 @@ "constructor", "instantiate" ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -150,7 +142,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "exussum12/coverage-checker", @@ -196,24 +188,20 @@ } ], "description": "Allows checking the code coverage of a single pull request", - "support": { - "issues": "https://github.com/exussum12/coverageChecker/issues", - "source": "https://github.com/exussum12/coverageChecker/tree/1.0.2" - }, "time": "2022-01-19T14:42:51+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { @@ -249,30 +237,26 @@ "object", "object graph" ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" - }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.14.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -311,11 +295,7 @@ "parser", "php" ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" - }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2022-05-31T20:59:12+00:00" }, { "name": "phar-io/manifest", @@ -371,10 +351,6 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, "time": "2021-07-20T11:28:43+00:00" }, { @@ -422,31 +398,163 @@ } ], "description": "Library for handling version information and constraints", + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2023-09-20T22:06:18+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "time": "2023-07-16T21:39:41+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -461,8 +569,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { @@ -493,18 +601,13 @@ "testing", "xunit" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2022-12-28T12:41:10+00:00" }, { "name": "phpunit/php-file-iterator", @@ -554,10 +657,6 @@ "filesystem", "iterator" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -617,10 +716,6 @@ "keywords": [ "process" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -676,10 +771,6 @@ "keywords": [ "template" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -735,10 +826,6 @@ "keywords": [ "timer" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -749,20 +836,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.15", + "version": "9.5.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -773,7 +860,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -791,8 +878,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "*", + "ext-xdebug": "*" }, "bin": [ "phpunit" @@ -800,7 +887,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-master": "9.5-dev" } }, "autoload": { @@ -829,11 +916,6 @@ "testing", "xunit" ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" - }, "funding": [ { "url": "https://phpunit.de/sponsors.html", @@ -848,7 +930,7 @@ "type": "tidelift" } ], - "time": "2023-12-01T16:55:19+00:00" + "time": "2022-12-09T07:31:23+00:00" }, { "name": "sebastian/cli-parser", @@ -894,10 +976,6 @@ ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -950,10 +1028,6 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1005,10 +1079,6 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1079,10 +1149,6 @@ "compare", "equality" ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1093,20 +1159,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.7", "php": ">=7.3" }, "require-dev": { @@ -1136,30 +1202,26 @@ ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2020-10-26T15:52:27+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { @@ -1202,30 +1264,26 @@ "unidiff", "unified diff" ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -1265,17 +1323,13 @@ "environment", "hhvm" ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", @@ -1342,10 +1396,6 @@ "export", "exporter" ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1356,16 +1406,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -1406,34 +1456,30 @@ "keywords": [ "global state" ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.6", "php": ">=7.3" }, "require-dev": { @@ -1463,17 +1509,13 @@ ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", @@ -1520,10 +1562,6 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1575,10 +1613,6 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1589,16 +1623,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { @@ -1637,18 +1671,14 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2020-10-26T13:17:30+00:00" }, { "name": "sebastian/resource-operations", @@ -1693,10 +1723,6 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1707,16 +1733,16 @@ }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -1749,17 +1775,13 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -1802,10 +1824,6 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1816,16 +1834,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.8.1", + "version": "3.7.2", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", - "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -1835,11 +1853,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "bin": [ - "bin/phpcbf", - "bin/phpcs" + "bin/phpcs", + "bin/phpcbf" ], "type": "library", "extra": { @@ -1854,58 +1872,35 @@ "authors": [ { "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "role": "lead" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - } - ], - "time": "2024-01-11T20:47:48+00:00" + "time": "2023-02-22T23:07:41+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -1932,44 +1927,48 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" - }, "funding": [ { "url": "https://github.com/theseer", "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { "name": "wp-coding-standards/wpcs", - "version": "2.3.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "7da1894633f168fe244afc6de00d141f27517b62" + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62", - "reference": "7da1894633f168fe244afc6de00d141f27517b62", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", "shasum": "" }, "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.3.1" + "phpcsstandards/phpcsextra": "^1.1.0", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.2" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.0", - "phpcsstandards/phpcsdevtools": "^1.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", @@ -1986,6 +1985,7 @@ "keywords": [ "phpcs", "standards", + "static analysis", "wordpress" ], "support": { @@ -1993,7 +1993,13 @@ "source": "https://github.com/WordPress/WordPress-Coding-Standards", "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" }, - "time": "2020-05-13T23:57:56+00:00" + "funding": [ + { + "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "type": "custom" + } + ], + "time": "2023-09-14T07:06:09+00:00" }, { "name": "yoast/phpunit-polyfills", @@ -2063,5 +2069,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.1.0" -} + "plugin-api-version": "2.2.0" +} \ No newline at end of file From 6301d5e0a33d3674e967ff36a03a1fa5c8384d43 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 18 Jan 2024 18:10:09 +0000 Subject: [PATCH 076/126] Update composer.json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d0e791b0..5e2a268d 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^v0.7", - "wp-coding-standards/wpcs": "^2.3", + "wp-coding-standards/wpcs": "^3.0", "exussum12/coverage-checker": "^1.0", "phpunit/phpunit": "^9.5", "yoast/phpunit-polyfills": "^1.1.0" @@ -24,4 +24,4 @@ "autoload": { "psr-4": { "GoogleAnalyticsIntegration\\Tests\\": "tests/" } } -} +} \ No newline at end of file From 17abd4093a7a21c9c0adda29efce811a589f816f Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 19 Jan 2024 17:48:07 +0000 Subject: [PATCH 077/126] Remove calls to deleted functions --- includes/class-wc-google-analytics.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index eb0b8e99..fb2e656f 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -407,10 +407,6 @@ protected function enqueue_ecommerce_tracking_code( $order_id ) { return; } - $this->get_tracking_instance()->add_transaction( $order ); - $this->get_tracking_instance()->load_opt_out(); - $this->get_tracking_instance()->load_analytics(); - // Mark the order as tracked. $order->update_meta_data( '_ga_tracked', 1 ); $order->save(); From d9c754b5488416837688c555b7354c4e31a6558b Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 23 Jan 2024 13:09:51 +0000 Subject: [PATCH 078/126] Update cart formatting --- includes/class-wc-abstract-google-analytics-js.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index d19998f3..41653d53 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -40,7 +40,7 @@ public function attach_event_data(): void { add_action( 'woocommerce_before_checkout_form', function() { - $this->set_script_data( 'cart', $this->get_formatted_cart() ); + $this->set_script_data( 'cart', $this->get_formatted_cart(), null, true ); } ); @@ -132,7 +132,7 @@ function( $item ) { ), ); }, - WC()->cart->get_cart() + array_values( WC()->cart->get_cart() ) ), 'coupons' => WC()->cart->get_coupons(), 'totals' => array( From 8616b726bd9d9b39232a256117e29a3903216f00 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 23 Jan 2024 17:36:04 +0000 Subject: [PATCH 079/126] Add remove_from_cart tracking --- assets/js/src/integrations/blocks.js | 7 +++++- assets/js/src/integrations/classic.js | 24 +++++++++++++++++++ assets/js/src/utils/index.js | 7 +++--- .../class-wc-abstract-google-analytics-js.php | 16 +++++++------ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 2681f618..9e36eed0 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -21,6 +21,12 @@ addUniqueAction( tracker.event( 'add_to_cart' ).handler ); +addUniqueAction( + `${ ACTION_PREFIX }-cart-remove-item`, + NAMESPACE, + tracker.event( 'remove_from_cart' ).handler +); + /** * Temporarily remove all actions for demo purposes. */ @@ -31,7 +37,6 @@ removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-cart-set-item-quantity`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-cart-remove-item`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-product-view-link`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-product-search`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-store-notice-create`, NAMESPACE ); diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 07ce88d5..926f7f4e 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -37,4 +37,28 @@ export const trackClassicIntegration = () => { ), } ); }; + + /** + * Attach click event listeners to all remove from cart links on page load and when the cart is updated. + */ + const removeFromCartListener = () => { + document.querySelector( '.woocommerce-cart-form' )?.addEventListener( 'click', e => { + const item = e.target.closest( '.woocommerce-cart-form__cart-item .remove' ); + + if ( ! item || ! item.dataset.product_id ) { + return; + } + + tracker.event( 'remove_from_cart' ).handler( { + product: getProductFromID( + parseInt( item.dataset.product_id ), + true + ), + } ); + } ); + }; + + removeFromCartListener(); + + document.body.onupdated_wc_div = () => removeFromCartListener(); }; diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index f13fbcd0..ead6ea61 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -1,5 +1,5 @@ import { addAction, removeAction } from '@wordpress/hooks'; -import { products } from '../config.js'; +import { products, cart } from '../config.js'; /** * Formats data into the productFieldObject shape. @@ -140,7 +140,8 @@ const formatCategoryKey = ( index ) => { * @param {number} id The ID of the product to search for * @return {Object|undefined} The product object or undefined if not found */ -export const getProductFromID = ( id ) => { +export const getProductFromID = ( id, fromCart = false ) => { + const list = fromCart ? cart.items : products; /* eslint-disable-next-line camelcase */ - return products.find( ( { product_id } ) => product_id === id ); + return list.find( ( { product_id } ) => product_id === id ); }; diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 41653d53..c0396c21 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -37,6 +37,13 @@ public function __construct() { * @return void */ public function attach_event_data(): void { + add_action( + 'woocommerce_before_cart', + function() { + $this->set_script_data( 'cart', $this->get_formatted_cart(), null, true ); + } + ); + add_action( 'woocommerce_before_checkout_form', function() { @@ -123,13 +130,8 @@ public function get_formatted_cart(): array { 'items' => array_map( function( $item ) { return array( - 'id' => $this->get_product_identifier( $item['data'] ), - 'name' => $item['data']->get_name(), - 'quantity' => $item['quantity'], - 'prices' => array( - 'price' => $item['data']->get_price(), - 'currency_minor_unit' => wc_get_price_decimals(), - ), + ...$this->get_formatted_product( $item['data'] ), + 'quantity' => $item['quantity'] ); }, array_values( WC()->cart->get_cart() ) From 67bfd6bed6046f9dba614f4f82464a8886f85bd0 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 24 Jan 2024 14:53:46 +0000 Subject: [PATCH 080/126] Add begin_checkout tracking --- assets/js/src/integrations/blocks.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 9e36eed0..f00fd15e 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -27,11 +27,15 @@ addUniqueAction( tracker.event( 'remove_from_cart' ).handler ); +addUniqueAction( + `${ ACTION_PREFIX }-checkout-render-checkout-form`, + NAMESPACE, + tracker.event( 'begin_checkout' ).handler +); + /** * Temporarily remove all actions for demo purposes. */ - -removeAction( `${ ACTION_PREFIX }-checkout-render-checkout-form`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); From 3c4f670890f1951fa92c8854baa9e6e5261f21d4 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 24 Jan 2024 14:54:23 +0000 Subject: [PATCH 081/126] Add select_content tracking --- assets/js/src/integrations/blocks.js | 7 +++- assets/js/src/integrations/classic.js | 52 +++++++++++++++++++++------ assets/js/src/utils/index.js | 1 + includes/class-wc-google-gtag-js.php | 1 - 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index f00fd15e..ff71cf56 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -33,6 +33,12 @@ addUniqueAction( tracker.event( 'begin_checkout' ).handler ); +addUniqueAction( + `${ ACTION_PREFIX }-product-view-link`, + NAMESPACE, + tracker.event( 'select_content' ).handler +); + /** * Temporarily remove all actions for demo purposes. */ @@ -41,6 +47,5 @@ removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-cart-set-item-quantity`, NAMESPACE ); -removeAction( `${ ACTION_PREFIX }-product-view-link`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-product-search`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-store-notice-create`, NAMESPACE ); diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 926f7f4e..c63d0d94 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -42,23 +42,53 @@ export const trackClassicIntegration = () => { * Attach click event listeners to all remove from cart links on page load and when the cart is updated. */ const removeFromCartListener = () => { - document.querySelector( '.woocommerce-cart-form' )?.addEventListener( 'click', e => { - const item = e.target.closest( '.woocommerce-cart-form__cart-item .remove' ); + document + .querySelector( '.woocommerce-cart-form' ) + ?.addEventListener( 'click', ( e ) => { + const item = e.target.closest( + '.woocommerce-cart-form__cart-item .remove' + ); - if ( ! item || ! item.dataset.product_id ) { - return; - } + if ( ! item || ! item.dataset.product_id ) { + return; + } - tracker.event( 'remove_from_cart' ).handler( { - product: getProductFromID( - parseInt( item.dataset.product_id ), - true - ), + tracker.event( 'remove_from_cart' ).handler( { + product: getProductFromID( + parseInt( item.dataset.product_id ), + true + ), + } ); } ); - } ); }; removeFromCartListener(); document.body.onupdated_wc_div = () => removeFromCartListener(); + + /** + * Attach click event listeners to all product listings and send select_content events for specific targets. + */ + document + .querySelectorAll( '.product a[data-product_id]' ) + ?.forEach( ( button ) => { + const productId = button.dataset.product_id; + + button.parentNode.addEventListener( 'click', ( listing ) => { + const targetLink = listing.target.closest( + '.woocommerce-loop-product__link' + ); + const isAddToCartButton = + button.classList.contains( 'add_to_cart_button' ) && + ! button.classList.contains( 'product_type_variable' ); + + if ( ! targetLink && isAddToCartButton ) { + return; + } + + tracker.event( 'select_content' ).handler( { + product: getProductFromID( parseInt( productId ) ), + } ); + } ); + } ); }; diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index ead6ea61..2217a947 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -138,6 +138,7 @@ const formatCategoryKey = ( index ) => { * Searches through the global wcgaiData.products object to find a single product by its ID * * @param {number} id The ID of the product to search for + * @param {boolean} fromCart If true then product will be retreived from wcgaiData.cart.items * @return {Object|undefined} The product object or undefined if not found */ export const getProductFromID = ( id, fromCart = false ) => { diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 91ece4a1..92ca648a 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -27,7 +27,6 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { 'add_to_cart' => 'woocommerce_add_to_cart', 'remove_from_cart' => 'woocommerce_cart_item_removed', 'view_item' => 'woocommerce_after_single_product', - 'select_content' => 'woocommerce_after_single_product', ); /** From af0b66543dee9ebd5ac33a11976793e58aa74c92 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 24 Jan 2024 19:03:33 +0000 Subject: [PATCH 082/126] Add purchase tracking --- assets/js/src/config.js | 1 + assets/js/src/integrations/classic.js | 3 +- assets/js/src/tracker/data-formatting.js | 14 ++++++ .../class-wc-abstract-google-analytics-js.php | 43 ++++++++++++++++++- includes/class-wc-google-gtag-js.php | 2 +- 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index caf36612..c036d8c2 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -4,3 +4,4 @@ export const events = wcgaiData.events ?? {}; export const cart = wcgaiData.cart ?? {}; export const products = wcgaiData.products ?? {}; export const product = wcgaiData.product ?? {}; +export const order = wcgaiData.order ?? {}; diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index c63d0d94..69900794 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -1,6 +1,6 @@ import { tracker } from '../tracker'; import { getProductFromID } from '../utils'; -import { events, cart, products, product } from '../config.js'; +import { events, cart, products, product, order } from '../config.js'; /** * The Google Analytics integration for classic WooCommerce pages @@ -16,6 +16,7 @@ export const trackClassicIntegration = () => { storeCart: cart, products, product, + order, }; Object.values( events ?? {} ).forEach( ( eventName ) => { diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js index fad5d973..eff2af5c 100644 --- a/assets/js/src/tracker/data-formatting.js +++ b/assets/js/src/tracker/data-formatting.js @@ -166,6 +166,20 @@ export const view_item = ( { }; }; +/** + * Formats order data for the purchase event + * + * @param {Object} params The function params + * @param {Object} params.storeCart The cart object + */ +export const purchase = ( { order } ) => { + return { + currency: order.currency, + value: parseInt( order.value ), + items: order.items.map( getProductFieldObject ), + }; +}; + /* eslint-enable camelcase */ /** diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index c0396c21..776299fd 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -66,6 +66,13 @@ function() { $this->set_script_data( 'products', $this->get_formatted_product( $product ) ); } ); + + add_action( + 'woocommerce_thankyou', + function( $order_id ) { + $this->set_script_data( 'order', $this->get_formatted_order( $order_id ), null, true ); + } + ); } /** @@ -162,12 +169,46 @@ public function get_formatted_product( WC_Product $product ): array { wc_get_product_terms( $product->get_id(), 'product_cat', array( 'number' => 5 ) ) ), 'prices' => array( - 'price' => $product->get_price(), + 'price' => $this->get_formatted_price( $product->get_price() ), 'currency_minor_unit' => wc_get_price_decimals(), ), ); } + /** + * Returns an array of order data in the required format + * + * @param string $order_id The ID of the order + * + * @return array + */ + public function get_formatted_order( int $order_id ): array { + $order = wc_get_order( $order_id ); + + return array( + 'currency' => $order->get_currency(), + 'value' => $order->get_total(), + 'items' => array_map( + function( $item ) { + return array( + ...$this->get_formatted_product( $item->get_product() ), + 'quantity' => $item->get_quantity(), + ); + }, + array_values( $order->get_items() ), + ), + ); + } + + public function get_formatted_price( $value ) { + return intval( + round( + ( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( wc_get_price_decimals() ) ), + 0 + ) + ); + } + /** * Returns the tracker variable this integration should use * diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 92ca648a..4922900c 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -22,7 +22,7 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var array $mappings A map of the GA4 events and the classic WooCommerce hooks that trigger them */ private $mappings = array( 'begin_checkout' => 'woocommerce_before_checkout_form', - 'add_shipping_info' => 'woocommerce_thankyou', + 'purchase' => 'woocommerce_thankyou', 'view_item_list' => 'woocommerce_before_shop_loop_item', 'add_to_cart' => 'woocommerce_add_to_cart', 'remove_from_cart' => 'woocommerce_cart_item_removed', From 3a249945d72bb61c3b88276967c6458ddb6be013 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 24 Jan 2024 19:06:10 +0000 Subject: [PATCH 083/126] Fix param description --- assets/js/src/tracker/data-formatting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js index eff2af5c..93dc0c6c 100644 --- a/assets/js/src/tracker/data-formatting.js +++ b/assets/js/src/tracker/data-formatting.js @@ -170,7 +170,7 @@ export const view_item = ( { * Formats order data for the purchase event * * @param {Object} params The function params - * @param {Object} params.storeCart The cart object + * @param {Object} params.order The order object */ export const purchase = ( { order } ) => { return { From e12c4428efb7b3ab3904700a7fc76c7aa92e9583 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 10:20:55 +0000 Subject: [PATCH 084/126] Fix price formatting --- includes/class-wc-abstract-google-analytics-js.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 776299fd..219002b1 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -146,7 +146,7 @@ function( $item ) { 'coupons' => WC()->cart->get_coupons(), 'totals' => array( 'currency_code' => get_woocommerce_currency(), - 'total_price' => WC()->cart->get_total( 'edit' ), + 'total_price' => $this->get_formatted_price( WC()->cart->get_total( 'edit' ) ), 'currency_minor_unit' => wc_get_price_decimals(), ), ); @@ -187,7 +187,7 @@ public function get_formatted_order( int $order_id ): array { return array( 'currency' => $order->get_currency(), - 'value' => $order->get_total(), + 'value' => $this->get_formatted_product( $order->get_total() ), 'items' => array_map( function( $item ) { return array( @@ -200,7 +200,12 @@ function( $item ) { ); } - public function get_formatted_price( $value ) { + /** + * Formats a price the same way WooCommerce Blocks does + * + * @return int + */ + public function get_formatted_price( $value ): int { return intval( round( ( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( wc_get_price_decimals() ) ), From f99c6201ffa21ca613d8a120066b8f8c64678709 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 14:23:55 +0000 Subject: [PATCH 085/126] Add check for enabled events --- assets/js/src/tracker/index.js | 12 +++++++----- includes/class-wc-google-gtag-js.php | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 4154c2bc..cc7d31f9 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -66,11 +66,13 @@ class Tracker { return { handler: ( data ) => { - window[ config.tracker_var ]( - 'event', - name, - formatters[ name ]( data ) - ); + if ( config.events.includes( name ) ) { + window[ config.tracker_var ]( + 'event', + name, + formatters[ name ]( data ) + ); + } }, }; } diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 4922900c..23c78b7b 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -173,9 +173,36 @@ public function load_analytics_config(): void { 'custom_map' => array( 'dimension1' => 'logged_in', ), + 'events' => self::get_enabled_events(), ); } + /** + * Get an array containing the names of all enabled events + * + * @return array + */ + public static function get_enabled_events(): array { + $events = array(); + $settings = array( + 'purchase' => 'ga_ecommerce_tracking_enabled', + 'add_to_cart' => 'ga_event_tracking_enabled', + 'remove_from_cart' => 'ga_enhanced_remove_from_cart_enabled', + 'view_item_list' => 'ga_enhanced_product_impression_enabled', + 'select_content' => 'ga_enhanced_product_click_enabled', + 'view_item' => 'ga_enhanced_product_detail_view_enabled', + 'begin_checkout' => 'ga_enhanced_checkout_process_enabled', + ); + + foreach( $settings as $event => $option_name ) { + if ( 'yes' === self::get( $option_name ) ) { + $events[] = $event; + } + } + + return $events; + } + /** * Get the class instance * From 5ef1fb6c3ae61729bf731889652ef302c80d8862 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 14:31:53 +0000 Subject: [PATCH 086/126] Use correct product identifier --- assets/js/src/utils/index.js | 22 ++++++--- .../class-wc-abstract-google-analytics-js.php | 48 ++++++++++++++++++- includes/class-wc-google-gtag-js.php | 1 + 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 2217a947..61b156ef 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -1,5 +1,5 @@ import { addAction, removeAction } from '@wordpress/hooks'; -import { products, cart } from '../config.js'; +import { config, products, cart } from '../config.js'; /** * Formats data into the productFieldObject shape. @@ -70,14 +70,24 @@ export const addUniqueAction = ( hookName, namespace, callback ) => { }; /** - * Returns the product ID by checking if the product has a SKU, if not, it returns '#' concatenated with the product ID. + * Returns the product ID by checking if the product data includes the formatted + * identifier. If the identifier is not present then it will return either the product + * SKU, the product ID prefixed with #, or the product ID depending on the site settings * * @param {Object} product - The product object * * @return {string} - The product ID */ export const getProductId = ( product ) => { - return product.sku ? product.sku : '#' + product.id; + const identifier = product.extensions?.woocommerce_google_analytics_integration?.identifier; + + if ( identifier !== undefined ) { + return identifier; + } else if ( config.identifier === 'product_sku' ) { + return product.sku ? product.sku : '#' + product.id; + } else { + return product.id; + } }; /** @@ -137,12 +147,12 @@ const formatCategoryKey = ( index ) => { /** * Searches through the global wcgaiData.products object to find a single product by its ID * - * @param {number} id The ID of the product to search for + * @param {number} search The ID of the product to search for * @param {boolean} fromCart If true then product will be retreived from wcgaiData.cart.items * @return {Object|undefined} The product object or undefined if not found */ -export const getProductFromID = ( id, fromCart = false ) => { +export const getProductFromID = ( search, fromCart = false ) => { const list = fromCart ? cart.items : products; /* eslint-disable-next-line camelcase */ - return list.find( ( { product_id } ) => product_id === id ); + return list.find( ( { id } ) => id === search ); }; diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 219002b1..188798f5 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -4,6 +4,8 @@ exit; } +use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema; + /** * WC_Abstract_Google_Analytics_JS class * @@ -28,6 +30,18 @@ abstract class WC_Abstract_Google_Analytics_JS { */ public function __construct() { $this->attach_event_data(); + + if ( did_action( 'woocommerce_blocks_loaded' ) ) { + woocommerce_store_api_register_endpoint_data( + array( + 'endpoint' => ProductSchema::IDENTIFIER, + 'namespace' => 'woocommerce_google_analytics_integration', + 'data_callback' => array( $this, 'data_callback' ), + 'schema_callback' => array( $this, 'schema_callback' ), + 'schema_type' => ARRAY_A, + ) + ); + } } /** @@ -161,8 +175,7 @@ function( $item ) { */ public function get_formatted_product( WC_Product $product ): array { return array( - 'product_id' => $product->get_id(), - 'id' => $this->get_product_identifier( $product ), + 'id' => $product->get_id(), 'name' => $product->get_name(), 'categories' => array_map( fn( $category ) => array( 'name' => $category->name ), @@ -172,6 +185,11 @@ public function get_formatted_product( WC_Product $product ): array { 'price' => $this->get_formatted_price( $product->get_price() ), 'currency_minor_unit' => wc_get_price_decimals(), ), + 'extensions' => array( + 'woocommerce_google_analytics_integration' => array( + 'identifier' => $this->get_product_identifier( $product ), + ), + ), ); } @@ -214,6 +232,32 @@ public function get_formatted_price( $value ): int { ); } + /** + * Add product identifier to StoreAPI + * + * @return array + */ + public function data_callback( WC_Product $product ): array { + return array( + 'identifier' => (string) $this->get_product_identifier( $product ), + ); + } + + /** + * Schema for the extended StoreAPI data + * + * @return array + */ + public function schema_callback(): array { + return array( + 'identifier' => array( + 'description' => __( 'The formatted product identifier to use in Google Analytics events.', 'woocommerce-google-analytics-integration' ), + 'type' => 'string', + 'readonly' => true, + ) + ); + } + /** * Returns the tracker variable this integration should use * diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 23c78b7b..7296cb1b 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -174,6 +174,7 @@ public function load_analytics_config(): void { 'dimension1' => 'logged_in', ), 'events' => self::get_enabled_events(), + 'identifier' => self::get( 'ga_product_identifier' ), ); } From 2646ad928917cd264f841aac5cd8273459c1bdde Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 15:16:04 +0000 Subject: [PATCH 087/126] Add product identifier to cart item --- .../class-wc-abstract-google-analytics-js.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 188798f5..dd67f7fa 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -5,6 +5,7 @@ } use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema; +use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema; /** * WC_Abstract_Google_Analytics_JS class @@ -41,6 +42,16 @@ public function __construct() { 'schema_type' => ARRAY_A, ) ); + + woocommerce_store_api_register_endpoint_data( + array( + 'endpoint' => CartItemSchema::IDENTIFIER, + 'namespace' => 'woocommerce_google_analytics_integration', + 'data_callback' => array( $this, 'data_callback' ), + 'schema_callback' => array( $this, 'schema_callback' ), + 'schema_type' => ARRAY_A, + ) + ); } } @@ -235,9 +246,13 @@ public function get_formatted_price( $value ): int { /** * Add product identifier to StoreAPI * + * @param WC_Product|array $product Either an instance of WC_Product or a cart item array depending on the endpoint + * * @return array */ - public function data_callback( WC_Product $product ): array { + public function data_callback( $product ): array { + $product = is_a( $product, 'WC_Product' ) ? $product : $product['data']; + return array( 'identifier' => (string) $this->get_product_identifier( $product ), ); From 48ee481d84bf0f9c3a07196ae5be577ed50635f3 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 17:24:24 +0000 Subject: [PATCH 088/126] Add unit tests --- tests/unit-tests/WCGoogleGtagJS.php | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index c88ad459..30919015 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -71,4 +71,118 @@ function( $product ) { $this->assertEquals( 'filtered', $mock_id::get_product_identifier( $this->get_product() ) ); } + /** + * Test that events are correctly mapped to WooCommerce hooks and + * are added to the script data array when the action happens. + * + * @return void + */ + public function test_map_actions(): void { + $gtag = new WC_Google_Gtag_JS; + $mappings = array( + 'begin_checkout' => 'woocommerce_before_checkout_form', + 'purchase' => 'woocommerce_thankyou', + 'view_item_list' => 'woocommerce_before_shop_loop_item', + 'add_to_cart' => 'woocommerce_add_to_cart', + 'remove_from_cart' => 'woocommerce_cart_item_removed', + 'view_item' => 'woocommerce_after_single_product', + ); + + array_map( 'remove_all_actions', $mappings ); + + $gtag->map_actions(); + + foreach( $mappings as $event => $hook ) { + do_action( $hook ); + + $script_data = json_decode( $gtag->get_script_data(), true ); + + $this->assertEquals( + $script_data['events'], + array( + $event => $event + ) + ); + + // Reset event data + $gtag->set_script_data( 'events', array(), null, true ); + } + } + + /** + * Test that script data is correctly set + * + * @return void + */ + public function test_script_data(): void { + $gtag = new WC_Google_Gtag_JS; + $default = json_decode( $gtag->get_script_data(), true ); + + $gtag->set_script_data( 'test', 'value' ); + $script_data = json_decode( $gtag->get_script_data(), true ); + + $this->assertEquals( $script_data, array( + ...$default, + 'test' => array( + 'value' + ) + ) ); + + $gtag->set_script_data( 'test', 'value2', 'key' ); + $script_data = json_decode( $gtag->get_script_data(), true ); + + $this->assertEquals( $script_data, array( + ...$default, + 'test' => array( + 0 => 'value', + 'key' => 'value2', + ) + ) ); + + $gtag->set_script_data( 'test', 'value', null, true ); + $script_data = json_decode( $gtag->get_script_data(), true ); + + $this->assertEquals( $script_data, array( + ...$default, + 'test' => 'value' + ) ); + } + + /** + * Test the tracker_var filter `woocommerce_gtag_tracker_variable` + * + * @return void + */ + public function test_tracker_var(): void { + $gtag = new WC_Google_Gtag_JS; + + $this->assertEquals( $gtag->tracker_var(), 'gtag' ); + + add_filter( 'woocommerce_gtag_tracker_variable', function( $var ) { + return 'filtered'; + } ); + $this->assertEquals( $gtag->tracker_var(), 'filtered' ); + } + + /** + * Test only events enabled in settings will be returned for config + * + * @return void + */ + public function test_get_enabled_events(): void { + $settings = array( + 'purchase' => 'ga_ecommerce_tracking_enabled', + 'add_to_cart' => 'ga_event_tracking_enabled', + 'remove_from_cart' => 'ga_enhanced_remove_from_cart_enabled', + 'view_item_list' => 'ga_enhanced_product_impression_enabled', + 'select_content' => 'ga_enhanced_product_click_enabled', + 'view_item' => 'ga_enhanced_product_detail_view_enabled', + 'begin_checkout' => 'ga_enhanced_checkout_process_enabled', + ); + + foreach( $settings as $event => $option_name ) { + $gtag = new WC_Google_Gtag_JS( array( $option_name => 'yes' ) ); + $this->assertEquals( $gtag->get_enabled_events(), array( $event ) ); + } + } } From dc7fb24444b1aa3657711b4e91237d2328e8db70 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 17:30:11 +0000 Subject: [PATCH 089/126] Refactor test_script_data to support previous versions of PHP --- tests/unit-tests/WCGoogleGtagJS.php | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 30919015..01e0fe68 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -121,30 +121,36 @@ public function test_script_data(): void { $gtag->set_script_data( 'test', 'value' ); $script_data = json_decode( $gtag->get_script_data(), true ); - $this->assertEquals( $script_data, array( - ...$default, - 'test' => array( - 'value' + $this->assertEquals( $script_data, array_merge( + $default, + array( + 'test' => array( + 'value' + ), ) ) ); $gtag->set_script_data( 'test', 'value2', 'key' ); $script_data = json_decode( $gtag->get_script_data(), true ); - $this->assertEquals( $script_data, array( - ...$default, - 'test' => array( - 0 => 'value', - 'key' => 'value2', + $this->assertEquals( $script_data, array_merge( + $default, + array( + 'test' => array( + 0 => 'value', + 'key' => 'value2', + ), ) ) ); $gtag->set_script_data( 'test', 'value', null, true ); $script_data = json_decode( $gtag->get_script_data(), true ); - $this->assertEquals( $script_data, array( - ...$default, - 'test' => 'value' + $this->assertEquals( $script_data, array_merge( + $default, + array( + 'test' => 'value', + ) ) ); } From 66142e273047f086bf00a780d72bd0b1cb1e5781 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 25 Jan 2024 17:32:07 +0000 Subject: [PATCH 090/126] Refactor getProductId util --- assets/js/src/utils/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 61b156ef..c9920d25 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -79,15 +79,19 @@ export const addUniqueAction = ( hookName, namespace, callback ) => { * @return {string} - The product ID */ export const getProductId = ( product ) => { - const identifier = product.extensions?.woocommerce_google_analytics_integration?.identifier; + const identifier = + product.extensions?.woocommerce_google_analytics_integration + ?.identifier; if ( identifier !== undefined ) { return identifier; - } else if ( config.identifier === 'product_sku' ) { + } + + if ( config.identifier === 'product_sku' ) { return product.sku ? product.sku : '#' + product.id; - } else { - return product.id; } + + return product.id; }; /** From 9104cb986fc46a2e411d49e6684862025e7687d8 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 26 Jan 2024 12:34:21 +0000 Subject: [PATCH 091/126] Add support for product variations --- assets/js/src/config.js | 1 + assets/js/src/integrations/classic.js | 15 ++++++-- assets/js/src/utils/index.js | 8 ++++- .../class-wc-abstract-google-analytics-js.php | 35 +++++++++++++++++-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index c036d8c2..28b4b2f8 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -4,4 +4,5 @@ export const events = wcgaiData.events ?? {}; export const cart = wcgaiData.cart ?? {}; export const products = wcgaiData.products ?? {}; export const product = wcgaiData.product ?? {}; +export const addedToCart = wcgaiData.added_to_cart ?? {}; export const order = wcgaiData.order ?? {}; diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 69900794..29dfa3bf 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -1,6 +1,13 @@ import { tracker } from '../tracker'; import { getProductFromID } from '../utils'; -import { events, cart, products, product, order } from '../config.js'; +import { + events, + cart, + products, + product, + addedToCart, + order, +} from '../config.js'; /** * The Google Analytics integration for classic WooCommerce pages @@ -20,7 +27,11 @@ export const trackClassicIntegration = () => { }; Object.values( events ?? {} ).forEach( ( eventName ) => { - tracker.event( eventName ).handler( eventData ); + if ( eventName === 'add_to_cart' ) { + tracker.event( eventName ).handler( { product: addedToCart } ); + } else { + tracker.event( eventName ).handler( eventData ); + } } ); /** diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index c9920d25..e2faa2f0 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -11,15 +11,21 @@ import { config, products, cart } from '../config.js'; * @return {Object} The product data */ export const getProductFieldObject = ( product, quantity ) => { + const variantData = {}; + if ( product.variation ) { + variantData.item_variant = product.variation; + } + return { item_id: getProductId( product ), item_name: product.name, - quantity: product.quantity ?? quantity, ...getProductCategories( product ), + quantity: product.quantity ?? quantity, price: formatPrice( product.prices.price, product.prices.currency_minor_unit ), + ...variantData, }; }; diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index dd67f7fa..e6d28de0 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -84,6 +84,15 @@ function() { } ); + add_action( + 'woocommerce_add_to_cart', + function( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { + $this->set_script_data( 'added_to_cart', $this->get_formatted_product( wc_get_product( $product_id ), $variation ), null, true ); + }, + 10, + 5 + ); + add_action( 'woocommerce_shop_loop_item_title', function() { @@ -180,12 +189,13 @@ function( $item ) { /** * Returns an array of product data in the required format * - * @param WC_Product $product The product to format. + * @param WC_Product $product The product to format. + * @param array|bool $variation An array containing product variation attributes to include in the product data. * * @return array */ - public function get_formatted_product( WC_Product $product ): array { - return array( + public function get_formatted_product( WC_Product $product, $variation = false ): array { + $formatted = array( 'id' => $product->get_id(), 'name' => $product->get_name(), 'categories' => array_map( @@ -202,6 +212,25 @@ public function get_formatted_product( WC_Product $product ): array { ), ), ); + + if ( false !== $variation ) { + $formatted['variation'] = implode( + ', ', + array_map( + function( $attribute, $value ) { + return sprintf( + '%s: %s', + $attribute, + $value + ); + }, + array_keys( $variation ), + array_values( $variation ) + ) + ); + } + + return $formatted; } /** From 9ba0a7f247fc653d195dbcb140bbaf5750fec559 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Wed, 31 Jan 2024 14:51:19 +0000 Subject: [PATCH 092/126] Update plugin name to Google Analytics for WooCommerce --- .github/CONTRIBUTING.md | 6 +++--- README.md | 6 +++--- includes/class-wc-google-analytics-task.php | 4 ++-- includes/class-wc-google-analytics.php | 4 ++-- readme.txt | 2 +- woocommerce-google-analytics-integration.php | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e98bd09b..614dc43d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,14 +1,14 @@ # Contributing -Thanks for your interest in contributing to WooCommerce Google Analytics Integration! +Thanks for your interest in contributing to Google Analytics for WooCommerce! ## Guidelines Like the WooCommerce project, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](./CODE_OF_CONDUCT.md). -If you wish to contribute code, please read the information in the sections below. Then [fork](https://help.github.com/articles/fork-a-repo/) WooCommerce Google Analytics Integration, commit your changes, and [submit a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) 🎉 +If you wish to contribute code, please read the information in the sections below. Then [fork](https://help.github.com/articles/fork-a-repo/) Google Analytics for WooCommerce, commit your changes, and [submit a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) 🎉 -WooCommerce Google Analytics Integration is licensed under the GPLv3+, and all contributions to the project will be released under the same license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv3+ license. +Google Analytics for WooCommerce is licensed under the GPLv3+, and all contributions to the project will be released under the same license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv3+ license. ## Reporting Security Issues diff --git a/README.md b/README.md index cdd8adc3..5bc4879c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# WooCommerce Google Analytics Integration +# Google Analytics for WooCommerce WordPress plugin: Provides the integration between WooCommerce and Google Analytics. @@ -10,7 +10,7 @@ Will be required for WooCommerce shops using the integration from WooCommerce 2. ## NPM Scripts -WooCommerce Google Analytics Integration utilizes npm scripts for task management utilities. +Google Analytics for WooCommerce utilizes npm scripts for task management utilities. `npm run build` - Runs the tasks necessary for a release. These may include building JavaScript, SASS, CSS minification, and language files. @@ -34,4 +34,4 @@ Alternatively, run `npm run lint:php:diff` to run coding standards checks agains ## Docs -- [Hooks defined or used in WooCommerce Google Analytics Integration](./docs/Hooks.md) +- [Hooks defined or used in Google Analytics for WooCommerce](./docs/Hooks.md) diff --git a/includes/class-wc-google-analytics-task.php b/includes/class-wc-google-analytics-task.php index a46dccc0..58c8a85e 100644 --- a/includes/class-wc-google-analytics-task.php +++ b/includes/class-wc-google-analytics-task.php @@ -1,8 +1,8 @@ ' . $policy_text . '

'; - wp_add_privacy_policy_content( 'WooCommerce Google Analytics Integration', wpautop( $content, false ) ); + wp_add_privacy_policy_content( 'Google Analytics for WooCommerce', wpautop( $content, false ) ); } /** diff --git a/readme.txt b/readme.txt index 050bedaa..38a38135 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -=== WooCommerce Google Analytics Integration === +=== Google Analytics for WooCommerce === Contributors: woocommerce, automattic, claudiosanches, bor0, royho, laurendavissmith001, c-shultz Tags: woocommerce, google analytics Requires at least: 3.9 diff --git a/woocommerce-google-analytics-integration.php b/woocommerce-google-analytics-integration.php index 337e0b70..329f977c 100644 --- a/woocommerce-google-analytics-integration.php +++ b/woocommerce-google-analytics-integration.php @@ -45,7 +45,7 @@ function () { ); /** - * WooCommerce Google Analytics Integration main class. + * Google Analytics for WooCommerce main class. */ class WC_Google_Analytics_Integration { @@ -162,7 +162,7 @@ public function woocommerce_missing_notice() { * Add a new integration to WooCommerce. * * @param array $integrations WooCommerce integrations. - * @return array Google Analytics integration added. + * @return array Google Analytics for WooCommerce added. */ public function add_integration( $integrations ) { $integrations[] = 'WC_Google_Analytics'; From 2ea428cc77745a7fad8d5c60635ddd867ca053f4 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Thu, 1 Feb 2024 16:49:19 +0000 Subject: [PATCH 093/126] Additional name updates --- package.json | 2 +- woocommerce-google-analytics-integration.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 90b037d6..6ec77201 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-google-analytics-integration", - "title": "WooCommerce Google Analytics Integration", + "title": "Google Analytics for WooCommerce", "version": "1.8.6", "license": "GPL-2.0", "homepage": "https://wordpress.org/plugins/woocommerce-google-analytics-integration/", diff --git a/woocommerce-google-analytics-integration.php b/woocommerce-google-analytics-integration.php index 329f977c..43e7c60d 100644 --- a/woocommerce-google-analytics-integration.php +++ b/woocommerce-google-analytics-integration.php @@ -1,6 +1,6 @@

' . wp_kses_post( $error ) . '

'; From 53959916796ebb55d81982da7a7d1b64d181294e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Thu, 8 Feb 2024 23:03:50 +0100 Subject: [PATCH 094/126] Simplify tracker event handler API, Remove not used "event" object. Addresses https://github.com/woocommerce/woocommerce-google-analytics-integration/pull/328#discussion_r1483531223 --- assets/js/src/integrations/blocks.js | 12 ++++++------ assets/js/src/integrations/classic.js | 10 +++++----- assets/js/src/tracker/index.js | 24 +++++++++++------------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index ff71cf56..8cd5b715 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -6,37 +6,37 @@ import { ACTION_PREFIX, NAMESPACE } from '../constants'; addUniqueAction( `${ ACTION_PREFIX }-product-list-render`, NAMESPACE, - tracker.event( 'view_item_list' ).handler + tracker.eventHandler( 'view_item_list' ) ); addUniqueAction( `${ ACTION_PREFIX }-product-render`, NAMESPACE, - tracker.event( 'view_item' ).handler + tracker.eventHandler( 'view_item' ) ); addUniqueAction( `${ ACTION_PREFIX }-cart-add-item`, NAMESPACE, - tracker.event( 'add_to_cart' ).handler + tracker.eventHandler( 'add_to_cart' ) ); addUniqueAction( `${ ACTION_PREFIX }-cart-remove-item`, NAMESPACE, - tracker.event( 'remove_from_cart' ).handler + tracker.eventHandler( 'remove_from_cart' ) ); addUniqueAction( `${ ACTION_PREFIX }-checkout-render-checkout-form`, NAMESPACE, - tracker.event( 'begin_checkout' ).handler + tracker.eventHandler( 'begin_checkout' ) ); addUniqueAction( `${ ACTION_PREFIX }-product-view-link`, NAMESPACE, - tracker.event( 'select_content' ).handler + tracker.eventHandler( 'select_content' ) ); /** diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 29dfa3bf..04931ff9 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -28,9 +28,9 @@ export const trackClassicIntegration = () => { Object.values( events ?? {} ).forEach( ( eventName ) => { if ( eventName === 'add_to_cart' ) { - tracker.event( eventName ).handler( { product: addedToCart } ); + tracker.eventHandler( eventName )( { product: addedToCart } ); } else { - tracker.event( eventName ).handler( eventData ); + tracker.eventHandler( eventName )( eventData ); } } ); @@ -43,7 +43,7 @@ export const trackClassicIntegration = () => { * @param {HTMLElement[]} button - An array of HTML elements representing the add to cart button. */ document.body.onadded_to_cart = ( e, fragments, cartHash, button ) => { - tracker.event( 'add_to_cart' ).handler( { + tracker.eventHandler( 'add_to_cart' )( { product: getProductFromID( parseInt( button[ 0 ].dataset.product_id ) ), @@ -65,7 +65,7 @@ export const trackClassicIntegration = () => { return; } - tracker.event( 'remove_from_cart' ).handler( { + tracker.eventHandler( 'remove_from_cart' )( { product: getProductFromID( parseInt( item.dataset.product_id ), true @@ -98,7 +98,7 @@ export const trackClassicIntegration = () => { return; } - tracker.event( 'select_content' ).handler( { + tracker.eventHandler( 'select_content' )( { product: getProductFromID( parseInt( productId ) ), } ); } ); diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index cc7d31f9..031ac83b 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -52,28 +52,26 @@ class Tracker { } /** - * Creates and returns an event handler object for a specified event name. + * Creates and returns an event handler for a specified event name. * * @param {string} name The name of the event. - * @return {{handler: function(*): void}} An object with a `handler` method for processing and tracking the event. + * @return {function(*): void} Function for processing and tracking the event. * @throws {Error} If the event name is not supported. */ - event( name ) { + eventHandler( name ) { /* eslint import/namespace: [ 'error', { allowComputed: true } ] */ if ( ! formatters[ name ] ) { throw new Error( `Event ${ name } is not supported.` ); } - return { - handler: ( data ) => { - if ( config.events.includes( name ) ) { - window[ config.tracker_var ]( - 'event', - name, - formatters[ name ]( data ) - ); - } - }, + return function trackerEventHandler( data ) { + if ( config.events.includes( name ) ) { + window[ config.tracker_var ]( + 'event', + name, + formatters[ name ]( data ) + ); + } }; } } From c1bb15ab415acc4d761d70ad17ed3a4ec7376bbc Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 14:30:40 +0000 Subject: [PATCH 095/126] Fix price formatting --- includes/class-wc-abstract-google-analytics-js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index e6d28de0..78f5104f 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -245,7 +245,7 @@ public function get_formatted_order( int $order_id ): array { return array( 'currency' => $order->get_currency(), - 'value' => $this->get_formatted_product( $order->get_total() ), + 'value' => $this->get_formatted_price( $order->get_total() ), 'items' => array_map( function( $item ) { return array( From b1a0de1fe2d094fa2656c546b18656cf23592eef Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 14:32:32 +0000 Subject: [PATCH 096/126] Remove unnecessary check --- assets/js/src/tracker/index.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 031ac83b..4bcbc452 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -65,13 +65,11 @@ class Tracker { } return function trackerEventHandler( data ) { - if ( config.events.includes( name ) ) { - window[ config.tracker_var ]( - 'event', - name, - formatters[ name ]( data ) - ); - } + window[ config.tracker_var ]( + 'event', + name, + formatters[ name ]( data ) + ); }; } } From bed5877431a758f7c46b98315f4306ead9eab087 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 14:40:33 +0000 Subject: [PATCH 097/126] Remove demo comment --- assets/js/src/integrations/blocks.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 8cd5b715..0fafcb3e 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -39,9 +39,6 @@ addUniqueAction( tracker.eventHandler( 'select_content' ) ); -/** - * Temporarily remove all actions for demo purposes. - */ removeAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); From 7fcab4197e21146b11a0cfe71c994995d2dbf337 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 14:42:27 +0000 Subject: [PATCH 098/126] Assign formatter to constant inside eventHandler --- assets/js/src/tracker/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 4bcbc452..20d03249 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -60,7 +60,8 @@ class Tracker { */ eventHandler( name ) { /* eslint import/namespace: [ 'error', { allowComputed: true } ] */ - if ( ! formatters[ name ] ) { + const formatter = formatters[ name ]; + if ( typeof formatter !== 'function' ) { throw new Error( `Event ${ name } is not supported.` ); } @@ -68,7 +69,7 @@ class Tracker { window[ config.tracker_var ]( 'event', name, - formatters[ name ]( data ) + formatter( data ) ); }; } From c4931e3b7484b77448c3b1d5f53a32b8c9a7a91f Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 15:20:56 +0000 Subject: [PATCH 099/126] Simplify set_script_data --- .../class-wc-abstract-google-analytics-js.php | 25 ++++++++---- includes/class-wc-google-gtag-js.php | 39 +++++++++++-------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 78f5104f..db4dd4ae 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -65,14 +65,14 @@ public function attach_event_data(): void { add_action( 'woocommerce_before_cart', function() { - $this->set_script_data( 'cart', $this->get_formatted_cart(), null, true ); + $this->set_script_data( 'cart', $this->get_formatted_cart() ); } ); add_action( 'woocommerce_before_checkout_form', function() { - $this->set_script_data( 'cart', $this->get_formatted_cart(), null, true ); + $this->set_script_data( 'cart', $this->get_formatted_cart() ); } ); @@ -80,14 +80,14 @@ function() { 'woocommerce_before_single_product', function() { global $product; - $this->set_script_data( 'product', $this->get_formatted_product( $product ), null, true ); + $this->set_script_data( 'product', $this->get_formatted_product( $product ) ); } ); add_action( 'woocommerce_add_to_cart', function( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { - $this->set_script_data( 'added_to_cart', $this->get_formatted_product( wc_get_product( $product_id ), $variation ), null, true ); + $this->set_script_data( 'added_to_cart', $this->get_formatted_product( wc_get_product( $product_id ), $variation ) ); }, 10, 5 @@ -97,14 +97,14 @@ function( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { 'woocommerce_shop_loop_item_title', function() { global $product; - $this->set_script_data( 'products', $this->get_formatted_product( $product ) ); + $this->append_script_data( 'products', $this->get_formatted_product( $product ) ); } ); add_action( 'woocommerce_thankyou', function( $order_id ) { - $this->set_script_data( 'order', $this->get_formatted_order( $order_id ), null, true ); + $this->set_script_data( 'order', $this->get_formatted_order( $order_id ) ); } ); } @@ -314,11 +314,20 @@ abstract public static function tracker_var(): string; * * @param string $type The type of event this data is related to. * @param string|array $data The event data to add. - * @param string $key If not null then the $data will be added as a new array item with this key. * * @return void */ - abstract public function set_script_data( string $type, $data, ?string $key = null ): void; + abstract public function set_script_data( string $type, $data ): void; + + /** + * Append data to an existing script data array + * + * @param string $type The type of event this data is related to. + * @param string|array $data The event data to add. + * + * @return void + */ + abstract public function append_script_data( string $type, $data ): void; /** * Get the class instance diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 7296cb1b..87b9696f 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -101,7 +101,9 @@ function( $hook, $gtag_event ) { add_action( $hook, function() use ( $gtag_event ) { - $this->set_script_data( 'events', $gtag_event, $gtag_event ); + if ( ! in_array( $gtag_event, $this->script_data['events'] ?? [] ) ) { + $this->append_script_data( 'events', $gtag_event ); + } } ); } @@ -109,28 +111,33 @@ function() use ( $gtag_event ) { } /** - * Add an event to the script data + * Set script data for a specific event * * @param string $type The type of event this data is related to. * @param string|array $data The event data to add. - * @param string $key If not null then the $data will be added as a new array item with this key. - * @param bool $single If true then no other data will be added for this type. * * @return void */ - public function set_script_data( string $type, $data, ?string $key = null, bool $single = false ): void { - if ( ! isset( $this->script_data[ $type ] ) ) { - $this->script_data[ $type ] = array(); - } - - if ( $single ) { - $this->script_data[ $type ] = $data; - } elseif ( ! is_null( $key ) ) { - $this->script_data[ $type ][ $key ] = $data; - } else { - $this->script_data[ $type ][] = $data; - } + public function set_script_data( string $type, $data ): void { + $this->script_data[ $type ] = $data; + } + /** + * Append data to an existing script data array + * + * @param string $type The type of event this data is related to. + * @param string|array $data The event data to add. + * + * @return void + */ + public function append_script_data( string $type, $data ): void { + $this->set_script_data( + $type, + array( + ...$this->script_data[ $type ] ?? [], + $data + ) + ); } /** From 46dd2223b63a0a70d0d3c8b5c4e2644668767237 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 15:32:13 +0000 Subject: [PATCH 100/126] Rename tracker_var to tracker_function_name --- assets/js/src/tracker/index.js | 6 ++--- .../class-wc-abstract-google-analytics-js.php | 2 +- includes/class-wc-google-gtag-js.php | 26 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index 20d03249..f1244f9f 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -26,7 +26,7 @@ class Tracker { * Initializes the tracker and dataLayer if not already done. */ init() { - if ( window[ config.tracker_var ] ) { + if ( window[ config.tracker_function_name ] ) { // Tracker already initialized. Do nothing. return; } @@ -37,7 +37,7 @@ class Tracker { window.dataLayer.push( arguments ); } - window[ config.tracker_var ] = gtag; + window[ config.tracker_function_name ] = gtag; gtag( 'js', new Date() ); gtag( 'set', `developer_id.${ config.developer_id }`, true ); @@ -66,7 +66,7 @@ class Tracker { } return function trackerEventHandler( data ) { - window[ config.tracker_var ]( + window[ config.tracker_function_name ]( 'event', name, formatter( data ) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index db4dd4ae..ae00dc57 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -307,7 +307,7 @@ public function schema_callback(): array { * * @return string */ - abstract public static function tracker_var(): string; + abstract public static function tracker_function_name(): string; /** * Add an event to the script data diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 87b9696f..64ceec71 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -154,7 +154,7 @@ public function get_script_data(): string { * * @return string */ - public static function tracker_var(): string { + public static function tracker_function_name(): string { return apply_filters( 'woocommerce_gtag_tracker_variable', 'gtag' ); } @@ -165,23 +165,23 @@ public static function tracker_var(): string { */ public function load_analytics_config(): void { $this->script_data['config'] = array( - 'developer_id' => self::DEVELOPER_ID, - 'gtag_id' => self::get( 'ga_id' ), - 'tracker_var' => self::tracker_var(), - 'track_404' => 'yes' === self::get( 'ga_404_tracking_enabled' ), - 'allow_google_signals' => 'yes' === self::get( 'ga_support_display_advertising' ), - 'link_attribution' => 'yes' === self::get( 'ga_support_enhanced_link_attribution' ), - 'anonymize_ip' => 'yes' === self::get( 'ga_anonymize_enabled' ), - 'logged_in' => is_user_logged_in(), - 'linker' => array( + 'developer_id' => self::DEVELOPER_ID, + 'gtag_id' => self::get( 'ga_id' ), + 'tracker_function_name' => self::tracker_function_name(), + 'track_404' => 'yes' === self::get( 'ga_404_tracking_enabled' ), + 'allow_google_signals' => 'yes' === self::get( 'ga_support_display_advertising' ), + 'link_attribution' => 'yes' === self::get( 'ga_support_enhanced_link_attribution' ), + 'anonymize_ip' => 'yes' === self::get( 'ga_anonymize_enabled' ), + 'logged_in' => is_user_logged_in(), + 'linker' => array( 'domains' => ! empty( self::get( 'ga_linker_cross_domains' ) ) ? array_map( 'esc_js', explode( ',', self::get( 'ga_linker_cross_domains' ) ) ) : array(), 'allow_incoming' => 'yes' === self::get( 'ga_linker_allow_incoming_enabled' ), ), - 'custom_map' => array( + 'custom_map' => array( 'dimension1' => 'logged_in', ), - 'events' => self::get_enabled_events(), - 'identifier' => self::get( 'ga_product_identifier' ), + 'events' => self::get_enabled_events(), + 'identifier' => self::get( 'ga_product_identifier' ), ); } From 930cf21d51994ee53dae208b11c9e2373afeb237 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 16:04:28 +0000 Subject: [PATCH 101/126] Update unit tests --- tests/unit-tests/WCGoogleGtagJS.php | 63 +++++++++++------------------ 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 01e0fe68..2ce828cc 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -97,15 +97,10 @@ public function test_map_actions(): void { $script_data = json_decode( $gtag->get_script_data(), true ); - $this->assertEquals( - $script_data['events'], - array( - $event => $event - ) - ); + $this->assertTrue( in_array( $event, $script_data['events'] ) ); // Reset event data - $gtag->set_script_data( 'events', array(), null, true ); + $gtag->set_script_data( 'events', array() ); } } @@ -114,44 +109,32 @@ public function test_map_actions(): void { * * @return void */ - public function test_script_data(): void { - $gtag = new WC_Google_Gtag_JS; - $default = json_decode( $gtag->get_script_data(), true ); + public function test_set_script_data(): void { + $gtag = new WC_Google_Gtag_JS; + $example_data = array( + 'key' => 'value' + ); - $gtag->set_script_data( 'test', 'value' ); - $script_data = json_decode( $gtag->get_script_data(), true ); + $gtag->set_script_data( 'test', $example_data ); - $this->assertEquals( $script_data, array_merge( - $default, - array( - 'test' => array( - 'value' - ), - ) - ) ); - - $gtag->set_script_data( 'test', 'value2', 'key' ); $script_data = json_decode( $gtag->get_script_data(), true ); + $this->assertEquals( $script_data['test'], $example_data ); + } - $this->assertEquals( $script_data, array_merge( - $default, - array( - 'test' => array( - 0 => 'value', - 'key' => 'value2', - ), - ) - ) ); + /** + * Test script data can be appended + * + * @return void + */ + public function test_append_script_data(): void { + $gtag = new WC_Google_Gtag_JS; - $gtag->set_script_data( 'test', 'value', null, true ); + $gtag->append_script_data( 'test', 'first' ); + $gtag->append_script_data( 'test', 'second' ); + $script_data = json_decode( $gtag->get_script_data(), true ); - $this->assertEquals( $script_data, array_merge( - $default, - array( - 'test' => 'value', - ) - ) ); + $this->assertEquals( $script_data['test'], array( 'first', 'second' ) ); } /** @@ -162,12 +145,12 @@ public function test_script_data(): void { public function test_tracker_var(): void { $gtag = new WC_Google_Gtag_JS; - $this->assertEquals( $gtag->tracker_var(), 'gtag' ); + $this->assertEquals( $gtag->tracker_function_name(), 'gtag' ); add_filter( 'woocommerce_gtag_tracker_variable', function( $var ) { return 'filtered'; } ); - $this->assertEquals( $gtag->tracker_var(), 'filtered' ); + $this->assertEquals( $gtag->tracker_function_name(), 'filtered' ); } /** From db37466a1648d791e9105d5fc9170953c6e8555d Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 16:09:25 +0000 Subject: [PATCH 102/126] Update config export --- assets/js/src/config.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index 28b4b2f8..ea879fa1 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -1,8 +1,10 @@ /* global wcgaiData */ -export const config = wcgaiData.config ?? {}; -export const events = wcgaiData.events ?? {}; -export const cart = wcgaiData.cart ?? {}; -export const products = wcgaiData.products ?? {}; -export const product = wcgaiData.product ?? {}; -export const addedToCart = wcgaiData.added_to_cart ?? {}; -export const order = wcgaiData.order ?? {}; +export const { + config, + events, + cart, + products, + product, + addedToCart: added_to_cart, + order, +} = wcgaiData; \ No newline at end of file From d6259842a405e9387b537a38a60c54ec48ddb168 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Fri, 9 Feb 2024 16:23:28 +0000 Subject: [PATCH 103/126] Fix addedToCart declaration --- assets/js/src/config.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index ea879fa1..3740c342 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -1,10 +1,11 @@ /* global wcgaiData */ +/* eslint-disable camelcase */ export const { config, events, cart, products, product, - addedToCart: added_to_cart, + added_to_cart: addedToCart, order, -} = wcgaiData; \ No newline at end of file +} = wcgaiData; From 9f60881ef67756acf9ef514100e11244f1e3fc63 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 19 Feb 2024 16:32:39 +0000 Subject: [PATCH 104/126] Remove spread operator --- .../class-wc-abstract-google-analytics-js.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index ae00dc57..4e5eb572 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -170,9 +170,11 @@ public function get_formatted_cart(): array { return array( 'items' => array_map( function( $item ) { - return array( - ...$this->get_formatted_product( $item['data'] ), - 'quantity' => $item['quantity'] + return array_merge( + $this->get_formatted_product( $item['data'] ), + array( + 'quantity' => $item['quantity'], + ) ); }, array_values( WC()->cart->get_cart() ) @@ -248,9 +250,11 @@ public function get_formatted_order( int $order_id ): array { 'value' => $this->get_formatted_price( $order->get_total() ), 'items' => array_map( function( $item ) { - return array( - ...$this->get_formatted_product( $item->get_product() ), - 'quantity' => $item->get_quantity(), + return array_merge( + $this->get_formatted_product( $item->get_product() ), + array( + 'quantity' => $item->get_quantity(), + ) ); }, array_values( $order->get_items() ), From 9a1e058169a2a81d3b95eba3098191fe202d53e2 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 16:08:31 +0000 Subject: [PATCH 105/126] Update classic select_content tracking --- assets/js/src/integrations/classic.js | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 04931ff9..f691e3e3 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -79,28 +79,41 @@ export const trackClassicIntegration = () => { document.body.onupdated_wc_div = () => removeFromCartListener(); /** - * Attach click event listeners to all product listings and send select_content events for specific targets. + * Attaches click event listeners to non-block product listings that sends a + * `select_content` event if the target link takes the user to the product page. */ document - .querySelectorAll( '.product a[data-product_id]' ) - ?.forEach( ( button ) => { - const productId = button.dataset.product_id; + .querySelectorAll( '.product:not(.wp-block-post)' ) + ?.forEach( ( product ) => { + // Get the Product ID from a child node containing the relevant attribute + const productId = product + .querySelector( 'a[data-product_id]' ) + ?.getAttribute( 'data-product_id' ); - button.parentNode.addEventListener( 'click', ( listing ) => { - const targetLink = listing.target.closest( + if ( ! productId ) { + return; + } + + product.addEventListener( 'click', ( event ) => { + // Return early if the user has clicked on anything other + // than a product link or an Add to cart button. + const targetLink = event.target.closest( '.woocommerce-loop-product__link' ); + + const isButton = event.target.classList.contains( 'button' ); + const isAddToCartButton = - button.classList.contains( 'add_to_cart_button' ) && - ! button.classList.contains( 'product_type_variable' ); + event.target.classList.contains( 'add_to_cart_button' ) && + ! event.target.classList.contains( 'product_type_variable' ) - if ( ! targetLink && isAddToCartButton ) { + if ( ! targetLink && ( ! isButton || isAddToCartButton ) ) { return; } tracker.eventHandler( 'select_content' )( { product: getProductFromID( parseInt( productId ) ), } ); - } ); + }); } ); }; From 234fe84e30031761b093d17b8a83a0fd69b211f8 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 16:34:54 +0000 Subject: [PATCH 106/126] Update remove_from_cart tracking and add mini-cart support --- assets/js/src/integrations/classic.js | 43 +++++++++++-------- assets/js/src/utils/index.js | 7 +-- .../class-wc-abstract-google-analytics-js.php | 9 +--- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index f691e3e3..214275c4 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -51,33 +51,38 @@ export const trackClassicIntegration = () => { }; /** - * Attach click event listeners to all remove from cart links on page load and when the cart is updated. + * Attaches click event listeners to all remove from cart links */ const removeFromCartListener = () => { document - .querySelector( '.woocommerce-cart-form' ) - ?.addEventListener( 'click', ( e ) => { - const item = e.target.closest( - '.woocommerce-cart-form__cart-item .remove' - ); - - if ( ! item || ! item.dataset.product_id ) { - return; - } + .querySelectorAll( + '.woocommerce-cart-form .woocommerce-cart-form__cart-item .remove[data-product_id]' + ) + .forEach( ( item ) => + item.addEventListener( 'click', removeFromCartHandler ) + ); + } - tracker.eventHandler( 'remove_from_cart' )( { - product: getProductFromID( - parseInt( item.dataset.product_id ), - true - ), - } ); - } ); - }; + /** + * Handle remove from cart events + * + * @param {HTMLElement|Object} element - The HTML element clicked on to trigger this event + */ + function removeFromCartHandler( element ) { + tracker.eventHandler( 'remove_from_cart' )( { + product: getProductFromID( + parseInt( element.target.dataset.product_id ) + ), + } ); + } + // Attach event listeners on initial page load and when the cart div is updated removeFromCartListener(); - document.body.onupdated_wc_div = () => removeFromCartListener(); + // Trigger the handler when an item is removed from the mini-cart and WooCommerce dispatches the `removed_from_cart` event. + document.body.onremoved_from_cart = ( event, fragments, cart_hash, button ) => removeFromCartHandler( { target: button[0] } ); + /** * Attaches click event listeners to non-block product listings that sends a * `select_content` event if the target link takes the user to the product page. diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index e2faa2f0..f0b079fa 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -158,11 +158,8 @@ const formatCategoryKey = ( index ) => { * Searches through the global wcgaiData.products object to find a single product by its ID * * @param {number} search The ID of the product to search for - * @param {boolean} fromCart If true then product will be retreived from wcgaiData.cart.items * @return {Object|undefined} The product object or undefined if not found */ -export const getProductFromID = ( search, fromCart = false ) => { - const list = fromCart ? cart.items : products; - /* eslint-disable-next-line camelcase */ - return list.find( ( { id } ) => id === search ); +export const getProductFromID = ( search ) => { + return products?.find( ( { id } ) => id === search ) ?? cart?.items?.find( ( { id } ) => id === search ); }; diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 4e5eb572..6a0eb5b8 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -63,14 +63,7 @@ public function __construct() { */ public function attach_event_data(): void { add_action( - 'woocommerce_before_cart', - function() { - $this->set_script_data( 'cart', $this->get_formatted_cart() ); - } - ); - - add_action( - 'woocommerce_before_checkout_form', + 'wp_head', function() { $this->set_script_data( 'cart', $this->get_formatted_cart() ); } From 7ff8829d5bafba5da4079c493ea7297c4ab59c81 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 16:46:28 +0000 Subject: [PATCH 107/126] Update select_content selector --- assets/js/src/integrations/classic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 214275c4..4cc4d4c7 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -88,7 +88,7 @@ export const trackClassicIntegration = () => { * `select_content` event if the target link takes the user to the product page. */ document - .querySelectorAll( '.product:not(.wp-block-post)' ) + .querySelectorAll( '.products .product:not(.wp-block-post)' ) ?.forEach( ( product ) => { // Get the Product ID from a child node containing the relevant attribute const productId = product From 827253876312d2de3fe573b386c77a02275919eb Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 16:55:45 +0000 Subject: [PATCH 108/126] Add comment describing why some actions are removed --- assets/js/src/integrations/blocks.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/js/src/integrations/blocks.js b/assets/js/src/integrations/blocks.js index 0fafcb3e..a816ea6c 100644 --- a/assets/js/src/integrations/blocks.js +++ b/assets/js/src/integrations/blocks.js @@ -39,6 +39,11 @@ addUniqueAction( tracker.eventHandler( 'select_content' ) ); +/** + * Remove additional actions added by WooCommerce Core which are either + * not supported by Google Analytics for WooCommerce or are redundant + * since Google retired Universal Analytics. + */ removeAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE ); removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE ); From 81f719e1343dece1e47305848e435cbc4b234210 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 17:02:46 +0000 Subject: [PATCH 109/126] Fix linting errors --- assets/js/src/integrations/classic.js | 22 +++++++++++++++------- assets/js/src/utils/index.js | 5 ++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 4cc4d4c7..5f8b1467 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -61,7 +61,7 @@ export const trackClassicIntegration = () => { .forEach( ( item ) => item.addEventListener( 'click', removeFromCartHandler ) ); - } + }; /** * Handle remove from cart events @@ -81,7 +81,13 @@ export const trackClassicIntegration = () => { document.body.onupdated_wc_div = () => removeFromCartListener(); // Trigger the handler when an item is removed from the mini-cart and WooCommerce dispatches the `removed_from_cart` event. - document.body.onremoved_from_cart = ( event, fragments, cart_hash, button ) => removeFromCartHandler( { target: button[0] } ); + document.body.onremoved_from_cart = ( + event, + fragments, + /* eslint-disable-next-line camelcase */ + cart_hash, + button + ) => removeFromCartHandler( { target: button[ 0 ] } ); /** * Attaches click event listeners to non-block product listings that sends a @@ -89,9 +95,9 @@ export const trackClassicIntegration = () => { */ document .querySelectorAll( '.products .product:not(.wp-block-post)' ) - ?.forEach( ( product ) => { + ?.forEach( ( item ) => { // Get the Product ID from a child node containing the relevant attribute - const productId = product + const productId = item .querySelector( 'a[data-product_id]' ) ?.getAttribute( 'data-product_id' ); @@ -99,7 +105,7 @@ export const trackClassicIntegration = () => { return; } - product.addEventListener( 'click', ( event ) => { + item.addEventListener( 'click', ( event ) => { // Return early if the user has clicked on anything other // than a product link or an Add to cart button. const targetLink = event.target.closest( @@ -110,7 +116,9 @@ export const trackClassicIntegration = () => { const isAddToCartButton = event.target.classList.contains( 'add_to_cart_button' ) && - ! event.target.classList.contains( 'product_type_variable' ) + ! event.target.classList.contains( + 'product_type_variable' + ); if ( ! targetLink && ( ! isButton || isAddToCartButton ) ) { return; @@ -119,6 +127,6 @@ export const trackClassicIntegration = () => { tracker.eventHandler( 'select_content' )( { product: getProductFromID( parseInt( productId ) ), } ); - }); + } ); } ); }; diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index f0b079fa..4899196a 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -161,5 +161,8 @@ const formatCategoryKey = ( index ) => { * @return {Object|undefined} The product object or undefined if not found */ export const getProductFromID = ( search ) => { - return products?.find( ( { id } ) => id === search ) ?? cart?.items?.find( ( { id } ) => id === search ); + return ( + products?.find( ( { id } ) => id === search ) ?? + cart?.items?.find( ( { id } ) => id === search ) + ); }; From 0bc1a5b715cfb1debc264976a14b2a5a03738c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Mon, 26 Feb 2024 18:37:56 +0100 Subject: [PATCH 110/126] Remove options remapping, use settings directly. Fix https://github.com/woocommerce/woocommerce-google-analytics-integration/issues/359 --- includes/class-wc-google-analytics.php | 61 ++++++-------------------- includes/class-wc-google-gtag-js.php | 14 +++--- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 852bb155..0f4f6d4e 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -69,11 +69,10 @@ class WC_Google_Analytics extends WC_Integration { /** * Returns the proper class based on Gtag settings. * - * @param array $options Options * @return WC_Abstract_Google_Analytics_JS */ - protected function get_tracking_instance( $options = array() ) { - return WC_Google_Gtag_JS::get_instance( $options ); + protected function get_tracking_instance() { + return WC_Google_Gtag_JS::get_instance( $this->settings ); } /** @@ -88,14 +87,13 @@ public function __construct() { // Load the settings $this->init_form_fields(); $this->init_settings(); - $constructor = $this->init_options(); add_action( 'admin_notices', array( $this, 'universal_analytics_upgrade_notice' ) ); // Contains snippets/JS tracking code include_once 'class-wc-abstract-google-analytics-js.php'; include_once 'class-wc-google-gtag-js.php'; - $this->get_tracking_instance( $constructor ); + $this->get_tracking_instance(); // Display a task on "Things to do next section" add_action( 'init', array( $this, 'add_wc_setup_task' ), 20 ); @@ -135,38 +133,6 @@ public function universal_analytics_upgrade_notice() { } } - /** - * Loads all of our options for this plugin (stored as properties as well) - * - * @return array An array of options that can be passed to other classes - */ - public function init_options() { - $options = array( - 'ga_product_identifier' => 'product_sku', - 'ga_id' => null, - 'ga_standard_tracking_enabled' => null, - 'ga_support_display_advertising' => null, - 'ga_support_enhanced_link_attribution' => null, - 'ga_anonymize_enabled' => null, - 'ga_404_tracking_enabled' => null, - 'ga_enhanced_remove_from_cart_enabled' => null, - 'ga_enhanced_product_impression_enabled' => null, - 'ga_enhanced_product_click_enabled' => null, - 'ga_enhanced_checkout_process_enabled' => null, - 'ga_enhanced_product_detail_view_enabled' => null, - 'ga_event_tracking_enabled' => null, - 'ga_linker_cross_domains' => null, - 'ga_linker_allow_incoming_enabled' => null, - ); - - $constructor = array(); - foreach ( $options as $option => $default ) { - $constructor[ $option ] = $this->$option = $this->get_option( $option, $default ); - } - - return $constructor; - } - /** * Tells WooCommerce which settings to display under the "integration" tab */ @@ -316,21 +282,22 @@ public function show_options_info() { * @return array Updated WC Tracker data. */ public function track_options( $data ) { + $options = $this->settings; $data['wc-google-analytics'] = array( - 'standard_tracking_enabled' => $this->ga_standard_tracking_enabled, - 'support_display_advertising' => $this->ga_support_display_advertising, - 'support_enhanced_link_attribution' => $this->ga_support_enhanced_link_attribution, - 'anonymize_enabled' => $this->ga_anonymize_enabled, - 'ga_404_tracking_enabled' => $this->ga_404_tracking_enabled, - 'ecommerce_tracking_enabled' => $this->ga_ecommerce_tracking_enabled, - 'event_tracking_enabled' => $this->ga_event_tracking_enabled, + 'standard_tracking_enabled' => $options['ga_standard_tracking_enabled'], + 'support_display_advertising' => $options['ga_support_display_advertising'], + 'support_enhanced_link_attribution' => $options['ga_support_enhanced_link_attribution'], + 'anonymize_enabled' => $options['ga_anonymize_enabled'], + 'ga_404_tracking_enabled' => $options['ga_404_tracking_enabled'], + 'ecommerce_tracking_enabled' => $options['ga_ecommerce_tracking_enabled'], + 'event_tracking_enabled' => $options['ga_event_tracking_enabled'], 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, - 'linker_allow_incoming_enabled' => empty( $this->ga_linker_allow_incoming_enabled ) ? 'no' : 'yes', - 'linker_cross_domains' => $this->ga_linker_cross_domains, + 'linker_allow_incoming_enabled' => empty( $options['ga_linker_allow_incoming_enabled'] ) ? 'no' : 'yes', + 'linker_cross_domains' => $options['ga_linker_cross_domains'], ); // ID prefix, blank, or X for unknown - $prefix = strstr( strtoupper( $this->ga_id ), '-', true ); + $prefix = strstr( strtoupper( $options['ga_id'] ), '-', true ); if ( in_array( $prefix, array( 'UA', 'G', 'GT' ), true ) || empty( $prefix ) ) { $data['wc-google-analytics']['ga_id'] = $prefix; } else { diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 64ceec71..bca33d02 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -31,13 +31,13 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** * Constructor - * Takes our options from the parent class so we can later use them in the JS snippets + * Takes our settings from the parent class so we can later use them in the JS snippets * - * @param array $options Options + * @param array $settings Settings */ - public function __construct( $options = array() ) { + public function __construct( $settings = array() ) { parent::__construct(); - self::$options = $options; + self::$options = $settings; $this->load_analytics_config(); $this->map_actions(); @@ -214,12 +214,12 @@ public static function get_enabled_events(): array { /** * Get the class instance * - * @param array $options Options + * @param array $settings Settings * @return WC_Abstract_Google_Analytics_JS */ - public static function get_instance( $options = array() ): WC_Abstract_Google_Analytics_JS { + public static function get_instance( $settings = array() ): WC_Abstract_Google_Analytics_JS { if ( null === self::$instance ) { - self::$instance = new self( $options ); + self::$instance = new self( $settings ); } return self::$instance; From 899c0d3d0561487b78b57174b022106ea32f8316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Mon, 26 Feb 2024 18:55:28 +0100 Subject: [PATCH 111/126] Remove redundant WC_Google_Analytics properties use `$this->settings` directly. --- includes/class-wc-google-analytics.php | 47 +------------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 0f4f6d4e..7ec51154 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -16,51 +16,6 @@ */ class WC_Google_Analytics extends WC_Integration { - /** @var string $ga_id Google Analytics Tracking ID */ - public $ga_id; - - /** @var string $ga_standard_tracking_enabled Is standard tracking enabled (yes|no) */ - public $ga_standard_tracking_enabled; - - /** @var string $ga_support_display_advertising Supports display advertising (yes|no) */ - public $ga_support_display_advertising; - - /** @var string $ga_support_enhanced_link_attribution Use enhanced link attribution (yes|no) */ - public $ga_support_enhanced_link_attribution; - - /** @var string $ga_anonymize_enabled Anonymize IP addresses (yes|no) */ - public $ga_anonymize_enabled; - - /** @var string $ga_404_tracking_enabled Track 404 errors (yes|no) */ - public $ga_404_tracking_enabled; - - /** @var string $ga_ecommerce_tracking_enabled Purchase transactions (yes|no) */ - public $ga_ecommerce_tracking_enabled; - - /** @var string $ga_enhanced_remove_from_cart_enabled Track remove from cart events (yes|no) */ - public $ga_enhanced_remove_from_cart_enabled; - - /** @var string $ga_enhanced_product_impression_enabled Track product impressions (yes|no) */ - public $ga_enhanced_product_impression_enabled; - - /** @var string $ga_enhanced_product_click_enabled Track product clicks (yes|no) */ - public $ga_enhanced_product_click_enabled; - - /** @var string $ga_enhanced_checkout_process_enabled Track checkout initiated (yes|no) */ - public $ga_enhanced_checkout_process_enabled; - - /** @var string $ga_enhanced_product_detail_view_enabled Track product detail views (yes|no) */ - public $ga_enhanced_product_detail_view_enabled; - - /** @var string $ga_event_tracking_enabled Track add to cart events (yes|no) */ - public $ga_event_tracking_enabled; - - /** @var string $ga_linker_cross_domains Domains for automatic linking */ - public $ga_linker_cross_domains; - - /** @var string $ga_linker_allow_incoming_enabled Accept incoming linker (yes|no) */ - public $ga_linker_allow_incoming_enabled; - /** * Defines the script handles that should be async. */ @@ -386,7 +341,7 @@ protected function enqueue_ecommerce_tracking_code( $order_id ) { * @return bool True if tracking for a certain setting is disabled */ private function disable_tracking( $type ) { - return is_admin() || current_user_can( 'manage_options' ) || ( ! $this->ga_id ) || 'no' === $type || apply_filters( 'woocommerce_ga_disable_tracking', false, $type ); + return is_admin() || current_user_can( 'manage_options' ) || ( ! $this->settings['ga_id'] ) || 'no' === $type || apply_filters( 'woocommerce_ga_disable_tracking', false, $type ); } /** From 1bd5af374f2a6ed45eb05d9b9320a610c41219a0 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 17:59:58 +0000 Subject: [PATCH 112/126] Fix ID mismatch when removing a variation from cart --- assets/js/src/utils/index.js | 4 ++-- .../class-wc-abstract-google-analytics-js.php | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 4899196a..dc7e5743 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -162,7 +162,7 @@ const formatCategoryKey = ( index ) => { */ export const getProductFromID = ( search ) => { return ( - products?.find( ( { id } ) => id === search ) ?? - cart?.items?.find( ( { id } ) => id === search ) + cart?.items?.find( ( { id } ) => id === search ) ?? + products?.find( ( { id } ) => id === search ) ); }; diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 6a0eb5b8..7eda8555 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -141,7 +141,7 @@ function gaOptout() { * @return string */ public static function get_product_identifier( WC_Product $product ): string { - $identifier = $product->get_id(); + $identifier = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(); if ( 'product_sku' === self::get( 'ga_product_identifier' ) ) { if ( ! empty( $product->get_sku() ) ) { @@ -167,6 +167,10 @@ function( $item ) { $this->get_formatted_product( $item['data'] ), array( 'quantity' => $item['quantity'], + 'prices' => array( + 'price' => $this->get_formatted_price( $item['line_total'] ), + 'currency_minor_unit' => wc_get_price_decimals(), + ), ) ); }, @@ -191,8 +195,8 @@ function( $item ) { */ public function get_formatted_product( WC_Product $product, $variation = false ): array { $formatted = array( - 'id' => $product->get_id(), - 'name' => $product->get_name(), + 'id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(), + 'name' => $product->get_title(), 'categories' => array_map( fn( $category ) => array( 'name' => $category->name ), wc_get_product_terms( $product->get_id(), 'product_cat', array( 'number' => 5 ) ) @@ -208,6 +212,10 @@ public function get_formatted_product( WC_Product $product, $variation = false ) ), ); + if ( $product->is_type( 'variation' ) ) { + $variation = $product->get_attributes(); + } + if ( false !== $variation ) { $formatted['variation'] = implode( ', ', @@ -215,7 +223,7 @@ public function get_formatted_product( WC_Product $product, $variation = false ) function( $attribute, $value ) { return sprintf( '%s: %s', - $attribute, + str_replace( 'attribute_', '', $attribute ), $value ); }, From c979cf082b83893fc32b72c59f8f23c88b038ec5 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 26 Feb 2024 18:04:37 +0000 Subject: [PATCH 113/126] Adjust append_script_data --- includes/class-wc-google-gtag-js.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 64ceec71..664e3b60 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -130,14 +130,11 @@ public function set_script_data( string $type, $data ): void { * * @return void */ - public function append_script_data( string $type, $data ): void { - $this->set_script_data( - $type, - array( - ...$this->script_data[ $type ] ?? [], - $data - ) - ); + public function append_script_data( string $type, $data ): void { + if ( ! isset( $this->script_data[ $type ] ) ) { + $this->script_data[ $type ] = array(); + } + $this->script_data[ $type ][] = $data; } /** From 9fea878c015699077d6282d000eb1f38a005abff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Mon, 26 Feb 2024 19:45:17 +0100 Subject: [PATCH 114/126] Rename options to settings where applicable. --- .../class-wc-abstract-google-analytics-js.php | 18 ++++++------- includes/class-wc-google-analytics.php | 26 +++++++++---------- includes/class-wc-google-gtag-js.php | 6 ++--- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 4e5eb572..0937f56b 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -17,8 +17,8 @@ abstract class WC_Abstract_Google_Analytics_JS { /** @var WC_Abstract_Google_Analytics_JS $instance Class Instance */ protected static $instance; - /** @var array $options Inherited Analytics options */ - protected static $options; + /** @var array $settings Inherited Analytics settings */ + protected static $settings; /** @var string Developer ID */ public const DEVELOPER_ID = 'dOGY3NW'; @@ -110,14 +110,14 @@ function( $order_id ) { } /** - * Return one of our options + * Return one of our settings * - * @param string $option Key/name for the option. + * @param string $setting Key/name for the setting. * - * @return string|null Value of the option or null if not found + * @return string|null Value of the setting or null if not found */ - protected static function get( $option ): ?string { - return self::$options[ $option ] ?? null; + protected static function get( $setting ): ?string { + return self::$settings[ $setting ] ?? null; } /** @@ -336,8 +336,8 @@ abstract public function append_script_data( string $type, $data ): void; /** * Get the class instance * - * @param array $options Options + * @param array $settings Settings * @return WC_Abstract_Google_Analytics_JS */ - abstract public static function get_instance( $options = array() ): WC_Abstract_Google_Analytics_JS; + abstract public static function get_instance( $settings = array() ): WC_Abstract_Google_Analytics_JS; } diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 7ec51154..0a86045d 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -53,7 +53,7 @@ public function __construct() { // Display a task on "Things to do next section" add_action( 'init', array( $this, 'add_wc_setup_task' ), 20 ); // Admin Options - add_filter( 'woocommerce_tracker_data', array( $this, 'track_options' ) ); + add_filter( 'woocommerce_tracker_data', array( $this, 'track_settings' ) ); add_action( 'woocommerce_update_options_integration_google_analytics', array( $this, 'process_admin_options' ) ); add_action( 'woocommerce_update_options_integration_google_analytics', array( $this, 'show_options_info' ) ); add_action( 'admin_init', array( $this, 'privacy_policy' ) ); @@ -236,23 +236,23 @@ public function show_options_info() { * @param array $data Current WC tracker data. * @return array Updated WC Tracker data. */ - public function track_options( $data ) { - $options = $this->settings; + public function track_settings( $data ) { + $settings = $this->settings; $data['wc-google-analytics'] = array( - 'standard_tracking_enabled' => $options['ga_standard_tracking_enabled'], - 'support_display_advertising' => $options['ga_support_display_advertising'], - 'support_enhanced_link_attribution' => $options['ga_support_enhanced_link_attribution'], - 'anonymize_enabled' => $options['ga_anonymize_enabled'], - 'ga_404_tracking_enabled' => $options['ga_404_tracking_enabled'], - 'ecommerce_tracking_enabled' => $options['ga_ecommerce_tracking_enabled'], - 'event_tracking_enabled' => $options['ga_event_tracking_enabled'], + 'standard_tracking_enabled' => $settings['ga_standard_tracking_enabled'], + 'support_display_advertising' => $settings['ga_support_display_advertising'], + 'support_enhanced_link_attribution' => $settings['ga_support_enhanced_link_attribution'], + 'anonymize_enabled' => $settings['ga_anonymize_enabled'], + 'ga_404_tracking_enabled' => $settings['ga_404_tracking_enabled'], + 'ecommerce_tracking_enabled' => $settings['ga_ecommerce_tracking_enabled'], + 'event_tracking_enabled' => $settings['ga_event_tracking_enabled'], 'plugin_version' => WC_GOOGLE_ANALYTICS_INTEGRATION_VERSION, - 'linker_allow_incoming_enabled' => empty( $options['ga_linker_allow_incoming_enabled'] ) ? 'no' : 'yes', - 'linker_cross_domains' => $options['ga_linker_cross_domains'], + 'linker_allow_incoming_enabled' => empty( $settings['ga_linker_allow_incoming_enabled'] ) ? 'no' : 'yes', + 'linker_cross_domains' => $settings['ga_linker_cross_domains'], ); // ID prefix, blank, or X for unknown - $prefix = strstr( strtoupper( $options['ga_id'] ), '-', true ); + $prefix = strstr( strtoupper( $settings['ga_id'] ), '-', true ); if ( in_array( $prefix, array( 'UA', 'G', 'GT' ), true ) || empty( $prefix ) ) { $data['wc-google-analytics']['ga_id'] = $prefix; } else { diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index bca33d02..2f1ec070 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -37,7 +37,7 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { */ public function __construct( $settings = array() ) { parent::__construct(); - self::$options = $settings; + self::$settings = $settings; $this->load_analytics_config(); $this->map_actions(); @@ -202,8 +202,8 @@ public static function get_enabled_events(): array { 'begin_checkout' => 'ga_enhanced_checkout_process_enabled', ); - foreach( $settings as $event => $option_name ) { - if ( 'yes' === self::get( $option_name ) ) { + foreach( $settings as $event => $setting_name ) { + if ( 'yes' === self::get( $setting_name ) ) { $events[] = $event; } } From e893d8c7ea86b454d28facd443087a8286308cf9 Mon Sep 17 00:00:00 2001 From: martynmjones <40762232+martynmjones@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:40:14 +0000 Subject: [PATCH 115/126] Avoid overwriting third-party event handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomek Wytrębowicz --- assets/js/src/integrations/classic.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 5f8b1467..e9cd3bd2 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -78,16 +78,22 @@ export const trackClassicIntegration = () => { // Attach event listeners on initial page load and when the cart div is updated removeFromCartListener(); - document.body.onupdated_wc_div = () => removeFromCartListener(); + const oldOnupdatedWcDiv = document.body.onupdated_wc_div; + document.body.onupdated_wc_div = ( ...args ) => { + if ( typeof oldOnupdatedWcDiv === 'function' ) { + oldOnupdatedWcDiv( ...args ); + } + removeFromCartListener(); + }; // Trigger the handler when an item is removed from the mini-cart and WooCommerce dispatches the `removed_from_cart` event. - document.body.onremoved_from_cart = ( - event, - fragments, - /* eslint-disable-next-line camelcase */ - cart_hash, - button - ) => removeFromCartHandler( { target: button[ 0 ] } ); + const oldOnRemovedFromCart = document.body.onremoved_from_cart; + document.body.onremoved_from_cart = ( ...args ) => { + if ( typeof oldOnRemovedFromCart === 'function' ) { + oldOnRemovedFromCart( ...args ); + } + removeFromCartHandler( { target: args[ 3 ][ 0 ] } ); + }; /** * Attaches click event listeners to non-block product listings that sends a From 94cc036583bc03f5b9ef6dd829401ab9a4207225 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 27 Feb 2024 17:00:16 +0000 Subject: [PATCH 116/126] Adjust select_content conditions and update comment --- assets/js/src/integrations/classic.js | 59 +++++++++++++++------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index e9cd3bd2..84465dbd 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -100,39 +100,44 @@ export const trackClassicIntegration = () => { * `select_content` event if the target link takes the user to the product page. */ document - .querySelectorAll( '.products .product:not(.wp-block-post)' ) - ?.forEach( ( item ) => { - // Get the Product ID from a child node containing the relevant attribute - const productId = item - .querySelector( 'a[data-product_id]' ) - ?.getAttribute( 'data-product_id' ); + .querySelectorAll( '.products .product:not(.wp-block-post)' ) + ?.forEach( ( item ) => { + // Get the Product ID from a child node containing the relevant attribute + const productId = item + .querySelector( 'a[data-product_id]' ) + ?.getAttribute( 'data-product_id' ); - if ( ! productId ) { - return; - } + if ( ! productId ) { + return; + } - item.addEventListener( 'click', ( event ) => { - // Return early if the user has clicked on anything other - // than a product link or an Add to cart button. - const targetLink = event.target.closest( - '.woocommerce-loop-product__link' - ); + item.addEventListener( 'click', ( event ) => { + // Return early if the user has clicked on an + // "Add to cart" button or anything other than a product link + const targetLink = event.target.closest( + '.woocommerce-loop-product__link' + ); - const isButton = event.target.classList.contains( 'button' ); + const isProductButton = + event.target.classList.contains( 'button' ) && + event.target.hasAttribute( 'data-product_id' ); - const isAddToCartButton = - event.target.classList.contains( 'add_to_cart_button' ) && - ! event.target.classList.contains( - 'product_type_variable' - ); + const isAddToCartButton = + event.target.classList.contains( 'add_to_cart_button' ) && + ! event.target.classList.contains( + 'product_type_variable' + ); - if ( ! targetLink && ( ! isButton || isAddToCartButton ) ) { - return; - } + if ( + ! targetLink && + ( ! isProductButton || isAddToCartButton ) + ) { + return; + } - tracker.eventHandler( 'select_content' )( { - product: getProductFromID( parseInt( productId ) ), - } ); + tracker.eventHandler( 'select_content' )( { + product: getProductFromID( parseInt( productId ) ), } ); } ); + } ); }; From e27406d8d0fbc49132c5a6ce95ea165c6919a579 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Tue, 27 Feb 2024 17:09:21 +0000 Subject: [PATCH 117/126] Fix spacing --- assets/js/src/integrations/classic.js | 64 +++++++++++++-------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 84465dbd..da2c0ff6 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -100,44 +100,44 @@ export const trackClassicIntegration = () => { * `select_content` event if the target link takes the user to the product page. */ document - .querySelectorAll( '.products .product:not(.wp-block-post)' ) - ?.forEach( ( item ) => { - // Get the Product ID from a child node containing the relevant attribute - const productId = item - .querySelector( 'a[data-product_id]' ) - ?.getAttribute( 'data-product_id' ); + .querySelectorAll( '.products .product:not(.wp-block-post)' ) + ?.forEach( ( item ) => { + // Get the Product ID from a child node containing the relevant attribute + const productId = item + .querySelector( 'a[data-product_id]' ) + ?.getAttribute( 'data-product_id' ); - if ( ! productId ) { - return; - } + if ( ! productId ) { + return; + } - item.addEventListener( 'click', ( event ) => { - // Return early if the user has clicked on an - // "Add to cart" button or anything other than a product link - const targetLink = event.target.closest( - '.woocommerce-loop-product__link' - ); + item.addEventListener( 'click', ( event ) => { + // Return early if the user has clicked on an + // "Add to cart" button or anything other than a product link + const targetLink = event.target.closest( + '.woocommerce-loop-product__link' + ); - const isProductButton = - event.target.classList.contains( 'button' ) && - event.target.hasAttribute( 'data-product_id' ); + const isProductButton = + event.target.classList.contains( 'button' ) && + event.target.hasAttribute( 'data-product_id' ); - const isAddToCartButton = - event.target.classList.contains( 'add_to_cart_button' ) && - ! event.target.classList.contains( - 'product_type_variable' - ); + const isAddToCartButton = + event.target.classList.contains( 'add_to_cart_button' ) && + ! event.target.classList.contains( + 'product_type_variable' + ); - if ( - ! targetLink && - ( ! isProductButton || isAddToCartButton ) - ) { - return; - } + if ( + ! targetLink && + ( ! isProductButton || isAddToCartButton ) + ) { + return; + } - tracker.eventHandler( 'select_content' )( { - product: getProductFromID( parseInt( productId ) ), + tracker.eventHandler( 'select_content' )( { + product: getProductFromID( parseInt( productId ) ), + } ); } ); } ); - } ); }; From 4cf040d0bc78ec20e8f8424693ec702f3da3b266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Tue, 27 Feb 2024 18:46:34 +0100 Subject: [PATCH 118/126] Implement MVP for consent mode support. Deny all for EEA visitors. Document the customization snippet. --- README.md | 23 ++++++++++++++++++++++ assets/js/src/config.js | 35 ++++++++++++++++++++++++++++++++++ assets/js/src/tracker/index.js | 11 ++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bc4879c..a785ee5b 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,26 @@ Alternatively, run `npm run lint:php:diff` to run coding standards checks agains ## Docs - [Hooks defined or used in Google Analytics for WooCommerce](./docs/Hooks.md) + +### Consent Mode + +The extension sets up [the default state of consent mode](https://developers.google.com/tag-platform/security/guides/consent?hl=en&consentmode=advanced#default-consent), denying all parameters for the EEA region. You can append or overwrite that configuration using the following snippet: + +```php +add_action( 'wp_enqueue_scripts', function () { + $customConsentConfig = " + gtag( 'consent', 'default', { + analytics_storage: 'granted', + ad_storage: 'granted', + ad_user_data: 'denied', + ad_personalization: 'denied', + region: 'ES', + } );"; + + wp_register_script( 'my-custom-consent-mode', '', array('woocommerce-google-analytics-integration'), null, false ); + wp_add_inline_script( 'my-custom-consent-mode', $customConsentConfig ); + wp_enqueue_script( 'my-custom-consent-mode' ); +} ); +``` + +After the page loads, the consent for particular parameters can be updated by other plugins or custom code, implementing UI for customer-facing configuration using [Google's consent API](https://developers.google.com/tag-platform/security/guides/consent?hl=en&consentmode=advanced#update-consent) (`gtag('consent', 'update', {…})`). diff --git a/assets/js/src/config.js b/assets/js/src/config.js index 3740c342..25c23773 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -9,3 +9,38 @@ export const { added_to_cart: addedToCart, order, } = wcgaiData; + +export const EEARegions = [ + 'AT', + 'BE', + 'BG', + 'HR', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HU', + 'IS', + 'IE', + 'IT', + 'LV', + 'LI', + 'LT', + 'LU', + 'MT', + 'NL', + 'NO', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'UK', + 'CH', +]; diff --git a/assets/js/src/tracker/index.js b/assets/js/src/tracker/index.js index f1244f9f..25b69071 100644 --- a/assets/js/src/tracker/index.js +++ b/assets/js/src/tracker/index.js @@ -1,4 +1,4 @@ -import { config } from '../config'; +import { config, EEARegions } from '../config'; import * as formatters from './data-formatting'; let instance; @@ -39,6 +39,15 @@ class Tracker { window[ config.tracker_function_name ] = gtag; + // Set up default consent state, denying all for EEA visitors. + gtag( 'consent', 'default', { + analytics_storage: 'denied', + ad_storage: 'denied', + ad_user_data: 'denied', + ad_personalization: 'denied', + region: EEARegions, + } ); + gtag( 'js', new Date() ); gtag( 'set', `developer_id.${ config.developer_id }`, true ); gtag( 'config', config.gtag_id, { From 6d1a82c7ac8ca39b7456b022f305aa8db7612af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Tue, 27 Feb 2024 20:51:57 +0100 Subject: [PATCH 119/126] Update custom consent snippet to cover the quirks of wcgaiData not being loaded otherwise. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a785ee5b..f1064b59 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ add_action( 'wp_enqueue_scripts', function () { region: 'ES', } );"; - wp_register_script( 'my-custom-consent-mode', '', array('woocommerce-google-analytics-integration'), null, false ); + wp_register_script( 'my-custom-consent-mode', '', array('woocommerce-google-analytics-integration'), null, [ 'in_footer' => true ] ); wp_add_inline_script( 'my-custom-consent-mode', $customConsentConfig ); wp_enqueue_script( 'my-custom-consent-mode' ); } ); From 352fa295175f2a7c4d563535de0b37942b910a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Wed, 28 Feb 2024 20:33:18 +0100 Subject: [PATCH 120/126] Set `wp_enqueue_scripts` priority Co-authored-by: martynmjones --- includes/class-wc-google-gtag-js.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 664e3b60..7e47425a 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -43,7 +43,7 @@ public function __construct( $options = array() ) { $this->map_actions(); // Setup frontend scripts - add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ), 5 ); add_action( 'wp_footer', array( $this, 'inline_script_data' ) ); } @@ -130,7 +130,7 @@ public function set_script_data( string $type, $data ): void { * * @return void */ - public function append_script_data( string $type, $data ): void { + public function append_script_data( string $type, $data ): void { if ( ! isset( $this->script_data[ $type ] ) ) { $this->script_data[ $type ] = array(); } From 8a7eb7bbc5df8d4e2e6d443e1d68387a83c9bb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Wed, 28 Feb 2024 20:38:56 +0100 Subject: [PATCH 121/126] Dequeue the WC Blocks GA integration srcipt not to let it register its `gtag` function so that we could provide a more detailed configuration. Co-authored-by: martynmjones --- includes/class-wc-google-analytics.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 852bb155..94701041 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -111,6 +111,13 @@ public function __construct() { // utm_nooverride parameter for Google AdWords add_filter( 'woocommerce_get_return_url', array( $this, 'utm_nooverride' ) ); + + // Dequeue the WooCommerce Blocks Google Analytics integration, + // not to let it register its `gtag` function so that we could provide a more detailed configuration. + add_action( 'wp_enqueue_scripts', function() { + wp_dequeue_script( 'wc-blocks-google-analytics' ); + }); + } /** From afa53c943d34c2d60212a119e291faddd924e92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Wed, 28 Feb 2024 22:44:38 +0100 Subject: [PATCH 122/126] Process tracker config separately from feeding data to classic tracking Set up tracker synchronously once the script is loaded (possibly in the ``). Track classic WC events only after data is ready and the document is loaded - at leas on `wp_footer`. Address https://github.com/woocommerce/woocommerce-google-analytics-integration/pull/322#issuecomment-1967508597 --- assets/js/src/config.js | 13 +------ assets/js/src/index.js | 4 +- assets/js/src/integrations/classic.js | 56 +++++++++++++++++---------- assets/js/src/utils/index.js | 6 ++- includes/class-wc-google-gtag-js.php | 47 +++++++++++++++------- 5 files changed, 78 insertions(+), 48 deletions(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index 3740c342..0477cb8b 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -1,11 +1,2 @@ -/* global wcgaiData */ -/* eslint-disable camelcase */ -export const { - config, - events, - cart, - products, - product, - added_to_cart: addedToCart, - order, -} = wcgaiData; +/* global wcgai */ +export const config = wcgai.config; diff --git a/assets/js/src/index.js b/assets/js/src/index.js index 72d2e091..7f198bf2 100644 --- a/assets/js/src/index.js +++ b/assets/js/src/index.js @@ -1,6 +1,6 @@ // Initialize tracking for classic WooCommerce pages -import { trackClassicIntegration } from './integrations/classic'; -trackClassicIntegration(); +import { trackClassicPages } from './integrations/classic'; +window.wcgai.trackClassicPages = trackClassicPages; // Initialize tracking for Block based WooCommerce pages import './integrations/blocks'; diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index da2c0ff6..6c731858 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -1,31 +1,39 @@ import { tracker } from '../tracker'; import { getProductFromID } from '../utils'; -import { - events, - cart, - products, - product, - addedToCart, - order, -} from '../config.js'; /** * The Google Analytics integration for classic WooCommerce pages * triggers events using three different methods. * - * 1. Automatically handle events listed in the global `wcgaiData.events` object. + * 1. Instantly handle events listed in the `events` object. * 2. Listen for custom events from WooCommerce core. * 3. Listen for various actions (i.e clicks) on specific elements. + * + * To be executed once data set is complete, and `document` is ready. + * + * @param {Object} data - The tracking data from the current page load, containing the following properties: + * @param {Object} data.events - An object containing the events to be instantly tracked. + * @param {Object} data.cart - The cart object. + * @param {Object[]} data.products - An array of all product from the current page. + * @param {Object} data.product - The single product object. + * @param {Object} data.added_to_cart - The product added to cart. + * @param {Object} data.order - The order object. */ - -export const trackClassicIntegration = () => { +export function trackClassicPages( { + events, + cart, + products, + product, + added_to_cart: addedToCart, + order, +} ) { + // Instantly track the events listed in the `events` object. const eventData = { storeCart: cart, products, product, order, }; - Object.values( events ?? {} ).forEach( ( eventName ) => { if ( eventName === 'add_to_cart' ) { tracker.eventHandler( eventName )( { product: addedToCart } ); @@ -34,6 +42,7 @@ export const trackClassicIntegration = () => { } } ); + // Handle runtime cart events. /** * Track the custom add to cart event dispatched by WooCommerce Core * @@ -45,7 +54,9 @@ export const trackClassicIntegration = () => { document.body.onadded_to_cart = ( e, fragments, cartHash, button ) => { tracker.eventHandler( 'add_to_cart' )( { product: getProductFromID( - parseInt( button[ 0 ].dataset.product_id ) + parseInt( button[ 0 ].dataset.product_id ), + products, + cart ), } ); }; @@ -71,7 +82,9 @@ export const trackClassicIntegration = () => { function removeFromCartHandler( element ) { tracker.eventHandler( 'remove_from_cart' )( { product: getProductFromID( - parseInt( element.target.dataset.product_id ) + parseInt( element.target.dataset.product_id ), + products, + cart ), } ); } @@ -95,10 +108,9 @@ export const trackClassicIntegration = () => { removeFromCartHandler( { target: args[ 3 ][ 0 ] } ); }; - /** - * Attaches click event listeners to non-block product listings that sends a - * `select_content` event if the target link takes the user to the product page. - */ + // Handle product selection events. + // Attach click event listeners to non-block product listings + // to send a `select_content` event if the target link takes the user to the product page. document .querySelectorAll( '.products .product:not(.wp-block-post)' ) ?.forEach( ( item ) => { @@ -136,8 +148,12 @@ export const trackClassicIntegration = () => { } tracker.eventHandler( 'select_content' )( { - product: getProductFromID( parseInt( productId ) ), + product: getProductFromID( + parseInt( productId ), + products, + cart + ), } ); } ); } ); -}; +} diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index dc7e5743..336abd9c 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -1,5 +1,5 @@ import { addAction, removeAction } from '@wordpress/hooks'; -import { config, products, cart } from '../config.js'; +import { config } from '../config.js'; /** * Formats data into the productFieldObject shape. @@ -158,9 +158,11 @@ const formatCategoryKey = ( index ) => { * Searches through the global wcgaiData.products object to find a single product by its ID * * @param {number} search The ID of the product to search for + * @param {Object[]} products The array of available products + * @param {Object} cart The cart object * @return {Object|undefined} The product object or undefined if not found */ -export const getProductFromID = ( search ) => { +export const getProductFromID = ( search, products, cart ) => { return ( cart?.items?.find( ( { id } ) => id === search ) ?? products?.find( ( { id } ) => id === search ) diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 7e47425a..7fcffadb 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -16,6 +16,9 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var string $script_handle Handle for the front end JavaScript file */ public $script_handle = 'woocommerce-google-analytics-integration'; + /** @var string $script_handle Handle for the event data inline script */ + public $data_script_handle = 'woocommerce-google-analytics-integration-data'; + /** @var string $script_data Data required for frontend event tracking */ private $script_data = array(); @@ -39,20 +42,20 @@ public function __construct( $options = array() ) { parent::__construct(); self::$options = $options; - $this->load_analytics_config(); $this->map_actions(); // Setup frontend scripts - add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ), 5 ); + add_action( 'wp_enqueue_scripts', array( $this, 'enquque_tracker' ), 5 ); add_action( 'wp_footer', array( $this, 'inline_script_data' ) ); } /** - * Register front end scripts and inline script data + * Register tracker scripts and its inline config. + * We need to execute tracker.js w/ `gtag` configuration before any trackable action may happen. * * @return void */ - public function register_scripts(): void { + public function enquque_tracker(): void { wp_enqueue_script( 'google-tag-manager', 'https://www.googletagmanager.com/gtag/js?id=' . self::get( 'ga_id' ), @@ -60,7 +63,8 @@ public function register_scripts(): void { null, false ); - + // tracker.js needs to be executed ASAP, the remaining bits for main.js could be deffered, + // but to reduce the traffic, we ship it all together. wp_enqueue_script( $this->script_handle, Plugin::get_instance()->get_js_asset_url( 'main.js' ), @@ -71,22 +75,39 @@ public function register_scripts(): void { Plugin::get_instance()->get_js_asset_version( 'main' ), true ); + // Provide tracker's configuration. + wp_add_inline_script( + $this->script_handle, + sprintf( + 'var wcgai = {config: %s};', + wp_json_encode( $this->get_analytics_config() ) + ), + 'before' + ); } /** - * Add inline script data to the front end + * Feed classic tracking with event data via inline script. + * Make sure it's added at the bottom of the page, so all the data is collected. * * @return void */ public function inline_script_data(): void { + wp_register_script( + $this->data_script_handle, + '', + array( $this->script_handle ), + ); + wp_add_inline_script( - $this->script_handle, + $this->data_script_handle, sprintf( - 'const wcgaiData = %s;', + 'wcgai.trackClassicPages( %s );', $this->get_script_data() - ), - 'before' + ) ); + + wp_enqueue_script( $this->data_script_handle ); } /** @@ -156,12 +177,12 @@ public static function tracker_function_name(): string { } /** - * Add Google Analytics configuration data to the script data + * Return Google Analytics configuration, for JS to read. * * @return void */ - public function load_analytics_config(): void { - $this->script_data['config'] = array( + public function get_analytics_config(): array { + return array( 'developer_id' => self::DEVELOPER_ID, 'gtag_id' => self::get( 'ga_id' ), 'tracker_function_name' => self::tracker_function_name(), From 372dc75e0e0284ad03b64e40ffe1aff29c121398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Thu, 29 Feb 2024 20:55:00 +0100 Subject: [PATCH 123/126] Update README after https://github.com/woocommerce/woocommerce-google-analytics-integration/pull/363 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1064b59..a785ee5b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ add_action( 'wp_enqueue_scripts', function () { region: 'ES', } );"; - wp_register_script( 'my-custom-consent-mode', '', array('woocommerce-google-analytics-integration'), null, [ 'in_footer' => true ] ); + wp_register_script( 'my-custom-consent-mode', '', array('woocommerce-google-analytics-integration'), null, false ); wp_add_inline_script( 'my-custom-consent-mode', $customConsentConfig ); wp_enqueue_script( 'my-custom-consent-mode' ); } ); From d8c750cb182013489e7de44db547a61f3f3b61fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Fri, 1 Mar 2024 17:47:16 +0100 Subject: [PATCH 124/126] Fix the consent region code from `UK` to `GB` Co-authored-by: martynmjones <40762232+martynmjones@users.noreply.github.com> --- assets/js/src/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/src/config.js b/assets/js/src/config.js index 1e504a3d..7c072e42 100644 --- a/assets/js/src/config.js +++ b/assets/js/src/config.js @@ -31,6 +31,6 @@ export const EEARegions = [ 'SI', 'ES', 'SE', - 'UK', + 'GB', 'CH', ]; From b2def8a74bdb07ee815e01c8c8eca544fb4e75df Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 4 Mar 2024 11:54:42 +0000 Subject: [PATCH 125/126] PHPCS --- .../class-wc-abstract-google-analytics-js.php | 24 +++++---- includes/class-wc-google-analytics.php | 14 ++--- includes/class-wc-google-gtag-js.php | 26 +++++---- tests/unit-tests/WCGoogleGtagJS.php | 53 ++++++++++--------- 4 files changed, 64 insertions(+), 53 deletions(-) diff --git a/includes/class-wc-abstract-google-analytics-js.php b/includes/class-wc-abstract-google-analytics-js.php index 650206ae..9e49138c 100644 --- a/includes/class-wc-abstract-google-analytics-js.php +++ b/includes/class-wc-abstract-google-analytics-js.php @@ -64,14 +64,14 @@ public function __construct() { public function attach_event_data(): void { add_action( 'wp_head', - function() { + function () { $this->set_script_data( 'cart', $this->get_formatted_cart() ); } ); add_action( 'woocommerce_before_single_product', - function() { + function () { global $product; $this->set_script_data( 'product', $this->get_formatted_product( $product ) ); } @@ -79,7 +79,7 @@ function() { add_action( 'woocommerce_add_to_cart', - function( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { + function ( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { $this->set_script_data( 'added_to_cart', $this->get_formatted_product( wc_get_product( $product_id ), $variation ) ); }, 10, @@ -88,7 +88,7 @@ function( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) { add_action( 'woocommerce_shop_loop_item_title', - function() { + function () { global $product; $this->append_script_data( 'products', $this->get_formatted_product( $product ) ); } @@ -96,7 +96,7 @@ function() { add_action( 'woocommerce_thankyou', - function( $order_id ) { + function ( $order_id ) { $this->set_script_data( 'order', $this->get_formatted_order( $order_id ) ); } ); @@ -162,12 +162,12 @@ public static function get_product_identifier( WC_Product $product ): string { public function get_formatted_cart(): array { return array( 'items' => array_map( - function( $item ) { + function ( $item ) { return array_merge( $this->get_formatted_product( $item['data'] ), array( 'quantity' => $item['quantity'], - 'prices' => array( + 'prices' => array( 'price' => $this->get_formatted_price( $item['line_total'] ), 'currency_minor_unit' => wc_get_price_decimals(), ), @@ -220,7 +220,7 @@ public function get_formatted_product( WC_Product $product, $variation = false ) $formatted['variation'] = implode( ', ', array_map( - function( $attribute, $value ) { + function ( $attribute, $value ) { return sprintf( '%s: %s', str_replace( 'attribute_', '', $attribute ), @@ -239,7 +239,7 @@ function( $attribute, $value ) { /** * Returns an array of order data in the required format * - * @param string $order_id The ID of the order + * @param int $order_id The ID of the order * * @return array */ @@ -250,7 +250,7 @@ public function get_formatted_order( int $order_id ): array { 'currency' => $order->get_currency(), 'value' => $this->get_formatted_price( $order->get_total() ), 'items' => array_map( - function( $item ) { + function ( $item ) { return array_merge( $this->get_formatted_product( $item->get_product() ), array( @@ -266,6 +266,8 @@ function( $item ) { /** * Formats a price the same way WooCommerce Blocks does * + * @param mixed $value The price value for format + * * @return int */ public function get_formatted_price( $value ): int { @@ -303,7 +305,7 @@ public function schema_callback(): array { 'description' => __( 'The formatted product identifier to use in Google Analytics events.', 'woocommerce-google-analytics-integration' ), 'type' => 'string', 'readonly' => true, - ) + ), ); } diff --git a/includes/class-wc-google-analytics.php b/includes/class-wc-google-analytics.php index 1e0ab27d..b95253d9 100644 --- a/includes/class-wc-google-analytics.php +++ b/includes/class-wc-google-analytics.php @@ -67,10 +67,12 @@ public function __construct() { // Dequeue the WooCommerce Blocks Google Analytics integration, // not to let it register its `gtag` function so that we could provide a more detailed configuration. - add_action( 'wp_enqueue_scripts', function() { - wp_dequeue_script( 'wc-blocks-google-analytics' ); - }); - + add_action( + 'wp_enqueue_scripts', + function () { + wp_dequeue_script( 'wc-blocks-google-analytics' ); + } + ); } /** @@ -80,7 +82,7 @@ public function __construct() { */ public function universal_analytics_upgrade_notice() { if ( 'ua' === substr( strtolower( $this->get_option( 'ga_id' ) ), 0, 2 ) ) { - echo sprintf( + printf( '

%2$s

', 'notice notice-error', sprintf( @@ -244,7 +246,7 @@ public function show_options_info() { * @return array Updated WC Tracker data. */ public function track_settings( $data ) { - $settings = $this->settings; + $settings = $this->settings; $data['wc-google-analytics'] = array( 'standard_tracking_enabled' => $settings['ga_standard_tracking_enabled'], 'support_display_advertising' => $settings['ga_support_display_advertising'], diff --git a/includes/class-wc-google-gtag-js.php b/includes/class-wc-google-gtag-js.php index 7b4c2ab7..34464266 100644 --- a/includes/class-wc-google-gtag-js.php +++ b/includes/class-wc-google-gtag-js.php @@ -24,12 +24,12 @@ class WC_Google_Gtag_JS extends WC_Abstract_Google_Analytics_JS { /** @var array $mappings A map of the GA4 events and the classic WooCommerce hooks that trigger them */ private $mappings = array( - 'begin_checkout' => 'woocommerce_before_checkout_form', - 'purchase' => 'woocommerce_thankyou', - 'view_item_list' => 'woocommerce_before_shop_loop_item', - 'add_to_cart' => 'woocommerce_add_to_cart', - 'remove_from_cart' => 'woocommerce_cart_item_removed', - 'view_item' => 'woocommerce_after_single_product', + 'begin_checkout' => 'woocommerce_before_checkout_form', + 'purchase' => 'woocommerce_thankyou', + 'view_item_list' => 'woocommerce_before_shop_loop_item', + 'add_to_cart' => 'woocommerce_add_to_cart', + 'remove_from_cart' => 'woocommerce_cart_item_removed', + 'view_item' => 'woocommerce_after_single_product', ); /** @@ -97,6 +97,10 @@ public function inline_script_data(): void { $this->data_script_handle, '', array( $this->script_handle ), + null, + array( + 'in_footer' => true, + ) ); wp_add_inline_script( @@ -118,11 +122,11 @@ public function inline_script_data(): void { public function map_actions(): void { array_walk( $this->mappings, - function( $hook, $gtag_event ) { + function ( $hook, $gtag_event ) { add_action( $hook, - function() use ( $gtag_event ) { - if ( ! in_array( $gtag_event, $this->script_data['events'] ?? [] ) ) { + function () use ( $gtag_event ) { + if ( ! in_array( $gtag_event, $this->script_data['events'] ?? [], true ) ) { $this->append_script_data( 'events', $gtag_event ); } } @@ -179,7 +183,7 @@ public static function tracker_function_name(): string { /** * Return Google Analytics configuration, for JS to read. * - * @return void + * @return array */ public function get_analytics_config(): array { return array( @@ -220,7 +224,7 @@ public static function get_enabled_events(): array { 'begin_checkout' => 'ga_enhanced_checkout_process_enabled', ); - foreach( $settings as $event => $setting_name ) { + foreach ( $settings as $event => $setting_name ) { if ( 'yes' === self::get( $setting_name ) ) { $events[] = $event; } diff --git a/tests/unit-tests/WCGoogleGtagJS.php b/tests/unit-tests/WCGoogleGtagJS.php index 2ce828cc..fd42f9cb 100644 --- a/tests/unit-tests/WCGoogleGtagJS.php +++ b/tests/unit-tests/WCGoogleGtagJS.php @@ -63,7 +63,7 @@ public function test_get_product_identifier() { add_filter( 'woocommerce_ga_product_identifier', - function( $product ) { + function ( $product ) { return 'filtered'; } ); @@ -78,27 +78,27 @@ function( $product ) { * @return void */ public function test_map_actions(): void { - $gtag = new WC_Google_Gtag_JS; + $gtag = new WC_Google_Gtag_JS(); $mappings = array( - 'begin_checkout' => 'woocommerce_before_checkout_form', - 'purchase' => 'woocommerce_thankyou', - 'view_item_list' => 'woocommerce_before_shop_loop_item', - 'add_to_cart' => 'woocommerce_add_to_cart', - 'remove_from_cart' => 'woocommerce_cart_item_removed', - 'view_item' => 'woocommerce_after_single_product', + 'begin_checkout' => 'woocommerce_before_checkout_form', + 'purchase' => 'woocommerce_thankyou', + 'view_item_list' => 'woocommerce_before_shop_loop_item', + 'add_to_cart' => 'woocommerce_add_to_cart', + 'remove_from_cart' => 'woocommerce_cart_item_removed', + 'view_item' => 'woocommerce_after_single_product', ); - + array_map( 'remove_all_actions', $mappings ); $gtag->map_actions(); - foreach( $mappings as $event => $hook ) { + foreach ( $mappings as $event => $hook ) { do_action( $hook ); - + $script_data = json_decode( $gtag->get_script_data(), true ); - - $this->assertTrue( in_array( $event, $script_data['events'] ) ); - + + $this->assertTrue( in_array( $event, $script_data['events'], true ) ); + // Reset event data $gtag->set_script_data( 'events', array() ); } @@ -110,11 +110,11 @@ public function test_map_actions(): void { * @return void */ public function test_set_script_data(): void { - $gtag = new WC_Google_Gtag_JS; + $gtag = new WC_Google_Gtag_JS(); $example_data = array( - 'key' => 'value' + 'key' => 'value', ); - + $gtag->set_script_data( 'test', $example_data ); $script_data = json_decode( $gtag->get_script_data(), true ); @@ -127,8 +127,8 @@ public function test_set_script_data(): void { * @return void */ public function test_append_script_data(): void { - $gtag = new WC_Google_Gtag_JS; - + $gtag = new WC_Google_Gtag_JS(); + $gtag->append_script_data( 'test', 'first' ); $gtag->append_script_data( 'test', 'second' ); @@ -143,13 +143,16 @@ public function test_append_script_data(): void { * @return void */ public function test_tracker_var(): void { - $gtag = new WC_Google_Gtag_JS; + $gtag = new WC_Google_Gtag_JS(); $this->assertEquals( $gtag->tracker_function_name(), 'gtag' ); - - add_filter( 'woocommerce_gtag_tracker_variable', function( $var ) { - return 'filtered'; - } ); + + add_filter( + 'woocommerce_gtag_tracker_variable', + function ( $variable ) { + return 'filtered'; + } + ); $this->assertEquals( $gtag->tracker_function_name(), 'filtered' ); } @@ -169,7 +172,7 @@ public function test_get_enabled_events(): void { 'begin_checkout' => 'ga_enhanced_checkout_process_enabled', ); - foreach( $settings as $event => $option_name ) { + foreach ( $settings as $event => $option_name ) { $gtag = new WC_Google_Gtag_JS( array( $option_name => 'yes' ) ); $this->assertEquals( $gtag->get_enabled_events(), array( $event ) ); } From b47bf9933d6d565ab2abe9898ad1626333730641 Mon Sep 17 00:00:00 2001 From: martynmjones Date: Mon, 4 Mar 2024 12:01:50 +0000 Subject: [PATCH 126/126] Fix JSDoc spacing --- assets/js/src/integrations/classic.js | 22 +++++++-------- assets/js/src/tracker/data-formatting.js | 36 ++++++++++++------------ assets/js/src/utils/index.js | 16 +++++------ 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/assets/js/src/integrations/classic.js b/assets/js/src/integrations/classic.js index 6c731858..37df69fb 100644 --- a/assets/js/src/integrations/classic.js +++ b/assets/js/src/integrations/classic.js @@ -11,13 +11,13 @@ import { getProductFromID } from '../utils'; * * To be executed once data set is complete, and `document` is ready. * - * @param {Object} data - The tracking data from the current page load, containing the following properties: - * @param {Object} data.events - An object containing the events to be instantly tracked. - * @param {Object} data.cart - The cart object. - * @param {Object[]} data.products - An array of all product from the current page. - * @param {Object} data.product - The single product object. - * @param {Object} data.added_to_cart - The product added to cart. - * @param {Object} data.order - The order object. + * @param {Object} data - The tracking data from the current page load, containing the following properties: + * @param {Object} data.events - An object containing the events to be instantly tracked. + * @param {Object} data.cart - The cart object. + * @param {Object[]} data.products - An array of all product from the current page. + * @param {Object} data.product - The single product object. + * @param {Object} data.added_to_cart - The product added to cart. + * @param {Object} data.order - The order object. */ export function trackClassicPages( { events, @@ -46,10 +46,10 @@ export function trackClassicPages( { /** * Track the custom add to cart event dispatched by WooCommerce Core * - * @param {Event} e - The event object - * @param {Object} fragments - An object containing fragments of the updated cart. - * @param {string} cartHash - A string representing the hash of the cart after the update. - * @param {HTMLElement[]} button - An array of HTML elements representing the add to cart button. + * @param {Event} e - The event object + * @param {Object} fragments - An object containing fragments of the updated cart. + * @param {string} cartHash - A string representing the hash of the cart after the update. + * @param {HTMLElement[]} button - An array of HTML elements representing the add to cart button. */ document.body.onadded_to_cart = ( e, fragments, cartHash, button ) => { tracker.eventHandler( 'add_to_cart' )( { diff --git a/assets/js/src/tracker/data-formatting.js b/assets/js/src/tracker/data-formatting.js index 93dc0c6c..ab7d4283 100644 --- a/assets/js/src/tracker/data-formatting.js +++ b/assets/js/src/tracker/data-formatting.js @@ -12,8 +12,8 @@ import { /** * Formats data for the view_item_list event * - * @param {Object} params The function params - * @param {Array} params.products The products to track + * @param {Object} params The function params + * @param {Array} params.products The products to track * @param {string} [params.listName] The name of the list in which the item was presented to the user. */ export const view_item_list = ( { @@ -40,8 +40,8 @@ export const view_item_list = ( { /** * Formats data for the add_to_cart event * - * @param {Object} params The function params - * @param {Array} params.product The product to track + * @param {Object} params The function params + * @param {Array} params.product The product to track * @param {number} [params.quantity=1] The quantity of that product in the cart. */ export const add_to_cart = ( { product, quantity = 1 } ) => { @@ -53,8 +53,8 @@ export const add_to_cart = ( { product, quantity = 1 } ) => { /** * Formats data for the remove_from_cart event * - * @param {Object} params The function params - * @param {Array} params.product The product to track + * @param {Object} params The function params + * @param {Array} params.product The product to track * @param {number} [params.quantity=1] The quantity of that product in the cart. */ export const remove_from_cart = ( { product, quantity = 1 } ) => { @@ -66,8 +66,8 @@ export const remove_from_cart = ( { product, quantity = 1 } ) => { /** * Tracks change_cart_quantity event * - * @param {Object} params The function params - * @param {Array} params.product The product to track + * @param {Object} params The function params + * @param {Array} params.product The product to track * @param {number} [params.quantity=1] The quantity of that product in the cart. */ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { @@ -84,7 +84,7 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => { /** * Formats data for the begin_checkout event * - * @param {Object} params The function params + * @param {Object} params The function params * @param {Object} params.storeCart The cart object */ export const begin_checkout = ( { storeCart } ) => { @@ -102,7 +102,7 @@ export const begin_checkout = ( { storeCart } ) => { /** * Formats data for the add_shipping_info event * - * @param {Object} params The function params + * @param {Object} params The function params * @param {Object} params.storeCart The cart object */ export const add_shipping_info = ( { storeCart } ) => { @@ -124,7 +124,7 @@ export const add_shipping_info = ( { storeCart } ) => { /** * Formats data for the select_content event. * - * @param {Object} params The function params + * @param {Object} params The function params * @param {Object} params.product The product to track */ export const select_content = ( { product } ) => { @@ -137,7 +137,7 @@ export const select_content = ( { product } ) => { /** * Formats data for the search event. * - * @param {Object} params The function params + * @param {Object} params The function params * @param {string} params.searchTerm The search term to track */ export const search = ( { searchTerm } ) => { @@ -149,8 +149,8 @@ export const search = ( { searchTerm } ) => { /** * Formats data for the view_item event * - * @param {Object} params The function params - * @param {Object} params.product The product to track + * @param {Object} params The function params + * @param {Object} params.product The product to track * @param {string} [params.listName] The name of the list in which the item was presented to the user. */ export const view_item = ( { @@ -169,7 +169,7 @@ export const view_item = ( { /** * Formats order data for the purchase event * - * @param {Object} params The function params + * @param {Object} params The function params * @param {Object} params.order The order object */ export const purchase = ( { order } ) => { @@ -185,8 +185,8 @@ export const purchase = ( { order } ) => { /** * Formats data for the exception event * - * @param {Object} params The function params - * @param {string} params.status The status of the exception. It should be "error" for tracking it. + * @param {Object} params The function params + * @param {string} params.status The status of the exception. It should be "error" for tracking it. * @param {string} params.content The exception description */ export const trackException = ( { status, content } ) => { @@ -201,7 +201,7 @@ export const trackException = ( { status, content } ) => { /** * Track an event using the global gtag function. * - * @param {string} eventName - Name of the event to track + * @param {string} eventName - Name of the event to track * @param {Object} [eventParams] - Props to send within the event */ export const trackEvent = ( eventName, eventParams ) => { diff --git a/assets/js/src/utils/index.js b/assets/js/src/utils/index.js index 336abd9c..7a3c0f22 100644 --- a/assets/js/src/utils/index.js +++ b/assets/js/src/utils/index.js @@ -5,7 +5,7 @@ import { config } from '../config.js'; * Formats data into the productFieldObject shape. * * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#product-data - * @param {Object} product - The product data + * @param {Object} product - The product data * @param {number} quantity - The product quantity * * @return {Object} The product data @@ -33,7 +33,7 @@ export const getProductFieldObject = ( product, quantity ) => { * Formats data into the impressionFieldObject shape. * * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#impression-data - * @param {Object} product - The product data + * @param {Object} product - The product data * @param {string} listName - The list for this product * * @return {Object} - The product impression data @@ -54,7 +54,7 @@ export const getProductImpressionObject = ( product, listName ) => { /** * Returns the price of a product formatted as a string. * - * @param {string} price - The price to parse + * @param {string} price - The price to parse * @param {number} [currencyMinorUnit=2] - The number decimals to show in the currency * * @return {number} - The price of the product formatted @@ -66,9 +66,9 @@ export const formatPrice = ( price, currencyMinorUnit = 2 ) => { /** * Removes previous actions with the same hookName and namespace and then adds the new action. * - * @param {string} hookName The hook name for the action - * @param {string} namespace The unique namespace for the action - * @param {Function} callback The function to run when the action happens. + * @param {string} hookName The hook name for the action + * @param {string} namespace The unique namespace for the action + * @param {Function} callback The function to run when the action happens. */ export const addUniqueAction = ( hookName, namespace, callback ) => { removeAction( hookName, namespace ); @@ -157,9 +157,9 @@ const formatCategoryKey = ( index ) => { /** * Searches through the global wcgaiData.products object to find a single product by its ID * - * @param {number} search The ID of the product to search for + * @param {number} search The ID of the product to search for * @param {Object[]} products The array of available products - * @param {Object} cart The cart object + * @param {Object} cart The cart object * @return {Object|undefined} The product object or undefined if not found */ export const getProductFromID = ( search, products, cart ) => {