> ## Documentation Index
> Fetch the complete documentation index at: https://dokumentation.websale.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Materials: consent to data processing

> Complete example for the consent layer: the AgreementCookies section in shop.config, a Bootstrap-based HTML layer, a link to reopen it, and supplementary CSS.

This page contains a complete example for the consent layer: the matching section in `shop.config`, the HTML source code of the layer, a link to reopen it, and the supplementary CSS. The design example takes into account the CJEU ruling of October 1, 2019, according to which a pre-checked checkbox does not constitute valid consent.

You can try out the layer and configuration in the sample shop [v8s-demoshop.websale.biz](https://v8s-demoshop.websale.biz/).

Guide: Consent to data processing (cookies) · Reference: shop.config, AgreementCookies section

## The four building blocks

The layer consists of four parts that belong together. If you leave one of them out, the whole thing doesn't work.

| Building block | Located in | Purpose |
| - | - | - |
| `AgreementCookies` section | `shop.config` | defines which services exist and how they are grouped |
| HTML layer | Template | displays the checkboxes and saves the selection |
| Link to reopen | Privacy page or footer | allows the visitor to withdraw their consent |
| Supplementary CSS | Shop stylesheet | positions the layer at the bottom of the screen |

> [!NOTE]
>
> The example requires Bootstrap 4.1.3 and jQuery 3.4.1. It also needs the WEBSALE module `ws-module-agreementcookies`, which provides the two functions `ws_agreementcookies_set` and `ws_agreement_updatecheckboxes`. This is the only WS module that requires just jQuery and not the base component.
>
> The arrow icon on the expander comes from Font Awesome. Use a different icon there if your shop doesn't include Font Awesome.

Reference: WEBSALE JavaScript library

## Building block 1: The AgreementCookies section

The example defines ten services and combines them into three groups: convenience features, shop optimizations, and social media with marketing. All ten are set to `OptIn` with the preselection `unchecked`, so no checkbox is pre-checked.

> [accordion] shop.config, AgreementCookies section
>
> ```ini theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
> ###################################################################################
> ###             Einwilligung zur Datenverarbeitung                              ###
> ###################################################################################
>
> <AgreementCookies>
>     Allow                   = yes
>
>     <+Entry>
>         ID                  = cookiebasket
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Warenkorb für nächsten Besuch speichern
>         Descr               =
>         ConsentGroupID      = comfort
>         +WEBSALEService     = CookieBasket
>     </+Entry>
>
>     <+Entry>
>         ID                  = welcomecookie
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Persönliche Begrüßung
>         Descr               =
>         ConsentGroupID      = comfort
>         +WEBSALEService     = WelcomeCookie
>     </+Entry>
>
>     <+Entry>
>         ID                  = googleMaps
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Anfahrtsplanung mit Google Maps
>         Descr               =
>         ConsentGroupID      = comfort
>     </+Entry>
>
>     <+Entry>
>         ID                  = youtube
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Produktvideos über YouTube
>         Descr               =
>         ConsentGroupID      = comfort
>     </+Entry>
>
>     <+Entry>
>         ID                  = google
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Google Analytics
>         Descr               =
>         ConsentGroupID      = optimize
>     </+Entry>
>
>     <+Entry>
>         ID                  = econda
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Econda
>         Descr               =
>         ConsentGroupID      = optimize
>     </+Entry>
>
>     <+Entry>
>         ID                  = googleTagManager
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Google Tag Manager
>         Descr               =
>         ConsentGroupID      = marketing
>     </+Entry>
>
>     <+Entry>
>         ID                  = instagram
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Instagram
>         Descr               =
>         ConsentGroupID      = marketing
>     </+Entry>
>
>     <+Entry>
>         ID                  = pinterest
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Pinterest
>         Descr               =
>         ConsentGroupID      = marketing
>     </+Entry>
>
>     <+Entry>
>         ID                  = twitter
>         Type                = OptIn
>         Preselection        = unchecked
>         Name                = Twitter
>         Descr               =
>         ConsentGroupID      = marketing
>     </+Entry>
>
>     <+ConsentGroupInfo>
>         ConsentGroupID      = comfort
>         Name                = Komfortfunktionen
>         Descr               = Wir möchten die Bedienung dieses Shops für Sie möglichst komfortabel gestalten.
>     </+ConsentGroupInfo>
>
>     <+ConsentGroupInfo>
>         ConsentGroupID      = optimize
>         Name                = Shop-Optimierungen
>         Descr               = Um den Shop zu optimieren möchten wir Dienste nutzen die Daten erheben und aggregieren und uns diese als statistische Übersicht zur Verfügung stellen.
>     </+ConsentGroupInfo>
>
>     <+ConsentGroupInfo>
>         ConsentGroupID      = marketing
>         Name                = Social Media / Marketing Funktionen
>         Descr               = Zu Werbezwecken dürfen Nutzungsdaten erhoben und mit statistischen Methoden zu einem Nutzerprofil zusammengefasst werden. Dies wird Interessengruppen zugeordnet, an die webseitenübergreifend Werbung ausgespielt wird.
>     </+ConsentGroupInfo>
> </AgreementCookies>
> ```

Two things about this example are easy to overlook:

The first two entries have a `+WEBSALEService`, the other eight don't. With `+WEBSALEService`, you tell the shop that the entry represents a built-in WEBSALE feature. The shop then disables this feature itself if the visitor doesn't consent. The eight entries without this line are purely declarations to the visitor; you have to disable the third-party services yourself in the template.

The order of the groups in the layer comes from the order of the `<+ConsentGroupInfo>` blocks, not from the order of the services. Groups that have no `<+ConsentGroupInfo>` end up at the end.

## Building block 2: The HTML layer

In its collapsed state, the layer shows only the explanation, one checkbox for everything, and the save button. Only the "Details" expander reveals the groups with their individual services.

> [accordion] HTML layer based on Bootstrap 4.1.3
>
> ```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
> <body>
> ...
> <div id="agreementPrivacy" class="py-3 px-sm-3"{!AC-AgreementNotMadeOrNeedsUpdate} style="display: none;"{/!AC-AgreementNotMadeOrNeedsUpdate}>
>    <div class="container-fluid">
>       <div class="row">
>          <div class="col">
>             <p class="lead mb-1">Einwilligung zur Datenverarbeitung</p>
>             <p class="small">Wir möchten Dienste von Drittanbietern nutzen, um den Shop und unsere Dienste zu verbessern 
>                und optimal zu gestalten (Komfortfunktionen, Shop-Optimierung). Weiter wollen wir unsere Produkte bewerben (Social Media/Marketing).<br>
>                Dafür können Sie hier Ihre Einwilligung erteilen und jederzeit widerrufen. Weitere Informationen dazu finden Sie in unserer 
>                <a href="~WS-LoadTpl(tpl_datenschutz.htm,Datenschutz)~" class="text-body"><u>Datenschutzerklärung</u></a>.</p>
>          </div>
>          <div class="col-12 mt-2">
>             <div class="row">
>                <div class="col-12 col-sm-6">
>                   <input type="checkbox" value="" onclick="ws_agreement_updatecheckboxes();" class="~AC-ConsentAll_CheckboxClass~" ~AC-ConsentAll_CheckboxIsChecked~>
>                   <b>Alle Dienste erlauben</b>
>                </div>
>                <div class="col-12 col-sm-6">
>                   <input type="button" onclick="ws_agreementcookies_set(); $('#agreementPrivacy').hide(); $('#agreementPrivacy').removeClass('isFixed');
>                      $('#cookieDetails').addClass('d-none'); $('#cookieDetailToggler').removeClass('open');" value="Einstellungen speichern" class="btn btn-block btn-success">
>                </div>
>             </div>
>             <div class="cursorPointer clearfix mt-2">
>                <span id="cookieDetailToggler" onclick="$('#cookieDetails').toggleClass('d-none'); $('#agreementPrivacy').toggleClass('isFixed'); $(this).toggleClass('open');">
>                   Details<i class="fa fa-angle-down ml-1" aria-hidden="true"></i>
>                </span>
>             </div>
>          </div>
>       </div>
>       <div id="cookieDetails" class="d-none">
>          ~DC-DynamicLoop1_set($AC-ConsentGroupList$)~
>          {@DC-DynamicLoop1}
>             {AC-ConsentGroup($DC-DynamicLoop1_data$)}
>                <div class="mb-3">
>                   <div class="mt-3 p-3 border border-secondary">
>                      <div class="font-weight-bold mb-1">
>                         <input type="checkbox" value="" onclick="ws_agreement_updatecheckboxes();" class="~AC-ConsentGroup_CheckboxClass~" ~AC-ConsentGroup_CheckboxIsChecked~>
>                         <b>Alle ~AC-ConsentGroup_Name~ erlauben</b>
>                      </div>
>                      <div class="small">~AC-ConsentGroup_Descr~</div>
>                      {AC-Entry}
>                         <div class="row mt-2 ml-2">
>                            {@AC-Entry($DC-DynamicLoop1_data$)}
>                               <div class="col-6 mb-2">
>                                  <input type="checkbox" value="" onclick="ws_agreement_updatecheckboxes();" class="~AC-Entry_CheckboxClass~" ~AC-Entry_CheckboxIsChecked~>
>                                  ~AC-Entry_Name~
>                               </div>
>                            {/@AC-Entry($DC-DynamicLoop1_data$)}
>                         </div>
>                      {/AC-Entry}
>                   </div>
>                </div>
>             {/AC-ConsentGroup($DC-DynamicLoop1_data$)}
>          {/@DC-DynamicLoop1}
>       </div>
>    </div>
> </div>
> ...
> </body>
> ```

### How the layer is structured

The outer wrapper determines whether the layer is visible at all:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<div id="agreementPrivacy" class="py-3 px-sm-3"{!AC-AgreementNotMadeOrNeedsUpdate} style="display: none;"{/!AC-AgreementNotMadeOrNeedsUpdate}>
```

The layer is always in the document. Only if there is no consent yet, or a newly added service has no decision yet, is the `display: none` omitted and the layer becomes visible. That's why a simple `show()` is enough for the link further below.

The group loop doesn't run via a dedicated loop tag, but via the dynamic loop:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
~DC-DynamicLoop1_set($AC-ConsentGroupList$)~
{@DC-DynamicLoop1}
   {AC-ConsentGroup($DC-DynamicLoop1_data$)}
      ...
   {/AC-ConsentGroup($DC-DynamicLoop1_data$)}
{/@DC-DynamicLoop1}
```

`~AC-ConsentGroupList~` returns all group IDs as a comma-separated list in the order from `shop.config`. `DC-DynamicLoop1` splits this list, and `AC-ConsentGroup` switches to the matching group on each iteration. Within it, `{@AC-Entry(…)}` loops over the services of exactly this group.

The three levels of checkboxes are built the same way. Each one gets its classes from a `_CheckboxClass` tag and its check mark from a `_CheckboxIsChecked` tag, and each one calls `ws_agreement_updatecheckboxes()` on click. This function keeps the three levels in sync: if the visitor checks a group, all services in it are checked as well.

| Level | Class tag | Check mark tag |
| - | - | - |
| all services | `AC-ConsentAll_CheckboxClass` | `AC-ConsentAll_CheckboxIsChecked` |
| one group | `AC-ConsentGroup_CheckboxClass` | `AC-ConsentGroup_CheckboxIsChecked` |
| one service | `AC-Entry_CheckboxClass` | `AC-Entry_CheckboxIsChecked` |

> [!WARNING]
>
> The checkboxes have no `name` and no `value`. Saving relies solely on the classes that come from the `_CheckboxClass` tags, and only happens when `ws_agreementcookies_set()` is called. Don't remove these classes; only add your own in addition to them.

## Building block 3: Link to reopen

The visitor must be able to withdraw their consent at any time. To do this, place a link that shows the layer again, for example on the privacy page.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<body>
   ...
   <div class="btn btn-primary mb-5" onclick="$('#agreementPrivacy').show();">Einwilligung zur Datenverarbeitung ändern</div>
   ...
</body>
```

> [!NOTE]
>
> For this to work, the layer from building block 2 must be on the same page. So include it in all templates via an include, not just in the start page.

## Building block 4: Custom CSS

These rules come in addition to the standard Bootstrap classes. They pin the layer to the bottom of the screen and, from the Bootstrap breakpoint width upward, limit it to 500 pixels.

```css theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
/* Zusätzlich zu den Standard-CSS-Anweisungen von Bootstrap v4.1.3 */

#agreementPrivacy {
    position: fixed;
    background: #dde5ee;
    z-index: 9999999;
    right: 0;
    bottom: 0;
    left: 0;
    box-shadow: 0 0 10px rgba(0,0,0,.1);
}

