8000 Consent dialog by tijsverkoyen · Pull Request #3048 · forkcms/forkcms · GitHub
[go: up one dir, main page]

Skip to content

Consent dialog#3048

Merged
carakas merged 26 commits intomasterfrom
consent-dialog
Aug 17, 2020
Merged

Consent dialog#3048
carakas merged 26 commits intomasterfrom
consent-dialog

Conversation

@tijsverkoyen
Copy link
Member
@tijsverkoyen tijsverkoyen commented Feb 26, 2020

Type

  • Enhancement
  • Feature

Pull request description

This PR enables developers to build GDPR compliant websites. GDPR affects all
website owners that target EU citizens.

In essence it will show a visitor a dialog wherein they can select their privacy
preferences. For example:

Example of the consent dialog

The preferences are stored in functional cookies so they are remembered.

IMPORTANT: it is still the responsibility of the developer/marketeer to
respect the visitors choices. Fork gives you the ability to do so.

Configuration of the consent dialog

In the backend under "Settings" there is a new block which allows you to define
different privacy levels. You can add as many as needed. A visitor can agree to
each level separately.

Configuration of the levels

For each level you need to add 2 translations, these translations will be used
in the consent dialog.

  • Title, this is used as the label for the checkbox
  • Text, this is used as a description for the level. As GDPR states a user needs
    to make an informed disscision, so in the description you can describe what the
    level means an how you will use the visitors agreement.

The reference code/name of the translation uses the level like explained below:

{{ ('msg.PrivacyConsentLevel' ~ level|ucfirst ~ 'Title')|trans|raw }}
{{ ('msg.PrivacyConsentLevel' ~ level|ucfirst ~ 'Text')|trans|raw }}

So for instance if you define the level "statistics" you will need to add 2
translations with the names:

  • PrivacyConsentLevelStatisticsTitle
  • PrivacyConsentLevelStatisticsText

Remark: If you change the levels the consent dialog will be shown again
to all visitors. Previous choices will not be ticked.

There is one level that is always available: functional, this is always
allowed.
The user can not change this. It is added as it is a good practice to
explain your visitor what you store in these cookies.

Usage

Retrieving choices in JavaScript

The choices of the visitor are available in jsData in the privacyConsent-object.

  • possibleLevels contains all the levels that are defined thru the CMS.
  • levelsHash, this is the hash that is used to determine if the levels are changed.
  • visitorChoices, this contains the choice a user has made per level. The default is false.

Retrieving choices in PHP

A new service is available as ForkCMS\Privacy\ConsentDialog. This service
exposes some methods that you can use:

  • getLevels(bool $includeFunctional = false), this will return all the defined levels.
  • getVisitorChoices(): returns an array with the users chosen preferences
  • hasAgreedTo(string $level): returns a boolean if the user has agreed to a given level. Returns false per default, also for non existing levels.

So for example if you want to do personalations if the user has agreed to the
personalisation-level, you can use the snippet below:

if ($this->getContainer()->get(ForkCMS\Privacy\ConsentDialog::class)->hasAgreedTo('personalisation')) {
    ... your code ...
}

Anonymize IP

Google Analytics has a feature called anonymizeIp. If you use our Google Analytics-
or Google Tag Manager integration we use the anonymizeIp feature by default.

In this case you need to add a privacy level with the name statistics. If you do
so, and the visitor agrees to it we will disable the IP anonymization.

Warning: In Belgium this is not correct. The DPA decided that anonymizeIp
is not enough, therefor you should ask permission before tracking! See below how you
can achieve this.

Google Tag Manager

If you use Google Tag Manager it is completely up to you to adhere the visitors
preferences. Below is explained on how it can be configured.

Create a variables
  • Open Variables
  • Click "New" next to "User-Defined Variables"
  • Name it "Privacy Consent - DataLayer - Level XXX", eg: Privacy Consent - DataLayer - Level statistics
  • Choose "Data Layer Variable" for the type
  • Use "privacyConsentLevelXXXAgreed" as the value for "Data Layer Variable Name", replace XXX with the ucfirst version of you level name
  • Enable "Set Default Value", and set the Default Value to "false"

