Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically mark GA4 scripts as being PX-verified #7290

Merged
merged 36 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c8a5ed4
Add GA4 scripts sanitizer
thelovekesh Oct 10, 2022
91e57b9
Add AMP_GTag_Script_Sanitizer class in sanitizers
thelovekesh Oct 10, 2022
fa5bc0f
Add sandboxing options in the Option interface
thelovekesh Oct 10, 2022
86cdc26
Update sandboxing options to be used from Option interface
thelovekesh Oct 10, 2022
c8b6462
Update sandboxing options
thelovekesh Oct 10, 2022
59911c2
Update tests to use sandboxing level from Option interface
thelovekesh Oct 10, 2022
a03dd35
Update sandboxing service tests
thelovekesh Oct 10, 2022
a62b010
Update xpath to query gtag script tags more accurately
thelovekesh Oct 10, 2022
090b84c
Add test cases for AMP_GTag_Script_Sanitizer class
thelovekesh Oct 10, 2022
b6d5bae
Merge branch 'develop' into add/GA4-scripts-sanitizer
thelovekesh Oct 19, 2022
7c1cd64
Update comment for included `AMP_GTag_Script_Sanitizer` class in sani…
thelovekesh Oct 21, 2022
b88130f
Update xpath query for Google tag scripts
thelovekesh Oct 21, 2022
97b1053
Add deprecated tag on sandboxing service option keys constants
thelovekesh Oct 21, 2022
c47c831
Add space after class declaration
thelovekesh Oct 21, 2022
60a5ce8
Remove tear_down fixture as WordPress tests rollback the current data…
thelovekesh Oct 21, 2022
f6ba0a8
Remove setup fixture
thelovekesh Oct 21, 2022
5032593
Add constant to store gtag html markup
thelovekesh Oct 21, 2022
8d34d45
Update xpath query to be more specific
thelovekesh Oct 21, 2022
b28e9d3
Move tests to dataProvider
thelovekesh Oct 21, 2022
357f4cc
Add more script tags to make sure any non-gtag script tags are always…
thelovekesh Oct 21, 2022
74a3f9f
Merge branch 'develop' into add/GA4-scripts-sanitizer
thelovekesh Oct 21, 2022
8d13af0
Remove unused method
thelovekesh Oct 21, 2022
a1e9b6e
Add `@return` tag in dataProvider
thelovekesh Oct 21, 2022
3e7d2dc
Remove unused properties
thelovekesh Oct 21, 2022
0aca46e
Add more script tags for clear test cases
thelovekesh Oct 21, 2022
feb3910
Add assertions for scripts other then gtag scripts
thelovekesh Oct 21, 2022
df3ee7b
Add inline gtag event attrs as px verified attrs
thelovekesh Oct 25, 2022
1762657
Add test cases for px verified attrs with inline gtag event
thelovekesh Oct 25, 2022
ca84712
Merge branch 'develop' into add/GA4-scripts-sanitizer
thelovekesh Oct 25, 2022
5b0f6a4
Update xpath query to use starts-with while matching on* events
thelovekesh Oct 26, 2022
359bcc8
Update assertion method for finding a string match
thelovekesh Oct 26, 2022
e0a6906
Add early return if no gtag script is present
thelovekesh Oct 26, 2022
6bec836
Update xpath query in tests
thelovekesh Oct 26, 2022
d4b4bfd
Add test case when no gtag script is present in document
thelovekesh Oct 27, 2022
e50ee5b
qMerge branch 'develop' into add/GA4-scripts-sanitizer
thelovekesh Oct 27, 2022
aeacc33
Move up check for `DOMNodeList`
westonruter Oct 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions includes/amp-helper-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,9 @@ function amp_get_content_sanitizers( $post = null ) {
// from getting removed in PWA plugin offline/500 templates.
AMP_PWA_Script_Sanitizer::class => [],

// The AMP_GTag_Script_Sanitizer runs before AMP_Script_Sanitizer to mark the the Google Analytics script tags as being PX-verified.
AMP_GTag_Script_Sanitizer::class => [],

// The AMP_Script_Sanitizer runs here because based on whether it allows custom scripts
// to be kept, it may impact the behavior of other sanitizers. For example, if custom
// scripts are kept then this is a signal that tree shaking in AMP_Style_Sanitizer cannot be
Expand Down Expand Up @@ -1716,6 +1719,7 @@ function amp_get_content_sanitizers( $post = null ) {
AMP_Core_Theme_Sanitizer::class, // Must come before script sanitizer since onclick attributes are removed.
AMP_Bento_Sanitizer::class, // Bento scripts may be preserved here.
AMP_PWA_Script_Sanitizer::class, // Must come before script sanitizer since PWA offline page scripts are removed.
AMP_GTag_Script_Sanitizer::class, // Must come before script sanitizer since gtag.js is removed.
AMP_Script_Sanitizer::class, // Must come before sanitizers for images, videos, audios, comments, forms, and styles.
AMP_Form_Sanitizer::class, // Must come before comments sanitizer.
AMP_Comments_Sanitizer::class, // Also must come after the form sanitizer.
Expand Down
87 changes: 87 additions & 0 deletions includes/sanitizers/class-amp-gtag-script-sanitizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php
/**
* GA4 Script Sanitizer.
*
* - This sanitizer will facilitate using GA4 while waiting on an AMP implementation.
* - This sanitizer will be only used in Moderate or Loose sandboxing level
*
* @since 2.3.1
* @package AMP
*/

use AmpProject\AmpWP\Option;
use AmpProject\AmpWP\ValidationExemption;

/**
* Class AMP_GTag_Script_Sanitizer
*
* @since 2.3.1
* @internal
*/
class AMP_GTag_Script_Sanitizer extends AMP_Base_Sanitizer {

/**
* Sanitize the AMP response for GA4 scripts.
*
* @since 2.3.1
*/
public function sanitize() {
if ( ! AMP_Options_Manager::get_option( Option::SANDBOXING_ENABLED ) ) {
return;
}

$sandboxing_level = AMP_Options_Manager::get_option( Option::SANDBOXING_LEVEL );

if ( 1 !== $sandboxing_level && 2 !== $sandboxing_level ) {
return;
}

/**
* GTag Script looks like this:
*
* <script async src="https://www.googletagmanager.com/gtag/js?id=xxxxxx"></script>
* <script>
* window.dataLayer = window.dataLayer || [];
* function gtag(){dataLayer.push(arguments);}
* gtag('js', new Date());
*
* gtag('config', 'xxxxxx');
* </script>
*/
$scripts = $this->dom->xpath->query( '//script[ ( @async and starts-with( @src, "https://www.googletagmanager.com/gtag/js" ) ) or contains( text(), "function gtag(" ) ]' );
thelovekesh marked this conversation as resolved.
Show resolved Hide resolved
if ( ! $scripts instanceof DOMNodeList || 0 === $scripts->length ) {
return;
thelovekesh marked this conversation as resolved.
Show resolved Hide resolved
}

foreach ( $scripts as $script ) {
ValidationExemption::mark_node_as_px_verified( $script );
}

thelovekesh marked this conversation as resolved.
Show resolved Hide resolved
/**
* Mark inline gtag events as PX verified attributes.
*
* Such inline events can look like:
*
* onclick="gtag('event','click', { 'event_category':"click", 'event_label':"contactPage" })"
* onsubmit="gtag('event','submit', { 'event_category':"submit", 'event_label':"contactPage" })"
* onkeypress="gtag('event','keypress', { 'event_category':"keypress", 'event_label':"contactPage" })"
*/
$inline_events = $this->dom->xpath->query(
'
//@*[
starts-with(name(), "on")
and
name() != "on"
and
contains(., "gtag(")
]
'
);

if ( $inline_events instanceof DOMNodeList ) {
foreach ( $inline_events as $inline_event ) {
ValidationExemption::mark_node_as_px_verified( $inline_event );
}
}
}
}
15 changes: 15 additions & 0 deletions src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ interface Option {
*/
const SUPPRESSED_PLUGINS_USERNAME = 'username';


/**
* Option key for enabling sandboxing.
*
* @var string
*/
const SANDBOXING_ENABLED = 'sandboxing_enabled';

/**
* Option key for sandboxing level.
*
* @var string
*/
const SANDBOXING_LEVEL = 'sandboxing_level';

/**
* Version of the AMP plugin for which the options were last saved.
*
Expand Down
26 changes: 15 additions & 11 deletions src/Sandboxing.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use AMP_Style_Sanitizer;
use AmpProject\AmpWP\Infrastructure\Registerable;
use AmpProject\AmpWP\Infrastructure\Service;
use AmpProject\AmpWP\Option;
use AmpProject\Dom\Document;
use AmpProject\Dom\Element;
use AmpProject\Html\Attribute;
Expand All @@ -32,14 +33,17 @@ final class Sandboxing implements Service, Registerable {
/**
* Option key for enabling sandboxing.
*
* @deprecated Use Options interface for option keys.
*
* @var string
*/
const OPTION_ENABLED = 'sandboxing_enabled';

/**
* Option key for sandboxing level.
*
* @todo Move this to the Options interface once no longer experimental.
* @deprecated Use Options interface for option keys.
*
* @var string
*/
const OPTION_LEVEL = 'sandboxing_level';
Expand All @@ -57,11 +61,11 @@ final class Sandboxing implements Service, Registerable {
* @var array
*/
const DEFAULT_OPTIONS_SCHEMA = [
self::OPTION_ENABLED => [
Option::SANDBOXING_ENABLED => [
'type' => 'bool',
'default' => false,
],
self::OPTION_LEVEL => [
Option::SANDBOXING_LEVEL => [
'type' => 'int',
'enum' => self::LEVELS,
'default' => 1,
Expand Down Expand Up @@ -113,15 +117,15 @@ public function filter_default_options( $defaults ) {
* @return array Sanitized options.
*/
public function sanitize_options( $options, $new_options ) {
if ( isset( $new_options[ self::OPTION_ENABLED ] ) ) {
$options[ self::OPTION_ENABLED ] = (bool) $new_options[ self::OPTION_ENABLED ];
if ( isset( $new_options[ Option::SANDBOXING_ENABLED ] ) ) {
$options[ Option::SANDBOXING_ENABLED ] = (bool) $new_options[ Option::SANDBOXING_ENABLED ];
}
if (
isset( $new_options[ self::OPTION_LEVEL ] )
isset( $new_options[ Option::SANDBOXING_LEVEL ] )
&&
in_array( $new_options[ self::OPTION_LEVEL ], self::LEVELS, true )
in_array( $new_options[ Option::SANDBOXING_LEVEL ], self::LEVELS, true )
) {
$options[ self::OPTION_LEVEL ] = $new_options[ self::OPTION_LEVEL ];
$options[ Option::SANDBOXING_LEVEL ] = $new_options[ Option::SANDBOXING_LEVEL ];
}
return $options;
}
Expand All @@ -137,11 +141,11 @@ public function add_hooks() {
return;
}

if ( ! AMP_Options_Manager::get_option( self::OPTION_ENABLED ) ) {
if ( ! AMP_Options_Manager::get_option( Option::SANDBOXING_ENABLED ) ) {
return;
}

$sandboxing_level = AMP_Options_Manager::get_option( self::OPTION_LEVEL );
$sandboxing_level = AMP_Options_Manager::get_option( Option::SANDBOXING_LEVEL );

// Opt-in to the new script sanitization logic in the script sanitizer.
add_filter(
Expand Down Expand Up @@ -241,7 +245,7 @@ private function remove_required_amp_markup_if_not_used( Document $dom, $effecti
* @param int $effective_sandboxing_level Effective sandboxing level.
*/
public function finalize_document( Document $dom, $effective_sandboxing_level ) {
$actual_sandboxing_level = AMP_Options_Manager::get_option( self::OPTION_LEVEL );
$actual_sandboxing_level = AMP_Options_Manager::get_option( Option::SANDBOXING_LEVEL );

$meta_generator = $dom->xpath->query( '/html/head/meta[ @name = "generator" and starts-with( @content, "AMP Plugin" ) ]/@content' )->item( 0 );
if ( $meta_generator instanceof DOMAttr ) {
Expand Down
44 changes: 22 additions & 22 deletions tests/php/src/SandboxingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ public function test_filter_rest_options_schema() {

$filtered = $this->instance->filter_rest_options_schema( $existing );
$this->assertArrayHasKey( 'foo', $filtered );
$this->assertArrayHasKey( Sandboxing::OPTION_LEVEL, $filtered );
$this->assertArrayHasKey( Option::SANDBOXING_LEVEL, $filtered );
}

/** @covers ::filter_default_options() */
public function test_filter_default_options() {
$this->assertEquals(
[
Sandboxing::OPTION_LEVEL => 1,
Sandboxing::OPTION_ENABLED => false,
Option::SANDBOXING_LEVEL => 1,
Option::SANDBOXING_ENABLED => false,
],
$this->instance->filter_default_options( [] )
);
Expand All @@ -77,34 +77,34 @@ public function test_filter_default_options() {
/** @covers ::sanitize_options() */
public function test_sanitize_options() {
$this->assertEquals(
[ Sandboxing::OPTION_LEVEL => 2 ],
[ Option::SANDBOXING_LEVEL => 2 ],
$this->instance->sanitize_options(
[ Sandboxing::OPTION_LEVEL => 2 ],
[ Sandboxing::OPTION_LEVEL => 'bad' ]
[ Option::SANDBOXING_LEVEL => 2 ],
[ Option::SANDBOXING_LEVEL => 'bad' ]
)
);

$this->assertEquals(
[ Sandboxing::OPTION_LEVEL => 3 ],
[ Option::SANDBOXING_LEVEL => 3 ],
$this->instance->sanitize_options(
[ Sandboxing::OPTION_LEVEL => 2 ],
[ Sandboxing::OPTION_LEVEL => 3 ]
[ Option::SANDBOXING_LEVEL => 2 ],
[ Option::SANDBOXING_LEVEL => 3 ]
)
);

$this->assertEquals(
[ Sandboxing::OPTION_LEVEL => 1 ],
[ Option::SANDBOXING_LEVEL => 1 ],
$this->instance->sanitize_options(
[ Sandboxing::OPTION_LEVEL => 1 ],
[ Sandboxing::OPTION_LEVEL => 0 ]
[ Option::SANDBOXING_LEVEL => 1 ],
[ Option::SANDBOXING_LEVEL => 0 ]
)
);

$this->assertEquals(
[ Sandboxing::OPTION_LEVEL => 1 ],
[ Option::SANDBOXING_LEVEL => 1 ],
$this->instance->sanitize_options(
[ Sandboxing::OPTION_LEVEL => 1 ],
[ Sandboxing::OPTION_LEVEL => 4 ]
[ Option::SANDBOXING_LEVEL => 1 ],
[ Option::SANDBOXING_LEVEL => 4 ]
)
);
}
Expand All @@ -114,9 +114,9 @@ public function test_add_hooks_not_standard_mode() {
$this->register_settings_and_set_user();
AMP_Options_Manager::update_options(
[
Sandboxing::OPTION_ENABLED => true,
Option::SANDBOXING_ENABLED => true,
Option::THEME_SUPPORT => AMP_Theme_Support::TRANSITIONAL_MODE_SLUG,
Sandboxing::OPTION_LEVEL => 2,
Option::SANDBOXING_LEVEL => 2,
]
);
$this->instance->add_hooks();
Expand All @@ -128,7 +128,7 @@ public function test_add_hooks_without_enabled_level() {
$this->register_settings_and_set_user();
AMP_Options_Manager::update_options(
[
Sandboxing::OPTION_ENABLED => false,
Option::SANDBOXING_ENABLED => false,
Option::THEME_SUPPORT => AMP_Theme_Support::STANDARD_MODE_SLUG,
]
);
Expand Down Expand Up @@ -178,9 +178,9 @@ public function test_add_hooks( $level, $expected_sanitizer_args ) {
$this->register_settings_and_set_user();
AMP_Options_Manager::update_options(
[
Sandboxing::OPTION_ENABLED => true,
Option::SANDBOXING_ENABLED => true,
Option::THEME_SUPPORT => AMP_Theme_Support::STANDARD_MODE_SLUG,
Sandboxing::OPTION_LEVEL => $level,
Option::SANDBOXING_LEVEL => $level,
]
);
$this->instance->add_hooks();
Expand Down Expand Up @@ -250,8 +250,8 @@ public function test_finalize_document_and_get_effective_level( $min_level, $bod
$this->register_settings_and_set_user();
AMP_Options_Manager::update_options(
[
Sandboxing::OPTION_ENABLED => true,
Sandboxing::OPTION_LEVEL => $min_level,
Option::SANDBOXING_ENABLED => true,
Option::SANDBOXING_LEVEL => $min_level,
]
);

Expand Down
Loading