@media (min-width:576px) {
   #agreementPrivacy {
      right: auto;
      width: 500px;
   }
}

#agreementPrivacy.isFixed {
   height: 100%;
   overflow-y: auto;
}

#cookieDetailToggler.open > i.fa {
   transform: rotate(180deg);
}
```

The expander sets the `isFixed` class itself. It makes the expanded layer use the full screen and become scrollable when there are many services. `#cookieDetailToggler.open > i.fa` flips the arrow on the expander.

> [!NOTE]
>
> The `cursorPointer` class from the HTML example isn't included in this CSS. Add it yourself, otherwise the visitor won't see a pointer cursor on the expander:
>
> ```css theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
> .cursorPointer { cursor: pointer; }
> ```

> [!NOTE]
>
> The layer gets `z-index: 9999999`. Check that no other element of your shop sits on top of it, especially sticky headers and cart previews.

## What the example doesn't show

The example uses only part of the section's capabilities. The following points are added here; the detailed description belongs on the reference page for `shop.config`.

### Other values for Allow and Type

| Parameter | Value | Meaning |
| - | - | - |
| `Allow` | `test` | the procedure is only active in the shop's test mode and stays off in live mode |
| `Type` | `OptOutChecked` | a set check mark means the visitor objects |
| `Type` | `OptOutUnchecked` | a missing check mark means the visitor objects |