Example configuration for data layer variables

Do this for all the levels you defined.

Create triggers
  • Open Triggers
  • Click "New"
  • Name it "Privacy Consent - Check - Level XXX not allowed", eg: Privacy Consent - Check - Level statistics not allowed
  • Choose "Custom event" for the Trigger Type
  • Use ".*" as Event name, and tick "Use regex matching"
  • Select "Some custom Events"
  • Select the relevant variable, something like DataLayer - Privacy Consent - Level XXX"
  • Choose "Equals" as the condition and "false" as the value

Not allowed trigger configuration

  • Click "New"
  • Name it "Privacy Consent - Event - Level XXX - Agreed", eg: Privacy Consent - Event - Level statistics - Agreed
  • Choose "Custom event" for the Trigger Type
  • Use "privacyConsentLevelXXXAgreed" as Event name, replace XXX with the ucfirst name of your level name
  • Select "All Custom Events"

Event trigger configuration

Repeat these steps for each level you have defined

Load a tag only when it is allowed
  • Open the tag you want to configure. For instance Hotjar
  • Edit the triggers
  • Add "Privacy Consent - Event - Level XXX - Agreed" on the Firing Triggers. This will add the tag if the user agrees.
  • Add "Privacy Consent - Check - Level XXX not allowed" on the Exceptions. This will prevent the tag from loading when the user did not agree.

Tag configuration

By adding the exception the Tag won't be fired. This is the case when a visitor
has not agreed to anything. In that case the consent dialog is shown.
When the visitor clicks "Save my preferences", we will fire an event per agreed
level: privacyConsentLevelXXXAgreed. This will fire the tag, as it is configured
as a Firing trigger.

If the visitor visits another page the exception won't be triggered as the variable
is true. So the tag will be fired by the "All Pages" trigger.

Removed features

Adding new features also means cleaning up non relevant features, so some are removed:

Cookie bar

This new Consent Dialog replaces the previous Cookie Bar.

This means you can't use the following methods anymore:

  • $this->get('fork.cookie')->hasAllowedCookies()
  • $this->get('fork.cookie')->hasHiddenCookieBar()

Visitor Id

The visitor Id that was generated and stored in a cookie does not exist anymore.
If you rely on it you should build this feature yourself.

The module setting will fallback to `site_html_header` as that was the name before.
While at it I fixed the labels as they used hardcoded text or displayed faulty output.
The module setting will fallback to `site_html_footer` as that was the name before.
While at it I fixed the labels as they used hardcoded text or displayed faulty output.
It seems like this settings was already available but not displayed in the backend :s
Sorry, I copy/pasted it wrong.
I think I wrote it more readable:

* If Google Tag Manager is added thru the backend we expect Google Analytics to be loaded from within Google Tag Manager
* If the web property is empty we can't build a correct code therefore it should not be added
* If the web property is present in any of the site wide HTML than we should not add it.
gtag.js is the current way to add Google Analytics to a site.

See https://support.google.com/analytics/answer/7538414
The Analytics module should not be responsible for adding the Google Analytics
Tracking Code.
As we now have a separate setting we use that one to decide if the code should be added.
It should be as easy as:

	$container->get(ForkCMS\Google\TagManager\DataLayer::class)->add('key', $value);

We will add the anonimizeIp variable by default
This consent should enable developers to build GDPR compliant websites. In the
backend the developer can define privacy levels to which the user can agree.

There is a service ForkCMS\Privacy\ConsentDialog which can be used to retrieve
the preferences of the current user.

The data is also available in jsData for usage in JavaScript
This feature conflicts with GDPR, although we had a way to not set the cookie.
As GDPR states that the user should know he is tracked, we can't generate a way
of tracking per default.

If you need this feature you should build it yourself, you can look at the past
code on how it was done, but you should ask permission first.
@tijsverkoyen tijsverkoyen requested a review from a team as a code owner February 26, 2020 11:50
@sarahmey
Copy link
Contributor

Some considerations / ideas / ...:

  • I would keep the texts below the checks a lot simpler. A lot of people don't know hotjar or google analytics, they will just get more questions and are more likely not to want it because they don't understand it. (Genre "With these cookies we can make our website even better."

  • I'm not a big fan of how big it is and how much it is in the way. I would prefer it to more like this:


WE USE COOKIES
This is an infotext about what cookies do and why you really want them.


| Set preferences | | Accept all |



With set preferences a modal link, which has a backdrop that makes it more of a separate thing instead of 'noise' on your page.

If you don't go with this idea, I would still put the whole thing in a 8000 modal because it is too big for an alert over your content, and I would put the button on the left, for an easier flow.

Example: https://www.standaard.be/ (but I would go with a shorter infotext).

  • Setting the cookie names in the backend: doesn't that make it really easy for the website owner that knows nothing from website development to break the thing?

  • Having to add 2 translations: is this clear? Do you know you have to do it this way if you haven't read this PR? Maybe it's a better idea to be able to set them in the backend where you put the privacy consent dialog. Instead of setting the technical name there, you have the technical names and you can change the texts there.

@tijsverkoyen
Copy link
Member Author

@sarahmey I don't agree.

  • The text should inform the visitor what you do. As they need to make an informed and free consent. "With these cookies we can make our website even better." will not be enough. You have to tell which other parties you share the data with and what they do with it.
  • The "set preferences" link is a good idea, but from a marketing persepective, nobody ever will click on it.

About the backend:

  • I know it is technical, as it is really up to the developer, marketeer, website owner, ... to define the levels. This PR is more of a "framework" wherein the developer can build GDPR compliant stuff, and the needed data is sent to Google Tag Manager so the marketeer can do GDPR compliant stuff.
  • The translations are used, because I don't see a feasible way of setting texts for every language thru the interface. As this is something probably will only be configured once, I think it will be the developer that configures this, and they know about translations.

@tijsverkoyen
Copy link
Member Author

Also; the "accept all" button is considered a dark pattern as it does not really inform you.

@fspin
Copy link
fspin commented Feb 27, 2020

My two cents:

  1. Agree with @tijsverkoyen : it should be very explicit about what it precisely those cookies do.
  2. I wonder if it's possible to add a Social checkbox (so, you can opt-in the analytics, but opt-out the social media tracking)
  3. I do agree with @sarahmey about the backdrop. Although I hate it, the idea is to let the visitor to decide which cookies do they want to allow/reject before they keep navigating through the site.
  4. In a more complex question: if you click on the disclaimer link, you'll be redirected to a new page, but the cookie modal/popup will be on top of the text you need to read to make a conscious decision. This could be a problem.
  5. In a former project I set the cookie preferences also in a cookie,... but maybe in this case is not necessary.

@tijsverkoyen
Copy link
Member Author

@fspin
Re:2 the levels are defined by the developer/marketeer/... so if they want "social" they can add it.
Re:4 that is why I'm not a fan of a backdrop.
Re:5: preferences are stored in cookies (functional)

@fspin
Copy link
fspin commented Feb 27, 2020

Re:2 the levels are defined by the developer/marketeer/... so if they want "social" they can add it.

In your screenshots I see the possibility to add a new checkbox, indeed, but not an extra explanatory text.


Re:4 that is why I'm not a fan of a backdrop.

Even without a backdrop, you'll still have the problem if your modal/popup is fixed and large (or your screen is small).
This could be solved opening the disclaimers/policies within the modal, or in a new one. Or, changing the position of the modal from fixed/absolute, to relative: only in the disclaimer pages.


Re:5: preferences are stored in cookies (functional)

I meant that the cookies' preferences. For instance: if I agree with the analytics cookies, but not with the social cookies. Once I close the dialog, and start navigating... would it be enough to set a js variable in order to get the google analytics properly working?
In my project it wasn't. It was much easier to solve setting that preference (the user agrees to use analytics cookies ) within a cookie. I hope this clarifies a bit my point.

@tijsverkoyen
Copy link
Member Author
tijsverkoyen commented Feb 27, 2020

Re:2 the levels are defined by the developer/marketeer/... so if they want "social" they can add it.
In your screenshots I see the possibility to add a new checkbox, indeed, but not an extra explanatory text.

Those are added thru translations in Fork, as explained in the PR description.

Re:4 that is why I'm not a fan of a backdrop.
Even without a backdrop, you'll still have the problem if your modal/popup is fixed and large (or your screen is small).
This could be solved opening the disclaimers/policies within the modal, or in a new one. Or, changing the position of the modal from fixed/absolute, to relative: only in the disclaimer pages.

Okay, se in essence @sarahmey and yourself would prefer a modal which forces the user to make a choice before consuming the actual content. And we should provide a way to hide this modal on certain pages.

So to wrap it up:
The configuration of the consent dialog should be extended with:

  1. Configuring the title per level for each language
  2. Configuring the text per level for each language
  3. Configuring the before and after text for each language
  4. Selecting a page per language that is marked as the privacy-page where the visitor can find/read the full privacy statement.

In my personal opinion this will clutter up the interface, so if we do this I will move the whole Consent dialog to a separate item in the settings menu. I also think we are making a lot of decisions that are not up to us, but are decisions that should be made by the project-owner.

Re:5: preferences are stored in cookies (functional)
I meant that the cookies' preferences. For instance: if I agree with the analytics cookies, but not with the social cookies. Once I close the dialog, and start navigating... would it be enough to set a js variable in order to get the google analytics properly working?
In my project it wasn't. It was much easier to solve setting that preference (the user agrees to use analytics cookies ) within a cookie. I hope this clarifies a bit my point.

With the levels, a visitor choses to which he/she agrees. It could be that the visitor agrees to statistical but not social. It is up to the developer to handle these correctly. In the PR description there is an example on how to load Hotjar only if the user agrees, and hotter will be injected as soon as the user agrees.

@fspin
Copy link
fspin commented Feb 27, 2020

Re:4 that is why I'm not a fan of a backdrop.

Even without a backdrop, you'll still have the problem if your modal/popup is fixed and large (or your screen is small).
This could be solved opening the disclaimers/policies within the modal, or in a new one. Or, changing the position of the modal from fixed/absolute, to relative: only in the disclaimer pages.

Okay, se in essence @sarahmey and yourself would prefer a modal which forces the user to make a choice before consuming the actual content. And we should provide a way to hide this modal on certain pages.

In fact, you could let the user just navigate the site without making a decision. In which case it will means the user didn't accept optional cookies, right?

In order to do this, your modal should appear (and stay) relative-positioned at the top of each page. (see this example)

So to wrap it up:
The configuration of the consent dialog should be extended with:
1. Configuring the title per level for each language
2. Configuring the text per level for each language
3. Configuring the before and after text for each language
4. Selecting a page per language that is marked as the privacy-page where the visitor can find/read the full privacy statement.

In my personal opinion this will clutter up the interface, so if we do this I will move the whole Consent dialog to a separate item in the settings menu. I also think we are making a lot of decisions that are not up to us, but are decisions that should be made by the project-owner.

It will clutter the backend interface, you mean, right? Then I agree... it will be better to put it in its own section

I wonder which decisions are we making?

I just think this isn't easy. And it should be present in every site using analytics or social plugins (mandatory in the EU, for the moment, where we are located): So, it seems to me reasonable to put it by default. Afterwards, the project owner can always dismissed all this, but it will be a (quite irresponsible) opt-out.

@tijsverkoyen
Copy link
Member Author

In fact, you could let the user just navigate the site without making a decision. In which case it will means the user didn't accept optional cookies, right?
In order to do this, your modal should appear (and stay) relative-positioned at the top of each page. (see this example)

The example you give hides the possible options. The proposed implementation allows the user to navigate thur the site without being forced to choose anything (which is my personal way of handling it).

I wonder which decisions are we making?
I just think this isn't easy. And it should be present in every site using analytics or social plugins (mandatory in the EU, for the moment, where we are located): So, it seems to me reasonable to put it by default. Afterwards, the project owner can always dismissed all this, but it will be a (quite irresponsible) opt-out.

Fork is a "framework" to build sites it does not dictate on how something should look, or how the user interacts with it. So in my opinion you decide to "block" the user to visit the site if you make a "wall" the default.
But as I said, let's discuss this in person.

@carakas carakas modified the milestones: 5.8.0, 5.9.0 Mar 3, 2020
@carakas carakas mentioned this pull request Jul 31, 2020
@tijsverkoyen tijsverkoyen changed the title [WIP] Consent dialog Consent dialog Aug 5, 2020
@tijsverkoyen
Copy link
Member Author

I decided not to add the title and text per level per language as I think the translations are fine, however I will write a blogpost on https://www.fork-cms.com to explain how it should be used.

I fixed the close-button, so the consent-dialog can be closed when it is in the way of the content, in that case nothing is saved and it will appear again until the visitor has saved the chosen preferences. Ofcourse developers can change the behaviour and convert it into a real modal.

I also will discuss it in person with Fabian.

@tijsverkoyen tijsverkoyen requested a review from Katrienvh August 5, 2020 12:35
The ePrivacy Directive specifies that persistant cookies can not be
stored longer than 12 months, so we splitted it in half.

More information on: https://gdpr.eu/cookies/#:~:text=All%20persistent%20cookies%20have%20an,you%20do%20not%20take%20action.
public: true
arguments:
- "@fork.settings"
- "@fork.cookie" No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new line at the end of the file is missing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1383ce8

*/
public function hasAllowedCookies(): bool
{
return $this->get('cookie_bar_agree', 'N') === 'Y';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really love to put this in Fork 5 but this is a BC break.
Can you implement it in a way that in Fork 5 you can choose for the cookie bar or the consent dialog? And then just add a deprecation notice on the cookie bar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c963745


// facebook admins given?
// @deprecated remove this in Fork 6, Facebook should not be added automaticall
$facebookAppId = $this->get('fork.settings')->get('Core', 'facebook_app_id', null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation is wrong here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1383ce8

}
// if the consent dialog is disabled we will anonymize by default
if (!$this->modulesSettings->get('Core', 'show_consent_dialog', false)) {
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have it fallback here on the cookiebar and mark it as deprecated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c963745

<ul class="list-unstyled">
<li class="checkbox">
<label for="showCookieBar" class="control-label">{% form_field show_cookie_bar %} {{ 'msg.ShowCookieBar'|trans|ucfirst }}</label>
<label for="showConsentDialog" class="control-label">{% form_field show_consent_dialog %} {{ 'msg.ShowConsentDialog'|trans|ucfirst }}</label>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do this as a radiobutton that people have to choose between nothing, the cookiebar or the consent dialog for in Fork 5 and then remove the cookiebar in Fork 6

* Handles the cookieBar
* Handles the privacy consent dialog
*/
jsFrontend.cookieBar = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removing this is not BC

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c963745

Readded a lot of stuff to make the (deprecated) cookiebar work again.

I did not make it use a radiobutton as I think it should be decided by
the developer.
@tijsverkoyen
Copy link
Member Author

@carakas I did not opted for a radiobutton, I just restored the checkbox for the cookie-bar.
In my opinion the dev should decided which option should be used, having them both isn't the most sensible option.

@tijsverkoyen tijsverkoyen requested a review from carakas August 17, 2020 14:10
@carakas carakas merged commit 7246741 into master Aug 17, 2020
@carakas carakas deleted the consent-dialog branch August 17, 2020 20:00
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

Successfully merging this pull request may close these issues.

4 participants

0