Skip to content

Commit

Permalink
Generate and save a hashed gp_user_id on form submissions
Browse files Browse the repository at this point in the history
The hashed gp_user_id is saved to dataLayer and a cookie also called gp_user_id. In order to respect user consent and flexibility for different privacy laws, additional settings for default values for Google Consent Mode are also added.
  • Loading branch information
stduerre authored and comzeradd committed Jul 17, 2024
1 parent da2a50b commit ae7d42b
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 23 deletions.
68 changes: 60 additions & 8 deletions assets/src/js/cookies.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const setupCookies = () => {
const NECESSARY_ANALYTICAL = '3';
const NECESSARY_ANALYTICAL_MARKETING = '4';

const ALL_COOKIES = ENABLE_ANALYTICAL_COOKIES ? NECESSARY_ANALYTICAL_MARKETING : NECESSARY_MARKETING;
const ALL_COOKIES = NECESSARY_ANALYTICAL_MARKETING;

function gtag() {
dataLayer.push(arguments);
Expand Down Expand Up @@ -95,7 +95,7 @@ export const setupCookies = () => {
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
...ENABLE_ANALYTICAL_COOKIES && {analytics_storage: 'granted'},
analytics_storage: 'granted',
});
}

Expand Down Expand Up @@ -156,12 +156,20 @@ export const setupCookies = () => {

// Update ad storage and analytics storage if Google Consent Mode is enabled
if (ENABLE_GOOGLE_CONSENT_MODE) {
updateGoogleConsent({
ad_storage: marketingCookiesChecked ? 'granted' : 'denied',
ad_user_data: marketingCookiesChecked ? 'granted' : 'denied',
ad_personalization: marketingCookiesChecked ? 'granted' : 'denied',
...ENABLE_ANALYTICAL_COOKIES && {analytics_storage: analyticalCookiesChecked ? 'granted' : 'denied'},
});
if (ENABLE_ANALYTICAL_COOKIES) {
updateGoogleConsent({
ad_storage: marketingCookiesChecked ? 'granted' : 'denied',
ad_user_data: marketingCookiesChecked ? 'granted' : 'denied',
ad_personalization: marketingCookiesChecked ? 'granted' : 'denied',
analytics_storage: analyticalCookiesChecked ? 'granted' : 'denied',
});
} else {
updateGoogleConsent({
ad_storage: marketingCookiesChecked ? 'granted' : 'denied',
ad_user_data: marketingCookiesChecked ? 'granted' : 'denied',
ad_personalization: marketingCookiesChecked ? 'granted' : 'denied',
});
}
}

hideCookiesBox();
Expand All @@ -188,4 +196,48 @@ export const setupCookies = () => {

const rejectAllCookiesButtons = [...document.querySelectorAll('.reject-all-cookies')];
rejectAllCookiesButtons.forEach(rejectAllCookiesButton => rejectAllCookiesButton.onclick = rejectAllCookies);

const getConsentModeValues = () => {
const consentValues = {
analytics_storage: null,
ad_user_data: null,
ad_storage: null,
ad_personalization: null,
};

if (Array.isArray(window.dataLayer)) {
// Iterate through the events history in dataLayer and find most recent consent values.
for (let i = 0; i < window.dataLayer.length; i++) {
const event = window.dataLayer[i];

if (event.event === 'defaultConsent' || event.event === 'updateConsent') {
if (event.analytics_storage !== undefined) {
consentValues.analytics_storage = event.analytics_storage;
}
if (event.ad_user_data !== undefined) {
consentValues.ad_user_data = event.ad_user_data;
}
if (event.ad_storage !== undefined) {
consentValues.ad_storage = event.ad_storage;
}
if (event.ad_personalization !== undefined) {
consentValues.ad_personalization = event.ad_personalization;
}
}
}
}

return consentValues;
};

// Set the gp_user_id cookie when the event is triggered, but check for consent first.
document.addEventListener('gp_user_id_set', event => {
if (event.detail.hasOwnProperty('gp_user_id') && event.detail.gp_user_id !== '') {
const consentModeValues = getConsentModeValues();

if (consentModeValues.analytics_storage === 'granted') {
createCookie('gp_user_id', event.detail.gp_user_id, 365);
}
}
});
};
58 changes: 58 additions & 0 deletions src/GravityFormsExtensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,50 @@ public function p4_gf_confirmation_settings(array $fields): array
return $fields;
}

