Skip to content

Commit

Permalink
Merge pull request #2 from tagconcierge/feature/defer
Browse files Browse the repository at this point in the history
Feature/defer
  • Loading branch information
mfrankiewicz authored May 23, 2023
2 parents c7f13a9 + 4f350ba commit 16cf9c5
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 1 deletion.
18 changes: 18 additions & 0 deletions src/DependencyInjection/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@

namespace TagConcierge\GtmCookiesFree\DependencyInjection;

use TagConcierge\GtmCookiesFree\Service\GtmSnippetService;
use TagConcierge\GtmCookiesFree\Service\SettingsService;
use TagConcierge\GtmCookiesFree\Util\OutputUtil;
use TagConcierge\GtmCookiesFree\Util\SettingsUtil;

class Container
{
private $outputUtil;

private $gtmSnippetService;

private $settingsService;

private $settingsUtil;

public function __construct()
{
$this->outputUtil = new OutputUtil();
$this->settingsUtil = new SettingsUtil();
$this->gtmSnippetService = new GtmSnippetService($this->settingsUtil, $this->outputUtil);
$this->settingsService = new SettingsService($this->settingsUtil);
}

Expand All @@ -26,4 +34,14 @@ public function getSettingsService(): SettingsService
{
return $this->settingsService;
}

public function getOutputUtil(): OutputUtil
{
return $this->outputUtil;
}

public function getGtmSnippetService(): GtmSnippetService
{
return $this->gtmSnippetService;
}
}
218 changes: 218 additions & 0 deletions src/Service/GtmSnippetService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<?php

namespace TagConcierge\GtmCookiesFree\Service;

use TagConcierge\GtmCookiesFree\Util\OutputUtil;
use TagConcierge\GtmCookiesFree\Util\SettingsUtil;

