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

Google Tag Manager #23

Open
unaizp opened this issue Nov 17, 2023 · 14 comments
Open

Google Tag Manager #23

unaizp opened this issue Nov 17, 2023 · 14 comments

Comments

@unaizp
Copy link

unaizp commented Nov 17, 2023

Hello, this is a fantastic script for Laravel.
I use Google Tag Manager instead GA4 and dont't know how to create a custom code for this script.
Searched the manual and can't find anything.

¿How can I Use this type of code?
<script>(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','dataLayer','GTM-XXXXXX');</script>

Thanks in advance

@QuentinGab
Copy link

Hi,
you can follow this guide https://support.google.com/tagmanager/answer/10718549 to implement consent with Tag manager

@carlopaa
Copy link

carlopaa commented Dec 9, 2023

Hi @unaizp, have you made it work?

@MediKathi
Copy link

MediKathi commented Dec 19, 2023

I was able to make this workaround, based on the AnalyticCookiesCategory.php:

This is quick solution, don't use this without more reserach which cookies has to be mentioned

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use Whitecube\LaravelCookieConsent\Consent;
use Whitecube\LaravelCookieConsent\Facades\Cookies;

use Whitecube\LaravelCookieConsent\Cookie;
use Whitecube\LaravelCookieConsent\CookiesGroup;

class CookieServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        Cookies::essentials()
            ->session()
            ->csrf();


        if (config('cookieconsent.google.analyticsId') != null) {
            Cookies::analytics()
                ->google(config('cookieconsent.google.analyticsId'));
        } else if (config('cookieconsent.google.gtmId') != null) {
            Cookies::analytics()
                ->group(function (CookiesGroup $group) {
                    $group->name('gtm')
                        ->cookie(
                            fn (Cookie $cookie) => $cookie->name('_ga')
                                ->duration(2 * 365 * 24 * 60)
                                ->description(__('cookieConsent::cookies.defaults._ga'))
                        )
                        ->cookie(
                            fn (Cookie $cookie) => $cookie->name('_gid')
                                ->duration(24 * 60)
                                ->description(__('cookieConsent::cookies.defaults._gid'))
                        )
                        ->cookie(
                            fn (Cookie $cookie) => $cookie->name('_gat')
                                ->duration(1)
                                ->description(__('cookieConsent::cookies.defaults._gat'))
                        )
                        ->accepted(function (Consent $consent) {
                            $id = config('cookieconsent.google.gtmId');
                            $consent->script('<script>(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\',\'dataLayer\',\'' . $id . '\');</script>');
                        });
                });
        }
    }
}

This is quick solution, don't use this without more reserach which cookies has to be mentioned

@joshuadegier
Copy link

Just wanted to add my two cents here.

One of our clients uses Google Tag Manager as well and we had some feedback from the marketing company about being unable to get Tag Assistant by Google connected. Upon review, and this issue, I noticed the difference in JS code that was being injected.

What Laravel Cookie Consent does is inject gtag code:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
<script>
    window.dataLayer=window.dataLayer||[];
    function gtag(){dataLayer.push(arguments);}

    gtag('js',new Date());
    gtag(‘config', 'G-XXXXXX');
</script>

Whereas you would want the Google Tag Manager code in case you use GTM:

<script>
    (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)](https://www.googletagmanager.com/gtm.js?id=);
    })(window,document,'script','dataLayer','GTM-XXXXXX');
</script>

I can setup a PR including a new Cookies::analytics()->gtm() method that sets up the GTM code instead of the Gtag. But I'm not sure what the difference is going to be for what Cookies will be set when accepting. I suppose it still sets the base Google cookes (_ga, _gid, _gat), but as far as I know GTM can inject more code besides just a Gtag.

@joshuadegier
Copy link

I suppose this answers the question:

Google Tag Manager (GTM) itself does not set any cookies on a user’s device. GTM is a tool that allows website owners to manage and implement tracking tags on their website. It does not use cookies for its own functionality.

However, when you use GTM, you may be adding tracking tags to your website that do set cookies.

If you use Google Analytics through GTM, it will set the __ga cookie on the user’s device to track behavior on your website. Similarly, if you are using other tracking tags from third party platforms, they may also set cookies on the user’s device.

I'll draft a PR that creates a ->google() and ->gtm() method on Cookies and have them share the configuration for the cookie group as seen in AnalyticCookiesCategory.php. The first method will add them by default as that is what you want and the gtm method would take a parameter set to true before the cookies are added.

@toonvandenbos
Copy link
Member

Sounds nice! Thanks @joshuadegier 🙂

@joshuadegier
Copy link

joshuadegier commented Apr 12, 2024

I've tried to create a setup today, but not quiet happy with the outcome yet. Compared to the gtag code (or the gtag injected by the GTM code) the GTM code itself sets no cookies. In the example above by @MediKathi it injects the GTM code after acceptance but I'd rather have it injected on pageload and see if there is a way to have this plugin inject a GTM config that follows up loading the accepted tags.

See this link for some more info. The code I think is needed, one way or another, is something like this:

  gtag('consent','default',{
    'ad_storage':'denied',
    'analytics_storage':'denied',
    'personalization_storage':'denied'
  });

If anybody has some more background information about the working of GTM I'd gladly accept some help. It's a first time for me diving deeper in this than just having a consent popup that injects a gtag.

@florinche
Copy link

Hi,