With both opt-out variants, consent is considered given as long as the visitor doesn't make a decision. With `OptIn`, it's the other way around.

> [!WARNING]
>
> The name of the cookie that stores the decision contains the value of `Type`. If you change `Type` for an existing service, the shop then looks for a differently named cookie, doesn't find it, and all consents previously given for this service are treated as nonexistent. The layer then opens again for all visitors.
>
> The same applies to a change to `ID`.

### Other WEBSALE services

In addition to `CookieBasket` and `WelcomeCookie` from the example, the shop knows four more built-in services that it disables itself when consent is missing.

| Value for `+WEBSALEService` | Disables |
| - | - |
| `CookieBasket` | saving the cart for the next visit |
| `WelcomeCookie` | the personal greeting |
| `RefererCookie` | remembering where the visitor came from |
| `AffiliateProgram` | the affiliate programs |
| `DesignCookie` | the design cookie in which the shop remembers display settings |
| `TellAFriendCookie` | the cookie of the recommend-to-a-friend feature |

An entry can have multiple `+WEBSALEService` lines if one checkbox should cover several built-in services.

### Other entries per service

| Entry | Purpose |
| - | - |
| `Descr` | an explanatory text about the service, retrievable via the corresponding AC tag |
| `ExternalServiceID` | an ID for mapping to an external consent service |
| `iOSApp-AcceptViaTrackingFramework-List` | list for consent via the tracking framework of the iOS app |