class GtmSnippetService
{
private $settingsUtil;

private $outputUtil;

private $consentEventName = '';

private $requireConsentBeforeGtmLoad = false;

private $eventDeferring = false;

private $dataLayerVariableName = 'dataLayer';

public function __construct(SettingsUtil $settingsUtil, OutputUtil $outputUtil)
{
$this->settingsUtil = $settingsUtil;
$this->outputUtil = $outputUtil;

$this->eventDeferring = '1' === $this->settingsUtil->getOption('defer_events');
$this->requireConsentBeforeGtmLoad = '1' === $this->settingsUtil->getOption('gtm_snippet_consent_required');
$this->dataLayerVariableName = true === $this->eventDeferring || true === $this->requireConsentBeforeGtmLoad ? 'gtmCookieDataLayer' : 'dataLayer';
$this->consentEventName = $this->settingsUtil->getOption('consent_event_name');

$this->initialize();
}

private function initialize(): void
{
if ('1' === $this->settingsUtil->getOption('disabled')) {
return;
}

$requireConsentBeforeGtmLoad = $this->requireConsentBeforeGtmLoad ? 'true' : 'false';
$eventDeferring = $this->eventDeferring ? 'true' : 'false';

$script = <<<EOD
window.dataLayer = window.dataLayer || [];
window.gtmCookies = {
config: {
gtmLoaded: false,
eventDeferring: {$eventDeferring},
consentEventName: '{$this->consentEventName}',
requireConsentBeforeGtmLoad: {$requireConsentBeforeGtmLoad}
},
deferredEvents: [],
isConsentGranted: function () { return '1' === localStorage.getItem('GTM_COOKIES_CONSENT');},
setConsent: function (isGranted) { return localStorage.setItem('GTM_COOKIES_CONSENT', true === isGranted ? '1' : '0'); },
callbacks: {},
on: function(event, callback) {
if (false === gtmCookies.callbacks.hasOwnProperty(event)) {
gtmCookies.callbacks[event] = [];
}
gtmCookies.callbacks[event].push(callback);
},
emit: function(event, data) {
if (false === gtmCookies.callbacks.hasOwnProperty(event)) {
return;
}
gtmCookies.callbacks[event].forEach(function(callback) { callback(data); });
},
grantConsent: function() {
dataLayer.push({'event': gtmCookies.config.consentEventName});
gtmCookies.setConsent(true);
gtmCookies.emit('consentGranted');
},
repushEvents: function() {
let events = [];
while (0 < gtmCookies.deferredEvents.length) {
events.push(gtmCookies.deferredEvents.pop());
}
while (0 < events.length) {
{$this->dataLayerVariableName}.push(events.pop());
}
}
};
gtmCookies.on('gtmLoaded', function() {
gtmCookies.config.gtmLoaded = true;
});
const checkGtm = function() {
if ('undefined' === typeof window['google_tag_manager']) {
setTimeout(checkGtm, 500);
return;
}
gtmCookies.emit('gtmLoaded');
};
checkGtm();
EOD;
$this->outputUtil->addInlineScript($script, false);


if (true === $this->eventDeferring || true === $this->requireConsentBeforeGtmLoad) {
$script = <<<EOD
window.gtmCookieDataLayer = [];
dataLayer.originPush = dataLayer.push;
dataLayer.push = function(item) {
let result = null;
if (false === gtmCookies.config.gtmLoaded || (true === gtmCookies.config.eventDeferring && false === gtmCookies.isConsentGranted())) {
return gtmCookies.deferredEvents.push(item);
}
return gtmCookieDataLayer.push(item);
};
gtmCookies.on('consentGranted', function() {
if (true === gtmCookies.config.requireConsentBeforeGtmLoad) {
gtmCookies.on('gtmLoaded', function() {
gtmCookies.repushEvents();
});
} else {
gtmCookies.repushEvents();
}
});
gtmCookies.on('gtmLoaded', function() {
if (true === gtmCookies.isConsentGranted()) {
gtmCookies.repushEvents();
}
});
EOD;
$this->outputUtil->addInlineScript($script, false);
}


if (false === empty($headSnippet = $this->settingsUtil->getOption('gtm_snippet_head'))) {
if (true === $this->requireConsentBeforeGtmLoad) {
$gtmId = $this->extractGtmId($headSnippet);

$script = <<<EOD
const gtmCookiesLoadHeadSnippet = function() {
if (false === gtmCookies.isConsentGranted()) {
setTimeout(gtmCookiesLoadHeadSnippet, 1000);
return;
}
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','{$this->dataLayerVariableName}','{$gtmId}');
};
gtmCookiesLoadHeadSnippet();
EOD;
$this->outputUtil->addInlineScript($script);
} else {
add_action( 'wp_head', [$this, 'headSnippet'], 0 );
}
}

if (false === empty($bodySnippet = $this->settingsUtil->getOption('gtm_snippet_body'))) {
if (true === $this->requireConsentBeforeGtmLoad) {
$gtmId = $this->extractGtmId($bodySnippet);
$script = <<<EOD
const gtmCookiesLoadBodySnippet = function() {
if (false === gtmCookies.isConsentGranted()) {
setTimeout(gtmCookiesLoadBodySnippet, 1000);
return;
}
const noscript = document.createElement('noscript');
const iframe = document.createElement('iframe');
iframe.setAttribute('src', 'https://www.googletagmanager.com/ns.html?id={$gtmId}');
iframe.setAttribute('height', '0');
iframe.setAttribute('width', '0');
iframe.setAttribute('style', 'display:none;');
noscript.appendChild(iframe);
document.body.appendChild(noscript);
};
gtmCookiesLoadBodySnippet();
EOD;
$this->outputUtil->addInlineScript($script);
} else {
add_action( 'wp_body_open', [$this, 'bodySnippet'], 0 );
}
}
}

public function headSnippet(): void
{
$snippet = $this->settingsUtil->getOption('gtm_snippet_head');

if (true === $this->eventDeferring) {
$snippet = str_replace('\'dataLayer\',', '\''.$this->dataLayerVariableName.'\',', $snippet);
}

echo filter_var($snippet, FILTER_FLAG_STRIP_BACKTICK) . "\n";
}

public function bodySnippet(): void
{
echo filter_var($this->settingsUtil->getOption('gtm_snippet_body'), FILTER_FLAG_STRIP_BACKTICK) . "\n";
}

private function extractGtmId(string $string): string
{
preg_match('/GTM-[a-zA-Z0-9]+/', $string, $matches);

return false === empty($matches) ? $matches[0] : '';
}
}
2 changes: 1 addition & 1 deletion src/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public function settingsInit(): void
);

$this->settingsUtil->addSettingsField(
'_TODO_repush_events',
'defer_events',
'Defer events?',
[$this, 'checkboxField'],
'consent_event',
Expand Down
47 changes: 47 additions & 0 deletions src/Util/OutputUtil.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace TagConcierge\GtmCookiesFree\Util;

class OutputUtil
{
private $inlineScripts = ['header' => [], 'footer' => []];

public function __construct()
{
add_action( 'wp_head', [$this, 'wpHead'], 0 );
add_action( 'wp_footer', [$this, 'wpFooter'], 0 );
}

public function addInlineScript($script, $footer = true): OutputUtil
{
$this->inlineScripts[true === $footer ? 'footer' : 'header'][] = $script;

return $this;
}

public function wpHead(): void
{
if (0 === count($this->inlineScripts['header'])) {
echo '<!-- gtm-cookies no-header-scripts -->';
return;
}
echo '<script type="text/javascript" data-gtm-cookies-scripts>';
foreach ($this->inlineScripts['header'] as $script) {
echo filter_var($script, FILTER_FLAG_STRIP_BACKTICK) . "\n";
}
echo "</script>\n";
}

public function wpFooter(): void
{
if (0 === count($this->inlineScripts['footer'])) {
echo '<!-- gtm-cookies no-footer-scripts -->';
return;
}
echo '<script type="text/javascript" data-gtm-cookies-scripts>';
foreach ($this->inlineScripts['footer'] as $script) {
echo filter_var($script, FILTER_FLAG_STRIP_BACKTICK) . "\n";
}
echo "</script>\n";
}
}

0 comments on commit 16cf9c5

Please sign in to comment.