forked from google/site-kit-wp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request google#8406 from google/issue/8269-decoupled-gtag-…
…infra Issue / 8269 Decoupled GTag Infra
- Loading branch information
Showing
3 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<?php | ||
/** | ||
* Class Google\Site_Kit\Core\Tags\GTag | ||
* | ||
* @package Google\Site_Kit | ||
* @copyright 2024 Google LLC | ||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 | ||
* @link https://sitekit.withgoogle.com | ||
*/ | ||
|
||
namespace Google\Site_Kit\Core\Tags; | ||
|
||
use Google\Site_Kit\Core\Util\Method_Proxy_Trait; | ||
|
||
/** | ||
* Class to handle gtag rendering across modules. | ||
* | ||
* @since n.e.x.t | ||
* @access public | ||
* @ignore | ||
*/ | ||
class GTag { | ||
use Method_Proxy_Trait; | ||
|
||
const HANDLE = 'google_gtagjs'; | ||
/** | ||
* Holds an array of gtag ID's and their inline config elements. | ||
* | ||
* @var array $tags Array of tag ID's and their configs. | ||
*/ | ||
private $tags = array(); | ||
/** | ||
* Holds an array of gtag commands, their parameters and command positions. | ||
* | ||
* @var array $commands Array of gtag config commands. | ||
*/ | ||
private $commands = array(); | ||
|
||
/** | ||
* Register method called after class instantiation. | ||
* | ||
* @since n.e.x.t | ||
* @access public | ||
* | ||
* @return void | ||
*/ | ||
public function register() { | ||
add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_gtag_script' ), 20 ); | ||
|
||
add_filter( | ||
'wp_resource_hints', | ||
function ( $urls, $relation_type ) { | ||
if ( 'dns-prefetch' === $relation_type ) { | ||
$urls[] = '//www.googletagmanager.com'; | ||
} | ||
|
||
return $urls; | ||
}, | ||
10, | ||
2 | ||
); | ||
} | ||
|
||
/** | ||
* Method to add a gtag ID and config for output rendering. | ||
* | ||
* @since n.e.x.t | ||
* @access public | ||
* | ||
* @param string $tag_id The gtag ID. | ||
* @param array $config Array of inline gtag config values. | ||
* | ||
* @return void | ||
*/ | ||
public function add_tag( $tag_id, $config = array() ) { | ||
$this->tags[] = array( | ||
'tag_id' => $tag_id, | ||
'config' => $config, | ||
); | ||
} | ||
|
||
/** | ||
* Method to add a gtag command, associated parameters and output position. | ||
* | ||
* @since n.e.x.t | ||
* @access public | ||
* | ||
* @param string $command The gtag command to add. | ||
* @param array $parameters Array of command parameters. | ||
* @param string $position Position of command. "before|after". | ||
* | ||
* @return void | ||
*/ | ||
public function add_command( $command, $parameters, $position = 'after' ) { | ||
$this->commands[] = array( | ||
'command' => $command, // e.g. 'config', 'event', etc. | ||
'parameters' => $parameters, // e.g. array( 'send_to', 'AW-123456789' ). | ||
'position' => $position, // e.g. 'after', 'before'. This determines the position of the inline script relative to the gtag.js script. | ||
); | ||
} | ||
|
||
/** | ||
* Method used to enqueue the gtag script along with additional tags, | ||
* configs and commands. | ||
* | ||
* @since n.e.x.t | ||
* @access protected | ||
* | ||
* @return void | ||
*/ | ||
protected function enqueue_gtag_script() { | ||
// $this->tags and $this->commands will be populated via this action's handlers. | ||
do_action( 'googlesitekit_setup_gtag', $this ); | ||
|
||
if ( empty( $this->tags ) ) { | ||
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'] ); | ||
|
||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion | ||
wp_enqueue_script( self::HANDLE, $gtag_src, false, null, false ); | ||
wp_script_add_data( self::HANDLE, 'script_execution', 'async' ); | ||
|
||
// 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);}' ); | ||
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. | ||
|
||
foreach ( $this->tags as $tag ) { | ||
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; | ||
} | ||
|
||
$snippet_comment_begin = sprintf( "\n<!-- %s -->\n", esc_html__( 'Google tag (gtag.js) snippet added by Site Kit', 'google-site-kit' ) ); | ||
$snippet_comment_end = sprintf( "\n<!-- %s -->\n", esc_html__( 'End Google tag (gtag.js) snippet added by Site Kit', 'google-site-kit' ) ); | ||
|
||
return $snippet_comment_begin . $tag . $snippet_comment_end; | ||
}; | ||
|
||
add_filter( 'script_loader_tag', $filter_google_gtagjs, 20, 2 ); | ||
} | ||
|
||
/** | ||
* Method used to return gtag() config call for selected tag. | ||
* | ||
* @since n.e.x.t | ||
* @access protected | ||
* | ||
* @param array $tag The Gtag tag, along with its config parameters. | ||
* | ||
* @return string Gtag call for tag in question. | ||
*/ | ||
protected function get_gtag_call_for_tag( $tag ) { | ||
return empty( $tag['config'] ) | ||
? sprintf( 'gtag("config", "%s");', esc_js( $tag['tag_id'] ) ) | ||
: sprintf( 'gtag("config", "%s", %s);', esc_js( $tag['tag_id'] ), wp_json_encode( $tag['config'] ) ); | ||
} | ||
|
||
/** | ||
* Method used to return gtag call for specific command. | ||
* | ||
* @since n.e.x.t | ||
* @access protected | ||
* | ||
* @param array $command The command array with applicable command and params. | ||
* | ||
* @return string Gtag function call for specific command. | ||
*/ | ||
protected function get_gtag_call_for_command( $command ) { | ||
$gtag_args = array_merge( array( $command['command'] ), $command['parameters'] ); | ||
$gtag_args = array_map( | ||
function( $arg ) { | ||
return wp_json_encode( $arg ); | ||
}, | ||
$gtag_args | ||
); | ||
|
||
return sprintf( 'gtag(%s);', implode( ',', $gtag_args ) ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
/** | ||
* Class Google\Site_Kit\Tests\Tags\GTagTest | ||
* | ||
* @package Google\Site_Kit\Tests\Core\Tags\GTag | ||
* @copyright 2024 Google LLC | ||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 | ||
* @link https://sitekit.withgoogle.com | ||
*/ | ||
|
||
namespace Google\Site_Kit\Tests\Core\Tags; | ||
|
||
use Google\Site_Kit\Core\Tags\GTag; | ||
use Google\Site_Kit\Tests\TestCase; | ||
|
||
class GTagTest extends TestCase { | ||
|
||
/** | ||
* Holds an instance of the GTag class. | ||
* | ||
* @var GTag $gtag Gtag instance. | ||
*/ | ||
private $gtag; | ||
|
||
const TEST_TAG_ID_1 = 'GT-12345'; | ||
const TEST_TAG_ID_2 = 'GT-67890'; | ||
const TEST_TAG_ID_2_CONFIG = array( 'foo' => 'bar' ); | ||
const TEST_COMMAND_1 = 'foo'; | ||
const TEST_COMMAND_1_POSITION = 'before'; | ||
const TEST_COMMAND_1_PARAMS = array( 'bar', 'far' ); | ||
const TEST_COMMAND_2_POSITION = 'after'; | ||
const TEST_COMMAND_2 = 'foo'; | ||
const TEST_COMMAND_2_PARAMS = array( array( 'bar' => 'far' ) ); | ||
|
||
public function set_up() { | ||
parent::set_up(); | ||
|
||
$this->gtag = new GTag(); | ||
$this->gtag->register(); | ||
|
||
$this->gtag->add_tag( static::TEST_TAG_ID_1 ); | ||
|
||
// Add commands for testing. | ||
$this->gtag->add_command( static::TEST_COMMAND_1, static::TEST_COMMAND_1_PARAMS, static::TEST_COMMAND_1_POSITION ); | ||
$this->gtag->add_command( static::TEST_COMMAND_2, static::TEST_COMMAND_2_PARAMS, static::TEST_COMMAND_2_POSITION ); | ||
} | ||
|
||
public function test_gtag_class_instance() { | ||
$this->assertInstanceOf( GTag::class, $this->gtag ); | ||
} | ||
|
||
public function test_gtag_script_enqueue() { | ||
$this->assertFalse( wp_script_is( GTag::HANDLE ) ); | ||
|
||
do_action( 'wp_enqueue_scripts' ); | ||
|
||
// Assert that the gtag script is enqueued. | ||
$this->assertTrue( wp_script_is( GTag::HANDLE ) ); | ||
} | ||
|
||
public function test_gtag_script_src() { | ||
$scripts = wp_scripts(); | ||
$script = $scripts->registered[ GTag::HANDLE ]; | ||
|
||
// Assert that the gtag script src is correct. | ||
$this->assertEquals( 'https://www.googletagmanager.com/gtag/js?id=' . static::TEST_TAG_ID_1, $script->src ); | ||
} | ||
|
||
public function test_gtag_script_contains_gtag_call() { | ||
$scripts = wp_scripts(); | ||
$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] ); | ||
} | ||
|
||
public function test_gtag_script_commands() { | ||
$scripts = wp_scripts(); | ||
$script = $scripts->registered[ GTag::HANDLE ]; | ||
|
||
// Test commands in the before position. | ||
$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] ); | ||
} | ||
|
||
public function test_gtag_with_tag_config() { | ||
$this->gtag->add_tag( static::TEST_TAG_ID_2, static::TEST_TAG_ID_2_CONFIG ); | ||
|
||
// Remove already enqueued script to avoid duplication of output. | ||
global $wp_scripts; | ||
unset( $wp_scripts->registered[ GTag::HANDLE ] ); | ||
|
||
do_action( 'wp_enqueue_scripts' ); | ||
|
||
$scripts = wp_scripts(); | ||
$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] ); | ||
} | ||
|
||
} |