From my understanding using google documentation here it must done in 2 parts:

  1. First load the defaults (no scripts/cookies are loaded yet):
  <script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }
  gtag('consent', 'default', {
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'ad_storage': 'denied',
    'analytics_storage': 'denied',
    'wait_for_update': 500,
  });
  dataLayer.push({'gtm.start': new Date().getTime(), 'event': 'gtm.js'});
  </script>
  1. Upon receiving consent update the 'consents' to 'granted' and load the script, this can be done by tapping into the consent event with something like this:
<script>

  function loadGoogleTagManagerScript() {
     // update the initial consents from denied to granted
      gtag('consent', 'update', {
          ad_user_data: 'granted',
          ad_personalization: 'granted',
          ad_storage: 'granted',
          analytics_storage: 'granted'
      });

     // generate and load the google tag manager script
      var gtmScript = document.createElement('script');
      gtmScript.async = true;
      gtmScript.src = 'https://www.googletagmanager.com/gtm.js?id={YOUR GOOGLE TAG MANAGER ID}';

      var firstScript = document.getElementsByTagName('script')[0];
      firstScript.parentNode.insertBefore(gtmScript, firstScript);
  }

 // listen for the cookie consent event
  window.addEventListener("CookieConsentGranted", function(e) {
      loadGoogleTagManagerScript();
  });
  
</script>

I am currently looking to see if "google consent mode v2" requires express events from denied to granted in order to load the tags properly. Anyway with a javascript event is also more convenient since we don't need to reload the page after the consents are given.

@joshuadegier
Copy link

Thanks for adding a few examples! I haven't found time to proceed on the code I'm working on. As far as I can see this would require adding a little bit of js code to the package in order to fire the events without pageload. I agree that this would be a cleaner option compared to refreshing the page.

Any ideas on whether we need to separate the consent that is given for user data / personalization / storage / analytics storage?

@florinche
Copy link

Yes it needs to be separated, depending what other codes are you delivering trough google tag manager.
My company for example delivers trough google tag manager: analytics, google ads remarking codes, facebook pixel tracking code, linkedin, others. Each one follows into a different category: essential, marketing, analytics, etc.

I am stuck in project for the next couple of days, then i plan to try and see if can push a pr.

@hristoaleksandrov
Copy link

Hey, any idea when support for new policies will be added, so we can have the consent with GTM, our team is experienceing issues, google says we do not pass those policies, and I really do not want to implement the GTM consent as well as this vendor package.

Thanks.

@juliancarstairs
Copy link

@florinche were you able to create a PR for this? Issue is that there is no gtag denied prior to injecting the code so the consent is not necessarily viable, it works if you consent. It should be applied to the prior to consenting as denied:

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }
  gtag('consent', 'default', {
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'ad_storage': 'denied',
    'analytics_storage': 'denied',
    'wait_for_update': 500,
  });
  gtag('js', new Date());
  gtag('config', 'Google tag ID');
  </script>

Apologies if I am misunderstanding.

@florinche
Copy link

@juliancarstairs unfortunately not. Though i made it pass with various customizations on our platform we still ended up getting "hit" really hard in google ads by their rules to have "Google Consent Mode v2"

We had to turn to one of their google certified cmp partners (we got spammed on daily basis, basically felt forced to use one of theirs) just to pass the same diagnostics we passed before using custom implementation.

I have zero time for the next weeks. :(

@Chris-FXW
Copy link

Chris-FXW commented Jan 30, 2025

Google Tag Manager itself doesnt use Cookies. What i did was i added GTM code to head and body as needed to every page. In header, before GTM code, i added:

<script>
        // Define dataLayer and the gtag function.
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}

        // Set default consent to 'denied' as a placeholder
        // Determine actual values based on your own requirements
        gtag('consent', 'default', {
            'ad_storage': 'denied',
            'ad_user_data': 'denied',
            'ad_personalization': 'denied',
            'analytics_storage': 'denied'
        });
</script>

After GTM code i added:

<!--Cookie Consent -->
    <script>
        function updateConsentState() {
            gtag('consent', 'update', consentData);
        }
    </script>
    
    @cookieconsentscripts
<!-- End of Cookie Consent -->

And in CookieServiceProvider i added:

// Register Google Tag Manager cookies under analytics
Cookies::analytics()
    ->group(function (CookiesGroup $group) {
        $id = config('services.google.tag_manager');

        $group->name('google_tag_manager')
            ->cookie(fn(Cookie $cookie) => $cookie->name('_ga')
                ->duration(2 * 365 * 24 * 60)
                ->description(__('cookieConsent::cookies.defaults._ga'))
            )
            ->cookie(fn(Cookie $cookie) => $cookie->name('_gid')
                ->duration(24 * 60)
                ->description(__('cookieConsent::cookies.defaults._gid'))
            )
            ->cookie(fn(Cookie $cookie) => $cookie->name('_gat')
                ->duration(1)
                ->description(__('cookieConsent::cookies.defaults._gat'))
            )
            ->cookie(fn(Cookie $cookie) => $cookie->name('_gcl_au')
                ->duration(90 * 24 * 60)
                ->description(__('cookieConsent::cookies.defaults._gcl_au'))
            )
            ->accepted(fn(Consent $consent) => $consent
                ->script(
                    "<script>
                            const consentData = {
                                'ad_user_data': 'granted',
                                'ad_personalization': 'granted',
                                'ad_storage': 'granted',
                                'analytics_storage': 'granted',
                            };
                            updateConsentState(consentData);
                        </script>"
                )
            );
    });

Im not sure how "right" this is, but it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants