Skip to content

Commit

Permalink
feat: add ga4 user registered handler (#2281)
Browse files Browse the repository at this point in the history
* feat: add ga4 user registered handler

* feat: Refactor ga4 event handlers and add campaign details to register

* feat: linting

* docs: fix inline docs

* feat: Event class better handle for common input values

* feat: GA4 handler validates and santizes event before sending
  • Loading branch information
leogermani authored Mar 1, 2023
1 parent a151d53 commit 5eb2336
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 32 deletions.
5 changes: 3 additions & 2 deletions assets/blocks/reader-registration/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,12 +409,13 @@ function process_form() {
if ( ! empty( $lists ) ) {
$metadata['lists'] = $lists;
}
$metadata['current_page_url'] = home_url( add_query_arg( array(), \wp_get_referer() ) );
$metadata['referer'] = \wp_get_raw_referer(); // wp_get_referer() will return false because it's a POST request to the same page.
$metadata['current_page_url'] = home_url( add_query_arg( array(), $metadata['referer'] ) );
$metadata['registration_method'] = 'registration-block';

$popup_id = isset( $_REQUEST['newspack_popup_id'] ) ? (int) $_REQUEST['newspack_popup_id'] : false;
if ( $popup_id ) {
$metadata['popup_id'] = $popup_id;
$metadata['newspack_popup_id'] = $popup_id;
$metadata['registration_method'] = 'registration-block-popup';
}

Expand Down
13 changes: 7 additions & 6 deletions includes/data-events/class-popups.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,17 @@ public static function get_popup_metadata( $popup ) {
return $data;
}

$data['title'] = $popup['title'];
$data['campaign_id'] = $popup['id'];
$data['campaign_title'] = $popup['title'];

if ( isset( $popup['options'] ) ) {
$data['frequency'] = $popup['options']['frequency'] ?? '';
$data['placement'] = $popup['options']['placement'] ?? '';
$data['campaign_frequency'] = $popup['options']['frequency'] ?? '';
$data['campaign_placement'] = $popup['options']['placement'] ?? '';
}

$data['has_registration_block'] = has_block( 'newspack/reader-registration', $popup['content'] );
$data['has_donation_block'] = false; // TODO.
$data['has_newsletter_block'] = has_block( 'newspack-newsletters/subscribe', $popup['content'] );
$data['campaign_has_registration_block'] = has_block( 'newspack/reader-registration', $popup['content'] );
$data['campaign_has_donation_block'] = false; // TODO.
$data['campaign_has_newsletter_block'] = has_block( 'newspack-newsletters/subscribe', $popup['content'] );

return $data;
}
Expand Down
28 changes: 24 additions & 4 deletions includes/data-events/connectors/ga4/class-event.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,30 @@ public static function validate_name( $name ) {
* @return bool
*/
public static function validate_param_value( $value ) {
// Let's play nice and convert integers.
if ( is_int( $value ) ) {
$value = (string) $value;
}
$value = self::sanitize_value( $value );
return is_string( $value ) && strlen( $value ) <= 100;
}

/**
* Tries to santize a value to a string in cases where it's possible. It will not act on object or arrays, which will still produce a validation error
*
* @param mixed $value The input value.
* @return mixed The sanitized value, or the original value if it can't be sanitized
*/
public static function sanitize_value( $value ) {
switch ( gettype( $value ) ) {
case 'integer':
case 'double':
case 'NULL':
case 'string':
return substr( (string) $value, 0, 100 );
case 'boolean':
return $value ? 'yes' : 'no';
default:
return $value;
}
}

/**
* Get event name.
*
Expand Down Expand Up @@ -131,6 +148,9 @@ public function set_params( $params ) {
throw new \Exception( 'Invalid event parameters. Limit is 25 parameters, included the default parameters. Values must have a max of 100 characters.' );
}
$this->params = $params;
foreach ( $this->params as $param_name => $param_value ) {
$this->params[ $param_name ] = self::sanitize_value( $param_value );
}
}

/**
Expand Down
113 changes: 96 additions & 17 deletions includes/data-events/connectors/ga4/class-ga4.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

use Newspack\Data_Events;
use Newspack\Data_Events\Connectors\GA4\Event;
use Newspack\Data_Events\Popups as Popups_Events;
use Newspack\Logger;
use Newspack\Reader_Activation;
use WP_Error;

defined( 'ABSPATH' ) || exit;

Expand All @@ -21,16 +23,86 @@
*/
class GA4 {

/**
* The events being watched.
*
* @var array
*/
public static $watched_events = [
'reader_logged_in',
'reader_registered',
];

/**
* Initialize the class and registers the handlers
*
* @return void
*/
public static function init() {
Data_Events::register_handler( [ __CLASS__, 'handle_reader_logged_in' ], 'reader_logged_in' );
Data_Events::register_handler( [ __CLASS__, 'global_handler' ] );
add_filter( 'newspack_data_events_dispatch_body', [ __CLASS__, 'filter_event_body' ], 10, 2 );
}

/**
* Global handler for the Data Events API.
*
* @param string $event_name The event name.
* @param int $timestamp Timestamp of the event.
* @param array $data Data associated with the event.
* @param int $user_id ID of the client that triggered the event. It's a RAS ID.
*
* @throws \Exception If the event is invalid.
* @return void
*/
public static function global_handler( $event_name, $timestamp, $data, $user_id ) {
if ( ! in_array( $event_name, self::$watched_events, true ) ) {
return;
}

$params = $data['ga_params'];
$client_id = $data['ga_client_id'];

if ( empty( $client_id ) ) {
throw new \Exception( 'Missing client ID' );
}

if ( method_exists( __CLASS__, 'handle_' . $event_name ) ) {
$params = call_user_func( [ __CLASS__, 'handle_' . $event_name ], $params, $data );

if ( ! Event::validate_name( $event_name ) ) {
throw new \Exception( 'Invalid event name' );
}

foreach ( $params as $param_name => $param_value ) {
if ( ! Event::validate_name( $param_name ) ) {
unset( $params[ $param_name ] );
self::log( sprintf( 'Parameter %s has an invalid name. It was removed from the %s event.', $param_name, $event_name ) );
}

if ( ! Event::validate_param_value( $param_value ) ) {
unset( $params[ $param_name ] );
self::log( sprintf( 'Parameter %s has an invalid value. It was removed from the %s event.', $param_name, $event_name ) );
}
}

if ( count( $params ) > Event::MAX_PARAMS ) {
$discarded_params = array_slice( $params, Event::MAX_PARAMS );
$params = array_slice( $params, 0, Event::MAX_PARAMS );
self::log( sprintf( 'Event %s has too many parameters. Only the first 25 were kept.', $event_name, Event::MAX_PARAMS ) );
foreach ( array_keys( $discarded_params ) as $d_param_name ) {
self::log( sprintf( 'Discarded parameter: %s', $d_param_name ) );
}
}

$event = new Event( $event_name, $params );
self::send_event( $event, $client_id, $timestamp, $user_id );

} else {
throw new \Exception( 'Event handler method not found' );
}

}

/**
* Filters the event body before dispatching it.
*
Expand All @@ -39,8 +111,7 @@ public static function init() {
* @return array
*/
public static function filter_event_body( $body, $event_name ) {
$watched_events = [ 'reader_logged_in' ];
if ( ! in_array( $event_name, $watched_events, true ) ) {
if ( ! in_array( $event_name, self::$watched_events, true ) ) {
return $body;
}

Expand Down Expand Up @@ -68,24 +139,32 @@ public static function filter_event_body( $body, $event_name ) {
/**
* Handler for the reader_logged_in event.
*
* @param int $timestamp Timestamp of the event.
* @param array $data Data associated with the event.
* @param int $user_id ID of the client that triggered the event. It's a RAS ID.
* @param int $params The GA4 event parameters.
* @param array $data Data associated with the Data Events api event.
*
* @throws \Exception If the event is invalid.
* @return void
* @return array $params The final version of the GA4 event params that will be sent to GA.
*/
public static function handle_reader_logged_in( $timestamp, $data, $user_id ) {

$params = $data['ga_params'];
$client_id = $data['ga_client_id'];
public static function handle_reader_logged_in( $params, $data ) {
return $params;
}

if ( empty( $client_id ) ) {
throw new \Exception( 'Missing client ID' );
/**
* Handler for the reader_registered event.
*
* @param int $params The GA4 event parameters.
* @param array $data Data associated with the Data Events api event.
*
* @return array $params The final version of the GA4 event params that will be sent to GA.
*/
public static function handle_reader_registered( $params, $data ) {
$params['registration_method'] = $data['metadata']['registration_method'] ?? '';
if ( ! empty( $data['metadata']['newspack_popup_id'] ) ) {
$params = array_merge( $params, Popups_Events::get_popup_metadata( $data['metadata']['newspack_popup_id'] ) );
}

$event = new Event( 'reader_login', $params );
self::send_event( $event, $client_id, $timestamp, $user_id );
if ( ! empty( $data['metadata']['referer'] ) ) {
$params['referer'] = substr( $data['metadata']['referer'], 0, 100 );
}
return $params;
}

/**
Expand Down
69 changes: 66 additions & 3 deletions tests/unit-tests/ga4-connector.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function validate_param_value_data() {
],
[
'iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0i',
false,
true,
],
[
[ 123 ],
Expand All @@ -136,7 +136,15 @@ public function validate_param_value_data() {
],
[
true,
false,
true,
],
[
null,
true,
],
[
1.234,
true,
],
];
}
Expand All @@ -152,6 +160,61 @@ public function test_validate_param_value( $value, $expected ) {
$this->assertEquals( $expected, Event::validate_param_value( $value ) );
}

/**
* Data provider for test_validate_param_name
*/
public function sanitize_value_data() {
return [
[
'dsl2390ijd2m, #asd',
'dsl2390ijd2m, #asd',
],
[
123123232,
'123123232',
],
[
'iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0i',
'iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0iiiiiiiii0',
],
[
[ 123 ],
[ 123 ],
],
[
(object) [ 'asd' => 123 ],
(object) [ 'asd' => 123 ],
],
[
true,
'yes',
],
[
false,
'no',
],
[
null,
'',
],
[
1.234,
'1.234',
],
];
}

/**
* Tests the sanitize_value method
*
* @param mixed $value The param value.
* @param mixed $expected The expected result.
* @dataProvider sanitize_value_data
*/
public function test_sanitize_value( $value, $expected ) {
$this->assertEquals( $expected, Event::sanitize_value( $value ) );
}

/**
* Tests the validate_params method
*
Expand Down Expand Up @@ -179,7 +242,7 @@ public function validate_params_data() {
],
[
[
'invalid' => false,
'invalid' => [ 123 ],
'param2' => 'value2',
'param3' => 'value3',
'param4' => 'value4',
Expand Down

0 comments on commit 5eb2336

Please sign in to comment.