/**
* Find an email address in a form entry and return a SHA-256 hash of it. The returned hash is base64 encoded
* and any '/' characters are removed.
*
* @param array $form The form setting.
* @param array $entry A form entry.
*
* @return string The hashed email address or empty string if no email address is found.
*/
public function p4_gf_get_email_hash(array $form, array $entry): string
{
$email_address = '';

// Find the first email field in the form and extract the email address
foreach ($form["fields"] as $i => $field) {
if (get_class($field) === "GF_Field_Email") {
$email_address = $entry[$field["id"]];
break;
}
}

if (empty($email_address)) {
// Find any email address as a value in the form entry, in any type of form field
foreach ($entry as $key => $value) {
if (is_numeric($key) && filter_var(trim($value), FILTER_VALIDATE_EMAIL)) {
$email_address = trim($value);
break;
}
}
}

if (!empty($email_address)) {
// Hash and base64 encode the email using SHA-256
$hashed_email = base64_encode(hash('sha256', $email_address, true));

// Remove '/' characters if present in Base64 encoding
$hashed_email = str_replace('/', '', $hashed_email);

return $hashed_email;
}

return '';
}

/**
*
* Return custom confirmation message for Gravity Forms.
Expand Down Expand Up @@ -342,12 +386,15 @@ public function p4_gf_custom_confirmation($confirmation, $form, $entry)
}
}

$email_hash = $this->p4_gf_get_email_hash($form, $entry);

$event_parameters = [
"event" => "formSubmission",
"formID" => $form['id'],
"formPlugin" => "Gravity Form",
"gGoal" => ($form['p4_gf_type'] ?? self::DEFAULT_GF_TYPE),
"formTitle" => $form['title'],
"gpUserId" => $email_hash,
"userEmail" => $userEmail,
"eventTimeout" => 2000,
];
Expand All @@ -362,6 +409,10 @@ public function p4_gf_custom_confirmation($confirmation, $form, $entry)
window.dataLayer.push(push_data);
const gp_user_id_event = new CustomEvent("gp_user_id_set", {"detail":
{"gp_user_id": "' . $email_hash . '" }});
document.dispatchEvent(gp_user_id_event);
// Disable GFTrackEvent (GFTrackEvent belongs to Gravity Forms Google Analytics Add-On)
window.parent.gfgaTagManagerEventSent = true;
</script>';
Expand Down Expand Up @@ -701,12 +752,15 @@ public function p4_gf_custom_confirmation_redirect($confirmation, $form, $entry)
$options = get_option('planet4_options');
$gtm_id = $options['google_tag_manager_identifier'];

$email_hash = $this->p4_gf_get_email_hash($form, $entry);

$event_parameters = [
"event" => "formSubmission",
"formID" => $form['id'],
"formPlugin" => "Gravity Form",
"gGoal" => ($form['p4_gf_type'] ?? self::DEFAULT_GF_TYPE),
"formTitle" => $form['title'],
"gpUserId" => $email_hash,
"userEmail" => $userEmail,
"eventTimeout" => 2000,
];
Expand All @@ -732,6 +786,10 @@ public function p4_gf_custom_confirmation_redirect($confirmation, $form, $entry)
push_data = Object.assign(push_data, event_parameters);
window.dataLayer.push(push_data);
const gp_user_id_event = new CustomEvent("gp_user_id_set", {"detail":
{"gp_user_id": "' . $email_hash . '" }});
document.dispatchEvent(gp_user_id_event);
} else {
// Redirect latest after two seconds.
// This is a failsafe in case the request to tag manager is blocked.
Expand Down
8 changes: 8 additions & 0 deletions src/MasterSite.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,14 @@ function ($item) {
$context['google_tag_value'] = $options['google_tag_manager_identifier'] ?? '';
$context['google_tag_domain'] = !empty($options['google_tag_manager_domain']) ?
$options['google_tag_manager_domain'] : 'www.googletagmanager.com';
$context['consent_default_analytics_storage'] =
planet4_get_option('consent_default_analytics_storage') ?? 'denied';
$context['consent_default_ad_storage'] =
planet4_get_option('consent_default_ad_storage') ?? 'denied';
$context['consent_default_ad_user_data'] =
planet4_get_option('consent_default_ad_user_data') ?? 'denied';
$context['consent_default_ad_personalization'] =
planet4_get_option('consent_default_ad_personalization') ?? 'denied';
$context['ab_hide_selector'] = $options['ab_hide_selector'] ?? null;
$context['facebook_page_id'] = $options['facebook_page_id'] ?? '';
$context['preconnect_domains'] = [];
Expand Down
68 changes: 62 additions & 6 deletions src/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,68 @@ public function __construct()
'id' => 'enable_reject_all_cookies',
'type' => 'checkbox',
],
[
'name' => __('Enable Google Consent Mode', 'planet4-master-theme-backend'),
'desc' => __("Enabling the Consent Mode will affect your setup in Google Tag Manager. The Consent Mode will prevent tags with built-in consent checks (eg. Google Analytics) from running before the user's consent is granted.", 'planet4-master-theme-backend'),
'id' => 'enable_google_consent_mode',
'type' => 'checkbox',
],
[
'name' => __('Consent default: analytics_storage', 'planet4-master-theme-backend'),
'desc' => __(
'The default value for analytics_storage consent before visitors make their choice in the cookies box (Google Consent Mode V2).',
'planet4-master-theme-backend'
),
'id' => 'consent_default_analytics_storage',
'type' => 'select',
'default' => 'denied',
'options' => [
'denied' => __('Denied', 'planet4-master-theme-backend'),
'granted' => __('Granted', 'planet4-master-theme-backend'),
],
],
[
'name' => __('Consent default: ad_storage', 'planet4-master-theme-backend'),
'desc' => __(
'The default value for ad_storage consent before visitors make their choice in the cookies box (Google Consent Mode V2).',
'planet4-master-theme-backend'
),
'id' => 'consent_default_ad_storage',
'type' => 'select',
'default' => 'denied',
'options' => [
'denied' => __('Denied', 'planet4-master-theme-backend'),
'granted' => __('Granted', 'planet4-master-theme-backend'),
],
],
[
'name' => __('Consent default: ad_user_data', 'planet4-master-theme-backend'),
'desc' => __(
'The default value for ad_user_data consent before visitors make their choice in the cookies box (Google Consent Mode V2).',
'planet4-master-theme-backend'
),
'id' => 'consent_default_ad_user_data',
'type' => 'select',
'default' => 'denied',
'options' => [
'denied' => __('Denied', 'planet4-master-theme-backend'),
'granted' => __('Granted', 'planet4-master-theme-backend'),
],
],
[
'name' => __('Consent default: ad_personalization', 'planet4-master-theme-backend'),
'desc' => __(
'The default value for ad_personalization consent before visitors make their choice in the cookies box (Google Consent Mode V2).',
'planet4-master-theme-backend'
),
'id' => 'consent_default_ad_personalization',
'type' => 'select',
'default' => 'denied',
'options' => [
'denied' => __('Denied', 'planet4-master-theme-backend'),
'granted' => __('Granted', 'planet4-master-theme-backend'),
],
],
],
],
'planet4_settings_social' => [
Expand Down Expand Up @@ -420,12 +482,6 @@ public function __construct()
'id' => 'analytics_local_google_sheet_id',
'type' => 'text',
],
[
'name' => __('Enable Google Consent Mode', 'planet4-master-theme-backend'),
'desc' => __("Enabling the Consent Mode will affect your setup in Google Tag Manager. The Consent Mode will prevent tags with built-in consent checks (eg. Google Analytics) from running before the user's consent is granted.", 'planet4-master-theme-backend'),
'id' => 'enable_google_consent_mode',
'type' => 'checkbox',
],
// New IA special pages.
[
'name' => __('Select "Get Informed" page', 'planet4-master-theme-backend'),
Expand Down
18 changes: 9 additions & 9 deletions templates/blocks/google_tag_manager.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<script>
var google_tag_value = '{{ google_tag_value }}';
var google_tag_domain = '{{ google_tag_domain }}';
var consent_default_analytics_storage = '{{ consent_default_analytics_storage }}';
var consent_default_ad_storage = '{{ consent_default_ad_storage }}';
var consent_default_ad_user_data = '{{ consent_default_ad_user_data }}';
var consent_default_ad_personalization = '{{ consent_default_ad_personalization }}';
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); };
Expand Down Expand Up @@ -29,18 +33,14 @@
ad_storage: marketing_consent ? 'granted' : 'denied',
ad_user_data: marketing_consent ? 'granted' : 'denied',
ad_personalization: marketing_consent ? 'granted' : 'denied',
{% if cookies.enable_analytical_cookies %}
...{'analytics_storage': analytical_consent ? 'granted' : 'denied'}
{% endif %}
analytics_storage: analytical_consent ? 'granted' : 'denied'
};
} else {
var capabilities = {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
{% if cookies.enable_analytical_cookies %}
...{'analytics_storage': 'denied'}
{% endif %}
ad_storage: consent_default_ad_storage,
ad_user_data: consent_default_ad_user_data,
ad_personalization: consent_default_ad_personalization,
analytics_storage: consent_default_analytics_storage
};
}
gtag('consent', 'default', capabilities);
Expand Down

0 comments on commit ae7d42b

Please sign in to comment.