whitecube / laravel-cookie-consent

Register, configure and ask for cookies consent in a EU-compliant way
MIT License
305 stars 36 forks source link

Google Tag Manager #23

Open unaizp opened 9 months ago

unaizp commented 9 months ago

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?

Thanks in advance

QuentinGab commented 9 months ago

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

carlopaa commented 8 months ago

Hi @unaizp, have you made it work?

MediKathi commented 8 months ago

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 commented 4 months ago

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 commented 4 months ago

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 commented 4 months ago

Sounds nice! Thanks @joshuadegier 🙂

joshuadegier commented 4 months ago

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 commented 4 months ago

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>
  2. 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 commented 4 months ago

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 commented 4 months ago

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 commented 3 months ago

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.