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

Consent mode v2 #385

Merged
merged 7 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/03. user guide/09. settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ Adding a Facebook admin id allows the administrator to view the website in [Face

![Facebook](./assets/settings_general_facebook.png)

### Privacy settings

<div class="alert alert-warning" role="alert">
The Cookie bar option is not compliant with GDPR.
</div>

If you choose to use the Consent Dialog, you will be able to select which "consents" you are using on the website. These consents are aligned with [Consent Mode v2](https://support.google.com/google-ads/answer/10000067?hl=en).

If enabled a user will be presented with a consent dialog that allows to grant permission per consent that is activated. Every time you change something in this configuration, all users will be presented with the dialog again.

You can translate, or change the text that is shown through the [translations](#user-content-translations) by filtering on:

* Application: Frontend
* Types: Message
* Reference: PrivacyConsent

For the configuration in Google Tag Manager: see [Module guide → Consent Dialog](05.%20module%20guide/29.%20consent_dialog.md).

## Advanced

The more advanced settings are specially for the dedicated technical people. Although every administrator should change the names and email address where the cms will send from.
Expand Down
87 changes: 87 additions & 0 deletions docs/05. module guide/29. consent_dialog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Consent dialog

The Consent Dialog allows the visitor to give "consent" for different levels. We follow Google Consent Mode v2.

## Configuration in Google Tag Manager

1. Enable "Consent mode support"
* Click Admin → Container Settings
* Enable "Enable consent overview" under "Additional settings"
2. Use the `Consent initialization - All Pages` trigger instead of `All Pages`.
3. On all tags you are now able to configure the required consent.

See [https://support.google.com/tagmanager/answer/10718549?hl=en](https://support.google.com/tagmanager/answer/10718549?hl=en)
for all details.

### Advanced

If you want to trigger something when the visitor updates consent

1. Create a trigger
* Trigger Type: Custom Event
* Event name: `consentUpdate`
* This trigger fires on: All Custom Events
2. Add this trigger to all needed tags. In most cases you will need to add this as an extra trigger (or).


## Show the Consent Dialog with javascript

In some situations you will need to show the Consent Dialog again. For instance to allow the visitor to revisit
their consents, or if the user wants to see extra content wherefor consent is required (e.g.: YouTube embed).

The consent dialog can be reopened by executing the following JS:

```javascript
jsFrontend.consentDialog.show()
```


## Check for consent in Javascript

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

* `possibleLevels`, contains all the levels that are defined in 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.

So for example if you want to do some personalization if the user has given consent for `personalization_storage`:

```javascript
if (
jsData.privacyConsent.possibleLevels.includes('personalization_storage')
&& jsData.privacyConsent.visitorChoises.personalization_storage
) {
// do personalization
...
}
```


## Check for consent in PHP

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

* `public static function getConsentLevels(): array`: This will return all possible levels. These are the Consent Mode v2 levels.
* `public function isDialogEnabled(): bool`: Returns true if the Consent Dialog is enabled, false if not.
* `public function getLevels(bool $includeFunctional = false): array`: this will return all the levels that are enabled in the CMS.
* `public function getVisitorChoices(): array`: Returns an array with the users chosen preferences
* `public function hasAgreedTo(string $level): bool`: Returns a boolean if the user has agreed to a given level or not. Returns false per default, also for non existing levels.

So for example if you want to do personalization if the user has given consent for `personalization_storage`, you
can use the snippet below:

```php
<?php
...

use ForkCMS\Privacy\ConsentDialog;

...

$consentDialog = $this->getContainer()->get(ConsentDialog::class);
if($consentDialog->hasAgreedTo('personalization_storage')) {
// do personalisation
...
}
...
```
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Questions? Feel free to discuss on our [Slack channel](https://fork-cms.herokuap
1. [Implementing Twitter Cards](05.%20module%20guide/26.%20twittercards.md)
1. [Symfony Form File](05.%20module%20guide/27.%20symfony_form_file.md)
1. [Symfony Form Image](05.%20module%20guide/28.%20symfony_form_image.md)
1. [Consent Dialog](05.%20module%20guide/29.%20consent_dialog.md)

### 6. Contribute
1. [Introduction](06.%20contribute/01.%20introduction.md)
Expand Down
53 changes: 25 additions & 28 deletions src/Backend/Modules/Settings/Actions/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Backend\Modules\Settings\Actions;

use ForkCMS\Privacy\ConsentDialog;
use ForkCMS\Utility\Akismet;
use SpoonFilter;
use Backend\Core\Engine\Base\ActionIndex as BackendBaseActionIndex;
Expand Down Expand Up @@ -325,17 +326,22 @@ private function loadForm(): void
'show_consent_dialog',
$this->get('fork.settings')->get('Core', 'show_consent_dialog', false)
);
$this->form->addText(
'privacy_consent_levels',
implode(
',',

foreach (ConsentDialog::getConsentLevels() as $level) {
$checkbox = $this->form->addCheckbox(
'privacy_consent_level_' . $level,
$this->get('fork.settings')->get(
'Core',
'privacy_consent_levels',
[]
'privacy_consent_level_' . $level,
($level === ConsentDialog::CONSENT_FUNCTIONALITY_STORAGE)
)
)
);
);

if ($level === ConsentDialog::CONSENT_FUNCTIONALITY_STORAGE) {
$checkbox->setAttribute('disabled', 'disabled');
}
}
$this->template->assign('privacy_consent_levels', ConsentDialog::getConsentLevels());
}

protected function parse(): void
Expand Down Expand Up @@ -445,18 +451,6 @@ private function validateForm(): void
)->isInteger(BL::err('InvalidInteger'));
}

$privacyConsentLevelsField = $this->form->getField('privacy_consent_levels');
if ($privacyConsentLevelsField->isFilled()) {
$levels = explode(',', $privacyConsentLevelsField->getValue());
foreach ($levels as $level) {
if (!preg_match('/^[a-z_\x7f-\xff][a-z0-9_\x7f-\xff]*$/i', $level)) {
$privacyConsentLevelsField->setError(sprintf(BL::err('InvalidVariableName'), $level));
break;
}
}
}


// no errors ?
if ($this->form->isCorrect()) {
// general settings
Expand Down Expand Up @@ -687,15 +681,18 @@ private function validateForm(): void
'show_consent_dialog',
$this->form->getField('show_consent_dialog')->getChecked()
);
$privacyConsentLevels = [];
if ($privacyConsentLevelsField->isFilled()) {
$privacyConsentLevels = explode(',', $privacyConsentLevelsField->getValue());

foreach (ConsentDialog::getConsentLevels() as $level) {
$value = $this->form->getField('privacy_consent_level_' . $level)->getChecked();
if ($level === ConsentDialog::CONSENT_FUNCTIONALITY_STORAGE) {
$value = true;
}
$this->get('fork.settings')->set(
'Core',
'privacy_consent_level_' . $level,
$value
);
}
$this->get('fork.settings')->set(
'Core',
'privacy_consent_levels',
$privacyConsentLevels
);

// assign report
$this->template->assign('report', true);
Expand Down
122 changes: 109 additions & 13 deletions src/Backend/Modules/Settings/Installer/Data/locale.xml
Original file line number Diff line number Diff line change
Expand Up @@ -630,25 +630,65 @@
<translation language="nl"><![CDATA[Privacy toestemmingen]]></translation>
<translation language="en"><![CDATA[Privacy consents]]></translation>
</item>
<item type="label" name="TechnicalName">
<translation language="nl"><![CDATA[Technische naam]]></translation>
<translation language="en"><![CDATA[Technical name]]></translation>
</item>
<item type="message" name="HelpPrivacyConsents">
<translation language="nl"><![CDATA[De GDPR regelt hoe je als website eigenaar moet omgaan met cookies. Hieronder kan je verschillende niveaus instellen, waarmee je de bezoeker verschillende opties kan bieden.]]></translation>
<translation language="en"><![CDATA[GDPR defines how you as a website owner may use cookies. Below you are able to define several levels, so the user can choose from them.]]></translation>
</item>
<item type="message" name="NoPrivacyConsentLevels">
<translation language="nl"><![CDATA[Je hebt nog geen niveaus ingegeven.]]></translation>
<translation language="en"><![CDATA[No levels yet.]]></translation>
<item type="message" name="HelpPrivacyConsentLevelAdStorage">
<translation language="nl"><![CDATA[Tracking voor advertentiedoeleinden (bijvoorbeeld: Google Ads)]]></translation>
<translation language="en"><![CDATA[Tracking for advertisment purposes (e.g.: Google Ads)]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNameAdStorage">
<translation language="nl"><![CDATA[ad_storage]]></translation>
<translation language="en"><![CDATA[ad_storage]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevelAdUserData">
<translation language="nl"><![CDATA[Regelt de toestemming voor het delen van advertentiegerelateerde gebruikersdata]]></translation>
<translation language="en"><![CDATA[Is used to share advertisment related user data]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNameAdUserData">
<translation language="nl"><![CDATA[ad_user_data]]></translation>
<translation language="en"><![CDATA[ad_user_data]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevelAdPersonalization">
<translation language="nl"><![CDATA[Regelt de toestemming voor gepersonaliseerde advertenties]]></translation>
<translation language="en"><![CDATA[Permission related to personalised advertisements]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNameAdPersonalization">
<translation language="nl"><![CDATA[ad_personalization]]></translation>
<translation language="en"><![CDATA[ad_personalization]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevelAnalyticsStorage">
<translation language="nl"><![CDATA[Tracking voor analytische doeleinden (bijvoorbeeld: Google Analytics)]]></translation>
<translation language="en"><![CDATA[Tracking for analytical purposes (e.g.: Google Analytics]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevels">
<translation language="nl"><![CDATA[Dit zijn de namen van de variabelen die beschikbaar zullen zijn in JavaScript en Google Data Layer. Je kan dus geen spaties gebruiken.]]></translation>
<translation language="en"><![CDATA[These are the names of the variable that will be available in JavaScript and Google Data Layer. You can't use spaces or other illegal characters.]]></translation>
<item type="label" name="PrivacyConsentLevelNameAnalyticsStorage">
<translation language="nl"><![CDATA[analytics_storage]]></translation>
<translation language="en"><![CDATA[analytics_storage]]></translation>
</item>
<item type="error" name="InvalidVariableName">
<translation language="nl"><![CDATA[<code>%1$s</code> is een ongeldige variabele naam.]]></translation>
<translation language="en"><![CDATA[<code>%1$s</code> is an invalid variable name.]]></translation>
<item type="message" name="HelpPrivacyConsentLevelFunctionalityStorage">
<translation language="nl"><![CDATA[Tracking voor functionele doeleinden (bijvoorbeeld: taalinstellingen)]]></translation>
<translation language="en"><![CDATA[Tracking for functional purposes (e.g.: language preferences]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNameFunctionalityStorage">
<translation language="nl"><![CDATA[functionality_storage]]></translation>
<translation language="en"><![CDATA[functionality_storage]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevelPersonalizationStorage">
<translation language="nl"><![CDATA[Tracking voor persoonlijke aanbevelingen (bijvoorbeeld: persoonlijke productaanbevelingen)]]></translation>
<translation language="en"><![CDATA[Tracking for personalised recommendations (e.g.: personal recommended products]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNamePersonalizationStorage">
<translation language="nl"><![CDATA[personalization_storage]]></translation>
<translation language="en"><![CDATA[personalization_storage]]></translation>
</item>
<item type="message" name="HelpPrivacyConsentLevelSecurityStorage">
<translation language="nl"><![CDATA[Tracking voor beveiligingsdoeleinden (bijvoorbeeld: fraudedetectie)]]></translation>
<translation language="en"><![CDATA[Tracking for security purposes (e.g.: fraud detection]]></translation>
</item>
<item type="label" name="PrivacyConsentLevelNameSecurityStorage">
<translation language="nl"><![CDATA[security_storage]]></translation>
<translation language="en"><![CDATA[security_storage]]></translation>
</item>
<item type="label" name="GoogleRecaptcha">
<translation language="nl"><![CDATA[Google reCAPTCHA]]></translation>
Expand All @@ -671,6 +711,62 @@
<translation language="en"><![CDATA[Captcha code is invalid.]]></translation>
<translation language="pl"><![CDATA[Błędny kod Captcha.]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelFunctionalStorageTitle">
<translation language="nl"><![CDATA[Functionele cookies]]></translation>
<translation language="en"><![CDATA[Functional storage]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelFunctionalStorageText">
<translation language="nl"><![CDATA[Deze cookies zijn nodig om de website correct te laten werken. Dit is bijvoorbeeld je taalvoorkeur, &hellip;]]></translation>
<translation language="en"><![CDATA[These function cookies are required to make the website work correctly. For instance your language preferences is stored.]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdStorageTitle">
<translation language="nl"><![CDATA[Advertenties]]></translation>
<translation language="en"><![CDATA[Ad storage]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdStorageText">
<translation language="nl"><![CDATA[We houden data bij voor advertentiedoeleinden, deze informatie delen we met bijvoorbeeld Google Ads.]]></translation>
<translation language="en"><![CDATA[Tracking for advertisement purposes. E.g.: Google Ads.]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdUserDataTitle">
<translation language="nl"><![CDATA[Gebruikersdata voor advertenties]]></translation>
<translation language="en"><![CDATA[Advertisement user data]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdUserDataText">
<translation language="nl"><![CDATA[We delen je gebruikersdata met externe partijen die advertenties aanbieden. Bijvoorbeeld Google Ads.]]></translation>
<translation language="en"><![CDATA[Sharing your details with external parties who provide advertisements. E.g.: Google Ads]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdPersonalizationTitle">
<translation language="nl"><![CDATA[Gepersonaliseerde advertenties]]></translation>
<translation language="en"><![CDATA[Personalized advertisement]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAdPersonalizationText">
<translation language="nl"><![CDATA[We tonen je graag gepersonaliseerde advertenties. We delen daarom jouw gebruikersdata met partijen die advertenties aanbieden. Bijvoorbeeld Google Ads.]]></translation>
<translation language="en"><![CDATA[Sharing your details with external parties who provide advertisements. E.g.: Google Ads]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAnalyticsStorageTitle">
<translation language="nl"><![CDATA[Statistieken]]></translation>
<translation language="en"><![CDATA[Analytical]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelAnalyticsStorageText">
<translation language="nl"><![CDATA[We gebruiken Google Analytics om de website te optimaliseren. Jouw gegevens (welke pagina's bekijk je, op welke knoppen klik je, ...) houden we beperkt bij. Deze gegevens worden gebruikt om de website te verbeteren voor jou als gebruiker, maar ook voor zoekmachines om beter gevonden te worden.]]></translation>
<translation language="en"><![CDATA[We use analytical tools (e.g.: Google Analytics) to improve our website.]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelPersonalizationStorageTitle">
<translation language="nl"><![CDATA[Personalisatie]]></translation>
<translation language="en"><![CDATA[Personalization]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelPersonalizationStorageText">
<translation language="nl"><![CDATA[We gebruiken jouw gegevens om de website en advertenties te personaliseren. We delen deze gegevens met bijvoorbeeld Google Ads.]]></translation>
<translation language="en"><![CDATA[We use your data to personalise the website and advertisements.]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelSecurityStorageTitle">
<translation language="nl"><![CDATA[Beveiliging]]></translation>
<translation language="en"><![CDATA[Security]]></translation>
</item>
<item type="message" name="PrivacyConsentLevelSecurityStorageText">
<translation language="nl"><![CDATA[Tracking voor beveiligingsmaatregelen. Bijvoorbeeld: Fraude detectie.]]></translation>
<translation language="en"><![CDATA[Tracking for security purposes. E.g.: Fraud detection.]]></translation>
</item>
</Core>
</Frontend>
</locale>
Expand Down
Loading
Loading