### Two pitfalls in the configuration

> [!WARNING]
>
> A `ConsentGroupID` may only contain letters, digits, hyphens, and underscores. For any other character, and for an empty value, the shop logs a critical error and puts the service into a fallback group. This fallback group has no name and no description and stands out immediately in the layer.
>
> A `<+ConsentGroupInfo>` block whose `ConsentGroupID` isn't used by any service is skipped and logged. Check both places after every renaming.

> [!NOTE]
>
> The preselection from `Preselection` only applies as long as there is no decision for the service yet. As soon as the visitor has saved once, their decision counts, and a later change to `Preselection` doesn't change that.


## Related topics

- [Consent to data processing (cookies)](https://dokumentation.websale.de/en/v8s/einstellungen/einwilligung-zur-datenverarbeitung-cookies.md)
- [Cookies used](https://dokumentation.websale.de/en/v8s/referenz/verwendete-cookies.md)
- [WEBSALE JavaScript library](https://dokumentation.websale.de/en/v8s/materialien/websale-javascript-bibliothek.md)
- [AC- (AgreementCookies)](https://dokumentation.websale.de/en/v8s/referenz/tags/ac-agreementcookies.md)
- [Integration of econda](https://dokumentation.websale.de/en/v8s/integration-von-econda/integration-von-econda.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.