Skip to content

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 :

NotionCe que c'estQui la déclare
Servicece à quoi le visiteur dit oui : « Google Maps »une fois, par sa clé : le catalogue du socle, ou la brique qui l'apporte
Usagece qu'une brique fait du service : « carte des magasins »chaque brique, pour chaque service qu'elle utilise
Adaptateurle traducteur entre la passerelle et la CMP de la boutiqueSlab 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égorieLibelléExemplesWP Consent API
functionalNécessairespanier, session, le choix du visiteur lui-mêmefunctional
preferencesPréférenceslangue, affichage retenus d'une visite à l'autrepreferences
statisticsStatistiquesmesure d'audiencestatistics, statistics-anonymous
marketingMarketingpublicité, pixels de conversionmarketing
externalContenus externescartes, vidéos, publications de réseaux sociauxaucune

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.'),
);
ChampRôleObligatoire
keyla clé du service, stable (voir plus bas)oui
namele nom montré au visiteur et au marchandoui
categorysa catégorieoui
descriptionce que fait le service, en une phrase ; traduite, en closurenon
providerla société qui l'opèreoui
privacyUrlsa politique de confidentialitéoui
cookiesles 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éServiceCatégorie
google-mapsGoogle Mapsexternal
youtubeYouTubeexternal
vimeoVimeoexternal
dailymotionDailymotionexternal
spotify, soundcloudSpotify, SoundCloudexternal
facebook, instagram, twitter, tiktokpublications des réseaux sociauxexternal

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. Jamais google : un accord pour la carte ne vaut pas pour la mesure d'audience. Jamais store-locator-map : la clé nomme le service, pas la brique.
  • Minuscules, chiffres et tirets. Une autre forme lève une InvalidArgumentException dè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-maps depuis 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.
  • when dit 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éder define() : 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.

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éthodeRô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.

À 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 url est donnée, un lien « Voir sur Google Maps ».
  • purpose est 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 appelle Slab.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 service googlemaps natif 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é de stafe-cmp et branché par Slab.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écisionDate
La passerelle vit dans le socle, slab/framework, sous Core\ThirdParty2026-10-12
Le socle fournit un catalogue ; chaque brique peut ajouter ses services ; le premier déclaré gagne2026-10-12
Seul l'adaptateur intégré est livré ; ceux des CMP tierces s'écrivent au besoin2026-10-12
Pas d'IAB TCF2026-10-12
Sans CMP, aucun accord n'est retenu : le clic ouvre le contenu pour la page affichée2026-10-12
Les contenus collés dans les pages ne sont pas bloqués dans la première version2026-10-12
Pas de bannière dans le socle ; plus tard, un paquet optionnel slab/consent-banner2026-10-12