Apparence
Consentement aux services tiers
Une carte Google Maps, une vidéo YouTube, un calendrier de réservation : chacun charge le service d'une autre société chez le visiteur. En Europe, il faut son accord avant. Slab n'impose aucune plateforme de consentement (CMP) : il fournit une passerelle entre les briques qui utilisent des services tiers et la CMP de la boutique, quelle qu'elle soit, ou l'absence de CMP.
À ne pas confondre avec ConsentService
Le ConsentService du cœur enregistre ce qu'un client accepte sur son compte (newsletter, conditions), en base. Cette page traite du choix d'un visiteur, dans son navigateur, sur les services tiers d'une page.
Le problème
Le store locator affiche une carte Google Maps. Une autre brique affiche un plan d'accès, Google Maps aussi. Aucune des deux ne sait quelle CMP la boutique a installée : Axeptio, tarteaucitron, une CMP maison, ou rien. Et le visiteur qui a accepté la carte des magasins ne doit pas se voir redemander son accord pour le plan d'accès : c'est le même service.
Le principe
Trois notions, chacune à sa place :
| Notion | Ce que c'est | Qui la déclare |
|---|---|---|
| Service | ce à quoi le visiteur dit oui : « Google Maps » | une fois, par sa clé : le catalogue du socle, ou la brique qui l'apporte |
| Usage | ce qu'une brique fait du service : « carte des magasins » | chaque brique, pour chaque service qu'elle utilise |
| Adaptateur | le traducteur entre la passerelle et la CMP de la boutique | Slab livre l'adaptateur intégré ; les autres s'écrivent au besoin |
Le visiteur accepte un service, jamais une brique : un seul accord pour Google Maps ouvre la carte des magasins et le plan d'accès.
Les catégories
Chaque service appartient à une catégorie. Les quatre premières reprennent le vocabulaire de WP Consent API ; la cinquième, celle des contenus intégrés, n'a d'équivalent ni chez WordPress ni chez Google. C'est le découpage de stafe-cmp, la CMP de Stafe pour WordPress.
| Catégorie | Libellé | Exemples | WP Consent API |
|---|---|---|---|
functional | Nécessaires | panier, session, le choix du visiteur lui-même | functional |
preferences | Préférences | langue, affichage retenus d'une visite à l'autre | preferences |
statistics | Statistiques | mesure d'audience | statistics, statistics-anonymous |
marketing | Marketing | publicité, pixels de conversion | marketing |
external | Contenus externes | cartes, vidéos, publications de réseaux sociaux | aucune |
functional est toujours accordée : on la déclare pour informer, jamais pour bloquer.
La liste est fermée (enum ConsentCategory). Un adaptateur doit savoir traduire chaque catégorie vers sa CMP ; une catégorie inventée par une brique n'aurait d'équivalent dans aucune.
Les services
Déclarer un service
Un service se déclare par define(), dans le boot() du provider de la brique :
php
use Slab\Framework\Core\ThirdParty\ConsentCategory;
use Slab\Framework\Core\ThirdParty\Contracts\ThirdPartyServiceRegistry;
$this->app->make(ThirdPartyServiceRegistry::class)->define(
'calendly', 'Calendly', ConsentCategory::EXTERNAL,
provider: 'Calendly LLC',
privacyUrl: 'https://calendly.com/privacy',
description: fn (): string => __('Prise de rendez-vous en ligne.'),
);| Champ | Rôle | Obligatoire |
|---|---|---|
key | la clé du service, stable (voir plus bas) | oui |
name | le nom montré au visiteur et au marchand | oui |
category | sa catégorie | oui |
description | ce que fait le service, en une phrase ; traduite, en closure | non |
provider | la société qui l'opère | oui |
privacyUrl | sa politique de confidentialité | oui |
cookies | les cookies qu'il dépose sur le domaine de la boutique (['_ga', '_ga_*']) | non |
provider et privacyUrl sont obligatoires parce que l'encart les montre avant l'accord : le visiteur doit savoir quelle société il accepte, et où lire ce qu'elle fait de ses données (recommandation de la CNIL du 17 septembre 2020, § 18 et 19).
Le catalogue du socle
Le cœur déclare les services courants, avec les mêmes clés que stafe-cmp : une clé désigne le même service chez Stafe, sur WordPress comme sur Slab. Une brique n'a qu'à les utiliser.
| Clé | Service | Catégorie |
|---|---|---|
google-maps | Google Maps | external |
youtube | YouTube | external |
vimeo | Vimeo | external |
dailymotion | Dailymotion | external |
spotify, soundcloud | Spotify, SoundCloud | external |
facebook, instagram, twitter, tiktok | publications des réseaux sociaux | external |
Chaque clé du catalogue a sa constante (KnownServices::GOOGLE_MAPS), pour éviter les fautes de frappe. Le catalogue s'enrichit quand une brique du socle a besoin d'un nouveau service courant.
Les clés
- Une clé désigne un outil pour un usage, nommée d'après l'outil :
google-maps,google-analytics. Jamaisgoogle: un accord pour la carte ne vaut pas pour la mesure d'audience. Jamaisstore-locator-map: la clé nomme le service, pas la brique. - Minuscules, chiffres et tirets. Une autre forme lève une
InvalidArgumentExceptiondès le démarrage : l'erreur se voit à la première page. - Une clé ne se renomme jamais. Le choix du visiteur la retient, la configuration de la CMP aussi : la renommer reviendrait à perdre tous les accords donnés.
Conflits : le premier déclaré gagne
Deux briques peuvent déclarer la même clé. La première définition gagne, les suivantes sont ignorées.
- Le catalogue passe toujours en premier : le cœur le définit à la création du registre, avant qu'aucune brique ne puisse y toucher, même une brique dont le provider démarre avant celui du cœur. Redéclarer
google-mapsdepuis une brique n'a donc aucun effet. - Entre deux briques, l'ordre est celui du démarrage des providers : on ne compte pas dessus. Deux briques qui apportent le même service nouveau le déclarent à l'identique, ou l'une des deux se contente de l'utiliser.
- Une redéfinition qui diffère (nom, catégorie, société, politique, cookies) est écrite au journal, en
warning. Elle ne lève pas d'erreur : un paquet ne doit pas faire tomber une boutique à cause d'un autre. La description n'entre pas dans la comparaison : deux briques peuvent dire la même chose autrement.
Les usages
Une brique déclare ce qu'elle fait de chaque service, par uses() :
php
use Slab\Framework\Core\ThirdParty\KnownServices;
$this->app->make(ThirdPartyServiceRegistry::class)->uses(
KnownServices::GOOGLE_MAPS, fn (): string => __('Carte des magasins'),
when: fn (): bool => config('store-locator.map.provider') === 'google',
);- Le libellé de l'usage se montre au visiteur et au marchand : « Google Maps : carte des magasins, plan d'accès ». Deux briques qui annoncent le même usage ne le font lire qu'une fois.
- Un libellé traduit se passe en closure : il se traduit à la lecture, dans la langue du visiteur. Au démarrage, cette langue n'est pas encore connue.
whendit si l'usage est actif. Le store locator réglé sur OpenStreetMap n'utilise pas Google Maps : le service n'apparaît alors nulle part. La condition s'évalue au rendu, quand les réglages de la boutique sont appliqués, quel que soit l'ordre des providers.- Un service sans usage actif n'est pas exposé, ni au visiteur ni au marchand. Le catalogue ne fait apparaître aucun service par lui-même.
uses()peut précéderdefine(): une brique peut utiliser un service qu'une autre définit plus tard dans le démarrage. Tout se résout au rendu.- L'usage d'une clé que personne n'a définie est ignoré et écrit au journal, en
warning.
Dans le navigateur : Slab.consent
Le cœur greffe sur le slot front.head un court script en ligne : la liste des services actifs et l'objet Slab.consent. Tout thème qui respecte le contrat des slots l'a donc, avant ses propres scripts.
Seulement quand un service attend l'accord du visiteur. Une boutique qui n'en utilise aucun, ou seulement des services nécessaires (une carte sans cookie), ne reçoit rien : ni script, ni liste, sur aucune page. Une brique qui tient un contenu derrière un encart a toujours Slab.consent, puisque son service en attend un. Le style de l'encart, lui, vient avec le premier encart de la page, et seulement là.
| Méthode | Rôle |
|---|---|
services() | les services actifs, avec leur catégorie et leurs usages |
has(key) | vrai si le visiteur a accepté ce service |
whenGranted(key, fn) | exécute fn dès l'accord, tout de suite s'il est déjà donné |
ask(key) | le visiteur veut voir le contenu : l'adaptateur accorde, ou ouvre la CMP sur ce service |
onChange(fn) | fn(key, granted) à chaque changement d'accord, dans un sens ou dans l'autre |
use(adapter) | branche une CMP : remplace l'adaptateur actif (voir plus bas) |
Une brique ne sait jamais s'il y a une CMP, ni laquelle. Elle pose son encart, attend whenGranted(), et c'est l'adaptateur actif qui décide. Le même code sert sans CMP, avec tarteaucitron ou avec Axeptio.
Une brique ne charge jamais un script tiers hors de whenGranted(). Le store locator :
js
Slab.consent.whenGranted('google-maps', () => loadGoogleMaps().then(initMap));Le serveur ignore le choix du visiteur : la page se rend toujours à l'identique, et c'est le navigateur qui ouvre ou non les contenus. Le choix vit chez la CMP, et une page mise en cache reste juste pour tous les visiteurs.
L'encart : <x-consent-gate>
À la place du contenu, tant que le service n'est pas accepté :
blade
<x-consent-gate service="google-maps" :purpose="__('Carte des magasins')" :url="$store->mapUrl()">
<div class="store-map" data-store-map></div>
</x-consent-gate>- Le contenu est rendu inerte dans un
<template>: rien ne se charge. À l'accord, il est inséré et l'encart retiré. Tous les encarts du même service sur la page s'ouvrent ensemble. - L'encart ne charge rien du fournisseur : ni image de carte en fond, ni police, ni script. Tout ce qui vient de lui attend l'accord.
- Le texte de l'encart dit à quoi sert le contenu, quelle société le fournit, et ce qu'implique de l'afficher : « Carte des magasins : contenu fourni par Google Maps (Google). L'afficher permet à Google de déposer des cookies sur votre appareil. » Suivent un bouton « Afficher le contenu », un lien « Politique de confidentialité de Google » et, si
urlest donnée, un lien « Voir sur Google Maps ». purposeest facultatif : à défaut, l'encart reprend l'usage du service quand il n'en a qu'un.- Le focus suit le contenu : le bouton disparaît à l'ouverture, le focus passe au contenu ouvert plutôt que de retomber en haut de page.
- Un encart dont le service n'a pas d'usage actif reste fermé, et le développeur en est prévenu au journal : sans usage déclaré, une CMP ne connaît pas le service et ne pourrait jamais l'ouvrir.
- Le bouton est décrit par le texte de son encart (
aria-describedby) : plusieurs encarts peuvent se suivre dans une page, chacun nomme son service. - Le thème peut surcharger la vue de l'encart, comme toute vue du cœur (surcharger une vue).
Les adaptateurs
Un adaptateur relie Slab.consent à une CMP. Il en remplace un autre par Slab.consent.use() :
js
Slab.consent.use({
name: 'axeptio',
has(key) { /* l'accord actuel de la CMP pour ce service */ },
ask(key) { /* accepter ce service, ou ouvrir la CMP sur lui */ },
subscribe(notify) { /* appeler notify() quand la CMP change un choix */ },
});- Un seul adaptateur actif à la fois. L'adaptateur intégré est actif tant qu'aucun autre ne s'est déclaré.
- L'adaptateur est en place avant les briques. Son script est greffé dans le
<head>, juste après celui du cœur : quand une brique appelleSlab.consent, le bon adaptateur répond déjà. Si la CMP se charge plus tard, l'adaptateur l'attend lui-même ; d'ici là,has()répond non. - La traduction des clés est l'affaire de l'adaptateur. Il déclare les services de la boutique à la CMP sous leurs propres clés (exemple ci-dessous) ; ou, quand la CMP les fait saisir dans son interface (Axeptio, Didomi), le marchand fait le lien, service par service.
- Une CMP qui ne connaît que des catégories : l'adaptateur lit la catégorie du service.
- Côté serveur, un adaptateur n'est qu'un paquet qui greffe son script sur
front.head, après celui du cœur. Un registre d'adaptateurs ne viendra que s'il faut un jour en choisir un au back-office.
Slab ne livre que l'adaptateur intégré. Ceux des CMP tierces (tarteaucitron, Axeptio, Didomi, Orejime…) s'écrivent quand une boutique en a besoin.
Exemple : un adaptateur tarteaucitron
Pour montrer que le contrat suffit ; cet adaptateur n'est pas livré. tarteaucitron retient l'état de chaque service dans tarteaucitron.state, appelle la fonction js d'un service quand il est accepté, fallback quand il est refusé, et accepte un service par tarteaucitron.userInterface.respond().
js
const tac = (key) => 'slab-' + key; // la clé du service chez tarteaucitron
Slab.consent.use({
name: 'tarteaucitron',
has: (key) => window.tarteaucitron?.state[tac(key)] === true,
ask: (key) => tarteaucitron.userInterface.respond(document.getElementById(tac(key) + 'Allowed'), true),
subscribe(notify) {
// Chaque service de la boutique apparaît dans le panneau de tarteaucitron, sous son nom.
for (const service of Slab.consent.services()) {
tarteaucitron.services[tac(service.key)] = {
key: tac(service.key), type: 'other', name: service.name,
uri: service.privacyUrl, needConsent: true, cookies: service.cookies,
js: notify, fallback: notify,
};
(tarteaucitron.job = tarteaucitron.job || []).push(tac(service.key));
}
},
});- tarteaucitron ne charge rien lui-même : ses services ne font que prévenir Slab. C'est la brique qui charge Google Maps, dans
whenGranted(). Le servicegooglemapsnatif de tarteaucitron, lui, chargerait le script de Google une seconde fois. - Le choix est retenu par tarteaucitron, selon ses propres règles : avec cette CMP, l'accord donné dans l'encart vaut aussi pour les pages suivantes.
- Le visiteur accepte dans le bandeau ou dans l'encart, au choix : les deux passent par tarteaucitron, qui reste la seule source.
L'adaptateur intégré : sans CMP
Sans CMP, la boutique ne détient aucun accord : rien n'est accepté d'avance, rien n'est retenu.
- Pas de bannière. L'encart demande l'accord au moment où le visiteur veut le contenu. La CNIL admet ce recueil au moment de l'usage, quand rien ne se déclenche avant (recommandation du 17 septembre 2020, § 13).
- Le clic ne vaut que pour la page affichée et pour ce seul service. À la page suivante, l'encart revient.
- Rien n'est retenu : ni cookie, ni journal. Il n'y a donc rien à retirer : quitter la page met fin à l'accord. Les cookies que le fournisseur a déposés sur son propre domaine restent dans le navigateur, comme avec n'importe quelle CMP.
- La preuve repose sur le mécanisme : les versions publiées du code et des captures horodatées de l'encart, que la CNIL cite parmi les preuves recevables (§ 48). Aucun journal par visiteur.
- Seul un service qui s'affiche peut être accepté : l'accord se demande dans son encart. Une mesure d'audience ou un pixel publicitaire n'a rien à montrer : sans CMP, il ne se charge jamais.
- Avec une CMP sans adaptateur, l'adaptateur intégré reste actif : le visiteur peut avoir à accepter deux fois, dans la CMP puis dans l'encart. C'est plus strict, jamais moins.
Au back-office
Configuration › Boutique, un onglet « Services externes » : la liste, en lecture seule, des services que la boutique utilise réellement, avec leur fournisseur, leur catégorie, leurs usages et le lien vers leur politique de confidentialité.
Le marchand en a besoin pour sa politique de confidentialité et, s'il a une CMP sans adaptateur, pour la configurer. Texte d'introduction de l'onglet : « Les services d'autres sociétés que votre boutique utilise. Mentionnez-les dans votre politique de confidentialité. »
Ce que la passerelle ne fait pas
- Pas de bannière de cookies : c'est le rôle de la CMP. Quand une brique de mesure d'audience ou de marketing en aura besoin, Slab proposera la sienne dans un paquet optionnel,
slab/consent-banner, inspiré destafe-cmpet branché parSlab.consent.use()comme n'importe quel adaptateur. Le socle reste neutre : la boutique choisit ce paquet ou sa propre CMP. - Pas d'IAB TCF : il sert la publicité, que les boutiques ne vendent pas, et ne décrit pas les services comme une carte (décision du 2026-10-12). Un paquet pourra en écrire l'adaptateur.
- Pas de décision côté serveur : le serveur déclare, le navigateur ouvre.
- Pas de blocage des contenus collés dans les pages : l'éditeur des pages de contenu n'accepte aujourd'hui ni vidéo intégrée ni code HTML, il retire une
<iframe>collée. Le jour où il les accepte, leur rendu passe par l'encart. - Pas de renommage des services au back-office pour l'instant : le premier déclaré fait foi.
Décisions
| Décision | Date |
|---|---|
La passerelle vit dans le socle, slab/framework, sous Core\ThirdParty | 2026-10-12 |
| Le socle fournit un catalogue ; chaque brique peut ajouter ses services ; le premier déclaré gagne | 2026-10-12 |
| Seul l'adaptateur intégré est livré ; ceux des CMP tierces s'écrivent au besoin | 2026-10-12 |
| Pas d'IAB TCF | 2026-10-12 |
| Sans CMP, aucun accord n'est retenu : le clic ouvre le contenu pour la page affichée | 2026-10-12 |
| Les contenus collés dans les pages ne sont pas bloqués dans la première version | 2026-10-12 |
Pas de bannière dans le socle ; plus tard, un paquet optionnel slab/consent-banner | 2026-10-12 |