diff --git a/includes/Core/Tags/GTag.php b/includes/Core/Tags/GTag.php index 38d6596f058..6903b3932a4 100644 --- a/includes/Core/Tags/GTag.php +++ b/includes/Core/Tags/GTag.php @@ -116,9 +116,7 @@ protected function enqueue_gtag_script() { return; } - // Load the GTag scripts using the first tag ID - it doesn't matter which is used, all registered tags will be set up with a - // config command regardless of which is used to load the source. - $gtag_src = 'https://www.googletagmanager.com/gtag/js?id=' . rawurlencode( $this->tags[0]['tag_id'] ); + $gtag_src = $this->get_gtag_src(); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_script( self::HANDLE, $gtag_src, false, null, false ); @@ -126,6 +124,11 @@ protected function enqueue_gtag_script() { // Note that `gtag()` may already be defined via the `Consent_Mode` output, but this is safe to call multiple times. wp_add_inline_script( self::HANDLE, 'window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}' ); + + foreach ( $this->commands as $command ) { + wp_add_inline_script( self::HANDLE, $this->get_gtag_call_for_command( $command ), $command['position'] ); + } + wp_add_inline_script( self::HANDLE, 'gtag("js", new Date());' ); wp_add_inline_script( self::HANDLE, 'gtag("set", "developer_id.dZTNiMT", true);' ); // Site Kit developer ID. @@ -133,10 +136,6 @@ protected function enqueue_gtag_script() { wp_add_inline_script( self::HANDLE, $this->get_gtag_call_for_tag( $tag ) ); } - foreach ( $this->commands as $command ) { - wp_add_inline_script( self::HANDLE, $this->get_gtag_call_for_command( $command ), $command['position'] ); - } - $filter_google_gtagjs = function ( $tag, $handle ) { if ( self::HANDLE !== $handle ) { return $tag; @@ -188,4 +187,22 @@ function( $arg ) { return sprintf( 'gtag(%s);', implode( ',', $gtag_args ) ); } + + /** + * Returns the gtag source URL. + * + * @since n.e.x.t + * + * @return string|false The gtag source URL. False if no tags are added. + */ + public function get_gtag_src() { + if ( empty( $this->tags ) ) { + return false; + } + + // Load the GTag scripts using the first tag ID - it doesn't matter which is used, + // all registered tags will be set up with a config command regardless + // of which is used to load the source. + return 'https://www.googletagmanager.com/gtag/js?id=' . rawurlencode( $this->tags[0]['tag_id'] ); + } } diff --git a/includes/Modules/Analytics_4/Web_Tag.php b/includes/Modules/Analytics_4/Web_Tag.php index 3f9cbaddb4d..99c9f823f82 100644 --- a/includes/Modules/Analytics_4/Web_Tag.php +++ b/includes/Modules/Analytics_4/Web_Tag.php @@ -11,6 +11,7 @@ namespace Google\Site_Kit\Modules\Analytics_4; use Google\Site_Kit\Core\Modules\Tags\Module_Web_Tag; +use Google\Site_Kit\Core\Tags\GTag; use Google\Site_Kit\Core\Tags\Tag_With_DNS_Prefetch_Trait; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; @@ -101,13 +102,8 @@ protected function get_tag_blocked_on_consent_deprecated_args() { * @since 1.31.0 */ public function register() { - add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_gtag_script' ), 20 ); - add_filter( - 'wp_resource_hints', - $this->get_dns_prefetch_hints_callback( '//www.googletagmanager.com' ), - 10, - 2 - ); + add_action( 'googlesitekit_setup_gtag', $this->get_method_proxy( 'setup_gtag' ) ); + $this->do_init_tag_action(); } @@ -121,18 +117,15 @@ protected function render() { } /** - * Enqueues gtag script. + * Configures gtag script. * * @since 1.24.0 + * @since n.e.x.t Renamed and refactored to use new GTag infrastructure. + * + * @param GTag $gtag GTag instance. */ - protected function enqueue_gtag_script() { + protected function setup_gtag( GTag $gtag ) { $gtag_opt = $this->get_tag_config(); - $gtag_src = 'https://www.googletagmanager.com/gtag/js?id=' . rawurlencode( $this->tag_id ); - - // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion - wp_enqueue_script( 'google_gtagjs', $gtag_src, false, null, false ); - wp_script_add_data( 'google_gtagjs', 'script_execution', 'async' ); - wp_add_inline_script( 'google_gtagjs', 'window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}' ); /** * Filters the gtag configuration options for the Analytics snippet. @@ -148,66 +141,40 @@ protected function enqueue_gtag_script() { $gtag_opt = apply_filters( 'googlesitekit_gtag_opt', $gtag_opt ); if ( ! empty( $gtag_opt['linker'] ) ) { - $linker = wp_json_encode( $gtag_opt['linker'] ); - $linker = sprintf( "gtag('set', 'linker', %s );", $linker ); - wp_add_inline_script( 'google_gtagjs', $linker ); - } + $gtag->add_command( 'set', array( 'linker', $gtag_opt['linker'] ) ); - unset( $gtag_opt['linker'] ); + unset( $gtag_opt['linker'] ); + } - wp_add_inline_script( 'google_gtagjs', 'gtag("js", new Date());' ); - wp_add_inline_script( 'google_gtagjs', 'gtag("set", "developer_id.dZTNiMT", true);' ); // Site Kit developer ID. + $gtag->add_tag( $this->tag_id, $gtag_opt ); - $this->add_inline_config( $this->tag_id, $gtag_opt ); - $this->add_inline_ads_conversion_id_config(); + // TODO: Lift this out to the Ads module when it's ready. + if ( $this->ads_conversion_id ) { + $gtag->add_tag( $this->ads_conversion_id ); + } - $filter_google_gtagjs = function ( $tag, $handle ) use ( $gtag_src ) { - if ( 'google_gtagjs' !== $handle ) { + $filter_google_gtagjs = function ( $tag, $handle ) use ( $gtag ) { + if ( GTag::HANDLE !== $handle ) { return $tag; } - $snippet_comment_begin = sprintf( "\n\n", esc_html__( 'Google Analytics snippet added by Site Kit', 'google-site-kit' ) ); - $snippet_comment_end = sprintf( "\n\n", esc_html__( 'End Google Analytics snippet added by Site Kit', 'google-site-kit' ) ); + // Retain this comment for detection of Site Kit placed tag. + $snippet_comment = sprintf( "\n\n", esc_html__( 'Google Analytics snippet added by Site Kit', 'google-site-kit' ) ); $block_on_consent_attrs = $this->get_tag_blocked_on_consent_attribute(); if ( $block_on_consent_attrs ) { + $gtag_src = $gtag->get_gtag_src(); + $tag = $this->add_legacy_block_on_consent_attributes( $tag, $gtag_src, $block_on_consent_attrs ); } - return $snippet_comment_begin . $tag . $snippet_comment_end; + return $snippet_comment . $tag; }; add_filter( 'script_loader_tag', $filter_google_gtagjs, 10, 2 ); } - /** - * Adds an inline script to configure ads conversion tracking. - * - * @since 1.32.0 - */ - protected function add_inline_ads_conversion_id_config() { - if ( $this->ads_conversion_id ) { - $this->add_inline_config( $this->ads_conversion_id, array() ); - } - } - - /** - * Adds an inline script to configure provided tag including configuration options. - * - * @since 1.113.0 - * - * @param string $tag_id The tag ID to add config for. - * @param array $gtag_opt The gtag configuration. - */ - protected function add_inline_config( $tag_id, $gtag_opt ) { - $config = ! empty( $gtag_opt ) - ? sprintf( 'gtag("config", "%s", %s);', esc_js( $tag_id ), wp_json_encode( $gtag_opt ) ) - : sprintf( 'gtag("config", "%s");', esc_js( $tag_id ) ); - - wp_add_inline_script( 'google_gtagjs', $config ); - } - /** * Gets the tag config as used in the gtag data vars. * diff --git a/tests/phpunit/integration/Core/Tags/GTagTest.php b/tests/phpunit/integration/Core/Tags/GTagTest.php index d812513ef4e..2709808187a 100644 --- a/tests/phpunit/integration/Core/Tags/GTagTest.php +++ b/tests/phpunit/integration/Core/Tags/GTagTest.php @@ -71,8 +71,8 @@ public function test_gtag_script_contains_gtag_call() { $script = $scripts->registered[ GTag::HANDLE ]; // Assert the array of inline script data contains the necessary gtag config line. - // Should be in index 4, the first registered gtag. - $this->assertEquals( 'gtag("config", "' . static::TEST_TAG_ID_1 . '");', $script->extra['after'][4] ); + // Should be in index 5, the first registered gtag. + $this->assertEquals( 'gtag("config", "' . static::TEST_TAG_ID_1 . '");', $script->extra['after'][5] ); } public function test_gtag_script_commands() { @@ -83,7 +83,7 @@ public function test_gtag_script_commands() { $this->assertEquals( sprintf( 'gtag(%s");', '"' . static::TEST_COMMAND_1 . '","' . implode( '","', static::TEST_COMMAND_1_PARAMS ) ), $script->extra['before'][1] ); // Test commands in the after position. - $this->assertEquals( sprintf( 'gtag(%s);', '"' . static::TEST_COMMAND_2 . '",' . json_encode( static::TEST_COMMAND_2_PARAMS[0] ) ), $script->extra['after'][5] ); + $this->assertEquals( sprintf( 'gtag(%s);', '"' . static::TEST_COMMAND_2 . '",' . json_encode( static::TEST_COMMAND_2_PARAMS[0] ) ), $script->extra['after'][2] ); } public function test_gtag_with_tag_config() { @@ -99,8 +99,25 @@ public function test_gtag_with_tag_config() { $script = $scripts->registered[ GTag::HANDLE ]; // Assert the array of inline script data contains the necessary gtag entry for the second script. - // Should be in index 5, immediately after the first registered gtag. - $this->assertEquals( 'gtag("config", "' . static::TEST_TAG_ID_2 . '", ' . json_encode( self::TEST_TAG_ID_2_CONFIG ) . ');', $script->extra['after'][5] ); + // Should be in index 6, immediately after the first registered gtag. + $this->assertEquals( 'gtag("config", "' . static::TEST_TAG_ID_2 . '", ' . json_encode( self::TEST_TAG_ID_2_CONFIG ) . ');', $script->extra['after'][6] ); + } + + public function test_get_gtag_src() { + $this->assertEquals( 'https://www.googletagmanager.com/gtag/js?id=' . static::TEST_TAG_ID_1, $this->gtag->get_gtag_src() ); + + // Reset the GTag instance. + $this->gtag = new GTag(); + $this->gtag->register(); + + // Verify that this returns `false` when no tags are added. + $this->assertFalse( $this->gtag->get_gtag_src() ); + + // Add a different tag ID. + $this->gtag->add_tag( static::TEST_TAG_ID_2 ); + + // Verify that this returns the correct URL for the different tag ID. + $this->assertEquals( 'https://www.googletagmanager.com/gtag/js?id=' . static::TEST_TAG_ID_2, $this->gtag->get_gtag_src() ); } } diff --git a/tests/phpunit/integration/Modules/Analytics_4Test.php b/tests/phpunit/integration/Modules/Analytics_4Test.php index de21defaead..d6d2fa418e7 100644 --- a/tests/phpunit/integration/Modules/Analytics_4Test.php +++ b/tests/phpunit/integration/Modules/Analytics_4Test.php @@ -26,6 +26,7 @@ use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit\Core\Storage\User_Options; +use Google\Site_Kit\Core\Tags\GTag; use Google\Site_Kit\Modules\AdSense\Settings as AdSense_Settings; use Google\Site_Kit\Modules\Analytics_4; use Google\Site_Kit\Modules\Analytics_4\Custom_Dimensions_Data_Available; @@ -136,6 +137,8 @@ public function set_up() { $this->authentication = new Authentication( $this->context, $this->options, $this->user_options ); $this->analytics = new Analytics_4( $this->context, $this->options, $this->user_options, $this->authentication ); wp_set_current_user( $this->user->ID ); + remove_all_actions( 'wp_enqueue_scripts' ); + ( new GTag() )->register(); } public function test_register() { @@ -2792,6 +2795,9 @@ public function test_tracking_opt_out_snippet( $settings, $logged_in, $is_tracki // Remove irrelevant script from throwing errors in CI from readfile(). remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); + // Prevent test from failing in CI with deprecation notice. + remove_action( 'wp_print_styles', 'print_emoji_styles' ); + // Set the current user (can be 0 for no user) $role = $is_content_creator ? 'administrator' : 'subscriber'; $user = $logged_in ? @@ -2803,6 +2809,7 @@ public function test_tracking_opt_out_snippet( $settings, $logged_in, $is_tracki $analytics->get_settings()->set( $settings ); remove_all_actions( 'template_redirect' ); + remove_all_actions( 'googlesitekit_setup_gtag' ); $analytics->register(); do_action( 'template_redirect' ); @@ -3501,10 +3508,10 @@ public function test_register_template_redirect_non_amp() { remove_all_actions( 'template_redirect' ); $analytics->register(); - remove_all_actions( 'wp_enqueue_scripts' ); + remove_all_actions( 'googlesitekit_setup_gtag' ); do_action( 'template_redirect' ); - $this->assertFalse( has_action( 'wp_enqueue_scripts' ) ); + $this->assertFalse( has_action( 'googlesitekit_setup_gtag' ) ); $analytics->get_settings()->merge( array( @@ -3516,19 +3523,19 @@ public function test_register_template_redirect_non_amp() { ); do_action( 'template_redirect' ); - $this->assertTrue( has_action( 'wp_enqueue_scripts' ) ); + $this->assertTrue( has_action( 'googlesitekit_setup_gtag' ) ); // Tag not hooked when blocked. - remove_all_actions( 'wp_enqueue_scripts' ); + remove_all_actions( 'googlesitekit_setup_gtag' ); add_filter( 'googlesitekit_analytics-4_tag_blocked', '__return_true' ); do_action( 'template_redirect' ); - $this->assertFalse( has_action( 'wp_enqueue_scripts' ) ); + $this->assertFalse( has_action( 'googlesitekit_setup_gtag' ) ); // Tag hooked when only AMP blocked. add_filter( 'googlesitekit_analytics-4_tag_blocked', '__return_false' ); add_filter( 'googlesitekit_analytics-4_tag_amp_blocked', '__return_true' ); do_action( 'template_redirect' ); - $this->assertTrue( has_action( 'wp_enqueue_scripts' ) ); + $this->assertTrue( has_action( 'googlesitekit_setup_gtag' ) ); } /** @@ -3555,7 +3562,6 @@ public function test_block_on_consent_non_amp( $test_parameters ) { wp_scripts()->queue = array(); wp_scripts()->done = array(); remove_all_actions( 'template_redirect' ); - remove_all_actions( 'wp_enqueue_scripts' ); $analytics->register(); // Hook `wp_print_head_scripts` on placeholder action for capturing.