OW Consent — Documentation
La gestion du consentement complète dans WordPress : bannière multi-juridictions, blocage des traceurs, scanner, documents légaux, registre de preuve et portail de droits. Auteur : OptionWeb — Julien Daniel Page du plugin : https://optionweb.dev/fr/addons/ow-consent/ Licence : GPL-2.0-or-later Version couverte par ce document : 1.4.3
Table des matières
- Vue d'ensemble
- Installation
- Démarrage rapide
- Les onze profils de conformité
- Les catégories de cookies
- La bannière
- Le blocage automatique
- Le scanner de traceurs
- Le générateur de documents légaux
- Le registre de consentement
- Le portail de droits (DSAR)
- L'opt-out CCPA « Do Not Sell or Share »
- Google Consent Mode v2
- IAB TCF v2.2
- Global Privacy Control
- La détection régionale
- L'intégration OW Forms
- Le bouton flottant
- Shortcodes
- API REST
- Référence des réglages
- Dépannage
- FAQ
Vue d'ensemble
OW Consent est une suite de gestion du consentement pour WordPress. Elle couvre la chaîne entière : afficher un choix, l'appliquer réellement aux traceurs, en garder la preuve, publier les documents qui l'expliquent, et recevoir les demandes des personnes concernées.
Onze profils de conformité sont livrés — du RGPD à la CCPA en passant par la Loi 25 québécoise, la LGPD brésilienne ou la DPDP indienne. Le profil actif décide du modèle de consentement (opt-in ou opt-out), des valeurs par défaut de Google Consent Mode, du texte des documents générés, des droits publiés et de l'autorité de contrôle citée.
Tout tient dans votre site. Le plugin ne contacte qu'un seul service externe, la Global Vendor List d'IAB Europe, et seulement si vous activez le module TCF, qui est désactivé par défaut. Aucune télémétrie, aucun compte, aucun abonnement.
Compatible avec un cache de page complet, par construction
C'est le point d'architecture qui gouverne tout le reste. Rien de ce que le serveur rend ne dépend du cookie de consentement. Le HTML est identique pour tous les visiteurs : les traceurs sont réécrits en balises inertes pour tout le monde, et c'est un runtime JavaScript qui les libère dans le navigateur, catégorie par catégorie, en lisant le cookie avant le premier rendu.
Conséquence pratique : LiteSpeed Cache, WP Rocket, Varnish ou un CDN ne peuvent pas servir
les choix d'un visiteur à un autre. Seules les réponses réellement personnelles — la page
de confirmation d'identité d'une demande de droits, les réponses REST qui contiennent des
données du visiteur — sont explicitement marquées non cachables. Aucun nonce n'est imprimé
dans du HTML cachable non plus : la bannière en récupère un frais sur un endpoint no-store
juste avant chaque écriture.
Ce qui est livré
- Bannière de consentement : quatre positions, thème clair / sombre / automatique, « Tout refuser » aussi visible que « Tout accepter », panneau de préférences accessible au clavier, entièrement traduisible.
- Blocage automatique des traceurs : scripts, snippets en ligne, iframes, pixels,
indices de ressources (
preconnect,dns-prefetch,preload), feuilles de style analytics/marketing et médias tiers, à partir de 175 signatures livrées et éditables. - Scanner de traceurs : balayage de vos propres pages par WP-Cron, lecture des en-têtes
Set-Cookie, sonde navigateur réservée aux administrateurs, et un panneau qui dit toujours ce que le balayage a réellement couvert. - Générateur de documents légaux : politique de confidentialité et politique cookies pour les onze profils, dans la langue de la juridiction ; mentions légales et CGU/CGV en français.
- Registre de consentement : chaque action ajoutée à une table chaînée par HMAC, avec le profil en vigueur, l'empreinte des documents publiés et l'empreinte de la bannière réellement affichée.
- Portail de droits (DSAR) : formulaire par shortcode, vérification par e-mail, délai de l'article 12.3 déclenché à la confirmation d'identité, branchement sur l'exportateur et l'effaceur natifs de WordPress.
- Google Consent Mode v2, IAB TCF v2.2, Global Privacy Control, détection régionale, intégration OW Forms : chacun activable indépendamment.
- API REST complète sous le namespace
owc/v1.
Ce qu'il ne fait pas — à lire avant de s'engager
Sur un sujet juridique, une promesse excessive vous expose autant qu'elle expose l'éditeur. Voici les limites, telles qu'elles sont écrites dans le code.
- Les documents générés sont des modèles, pas un conseil juridique. Chaque document se termine par un avertissement qui le dit, et cet avertissement est activé par défaut. Faites relire vos documents avant de les publier.
- Le module TCF n'est pas un CMP enregistré auprès d'IAB Europe. Il exige un CMP ID que vous devez obtenir vous-même, il n'offre aucun choix au niveau vendeur, et les vendeurs sont fondés à rejeter son signal. Si un revenu publicitaire sous TCF compte pour vous, utilisez un CMP certifié.
- Le registre n'est infalsifiable que sous condition. La chaîne n'est une preuve que si sa clé de signature vit hors de la base de données. Le plugin détecte le cas contraire, le rapporte lui-même et vous le dit dans l'administration au lieu de prétendre l'inverse.
- Le scanner n'exécute aucun JavaScript. Il voit ce que votre HTML contient et ce que
vos en-têtes
Set-Cookiedéposent ; ce qu'un gestionnaire de balises injecte au runtime n'est vu que par une sonde réservée aux administrateurs connectés. - Le blocage par défaut n'est pas « tout ce qui est tiers ». Un script tiers inconnu est autorisé par défaut ; ce sont les iframes inconnues qui sont bloquées par défaut. Le blocage repose sur le catalogue de signatures, que vous pouvez enrichir.
- Sans JavaScript, aucun choix ne peut être enregistré. Le visiteur voit un bloc
<noscript>et rien de non essentiel n'est chargé, mais rien n'est enregistré non plus. - Pas de granularité état par état aux États-Unis ni province par province au Canada
dans la détection régionale : tout le territoire américain reçoit le profil
ccpa, tout le Canada reçoitquebec. - Mentions légales et CGU/CGV n'existent qu'en français. Pour toute juridiction dont la langue cible n'est pas le français, le générateur refuse de produire ces deux documents au lieu de publier un texte inadapté.
- Aucun rôle dédié : toute l'administration exige la capacité
manage_options.
Cette liste est un choix assumé, pas une roadmap masquée. Sur la conformité, un outil qui énonce ses trous vaut mieux qu'un outil qui les cache.
Installation
Depuis le .zip
- Téléchargez
ow-consent-1.4.3.zipdepuis https://optionweb.dev/fr/addons/ow-consent/ - Extensions → Ajouter → Téléverser une extension
- Choisissez le fichier, cliquez sur Installer, puis sur Activer
Par FTP
Décompressez l'archive et déposez le dossier ow-consent dans /wp-content/plugins/,
puis activez l'extension depuis Extensions.
Prérequis
- WordPress 6.2 ou plus récent — c'est un refus de démarrage, pas une recommandation (voir ci-dessous)
- PHP 7.4 ou plus récent
- MySQL 5.7+ / MariaDB 10.2+
- WP-Cron fonctionnel si vous utilisez le scanner, la rétention du registre, les rappels d'échéance DSAR ou le module TCF
Le garde-fou WordPress 6.2. Depuis la 1.3.0, chaque identifiant de table est lié par le marqueur
%idewpdb::prepare(), que WordPress ne comprend qu'à partir de 6.2. Sur un cœur plus ancien,prepare()renverrait une chaîne vide, le catalogue de traceurs se résoudrait silencieusement à un ensemble vide, et la bannière continuerait de promettre au visiteur un blocage qui n'aurait pas lieu. Le plugin refuse donc de démarrer et affiche un message d'erreur qui dit explicitement que rien n'est bloqué et qu'aucun consentement n'est enregistré. L'en-têteRequires at leastempêche déjà l'activation en dessous de 6.2, mais il ne couvre ni une rétrogradation du cœur sous une installation en service, ni une copie déposée par FTP.
Ce qui est installé
À l'activation, OW Consent crée quatre tables :
| Table | Contenu |
|---|---|
{prefix}owc_ledger | Le registre de consentement, chaîné par hachage |
{prefix}owc_dsar | Les demandes d'exercice de droits |
{prefix}owc_scanner | Les trouvailles du scanner de traceurs |
{prefix}owc_scripts | Le catalogue de signatures utilisé par le blocage |
Une cinquième table, {prefix}owc_form_links, est créée séparément par l'intégration
OW Forms si le plugin frère est actif (voir L'intégration OW Forms).
L'activation ajoute aussi :
- l'option
owc_settings, seedée vide — délibérément : tant que vous n'avez rien enregistré, tous les textes viennent des défauts anglais traduits à la volée dans la langue du site, au lieu de figer dans la base la locale de la personne qui a activé le plugin ; - l'option
owc_version; - le catalogue de traceurs livré (
data/tracker-catalog.json), inséré par paquets de 100 enINSERT IGNORE, avec un drapeau de seed par version (owc_catalog_seeded_1.4.3) ; - la tâche quotidienne
owc_daily_maintenance, planifiée une heure après l'activation.
Cinq autres événements cron sont armés par les modules qui les utilisent. Les six hooks
cron du plugin sont : owc_scanner_run, owc_scanner_run_batch, owc_ledger_retention,
owc_tcf_refresh_gvl, owc_run_upgrade et owc_daily_maintenance.
En multisite, les tables sont créées par site, jamais partagées
(wp_2_owc_ledger, etc.). Une activation réseau parcourt tous les sites uniquement si le
réseau compte au plus 200 sites ; au-delà, chaque site est provisionné paresseusement à sa
première requête. Un sous-site créé après une activation réseau est provisionné par le hook
wp_initialize_site.
À définir dans wp-config.php
Aucune de ces constantes n'est obligatoire, mais deux d'entre elles changent la valeur probante de ce que le plugin produit.
// Recommandé : sort la clé de signature du registre de la base de données.
define( 'OWC_LEDGER_KEY', 'une longue chaîne aléatoire, propre à ce site' );
// Recommandé : les salts WordPress standards. Sans eux, WordPress les stocke en base,
// et le registre le signale lui-même comme non infalsifiable.
define( 'AUTH_KEY', '…' );
define( 'AUTH_SALT', '…' );
// Si le site est derrière un CDN, un répartiteur de charge ou un reverse proxy.
// Sans cette constante, CF-Connecting-IP, X-Forwarded-For et X-Real-IP sont IGNORÉS
// et seul REMOTE_ADDR est utilisé — ce qui donne la même identité à tous vos visiteurs
// pour la limitation de débit.
define( 'OWC_TRUSTED_PROXY', '198.51.100.0/24, 2001:db8::/32' );
// `OWC_TRUSTED_PROXIES` est accepté comme alias ; une chaîne ou un tableau conviennent.
// Détection régionale seulement : déclare quels en-têtes de pays sont dignes de confiance.
define( 'OWC_GEO_TRUSTED_HEADERS', 'cloudflare' ); // 'cloudflare'|'cloudfront'|'proxy'|'all'
define( 'OWC_BEHIND_CLOUDFLARE', true );
define( 'OWC_BEHIND_CLOUDFRONT', true );
OWC_LEDGER_KEY sert aussi à dériver la clé du cookie de détection régionale. Si elle
n'est pas définie, la clé du registre retombe sur wp_salt('auth').
Qui a accès
Toute l'administration du plugin et toutes les routes REST d'administration exigent la
capacité manage_options. Il n'existe ni rôle dédié, ni capacité plus fine :
ouvrir l'écran du plugin à quelqu'un revient à lui donner les réglages du site.
Chemin de mise à jour
Le plugin ne migre jamais son schéma en ligne sur une page anonyme. Quand la version
change, owc_version et un drapeau owc_pending_upgrade sont écrits immédiatement, puis :
- la migration s'exécute inline si la requête est une requête d'administration hors AJAX, une exécution de cron ou une commande WP-CLI ;
- sinon un événement
owc_run_upgradeest planifié cinq secondes plus tard.
admin-ajax.php est traité comme une requête anonyme : c'est un point d'entrée public. La
migration s'exécute sous verrou (owc_upgrade_lock, volé au bout de 300 secondes), et un
filet sur admin_init rattrape les sites où WP-Cron est désactivé. Sur une version
identique, le coût total est un seul get_option().
Désactivation et suppression
Désactiver conserve toutes les données et n'efface que les six événements cron. En désactivation réseau, tous les sites sont parcourus par lots de 200 — contrairement à l'activation — parce qu'un cron laissé armé ne disparaîtrait jamais.
Supprimer le plugin déclenche uninstall.php, qui procède en deux temps :
- Toujours, quel que soit votre réglage : les six crons sont effacés et la table
{prefix}owc_dsarest supprimée, avec ses transients de limitation de débit. C'est la seule table qui contient des données personnelles directement identifiantes concernant des tiers (adresse e-mail, nom, texte libre) ; une fois le plugin parti, plus rien ne borne sa rétention et plus aucun écran ne permet d'y répondre, de l'exporter ou de l'effacer. Exportez les demandes que vous devez conserver avant de supprimer le plugin. - Seulement si
delete_data_on_uninstallest explicitement activé : suppression des tablesowc_ledger,owc_dsar,owc_scanner,owc_scriptsetowc_form_links, des options nommées, de toutes les options préfixéesowc_(transients compris) et desusermetapréfixéesowc_. En multisite, les options réseau suivent la décision du site principal.
Ce réglage est désactivé par défaut : la preuve de consentement, exigée par l'article 7.1 du RGPD, survit à la suppression du plugin.
Démarrage rapide
Après activation, ouvrez l'entrée OW Consent dans la barre latérale de l'administration. Tout le plugin tient sur cet écran unique, en dix onglets : Dashboard, Banner, Compliance, Legal identity, Categories, Policies, Scanner, Tracker catalogue, Audit ledger, DSAR requests.
Une sauvegarde ne concerne que l'onglet ouvert. C'est délibéré : chaque booléen a un champ caché jumeau, et une clé absente du formulaire signifie « ce champ est sur un autre onglet », jamais « décoché ». Sans cela, enregistrer un onglet écraserait les réglages de tous les autres.
1. Choisissez votre profil de conformité
Onglet Compliance. Le profil décide du modèle de consentement, des valeurs par défaut
de Consent Mode, des documents générés et des droits publiés. Par défaut : gdpr.
compliance_strict est activé par défaut : c'est ce qui étend le blocage au-delà des
seuls scripts enregistrés par WordPress. Laissez-le actif si vous voulez que les iframes,
les pixels et les scripts écrits en dur dans votre thème soient traités.
2. Remplissez l'identité légale
Onglet Legal identity. Ces champs sont la matière première des documents générés, et la génération est refusée tant qu'un champ obligatoire est vide — avec la liste des clés manquantes, pas un échec silencieux.
Minimum pour tous les documents : legal_company_name et legal_company_email. Pour tout
sauf la politique cookies, ajoutez l'adresse et le pays. Pour les mentions légales,
ajoutez aussi le téléphone, le directeur de la publication et les coordonnées complètes de
l'hébergeur ; en France, en Belgique et au Luxembourg, la forme juridique et le numéro
d'immatriculation deviennent obligatoires eux aussi.
Laissez legal_dpa_authority vide : l'autorité de contrôle est dérivée de votre pays
et de votre profil. La renseigner à la main sur un site multi-juridictions revient à
nommer le mauvais régulateur.
3. Vérifiez vos catégories
Onglet Categories. Les six catégories sont disponibles par défaut. Désactivez
celles que votre site n'utilise pas : une catégorie absente de l'interface n'est pas un
gain de conformité, c'est une ressource bloquée pour toujours sans interrupteur pour la
libérer — le blocage la reverse alors sur marketing.
Laissez les libellés vides tant qu'ils vous conviennent : ils suivent alors la langue du site. Dès que vous personnalisez un texte, ce texte cesse de suivre la langue.
4. Réglez la bannière
Onglet Banner. Position, thème, boutons, libellés, durée de renouvellement.
Deux points à ne pas manquer : laissez banner_reject_all activé (« Refuser » doit
être aussi simple et visible qu'« Accepter »), et laissez la croix de fermeture
désactivée — elle est absente par défaut parce qu'une fermeture sans choix équivaut à un
refus implicite. Si vous l'activez, un clic sur la croix exécute le chemin
« Tout refuser » complet, jamais une fermeture silencieuse.
5. Générez vos documents
Onglet Policies. Quatre documents : politique cookies, politique de confidentialité, mentions légales, CGU/CGV. Chacun devient une page WordPress versionnée, dont le lien est réinjecté dans les réglages.
Générez d'abord en prévisualisation, relisez, puis publiez. Et faites relire par un professionnel : le bandeau d'avertissement en bas de chaque document n'est pas décoratif.
6. Ouvrez le portail de droits
Créez une page et collez-y [owc_dsar_form]. Renseignez dsar_email dans l'onglet
DSAR requests : c'est l'adresse de contact publiée sous le formulaire et le
destinataire des notifications. Sans elle, le plugin retombe sur legal_dpo_email, puis
sur admin_email — mais admin_email n'est jamais publié sur une page publique.
7. Vérifiez avant d'ouvrir au public
Le tableau de bord exécute quinze contrôles de conformité et distingue les erreurs des avertissements : bannière désactivée, « Tout refuser » absent, croix de fermeture activée, identité légale incomplète, portail de droits fermé, registre désactivé, aucune politique liée, autorité de contrôle en contradiction avec le profil actif (avec un bouton « Fix this » qui la réinitialise), site pas en HTTPS, traceurs non catégorisés, demandes DSAR hors délai, règles de catalogue qui ne peuvent pas se déclencher.
Passez ces quinze lignes au vert avant d'annoncer que votre site est conforme.
Les onze profils de conformité
Le profil de conformité n'est pas cosmétique. Il pilote le modèle légal, l'interface, les signaux techniques et le contenu des documents publiés.
La liste
| Profil | Régime visé | Modèle |
|---|---|---|
gdpr | RGPD + ePrivacy (UE/EEE) | Opt-in |
uk_pecr | UK GDPR + PECR (Royaume-Uni) | Opt-in |
ch_nfadp | nLPD suisse | Opt-in |
quebec | Loi 25 (Québec) | Opt-in |
lgpd | LGPD (Brésil) | Opt-in |
popia | POPIA (Afrique du Sud) | Opt-in |
pipl | PIPL (Chine) | Opt-in |
dpdp | DPDP Act 2023 (Inde) | Opt-in |
ccpa | CCPA / CPRA (Californie) | Opt-out |
us_generic | Lois d'État américaines génériques | Opt-out |
au | Privacy Act (Australie) | Opt-out |
Réglage : compliance_profile, défaut gdpr.
Opt-in signifie qu'avant tout choix, seules les catégories forcées sont accordées.
Opt-out signifie que tout est accordé jusqu'au refus. Les trois profils opt-out sont
ccpa, us_generic et au. Le runtime JavaScript applique exactement la même règle que
PHP, de sorte que serveur et navigateur ne peuvent pas rapporter deux états différents.
Ce que le profil change réellement
| Ce qui change | Détail |
|---|---|
| Modèle de consentement | Opt-in, sauf ccpa, us_generic, au |
| Valeurs par défaut de Consent Mode | Les sept signaux passent à granted sous un profil opt-out |
| Lien « Do Not Sell or Share » obligatoire | ccpa et us_generic uniquement |
| GPC juridiquement contraignant | ccpa et us_generic uniquement — l'Australie est explicitement exclue : régime opt-out, mais qui ne reconnaît pas GPC |
| Périmètre « le RGPD s'applique » pour TCF | 27 pays de l'UE + IS, LI, NO + GB + CH, soit 31 codes |
| Langue du document généré | quebec → français ; lgpd → portugais ; gdpr avec un pays FR, BE ou LU → français ; tout le reste → anglais |
| Autorité de contrôle citée | Table par profil, affinée par pays pour le RGPD |
| Liste des droits publiée | Une liste rédigée par profil, avec citation d'article |
| Corps « régime cookies » du document anglais | Texte, périmètre de transfert et garanties distincts par profil |
Les documents générés par profil
La politique de confidentialité et la politique cookies existent pour les onze profils. Les
gabarits sont résolus dans cet ordre, premier trouvé gagnant :
<type>_<profil>_<langue>, puis <type>_<profil>, puis <type>_<langue>, puis <type>.
Les corps anglais de la politique cookies sont conscients du profil : règle applicable,
périmètre de transfert, garanties et libellé du panneau diffèrent pour gdpr, uk_pecr,
ch_nfadp, au, pipl, dpdp, ccpa et us_generic, avec les citations
correspondantes (art. 5(3) de la directive 2002/58, PECR reg. 6, art. 45c(b) LTC et
art. 19/6(7)(b) nLPD, APP 8, art. 24 PIPL, sections 5/6/7/9(3)/16 du DPDP Act 2023,
§1798.121 et Cal. Code Regs. tit. 11 §7025, VCDPA/CPA/CTDPA/UCPA/TDPSA).
Autorité de contrôle citée
| Profil | Autorité nommée |
|---|---|
gdpr, pays FR | CNIL |
gdpr, pays BE | APD-GBA |
gdpr, pays LU | CNPD |
gdpr, pays DE | BfDI |
gdpr, autre pays EEE | Formulation générique (« l'autorité de contrôle compétente ») |
uk_pecr | ICO |
ch_nfadp | PFPDT / FDPIC |
quebec | Commission d'accès à l'information |
lgpd | ANPD |
ccpa | California Privacy Protection Agency |
popia | Information Regulator (South Africa) |
pipl | Cyberspace Administration of China |
dpdp | Data Protection Board of India |
au | OAIC |
us_generic | Attorney General de votre État |
Si vous avez modifié legal_dpa_authority, votre saisie l'emporte sur cette dérivation.
Limites de la couverture juridictionnelle
- Seuls quatre pays de l'EEE ont une autorité nommée (FR, BE, LU, DE). Un site RGPD en Espagne, en Italie ou aux Pays-Bas publie une formulation générique.
- Pas de granularité état par état aux États-Unis. Le profil
us_genericexiste et porte ses propres textes VCDPA/CPA/CTDPA/UCPA/TDPSA, mais aucune détection automatique ne l'attribue : il se choisit à la main. - Pas de granularité provinciale au Canada.
- Trois langues de documents seulement : français, anglais, portugais. Les corps ne passent pas par le mécanisme de traduction de WordPress — c'est délibéré : un document légal doit être monolingue et sa langue suit la juridiction, jamais la locale de l'administrateur.
- Mentions légales et CGU/CGV : gabarits français uniquement. Le générateur refuse net
pour toute autre langue cible, à moins que vous ne fournissiez votre propre corps via le
filtre
owc_policy_template.
Les catégories de cookies
La liste canonique
Six catégories, dans cet ordre d'affichage, necessary toujours en tête :
| Slug | Libellé livré | Description livrée |
|---|---|---|
necessary | Necessary | Strictly required for the site to function (cart, login, language preferences). Cannot be disabled. |
functional | Functional | Enhance the experience (chat, embedded videos, maps). Without them some features may not work. |
analytics | Statistics | Help us understand how you use the site (anonymously). No personal data is shared for commercial purposes. |
marketing | Marketing | Enable us to show you ads and content tailored to your interests on other sites. |
preferences | Preferences | Remember your interface choices (layout, saved filters). |
social | Social & embeds | Allow embedded social content (YouTube, Instagram, X) to load. |
Les valeurs ci-dessus sont les défauts anglais stockables. Sur un site francophone, le visiteur voit la traduction — voir le mécanisme de traduction plus bas.
Disponibilité
Réglage cat_<slug>_available, un par catégorie. Les six sont disponibles par défaut.
L'ancienne clé cat_<slug>_enabled est encore lue en repli, sans migration.
preferences et social sont livrées disponibles parce que le catalogue de seed classe
YouTube, Spotify, SoundCloud, Instagram, X et Facebook en social : sans la catégorie dans
l'interface, ces intégrations resteraient bloquées définitivement sans moyen d'opt-in.
necessary est forcée : elle ne peut pas être désactivée (art. 5.3 ePrivacy). Elle
n'a donc pas d'interrupteur de disponibilité dans l'administration, seulement un libellé et
une description.
Le mécanisme de traduction
Réglages cat_<slug>_label et cat_<slug>_desc. La valeur stockée est renvoyée verbatim
uniquement si elle est non vide et différente du défaut anglais ; sinon c'est la
traduction qui sort.
Conséquence à connaître : tant que vous ne personnalisez rien, changer la langue du site change la langue des catégories. Dès que vous saisissez votre propre texte, ce texte est figé dans la langue où vous l'avez écrit. La même règle s'applique aux textes de la bannière.
La description traduite de necessary est construite à partir de deux chaînes : la
seconde nomme les cookies du plugin lui-même, owc_consent (jusqu'à 13 mois) et owc_geo
(24 heures, écrit uniquement quand la détection régionale est activée).
Correspondance Google Consent Mode v2 — contraignante
C'est la source de vérité unique du plugin : la bannière, le blocage et le bootstrap lisent tous cette table, ils ne peuvent donc pas diverger.
| Catégorie | Signaux Consent Mode v2 |
|---|---|
necessary | security_storage, functionality_storage |
functional | functionality_storage, personalization_storage |
analytics | analytics_storage |
marketing | ad_storage, ad_user_data, ad_personalization |
preferences | personalization_storage |
social | ad_storage, ad_user_data |
Nuance importante : si votre site expose la catégorie functional,
functionality_storage est retiré de la liste de necessary. Sinon il serait accordé
avant tout consentement, alors qu'il appartient à une catégorie optionnelle.
Correspondance TCF — descriptive uniquement
functional → functional, analytics → measurement, marketing → advertising,
preferences → personalization, social → social_media.
Ce sont des étiquettes d'affichage. La correspondance contraignante côté TCF est la table des identifiants numériques de finalités IAB, décrite dans la section IAB TCF v2.2. Ne branchez jamais un comportement TCF sur ces cinq étiquettes.
Que se passe-t-il si une catégorie disparaît
Si une règle du catalogue pointe vers une catégorie que votre site n'expose plus — vous
avez désactivé social, par exemple — le blocage reverse la ressource sur marketing, ou
à défaut sur la première catégorie optionnelle disponible. Sans cette normalisation, la
ressource serait bloquée pour toujours sans aucun interrupteur pour la libérer.
La bannière
Quand elle s'affiche
La bannière est rendue sur wp_footer en priorité 5, et ses ressources sont mises en file
sur wp_enqueue_scripts. Elle sort immédiatement si l'une de ces conditions est vraie :
contexte d'administration, flux RSS, robots.txt, ou banner_enabled désactivé. Elle sort
également si wp_head n'a jamais été déclenché — un thème qui n'appelle pas wp_head()
obtiendrait sinon un balisage inerte et non stylé.
Le HTML est identique pour tous les visiteurs. Toutes les cases du panneau sont rendues
à OFF côté serveur puis hydratées côté client depuis le cookie. C'est le JavaScript qui
pose <html data-owc="given|none"> de façon synchrone, avant le premier rendu ; le CSS
n'affiche la bannière que sur data-owc="none".
Corollaire à connaître : un visiteur sans JavaScript ne voit jamais la bannière, donc
il n'est jamais enfermé derrière une modale qu'il ne peut pas fermer. Il voit à la place un
bloc <noscript> qui explique que ses préférences ne peuvent pas être enregistrées et
qu'aucun cookie non essentiel n'est chargé tant qu'aucun choix n'a été fait.
Positions et thème
| Réglage | Valeurs | Défaut |
|---|---|---|
banner_position | bottom-bar, bottom-card, center-modal, top-bar | bottom-bar |
banner_style | auto, light, dark | auto |
Seul center-modal obtient role="dialog", aria-modal="true", un fond assombri, un
piège de focus et la touche Échap. Les trois autres positions sont un role="region" :
Échap n'est pas intercepté (le thème garde sa propre gestion) et le focus n'est pas
volé au premier rendu, ce qui pousserait un utilisateur clavier au-delà de tous les liens
d'évitement.
La palette sombre s'applique sous prefers-color-scheme: dark avec des gardes pour ne pas
écraser un thème qui déclare explicitement light. Le thème du site peut piloter la
palette par variables CSS : --owc-paper-tint, --owc-ink, --owc-smoke, --owc-fog,
--owc-ui, --owc-accent, --owc-accent-strong, --owc-accent-darker.
Détails d'implantation : z-index 99998 pour la bannière et 99997 pour le fond,
prise en charge de env(safe-area-inset-*) pour les encoches iOS, décalage automatique
sous la barre d'administration WordPress en position haute.
Les boutons
| Réglage | Défaut | Effet |
|---|---|---|
banner_accept_all | true | Affiche « Tout accepter » |
banner_reject_all | true | Affiche « Tout refuser » |
banner_preferences | true | Affiche « Personnaliser » |
banner_close_x | false | Affiche la croix de fermeture |
banner_show_logo | true | Affiche le logo du site |
« Tout accepter » et « Tout refuser » partagent la même classe de mise en forme : même fond, même bordure, même graisse, même remplissage. C'est la réponse à l'exigence CNIL / EDPB (lignes directrices 03/2022) : refuser doit être aussi simple et aussi visible qu'accepter.
La croix de fermeture
Elle est désactivée par défaut, parce qu'une croix de fermeture équivaut à un refus implicite. Trois comportements à connaître :
- Quand
banner_close_xest off, la croix est présente dans le balisage mais porte l'attributhidden, et le CSS la retire totalement : ni visible, ni focusable, ni annoncée aux lecteurs d'écran. - Quand elle est activée, cliquer la croix exécute le chemin « Tout refuser » complet, jamais une fermeture silencieuse.
- Elle est révélée par le runtime, et ré-étiquetée, dans un seul cas : lorsqu'un refus n'a pas pu être enregistré du tout. Elle ne refuse alors plus rien, elle range l'avis — et cette fermeture n'est pas comptée comme un choix. Un visiteur dont le serveur refuse la décision n'est pas laissé face à une bannière qu'il ne peut pas fermer.
La première couche
- Le titre (
text_title) et le message (text_message). Le message est le seul champ à HTML riche du plugin ; il est rendu avec unwp_kseslimité à<a href target rel>. - Le lien vers la politique de confidentialité :
link_privacy_policy, avec repli sur la page de politique déclarée dans WordPress si le réglage est vide. - Le lien vers la politique cookies :
link_cookie_policy, sans repli. - Le nom du responsable de traitement :
legal_company_name, sinon le nom du site, affiché comme « Responsable de traitement : … » (art. 13(1)(a)). - Le logo : le logo personnalisé du thème en taille
medium, sinon l'icône du site.
Le panneau de préférences
Une ligne par catégorie disponible, dans un role="group" nommé.
- Une catégorie forcée affiche un badge textuel « Always active », sans interrupteur.
- Une catégorie optionnelle affiche un
<button role="switch">avecaria-checked,aria-labelledbyetaria-describedby. L'état est porté pararia-checked, la position du curseur et un mot visible On/Off — jamais par la couleur seule. - Cible tactile agrandie à 44 px sur petit écran (WCAG 2.2 AA, critère 2.5.8).
- La barre d'actions du panneau est
stickyen bas, pour que « Enregistrer mes choix » reste atteignable pendant que la liste défile.
Si le module TCF est actif, deux blocs supplémentaires apparaissent : les finalités TCF atteignables et les fonctionnalités spéciales déclarées. Voir IAB TCF v2.2.
Renouvellement du consentement
| Réglage | Défaut | Bornes |
|---|---|---|
consent_renewal_months | 12 | 0 à 13 |
consent_policy_hash_check | true | — |
0 ne veut pas dire « ne jamais redemander ». La valeur 0 est convertie en 13 mois, et
13 mois est le plafond dur (délibération CNIL 2020-091). La durée effective est donc
toujours comprise entre 1 et 13 mois, et c'est cette même valeur qui gouverne la durée du
cookie, le contrôle de péremption côté serveur et la durée publiée dans les documents
générés. La constante OWC_COOKIE_TTL visible dans le code source n'est qu'un repli.
consent_policy_hash_check redemande le consentement quand vos documents changent : le
cookie porte une empreinte des pages de politique liées, et une différence rouvre la
bannière. L'empreinte vaut la chaîne vide quand aucune page n'est liée — c'est ce qui
permet de distinguer « inconnu » de « changé » et de ne jamais re-solliciter tout un site
sur une comparaison vide.
Les textes
Sept emplacements, tous vides par défaut et donc traduits automatiquement :
text_title, text_message, text_accept_all, text_reject_all, text_preferences,
text_save, et floating_button_label pour le bouton flottant.
Dans l'administration, chaque champ affiche son défaut traduit en indication de saisie : laisser vide conserve le défaut qui suit la langue du visiteur.
Le filtre owc_banner_texts permet de remplacer ces textes par code. Des emplacements
supplémentaires sont acceptés ; les valeurs non scalaires ou vides sont écartées pour qu'un
rappel maladroit ne puisse pas vider la bannière.
Comment le choix est réellement enregistré
Le runtime JavaScript est en ES5 strict — pas de fonctions fléchées, pas de littéraux de gabarit — pour fonctionner dans les navigateurs intégrés aux applications et les vieux WebViews. Sa séquence d'écriture mérite d'être connue, parce que c'est elle qui explique la plupart des messages d'erreur.
- Ré-ancrage de l'origine. Les URLs REST viennent de la configuration WordPress. Si
vos visiteurs naviguent sur un autre hôte (www contre apex, alias, domaine de
prévisualisation, proxy qui réécrit
Host), cette URL est cross-origin et le navigateur refuse de stocker leSet-Cookiependant que WordPress répond 200. Le runtime ré-ancre donc le chemin fourni par PHP sur l'origine réellement parcourue. - Nonce. Récupéré frais sur
GET /owc/v1/noncejuste avant l'écriture, jamais embarqué dans du HTML cachable, envoyé dans l'en-têteX-OWC-Nonce. La récupération est plafonnée à 4 secondes, et un nonce absent, vide ou injoignable n'empêche jamais l'écriture ni n'est rapporté au visiteur. page_urlexplicite dans le corps de la requête, pour que la ligne de registre ne dépende pas de l'en-têteRefererqu'une extension, une meta-referrer ou un proxy peut supprimer.- Garde anti-blocage de 15 secondes avec
AbortController: la requête est annulée, pas seulement ignorée, pour qu'un POST tardif n'écrive pas une seconde ligne. - Définition du succès. Le POST est un succès si, et seulement si : la réponse est
ok, le corps est du JSON analysable,json.ok === true, et le cookie est relu dans le navigateur. Une page de cache, un défi de bordure ou un WAF servi en 200 ne comptent pas. - Si le serveur répond
cookie_set: false, le runtime écrit lui-même le cookie avec les paramètres renvoyés, puis relit. Un échec reste un échec : rien n'est publié, rien n'est débloqué. - Une seule ré-émission, et seulement sur un 403 dont le code est
owc_bad_nonce,rest_cookie_invalid_nonceourest_nonce_invalid, et seulement si le nonce obtenu est réellement différent. - Ce que le runtime publie est ce que le serveur a stocké : les catégories rejetées par le serveur sont soustraites de l'état local.
Tant que le POST n'a pas réussi, rien n'est démasqué, rien n'est débloqué, et aucun signal Consent Mode « granted » n'est émis.
La taxonomie d'erreur
Le runtime distingue neuf causes, chacune avec son message visible : network, refused,
ratelimit, unexpected, timeout, browser, config, cookie,
owc_cookie_not_persisted. Le statut HTTP et le code serveur sont posés sur l'élément
(data-owc-status, data-owc-code) et journalisés une fois en console.warn — jamais
rendus en texte visible. Une capture d'écran de support nomme donc la cause sans qu'il
faille une trace réseau.
L'API JavaScript de la bannière
window.OWCBanner.show(); // ouvre en mode bannière
window.OWCBanner.hide(); // ferme
window.OWCBanner.openPreferences(); // ouvre le panneau de préférences
window.OWCBanner.openDnsmpi(); // ouvre le panneau avec marketing, social
// et preferences déjà sur OFF (entrée CCPA)
window.OWCBanner.reset(); // efface le cookie côté client, poste un retrait,
// puis recharge la page
window.OWCBanner.acceptCategory( 'social' ); // renvoie une promesse résolue à true
// seulement si le serveur a bien stocké
Un bootstrap plus léger est imprimé dans <head> en priorité 1 et expose
window.OWConsent : .config, .categories, .geo, .profile, .optOut, .state,
.has(cat), .refresh(), .paint(), .gcmSignals(state). Sa configuration est
filtrable par owc_bootstrap_config — mais tout ce qui s'y trouve est public et
partagé par le cache : n'y mettez jamais de donnée propre à un visiteur.
Ouvrir le panneau depuis vos pages
Trois manières, toutes équivalentes :
- le fragment d'URL
#owc-preferences(ou#owc-dnsmpi), pris en compte au chargement et surhashchange; - tout élément portant la classe
owc-open-preferencesou l'attributdata-owc-open; - l'attribut
data-owc-dnsmpi="1"pour forcer le comportement « Do Not Sell ».
Un événement owc:consent-changed est diffusé à chaque changement, avec la charge utile
envoyée en détail. Il est écouté par le bootstrap, le bouton flottant, le runtime CCPA et
le module TCF ; vous pouvez l'écouter aussi.
Limites de la bannière
- Tout dépend de
wp_head()etwp_footer(): un thème qui ne les appelle pas n'obtient rien. - Sans
window.fetchetwindow.Promise, aucune écriture n'est possible et l'erreurbrowserest affichée. - Un consentement peut être enregistré côté serveur sans être conservé côté navigateur (adresse du site différente de l'hôte parcouru, stockage bloqué, jar plein). Le runtime détecte ce cas, refuse de le compter comme un succès, affiche un message dédié et transmet le fait au serveur à l'écriture suivante.
- La bannière n'a pas de shortcode : elle s'affiche partout ou nulle part.
Le blocage automatique
C'est le module qui transforme un choix en effet réel. Il réécrit les balises porteuses de traceurs avant tout consentement, pour tout le monde, et laisse le runtime les libérer dans le navigateur.
Deux modes, un seul réglage
compliance_strict, activé par défaut.
- Désactivé (mode souple) : seul le filtre
script_loader_tagest branché. Autrement dit, seuls les scripts enregistrés parwp_enqueue_script()sont réécrits. Un<script>écrit en dur dans le thème, un embed, une iframe, un pixel<img>: rien n'est touché. - Activé (mode strict) : en plus du filtre, un tampon de sortie capture tout le
document, et le balisage rendu via l'API REST est traité lui aussi. En administration,
si la requête est un
admin-ajax.php, un tampon séparé couvre les réponses qui servent aussi le front (« charger plus », archives filtrées) — mais uniquement si le gestionnaire a lui-même déclaréContent-Type: text/html.
Ce qui n'est jamais bufferisé
Le tampon strict sort immédiatement pour : l'administration, les requêtes AJAX, les flux,
robots.txt, les trackbacks, le cron, les favicons, les requêtes REST, les requêtes JSON,
la prévisualisation de l'outil de personnalisation, les sitemaps, et les points d'entrée
wp-login.php, wp-register.php, wp-signup.php, xmlrpc.php, wp-cron.php,
wp-trackback.php — testés sur le nom exact du script, jamais en sous-chaîne de l'URL. Il
sort aussi pour les requêtes du scanner, qui ont besoin du HTML brut.
Les cinq passes
La réécriture se fait par expressions régulières calibrées, jamais par un analyseur DOM. Trois garde-fous encadrent l'ensemble : un document de plus de 8 Mo est renvoyé intact, un échec du moteur PCRE renvoie le document intact, et si le résultat fait moins de la moitié de la taille d'origine, l'original est renvoyé. Autrement dit, le mode de panne du blocage est « page servie sans blocage », jamais « page cassée ».
Passe 0 — masquage. Commentaires HTML, <style> et <textarea> sont remplacés par des
marqueurs à base de caractères de contrôle, pour qu'aucun motif ultérieur ne matche à
l'intérieur.
Passe 1 — <script>. Décision, dans l'ordre :
- déjà traité → intact ;
- script du plugin lui-même → intact ;
type="text/plain"ou type non-JS (ld+json,importmap,x-template) → intact ;- URL cherchée dans
src,data-src,data-rocket-src,data-lazy-src,data-litespeed-src,data-cfsrc— les attributs des plugins de performance sont donc couverts ; - hôte dans la liste blanche → intact ; ressource first-party qui ne ressemble pas à un traceur → intact ;
- correspondance au catalogue sur la forme
hôte + chemin; une URLdata:oujavascript:est décodée et jugée comme un corps en ligne ; - une règle classée
necessaryn'est jamais bloquée (Stripe.js, reCAPTCHA, Turnstile, cdnjs…) : la bloquer ne gagne aucune conformité et casse le snippet appelant ; - aucune correspondance →
blocker_unknown_script_policy, défautallow; - sinon réécriture en
type="text/plain"avecdata-owc-cat,data-owc-vendor,data-owc-srcet, si le type d'origine était particulier (module, par exemple),data-owc-typepour ne pas dégrader le script restauré.
Les corps en ligne sont comparés d'abord aux règles de catalogue de type
inline_signature, puis à quatorze signatures ancrées codées en dur : fbq(, _fbq.push,
gtag(, dataLayer.push(, ga('…, _gaq.push, _paq.push, hjid, clarity(,
ttq.load|track|page, snaptr(, twq(, lintrk(, pintrk(. Exception explicite :
un gtag('consent', …) seul, c'est-à-dire une déclaration Consent Mode par défaut sans
autre marqueur, reste exécuté.
Passe 2 — <iframe>. Mêmes attributs d'URL, plus data-original et data-srcset.
First-party ou liste blanche → intact. necessary → intact, sinon un formulaire à captcha
deviendrait insoumettable. Sans correspondance → blocker_unknown_iframe_policy, défaut
block. L'iframe bloquée devient src="about:blank" et est enveloppée dans un
substitut visuel contenant un bouton « Accepter
Passe 3 — <img>, pixels de tracking uniquement. Règle importante : une ligne de
catalogue seule ne suffit jamais à neutraliser une image. Si l'image correspond au
catalogue mais que sa catégorie n'est ni analytics ni marketing — un CDN d'images, un
service de gravatars, un hébergeur de polices — elle est laissée intacte, sinon on
supprimerait les médias du site au lieu d'un traceur. Seule l'heuristique de balise peut
bloquer : onze points de collecte connus (facebook.com/tr, px.ads.linkedin.com,
ct.pinterest.com, bat.bing.com, analytics.twitter.com, t.co/i/adsct,
tr.snapchat.com, analytics.tiktok.com, google-analytics.com/collect,
googleads.g.doubleclick.net, pixel.quantserve.com) ou une image tierce de 1×1 pixel. Le
src est alors remplacé par un GIF transparent, et tous les attributs porteurs d'URL
sont supprimés pour qu'un chargeur paresseux ne restaure pas l'original.
Passe 4 — <link>. Conditionnée à blocker_block_resource_hints, défaut true.
Ne traite que les hôtes déjà présents au catalogue, jamais un inconnu.
- Les indices de ressources (
preconnect,dns-prefetch,prefetch,prerender,preload,modulepreload) vers un tiers catalogué sont supprimés, pas différés : un indice ouvre une connexion TCP+TLS et divulgue l'IP et l'empreinte TLS du visiteur, et il n'y a rien à restaurer ensuite. - Une feuille de style classée
analyticsoumarketingest neutralisée. - Google Fonts et les autres hôtes de typographie classés
functionalne sont jamais touchés : les neutraliser donne un texte en police de repli sur tout le site, pour aucun gain de conformité.
Passe 5 — <object>, <embed>, <source>, <video>, <audio>. Jamais de blocage par
défaut : seul un tiers déjà catalogué est traité. Les attributs autoplay et preload
sont retirés.
Ce qui compte comme « first party »
L'URL du site, l'URL WordPress, l'URL de contenu, l'URL des includes, leurs équivalents
réseau en multisite, et la base des téléversements. Le www. est retiré à la comparaison.
Tout chemin commençant par /wp-content/ ou /wp-includes/ est first-party quel que soit
l'hôte, pour couvrir les réécritures CDN. Les schémas non-HTTP (data:, blob:,
javascript:) ne sont jamais first-party.
Contre-exception : une URL contenant gtag, gtm.js, analytics, pixel, fbevents,
hotjar, matomo, piwik ou clarity est traitée même sur l'hôte du site. C'est ce
qui attrape un GTM ou un Matomo auto-hébergé, et les proxys first-party.
Le runtime de déblocage
Imprimé dans <head> en priorité 2. Sa configuration JSON ne contient que le nom du cookie
et la liste d'hôtes surveillés — rien qui dépende du visiteur.
- Lecture d'état : le cookie
owc_consentest décodé jusqu'à stabilité, avec un maximum de trois passes, parce que les cookies écrits avant la 1.4.3 étaient encodés deux fois. - Garde des scripts injectés dynamiquement : le setter
HTMLScriptElement.prototype.srcetsetAttributesont enveloppés. Un chargeur first-party qui affectes.src = 'https://www.googletagmanager.com/gtm.js?id=…'est intercepté et l'élément est estampillé avant exécution. La liste surveillée compte vingt hôtes :googletagmanager.com,google-analytics.com,googleadservices.com,googlesyndication.com,doubleclick.net,connect.facebook.net,static.hotjar.com,script.hotjar.com,clarity.ms,cdn.matomo.cloud,analytics.tiktok.com,snap.licdn.com,sc-static.net,static.ads-twitter.com,bat.bing.com,s.pinimg.com,cdn.segment.com,js.hs-scripts.com,cdn.amplitude.com,cdn.mxpnl.com. Tout ce qui n'y figure pas passe sans être inquiété. Les hôtes de votre liste blanche sont retirés de cette liste avant impression. - Réinjection en ordre du document. Un script externe restauré bloque la file jusqu'à
son
onloadouonerror, avec un délai maximum de 5 secondes pour qu'un vendeur injoignable ne bloque pas le reste. L'attributasyncn'est appliqué que s'il était présent d'origine — sans cela, un script créé parcreateElementserait forcé en asynchrone et le snippet de configuration s'exécuterait avant sa bibliothèque. Pour un script en ligne restauré,document.writeest temporairement redirigé pour ne pas effacer le document. - Retrait de consentement : si une catégorie déjà appliquée passe explicitement à
false, le runtime recharge la page. Un script déjà exécuté ne peut pas être déchargé (art. 7.3 RGPD). Une catégorie simplement absente de la charge utile n'est pas un retrait. - Un
MutationObserverre-scanne le balisage injecté après chargement (AJAX, sections paresseuses). - Un clic sur le bouton d'un embed bloqué appelle
OWCBanner.acceptCategory(); si l'API de la bannière n'existe pas, l'embed est débloqué localement, sans rien persister.
Le catalogue de signatures
Table {prefix}owc_scripts. Le fichier livré, data/tracker-catalog.json, contient
175 lignes couvrant 71 vendeurs distincts :
| Répartition | Détail |
|---|---|
| Par cible | 110 motifs d'URL, 65 noms de cookies |
| Par type | 172 fragments de texte, 3 expressions régulières |
| Par catégorie | necessary 55, marketing 37, functional 37, analytics 36, social 10 |
Colonnes : pattern, pattern_type (host, regex, inline_signature), match_target
(url ou cookie), name, vendor, category, privacy_policy_url, gcm_signal,
tcf_vendor_id, retention_days.
Deux comportements à connaître :
- Les lignes
match_target = 'cookie'ne sont jamais utilisées par le blocage. Elles servent uniquement à la table des cookies des documents générés. Les comparer en sous-chaîne d'URL est précisément ce qui cassait le JavaScript des sites. - Un motif de type
hostvisant une URL, de moins de six caractères et sans point, est refusé : « fr » ou « IDE » matcheraient « frame.js » et « provider.js ».
Le catalogue est mis en cache dans le cache objet (1 heure) et dans un transient
(12 heures). Un résultat vide n'est jamais mis en cache. Toute écriture déclenche l'action
owc_catalog_updated, qui vide ces caches.
L'onglet Tracker catalogue permet d'ajouter, modifier, supprimer et rechercher des règles, avec un filtre « seulement les règles qui ne peuvent pas se déclencher » : motif vide ou invisible, expression régulière qui ne compile pas ou qui rétro-suit de façon catastrophique, motif trop court et sans point pour une URL, catégorie qui n'existe plus. Ce diagnostic est fait en PHP, parce que seul le moteur PCRE peut dire si une expression compile.
Réglages du blocage
| Réglage | Défaut | Valeurs |
|---|---|---|
compliance_strict | true | booléen |
blocker_unknown_script_policy | allow | allow, block |
blocker_unknown_iframe_policy | block | allow, block |
blocker_block_resource_hints | true | booléen |
blocker_allowlist | '' | un hôte par ligne |
Points d'extension
// Hôtes jamais bloqués, en plus de blocker_allowlist.
add_filter( 'owc_blocker_allowlist', function ( array $hosts ) {
$hosts[] = 'cdn.mon-partenaire.example';
return $hosts;
} );
// Lignes du catalogue avant validation.
add_filter( 'owc_scripts_catalog', function ( array $rows ) {
$rows[] = array(
'pattern' => 'tracker.example.com',
'pattern_type' => 'host',
'match_target' => 'url',
'name' => 'Exemple',
'vendor' => 'Exemple SA',
'category' => 'analytics',
);
return $rows;
} );
La méthode publique OWC_Blocker::block_html_fragment( $html ) applique le blocage à un
fragment qui n'est pas passé par le tampon de sortie.
Limites du blocage
- Sans
compliance_strict, seuls les scripts mis en file par WordPress sont traités. - Un script tiers inconnu est autorisé par défaut. Le blocage réel repose sur le catalogue et les signatures en ligne — enrichissez-les.
- La garde des scripts injectés par JavaScript ne couvre que vingt hôtes ; un traceur hors liste injecté par du code first-party passe.
- Les feuilles de style classées
functional(polices web) sont volontairement laissées passer. - Un traceur catalogué en
<img>mais classé autrement qu'analyticsoumarketingn'est pas neutralisé. - Les médias (
object,embed,source,video,audio) ne sont jamais bloqués par défaut : seul un tiers déjà catalogué est traité. - Un document de plus de 8 Mo, un échec PCRE ou une perte de plus de 50 % du contenu donnent une page servie sans aucun blocage, silencieusement.
- Le retrait de consentement provoque un rechargement complet de la page.
Le scanner de traceurs
Le scanner inventorie ce que vos pages chargent réellement. Il est désactivé par défaut : activez-le dans l'onglet Scanner.
Trois sources de détection
Chaque trouvaille conserve sa provenance et l'information « le blocage peut-il agir dessus ».
1. Le HTML servi. Le scanner récupère un échantillon des URLs de votre propre site
et lit le balisage : <script src> et <script data-owc-src> — les scripts déjà bloqués
sont donc vus quand même —, signatures en ligne, <iframe>, feuilles de style externes et
images dont l'URL contient 1x1, pixel, track, beacon ou impression. Aucun
JavaScript n'est exécuté : ce qu'un gestionnaire de balises injecte au runtime est
invisible pour cette passe.
2. Les en-têtes Set-Cookie. Ce sont les seuls cookies que le balayage serveur peut
prouver, y compris les cookies HttpOnly qu'une sonde navigateur ne verra jamais. Ils
sont marqués non blocables : rien dans le blocage client ne peut retenir un cookie
émis par le serveur. Les cookies du plugin lui-même sont ignorés.
3. La sonde navigateur. Imprimée en pied de page uniquement pour un utilisateur
connecté disposant de manage_options, et seulement si le scanner est activé. Elle
photographie document.cookie au chargement, observe les mutations du DOM — c'est ce qui
attrape ce qu'un gestionnaire de balises injecte —, interroge document.cookie toutes les
5 secondes pour les cookies écrits côté client, et transmet ses observations toutes les
2 secondes et sur beforeunload. La réponse portant la sonde est marquée non cachable.
Limite reconnue dans le code : un administrateur a en général déjà tout accepté. La sonde décrit donc l'état post-consentement, pas l'état pré-consentement.
Le balayage
La file d'URLs est construite pour couvrir des gabarits, pas des pages : les URLs cœur d'abord (accueil, page d'accueil statique, page des articles, et les quatre pages de politiques liées), puis un entrelacement en tourniquet par famille — jusqu'à 30 pages, 15 articles, 5 par type de contenu public personnalisé, 3 catégories et 3 étiquettes. Sans cet entrelacement, trente pages quasi identiques mangeaient tout le budget et les gabarits WooCommerce n'étaient jamais atteints.
Le filtre owc_scanner_urls permet d'ajouter des URLs, mais le résultat est re-restreint
à l'hôte du site : le scanner ne sort jamais de votre domaine.
Chaque requête est faite avec les redirections suivies à la main (deux sauts maximum, chaque
saut re-testé contre l'hôte du site), une réponse plafonnée à 2 Mo, un en-tête
Cache-Control: no-cache, no-store et un paramètre d'URL unique pour ne pas lire une page
de cache, et un user-agent OW-Consent-Scanner/<version>. Une réponse non-HTML est comptée
comme ignorée, pas comme un échec.
Un secret de contournement est envoyé dans l'en-tête X-OWC-Scanner pour que le
blocage se retire et laisse voir le HTML brut. Il est comparé en temps constant, et
l'en-tête est testé en premier pour qu'un visiteur ordinaire ne déclenche même pas la
lecture de l'option.
Budget, verrou, reprise
| Contrainte | Valeur |
|---|---|
| URLs par exécution | scanner_max_urls, défaut 25, bornes 1 à 500 |
| Délai par requête | scanner_timeout, défaut 8 s, bornes 1 à 60 |
| Budget mural par lot | max_execution_time − 10 s, sinon 45 s, borné entre 5 et 60 s |
| Verrou d'exécution | 15 minutes |
| Durée de vie de la file | 6 heures |
Si le budget s'épuise, une reprise est planifiée une minute plus tard et le balayage continue là où il s'était arrêté. Un second lancement pendant qu'un balayage tourne répond « un scan est déjà en cours ». Court-circuit : si trois requêtes échouent sans qu'aucune page n'ait été lue, le balayage s'arrête au lieu de brûler son délai sur vingt-deux URLs de plus — c'est le cas typique d'un site dont le loopback HTTP est bloqué.
Les signatures en ligne reconnues
Vingt-trois aiguilles, regroupées en vingt et un libellés : gtag('config', gtag('event',
gtag('js', gtm.start, ga('create', ga('send', fbq('init', fbq('track',
_paq.push, (h.hj=h.hj, clarity('init', clarity.ms/tag, mixpanel.init,
amplitude.init, amplitude.getInstance, _linkedin_partner_id, snaptr('init',
ttq.load, pintrk("load", pintrk('load', rdt('init', criteo_q.push, _etmc.push.
La clé de dédoublonnage est la signature, jamais une empreinte du corps : un vrai snippet gtag ou Pixel contient des valeurs propres à la page, ce qui donnerait une ligne par page.
La boucle de rétroaction : trouvaille → catalogue → blocage
C'est le point qui rend le scanner utile. Classer un traceur écrit une règle dans le catalogue de blocage. Un clic sur la catégorie d'une trouvaille fait trois choses : il écrit la catégorie sur la ligne, il mémorise votre décision manuelle, et il crée la règle correspondante. À la fin de chaque balayage complet, la même opération est faite en masse pour ce que le catalogue ne sait pas déjà classer.
Règles de conversion :
- une trouvaille de type
cookiedevient une règlematch_target = cookiesur le nom du cookie ; - une trouvaille de type
inline_scriptn'écrit une règle que si l'aiguille d'origine est retrouvée. Sans elle, aucune règle n'est écrite : une règle construite à partir du libellé « Google gtag (config) » serait morte à jamais et matcherait le nom de la trouvaille, ce qui la ferait passer pour « couverte » pour toujours ; - sinon, une règle
hostsur le domaine, refusée si le domaine est vide.
Vos décisions manuelles sont stockées à part (500 au maximum) et réappliquées à chaque nouvelle observation, parce que le drapeau « confirmé » signifie aussi « une règle de catalogue a matché » et ne peut donc pas porter seul l'information « l'administrateur a tranché ». Reclasser en « non catégorisé » supprime l'entrée : c'est un vrai retour arrière.
Le catalogue est comparé motif le plus long d'abord, pour qu'une règle large
(google-analytics.com) n'écrase pas la règle plus précise que vous avez écrite
(www.google-analytics.com).
Planification, alertes, rétention
scanner_frequency accepte hourly, twicedaily, daily et weekly — mais
l'administration ne propose que les récurrences que votre installation connaît réellement.
Défaut : weekly. Désactiver le scanner déprogramme les deux événements.
Deux e-mails distincts, jamais les deux à la fois :
- Traceurs non catégorisés — envoyé uniquement pour des identifiants réellement nouveaux, avec une mémoire plafonnée à 500 entrées.
- « Le balayage n'a pas pu lire ce site » — limité à un message par semaine et par signature d'erreur. C'est le mode de panne le plus grave, parce qu'un balayage qui ne lit rien ne produit aucune trouvaille, donc aucune alerte du premier type.
Destinataire : scanner_alert_email, sinon l'adresse d'administration du site.
Rétention : 90 jours. Les trouvailles non revues depuis 90 jours sont supprimées, dès qu'au moins une page a été lue — pas seulement quand un balayage se termine.
Le panneau de couverture
C'est la partie la plus importante de l'onglet Scanner, et elle alimente aussi l'encart de mise en garde des documents générés. Elle répond toujours à ces questions :
- un balayage a-t-il déjà été lancé ?
- le dernier a-t-il fini, ou a-t-il été interrompu par son budget de temps ?
- combien de pages ont été réellement récupérées (pas retirées de la file) ?
- combien n'ont pas pu l'être, et quelle a été la première erreur ?
- un navigateur a-t-il déjà rapporté quelque chose, ou aucun JavaScript n'a-t-il jamais été observé ?
- quelle est la fenêtre au-delà de laquelle un traceur non revu disparaît ?
Un balayage qui n'a atteint aucune page est présenté comme un échec, pas comme un résultat propre. C'est ce panneau qu'il faut lire avant de publier une politique cookies construite sur ces trouvailles.
L'onglet Scanner
Trois cartes (traceurs détectés, non catégorisés, dernier balayage et prochaine exécution),
le panneau de couverture, un bouton « lancer un scan maintenant », le formulaire de
planification, puis la liste des trouvailles : filtres par catégorie, recherche texte,
filtre par type d'élément (script, inline_script, iframe, stylesheet, pixel,
cookie, link, preconnect), filtre par état, export CSV, pagination par 25.
Classement à l'unité ou en masse — le sélecteur de masse a un premier élément vide
désactivé, pour qu'une soumission accidentelle ne devienne pas un déclassement général.
Réglages du scanner
| Réglage | Défaut | Bornes |
|---|---|---|
scanner_enabled | false | booléen |
scanner_frequency | weekly | hourly, twicedaily, daily, weekly |
scanner_max_urls | 25 | 1 à 500 |
scanner_timeout | 8 | 1 à 60 |
scanner_probe_mode | admins | admins, off |
scanner_alert_email | '' | adresse e-mail |
À savoir :
scanner_probe_moden'est pas consommé dans cette version. Le code le signale explicitement — la condition d'impression de la sonde ne lit plus ce réglage et se base uniquement surscanner_enabled. Enregistreroffstocke la valeur sans désactiver la sonde. Pour arrêter réellement la sonde, désactivez le scanner.
Limites du scanner
- Aucun JavaScript n'est exécuté par le balayage serveur. Sans la sonde, ce qu'un gestionnaire de balises injecte est absent de l'inventaire.
- La sonde est réservée aux administrateurs connectés et il n'existe aucun mode qui l'exécute pour un visiteur ordinaire : cela exigerait sa propre information préalable.
- Un cookie posé par en-tête
Set-Cookieest détecté mais non blocable. - Le balayage ne sort jamais du domaine et est plafonné à 25 URLs par défaut : un grand site n'est jamais couvert intégralement.
- Sur un hôte où le loopback HTTP est bloqué (authentification HTTP de préproduction, pare-feu), le balayage ne lit rien.
- Une trouvaille non revue depuis 90 jours disparaît, et disparaît donc aussi de la politique cookies.
- Le scanner dépend de WP-Cron : sur un site avec
DISABLE_WP_CRONet sans cron système, le balayage planifié ne se déclenche pas.
Le générateur de documents légaux
Les quatre documents
| Type | Contenu | Langues disponibles |
|---|---|---|
cookie_policy | Politique cookies | français, anglais, portugais |
privacy_policy | Politique de confidentialité | français, anglais, portugais |
legal_notice | Mentions légales | français uniquement |
terms | CGU / CGV | français uniquement |
Chaque document généré est une page WordPress, versionnée par des métadonnées :
_owc_policy_type, _owc_policy_version (incrémentée à chaque génération),
_owc_policy_hash (empreinte SHA-256 du HTML), _owc_policy_generated_at,
_owc_policy_profile, _owc_policy_lang, _owc_policy_manual_edit.
Après génération, le lien est réinjecté dans les réglages correspondants
(link_cookie_policy, link_privacy_policy, link_legal_notice, link_terms), et la
publication d'une politique de confidentialité met à jour la page de politique déclarée
dans WordPress.
Deux verrous avant publication
Verrou 1 — les champs obligatoires. La génération est refusée avec la liste des clés vides, plutôt que de publier des clauses à trous.
| Document | Champs exigés |
|---|---|
| Tous | legal_company_name, legal_company_email |
| Sauf la politique cookies | + legal_company_address, legal_country |
| Mentions légales | + legal_company_phone, legal_publication_director, legal_host_name, legal_host_address, legal_host_phone |
| Mentions légales, pays FR / BE / LU | + legal_company_legal_form, legal_company_reg_number |
Verrou 2 — la langue. Le générateur refuse de publier un document rédigé dans une langue que la juridiction n'utilise pas. En prévisualisation, le document sort avec un bandeau rouge d'avertissement ; en publication, c'est un refus net. Dans l'onglet Policies, le bouton de génération est masqué lorsqu'aucun gabarit n'existe pour le type et le profil actifs, plutôt que de s'afficher et d'échouer systématiquement.
Résolution de la langue
| Profil | Langue du document |
|---|---|
quebec | français |
lgpd | portugais |
gdpr avec legal_country ∈ {FR, BE, LU} | français |
| Tout le reste | anglais |
L'avertissement obligatoire
Tant que policy_disclaimer est activé — et il l'est par défaut — chaque document se
termine par un bloc qui dit que c'est un modèle généré automatiquement, qu'il doit être
relu par un professionnel qualifié avant publication, et qui porte la date de génération et
la version du plugin. Ne le désactivez qu'en connaissance de cause.
Le tableau des traceurs — les règles d'honnêteté
Le tableau publié dans la politique cookies vient de votre table de scanner, pas d'une base tierce. Cinq règles gouvernent ce qu'il affiche.
- Fenêtre de fraîcheur : seules les trouvailles revues dans les 90 derniers jours sont publiées. Si la colonne de dernière observation n'existe pas encore parce qu'une migration n'est pas passée, la fenêtre est ignorée plutôt que de publier « aucun traceur » sur un site qui en a — la sous-divulgation est la seule direction où un document légal ne doit jamais échouer.
- Encart de mise en garde en tête du tableau, dérivé du panneau de couverture du scanner : balayage jamais lancé, aucune page lue, balayage interrompu, N URLs en échec, aucune observation avec JavaScript actif, fenêtre de N jours. Un balayage incomplet est divulgué, pas publié comme un inventaire terminé.
- Les traceurs non classés ne sont pas cachés : ils ont leur propre section. Ce sont ceux que personne n'a examinés.
- Un cookie observé dans un en-tête
Set-Cookieest marqué d'un signe distinctif, avec une note expliquant qu'il est déposé par le serveur et qu'aucun blocage client ne peut le retenir. Il ne doit donc pas être présenté comme conditionné au consentement, et la clause qui affirme que les traceurs non classés ne sont déposés qu'après consentement porte l'exception correspondante. - Le nom du responsable et le lien vers sa politique viennent du catalogue, uniquement pour les règles ciblant une URL. Les motifs de nom de cookie sont exclus : on n'affirme pas dans un document légal qui traite des données sans preuve.
Les cookies du site et les durées publiées
La section « cookies déposés par ce site » liste les cookies du plugin lui-même :
owc_consent (durée dérivée du réglage de renouvellement), euconsent-v2 si TCF est
activé, owc_geo si la détection régionale est activée, owc_gpc (durée de session),
owc_gpc_notice (5 minutes), plus les cookies de connexion et de réglages de WordPress.
Le tableau des durées de conservation tire ses chiffres des réglages réels —
ledger_retention_days, retention_form_data_days, retention_dsar_days et la durée de
renouvellement du consentement. Aucune valeur décorative n'est publiée. La durée du cookie
de consentement est dérivée du même calcul que le cookie lui-même, ce qui garantit
qu'un réglage de 0 mois publie bien « 13 mois » et non « jamais ».
Détection du type d'activité (CGU/CGV)
Les CGU/CGV comptent 21 sections, avec bascule vendeur/prestataire, produits/services, devis/commande, et des clauses spécifiques par type d'activité.
legal_business_type (auto, vitrine, rental, ecommerce, services, saas,
content) est la source de vérité. Quand il vaut auto, une heuristique examine le
site — présence de WooCommerce, inscription ouverte, plugins d'abonnement, page de tarifs,
pages de services, puis densité de mots-clés sur les cinquante derniers contenus publiés —
mais son verdict n'est jamais imprimé dans un document publié : il ne sert qu'à choisir
les clauses optionnelles.
Les citations d'articles français ne sont insérées que si legal_country vaut FR. La
Belgique a ses propres citations (CDE art. VI.45 §1er, art. VI.47, Service de médiation
pour le consommateur) ; le Luxembourg et le Québec reçoivent une formulation neutre. Citer
un texte non vérifié serait le défaut, pas le remède.
Autres comportements
- La date du document est formatée dans la locale du document. Si le pack de traduction
correspondant n'est pas installé, le format bascule sur
jj/mm/aaaa: un document français ne peut pas s'ouvrir sur « 4 September 2026 ». - Édition manuelle détectée : si le contenu stocké ne correspond plus à son empreinte, une révision est enregistrée avant écrasement et l'écran affiche la pastille « modifications manuelles remplacées », avec un lien vers les révisions.
- Le générateur ne rétrograde jamais : une page publiée reste publiée même si la case « publier » est décochée, et un titre ou un permalien que vous avez renommé survit à une régénération.
- Injection dans le menu de pied de page :
auto_footer_menu_inject, désactivé par défaut. Le plugin ne modifie pas votre site public sans demande explicite. Emplacements reconnus quand vous l'activez :footer,footer-menu,footer_menu,footer-1,footer_1,secondary,legal.
Points d'extension
// Corps brut du gabarit, les {{variables}} encore en place.
add_filter( 'owc_policy_template', function ( $html, $type, $profile ) {
return $html;
}, 10, 3 );
// Les variables injectées dans le gabarit.
add_filter( 'owc_policy_vars', function ( array $vars, $type, $profile ) {
return $vars;
}, 10, 3 );
// Le HTML final, variables remplacées.
add_filter( 'owc_policy_html', function ( $html, array $vars ) {
return $html;
}, 10, 2 );
Fournir votre propre corps via owc_policy_template désactive le verrou de langue : un
site qui fournit son texte en assume la langue. C'est la voie officielle pour publier des
mentions légales non françaises.
Quatre blocs pré-générés sont disponibles comme variables réservées :
{{__trackers_table__}}, {{__categories_list__}}, {{__retention_table__}},
{{__jurisdictional_rights__}}. Toutes les variables sont déjà échappées dans le
contexte de leur usage. Trois méthodes publiques sont réutilisables par un gabarit tiers :
OWC_Policies::build_cookie_table(), build_data_retention_table() et
render_first_party_cookies().
Routes REST
| Méthode | Chemin | Paramètres | Accès |
|---|---|---|---|
POST | /owc/v1/policies/generate | type (requis), publish (booléen, défaut false) | manage_options |
GET | /owc/v1/policies/preview | type (requis) | manage_options |
Le HTML de la prévisualisation passe par wp_kses_post() avant d'être renvoyé.
Limites du générateur
- Ce sont des modèles, pas un conseil juridique. Faites-les relire.
- Mentions légales et CGU/CGV n'existent qu'en français. Toute autre juridiction se heurte au verrou de langue, sauf si vous fournissez votre propre corps.
- Trois langues seulement : français, anglais, portugais.
- Quatre pays de l'EEE ont une autorité nommée ; ailleurs, une formulation générique.
- Le tableau des traceurs ne vaut que ce que vaut votre balayage — et le document le dit.
- La classe du générateur pèse environ 440 Ko de gabarits juridiques : elle est chargée paresseusement, uniquement quand l'un des trois points d'entrée est réellement sollicité.
Le registre de consentement
Le registre est la réponse à l'article 7.1 du RGPD : pouvoir démontrer que la personne a consenti. Il est activé par défaut.
La table
{prefix}owc_ledger, toutes les dates en UTC :
| Colonne | Type | Contenu |
|---|---|---|
id | bigint | Clé primaire |
created_at | datetime | Horodatage UTC |
visitor_token | char(32) | Jeton pseudonyme du navigateur, 32 caractères hexadécimaux |
event | varchar(20) | accept_all, reject_all, save_preferences, withdraw, auto, gpc_opt_out |
categories | varchar(255) | Liste des catégories accordées |
profile | varchar(20) | Profil de conformité en vigueur au moment de l'action |
source | varchar(60) | banner, preferences, footer_link, api, auto |
ip_pseudonymous | varchar(45) | IP tronquée |
ua_hash | char(64) | Empreinte salée du user-agent |
page_url | varchar(500) | Page où l'action a eu lieu |
prev_hash | varchar(128) | Empreinte de la ligne précédente |
row_hash | varchar(128) | Empreinte de cette ligne |
policies_hash | char(64) | Empreinte des documents en vigueur |
banner_revision | varchar(40) | Empreinte de la bannière réellement affichée |
plugin_version | varchar(20) | Version du plugin au moment de l'écriture |
Les trois dernières colonnes permettent de reconstituer ce que le visiteur a vu, pas seulement ce qu'il a coché.
La chaîne
Chaque ligne est signée par HMAC sur une sérialisation canonique préfixée en longueur de son contenu et de l'empreinte de la ligne précédente. La genèse est une suite de 64 zéros. L'écriture se fait dans une transaction, avec un verrou de ligne sur la dernière entrée : c'est ce verrou qui sérialise réellement les écritures concurrentes et empêche la chaîne de bifurquer. Un verrou nommé MySQL est pris en second rempart, au mieux : s'il est refusé, l'ajout continue et une action est déclenchée pour que vous puissiez le tracer.
Les valeurs sont tronquées avant signature, pour que la valeur signée soit exactement la valeur stockée.
L'honnêteté sur l'infalsifiabilité
Le plugin détermine lui-même la provenance de sa clé de signature :
| Provenance | Condition | Verdict |
|---|---|---|
constant | OWC_LEDGER_KEY est définie | Infalsifiable |
wp-config | AUTH_KEY et AUTH_SALT sont définies, non vides, différentes entre elles et sans la phrase par défaut | Infalsifiable |
database | Sinon | Non infalsifiable |
Dans le troisième cas, WordPress stocke les salts en base : quiconque a accès à la base peut re-signer la chaîne. Le plugin le rapporte dans le résultat de vérification et affiche un avis d'administration qui le dit en toutes lettres. Une empreinte non réversible de la clé est conservée, ce qui permet de distinguer une rotation de sels d'une réécriture.
C'est la raison pour laquelle la section Installation recommande de
définir OWC_LEDGER_KEY.
La vérification
Le bouton « Verify the chain now » de l'onglet Audit ledger — ou la route
GET /owc/v1/ledger/verify — parcourt toute la chaîne par lots de 500 lignes.
Trois schémas de signature sont reconnus : le schéma canonique actuel, un schéma historique antérieur, et un schéma non clé issu des toutes premières versions. Ce dernier produit une empreinte que n'importe qui détenant la base peut recalculer : il n'est jamais considéré comme valide, il est compté à part et signalé comme une rupture. Un condensé non clé n'est pas une preuve.
Ruptures rapportées : hash_mismatch, chain_break, bad_genesis, unkeyed_rows,
table_emptied, tail_truncated, head_mismatch, count_mismatch.
Le résultat porte notamment : ok, total, checked, table_total, broken_at,
breaks, break_count, partial, legacy_rows, unverifiable_rows, anchor,
anchor_ok, key_source, tamper_evident, key_rotated.
L'ancre de tête
Une ancre est conservée hors de la table : identifiant, empreinte, nombre de lignes, horodatage. Sans elle, supprimer les lignes les plus récentes ou vider la table ne laisserait aucune trace. Une action est déclenchée à chaque déplacement de l'ancre, et le code invite explicitement à la répliquer hors base — fichier, syslog, endpoint externe — pour rendre un rejeu global détectable.
Le plugin refuse d'écraser une ancre existante et signale honnêtement qu'une ancre dérivée de la table elle-même ne rend détectables que les troncatures postérieures.
La rétention
ledger_retention_days, défaut 1825 jours (5 ans), bornes 0 à 3650. La valeur 0
signifie conservation illimitée.
L'élagage tourne sur son propre cron quotidien et n'enlève qu'un préfixe contigu : jamais de trou au milieu de la chaîne. L'empreinte de la dernière ligne supprimée est mémorisée pour que le reste demeure vérifiable. Plafond par exécution : 20 000 lignes (40 lots de 500). Un très gros retard d'élagage se résorbe donc sur plusieurs jours.
Minimisation des données
- Adresse IP : IPv4 avec le dernier octet mis à zéro (le /24 est conservé), IPv6 tronquée au /48 avec les 80 bits restants à zéro. Le masquage se fait sur la forme binaire ; une adresse IPv6 mappant une IPv4 est traitée comme de l'IPv4.
- User-agent : seule une empreinte salée est stockée, jamais la chaîne.
page_url: validée contre les hôtes du site. L'appelant fait autorité — s'il fournit la valeur, même vide, c'est la réponse ; leReferern'est consulté que s'il n'a rien dit du tout, et il est validé de la même façon.- Jeton visiteur : 32 caractères hexadécimaux issus d'un générateur cryptographique, sans lien avec une identité.
L'algorithme
ledger_hash_algo accepte sha256 (défaut) et sha3-256, intersectés avec les algorithmes
que votre PHP supporte réellement. Le réglage est affiché mais non modifiable dans
l'administration : changer d'algorithme ferait échouer la vérification de toutes les lignes
existantes. Les colonnes sont dimensionnées plus large, mais le choix réel se limite à ces
deux algorithmes.
L'onglet Audit ledger
Liste paginée par 25 : identifiant, date locale et horodatage UTC brut, jeton visiteur, événement, catégories, profil, IP pseudonymisée, et le maillon de chaîne (empreinte précédente → empreinte courante, tronquées à l'affichage, valeur complète en infobulle).
Filtres : jeton visiteur (32 hexadécimaux), événement, profil, plage de dates — saisies dans le fuseau du site et comparées aux horodatages UTC réellement stockés.
Exports CSV et JSON. Le JSON porte une enveloppe (format, algorithme, provenance de la clé) pour qu'une autorité puisse re-vérifier l'extrait sans disposer du reste de la chaîne. Toutes les cellules CSV sont neutralisées contre l'injection de formules de tableur.
Consulter le registre par l'API
GET /owc/v1/ledger accepte page, per_page (1 à 200, défaut 50), visitor_token,
from et to. Les trois derniers permettent de répondre à une demande d'accès (art. 15)
sans parcourir toute la chaîne. Les bornes sont lues en UTC, et une date au format
AAAA-MM-JJ est élargie à la journée entière. Les filtres réellement appliqués sont
renvoyés dans la réponse : un filtre rejeté par les contrôles ne doit pas se lire comme
« voici tout le registre ».
Ce qui se passe quand le registre refuse une écriture
C'est le comportement le plus important de tout le plugin. Si le registre est activé et que l'écriture de la ligne échoue, le cookie de consentement est annulé et la réponse est un 503. Rien n'est stocké, aucun traceur n'est libéré.
Le raisonnement est direct : un consentement qu'on ne peut pas prouver ne doit pas être revendiqué. Voir Dépannage pour la marche à suivre.
Points d'extension
add_action( 'owc_consent_updated', function ( $categories, $event, $source ) {}, 10, 3 );
add_action( 'owc_consent_cookie_not_sent', function ( $cookie, $event ) {}, 10, 2 );
add_action( 'owc_ledger_lock_failed', function ( $event, $token ) {}, 10, 2 );
add_action( 'owc_ledger_write_failed', function ( $error, $event, $token ) {}, 10, 3 );
add_action( 'owc_ledger_anchor', function ( array $anchor ) {} );
add_action( 'owc_ledger_pruned', function ( $deleted, $days ) {}, 10, 2 );
owc_ledger_anchor est le crochet à utiliser pour répliquer l'ancre hors de la base.
Limites du registre
- L'infalsifiabilité est conditionnelle, et le plugin le dit lui-même.
- Les lignes antérieures à la 1.2.0 portent un condensé non clé : elles ne sont jamais re-signées — la re-signature laisserait un attaquant faire re-signer un historique falsifié — et elles empêchent le verdict « chaîne intacte » tant qu'elles subsistent.
- La rétention est plafonnée à 20 000 lignes par exécution quotidienne.
- Le registre n'est pas supprimé par l'effaceur de données personnelles de WordPress : c'est une chaîne de hachage, la retirer détruirait la preuve qu'elle existe pour porter, et elle ne contient qu'un jeton pseudonyme et une IP tronquée. Un message explique ce choix à la personne concernée.
Le portail de droits (DSAR)
Le portail permet à une personne d'exercer ses droits depuis une page de votre site. Il est activé par défaut, mais il n'apparaît que là où vous placez le shortcode.
Le formulaire
[owc_dsar_form]
Attributs :
| Attribut | Défaut | Rôle |
|---|---|---|
types | access,rectification,erasure,portability,restrict,object,optout | Types proposés, séparés par des virgules |
title | « Exercer mes droits sur mes données personnelles » | Titre du bloc |
submit_label | « Envoyer ma demande » | Libellé du bouton |
Si dsar_enabled est désactivé, le shortcode affiche « le portail est désactivé » et la
route REST répond 404 : la désactivation est bien côté serveur.
La liste que vous demandez est intersectée avec la liste que le serveur accepte : une option refusée par l'endpoint n'est jamais proposée au visiteur.
Les huit types de demandes
access, rectification, erasure, portability, restrict, object, optout,
withdraw.
withdraw est volontairement retiré de ce que le formulaire propose. Retirer son
consentement aux cookies est instantané dans le panneau de préférences ; le faire passer
par une procédure écrite de 30 jours rendrait le retrait plus difficile que le consentement
(art. 7.3 RGPD). Un lien sous le formulaire ouvre directement le panneau de préférences pour
ce cas.
Le filtre owc_dsar_types est la source de vérité unique : la liste blanche du endpoint en
dérive.
Le cycle complet
1. Soumission. POST /owc/v1/dsar. Trois protections serveur, dans cet ordre :
- Piège à robots : un champ caché rempli entraîne un rejet 400 avec le message générique d'une soumission malformée — un robot n'apprend rien sur la raison de son refus. Côté navigateur, le formulaire affiche même le message de succès sans rien envoyer.
- Attestation (art. 12.6) : la case « je confirme exercer un droit sur MES propres données personnelles » doit être cochée, et c'est vérifié côté serveur, pas seulement dans le navigateur. Elle est enregistrée avec la demande.
- Délai minimum de 3 secondes entre l'affichage du formulaire et l'envoi, côté client.
Limites de débit : 3 par heure et par adresse IP, 3 par jour et par adresse e-mail visée — l'adresse est choisie par l'attaquant, c'est donc elle qu'il faut plafonner — et 30 par heure pour tout le site.
La ligne est insérée avec le statut pending, un jeton de 64 caractères dont seule
l'empreinte SHA-256 est stockée, une date d'expiration du jeton et une échéance de
réponse. L'identifiant n'est pas renvoyé : pas d'énumération, pas de fuite de
volumétrie. La réponse vaut {ok, mail_sent, message}, et mail_sent reflète l'échec réel
d'envoi.
Le DPO n'est pas notifié à ce stade. Sinon un appelant anonyme ferait partir deux e-mails par soumission depuis votre domaine.
2. E-mail de vérification. Le lien pointe vers votre page d'accueil avec le jeton en
clair en paramètre. Validité : dsar_token_ttl_days, défaut 7 jours, bornes 1 à 90.
L'e-mail porte un en-tête Reply-To mais jamais un From réécrit — réécrire
l'enveloppe est ce qui casse SPF. L'adresse de réponse est dsar_email, sinon
legal_dpo_email ; jamais l'adresse d'administration du site, qui ne doit pas être
publiée.
3. Confirmation d'identité. Le lien ouvre une page qui n'exécute rien : la confirmation se fait par un POST protégé par un nonce. C'est ce qui empêche un scanner de liens de messagerie (Safe Links, URL Defense, prévisualisation d'inbox) de confirmer une identité à la place de la personne.
La page est un document HTML autonome, servi hors du thème, avec noindex, nofollow et des
en-têtes non cachables. Les tentatives sont limitées à 30 par heure et par IP. Les réponses
sont distinctes : 404 pour un lien inconnu, 200 pour un lien déjà confirmé, 410 pour un lien
expiré, 403 pour un nonce périmé, 500 pour un échec d'écriture, 200 pour une confirmation.
4. Ce que la confirmation déclenche.
- Le statut passe à
verified. - L'échéance est recalculée à partir de la vérification. Article 12.3 : le délai court depuis que la demande est complète, pas depuis une soumission jamais confirmée.
- Le jeton est brûlé. Son empreinte est conservée volontairement, pour qu'une personne qui rouvre son lien lise « déjà confirmé » au lieu d'un 404 lui demandant de tout recommencer. L'usage unique est garanti par le contrôle de statut.
- Sont enregistrés : la date de confirmation, l'IP tronquée, le jeton pseudonyme du navigateur — seul pont possible entre le registre de consentement et la demande — et l'identifiant de la demande WordPress associée.
- Une demande WordPress native est ouverte avec l'identité déjà prouvée, donc
directement dans votre file Outils → Exporter / Effacer les données personnelles.
Cela fait que l'exécution couvre toutes les extensions du site, pas seulement
OW Consent. Correspondance :
accessetportability→ export ;erasure→ suppression ; les autres types n'ouvrent pas de demande native. - Le responsable est notifié à
dsar_emailetdsar_notify_email, avec repli sur l'adresse d'administration si aucune n'est valide.
5. Exécution. Un panneau au-dessus de l'onglet DSAR requests liste les 20 demandes vérifiées, triées par échéance, avec le nombre de jours restants ou de retard.
| Action | Comportement |
|---|---|
| Télécharger les données (JSON) | Refus 409 si l'identité n'a jamais été confirmée. Le fichier est nommé dsar-<id>-<AAAAMMJJ>.json |
| Effacer les données | Offert uniquement pour une demande de type erasure, et seulement après confirmation d'identité. La demande est clôturée avant l'effacement, sinon le texte libre de la demande en cours survivrait à sa propre exécution |
| +2 mois | Extension de l'art. 12.3, une seule fois, avec un motif de 500 caractères maximum. L'échéance recule de 60 jours, les rappels sont réarmés, et la personne concernée reçoit un e-mail avec la nouvelle échéance et les motifs |
| Clôturer | Statut resolved ou rejected, note obligatoire. Impossible de réécrire une demande déjà close, et impossible d'inscrire « honorée » sur une identité jamais confirmée |
| Renvoyer le lien | Émet un nouveau jeton, ce qui invalide le précédent |
Cinq statuts : pending, verified, resolved, rejected, expired.
L'écran ne charge jamais le jeton ni son empreinte, et n'affiche pas le nom du demandeur.
6. Surveillance du délai. Sur le cron quotidien : jusqu'à 200 demandes vérifiées parcourues par échéance, un rappel sept jours avant, un rappel au dépassement, une seule fois chacun. Un bandeau d'administration signale les demandes hors délai et celles à moins de sept jours.
7. Purge. Les soumissions jamais vérifiées sont supprimées après péremption de leur
jeton, et les demandes closes sont supprimées au-delà de retention_dsar_days. La mesure
part de la clôture, pas de la soumission.
Le bundle de portabilité
L'export produit un document au format ow-consent/dsar-export, avec l'horodatage, le site,
le sujet, la demande et les groupes de données. Il appelle tous les exportateurs
enregistrés du site, pagine jusqu'à 50 pages par exportateur et s'arrête à 20 secondes.
L'effacement fait la même chose côté effaceurs, avec les mêmes bornes.
Filtre : owc_dsar_export_bundle( $bundle, $email, $id ).
Ce que l'export et l'effacement couvrent dépend donc des extensions installées sur votre site : une extension qui n'enregistre aucun de ces crochets doit être traitée à la main.
Intégration avec l'outillage natif de WordPress
- Exportateur enregistré sous la clé
ow-consent, avec deux groupes : les demandes de droits et le registre de consentement. - Effaceur :
- les soumissions jamais confirmées sont supprimées — aucune valeur probatoire, que des données personnelles ;
- les demandes closes voient leur texte libre et le nom effacés, la ligne minimale (date,
type, statut) étant conservée comme preuve (art. 5.2) jusqu'à
retention_dsar_days; - une demande encore ouverte est conservée, avec un message expliquant qu'elle doit être répondue avant d'être supprimée ;
- le registre de consentement n'est pas supprimé, pour la raison expliquée plus haut.
- Le plugin alimente aussi le brouillon de politique de confidentialité de WordPress.
La mention d'information sous le formulaire
Sous le formulaire, un bloc dépliable porte la mention de l'article 13 : identité et
adresse du responsable, contact vie privée, finalité et base légale (art. 6.1.c), données
collectées, destinataires et durée de conservation, délai de réponse et possibilité
d'extension, autorité de contrôle compétente, lien vers la politique. Filtre :
owc_dsar_form_notice( $html, $context ).
L'autorité affichée vient de la dérivation décrite dans Les onze profils de conformité, et n'est retenue que si elle commence par une majuscule : lire le réglage brut nommerait l'autorité française sur un site sud-africain, indien, australien ou californien.
Sans JavaScript, le bouton d'envoi est masqué et un message propose l'adresse de contact — pour que le navigateur ne fasse jamais une soumission native qui mettrait l'adresse du demandeur dans l'URL, et donc dans les journaux de tous les serveurs et proxys traversés.
Réglages DSAR
| Réglage | Défaut | Bornes |
|---|---|---|
dsar_enabled | true | booléen |
dsar_email | '' | contact public et destinataire des notifications |
dsar_notify_email | '' | destinataire supplémentaire |
dsar_response_days | 30 | 1 à 30 — jamais plus d'un mois |
dsar_token_ttl_days | 7 | 1 à 90 |
retention_dsar_days | 1095 (3 ans) | 1 à 3650 |
Points d'extension
add_action( 'owc_dsar_submitted', function ( $type, $email, $message ) {}, 10, 3 );
add_action( 'owc_dsar_verified', function ( $id, array $row ) {}, 10, 2 );
add_action( 'owc_dsar_fulfilled', function ( $id, $what, $trace ) {}, 10, 3 );
add_action( 'owc_dsar_mail_failed', function ( $id, $email, $kind ) {}, 10, 3 );
Limites du portail
- L'identité repose sur un seul facteur : l'aller-retour par e-mail. Toute preuve supplémentaire au titre de l'art. 12.6 se saisit à la main dans les notes de la demande.
- Un envoi d'e-mail défaillant bloque le cycle : la réponse expose
mail_sent: falseet le formulaire affiche un message renvoyant vers l'adresse de contact, mais la demande restependinget le délai légal ne démarre pas. - Les rappels d'échéance et la purge dépendent de WP-Cron.
- La table des demandes est toujours supprimée à la désinstallation du plugin, quel que soit votre réglage. Exportez avant de supprimer.
L'opt-out CCPA « Do Not Sell or Share »
Sous les profils américains, la loi exige un contrôle nommé, visible, et qui exécute réellement l'opt-out.
Deux façons de le placer
Shortcode, où vous voulez :
[owc_dnsmpi]
[owc_dnsmpi label="Ne pas vendre ni partager mes informations personnelles" class="mon-lien"]
| Attribut | Défaut |
|---|---|
label | « Do Not Sell or Share My Personal Information » |
class | owc-dnsmpi |
Injection automatique en pied de page : réglage ccpa_inject_footer, activé par
défaut, rendu sur wp_footer en priorité 20.
Ce que fait un clic
Un clic exécute l'opt-out. Il n'ouvre pas un panneau. Le runtime récupère un nonce frais
puis envoie un reject_all avec la source footer_link et toutes les catégories
optionnelles à false. Le lien passe en aria-busy="true" et une zone role="status"
affiche l'état : « enregistrement en cours », puis « votre opt-out a été enregistré sur ce
navigateur », ou le message d'erreur.
Le code cite la raison : selon les règlements d'application de la CCPA (§7026(a)(1)), un lien qui se contente d'ouvrir un panneau n'est pas un mécanisme conforme.
Repli sans fetch ni Promise (WebView ancien, navigateur intégré) : le clic ouvre le
panneau de préférences avec les catégories publicitaires déjà positionnées sur off.
La contrainte de cache
Le HTML servi est identique pour tous. Le lien est donc rendu visible si le profil
configuré du site est ccpa ou us_generic ; sinon il est rendu masqué et révélé côté
client pour les visiteurs dont la juridiction l'exige, d'après le cookie de détection
régionale.
Si la détection régionale est désactivée et que votre profil configuré n'est pas américain, le bloc n'est carrément pas imprimé : pas de balisage mort.
Aucune donnée de visiteur ne voyage dans la configuration JavaScript de ce module — seules les listes de profils, jamais le booléen résolu.
Les ressources de ce module sont imprimées dans <head>, pas en pied de page, pour que le
refus Consent Mode atteigne gtag avant qu'un gestionnaire de balises ne se déclenche. Le
JavaScript est en ES5 uniquement.
Limites
- Le contrôle en un clic exige
fetchetPromise. - Le lien n'est obligatoire que sous les profils
ccpaetus_generic; sous tout autre profil, il n'est ni affiché ni révélé. - La révélation côté client dépend du cookie de détection régionale, donc de JavaScript.
Google Consent Mode v2
Le module est activé par défaut (gcm_enabled).
Les sept signaux
Les sept signaux de Consent Mode v2 sont émis : ad_storage, ad_user_data,
ad_personalization, analytics_storage, functionality_storage,
personalization_storage et security_storage. Les six catégories du plugin y sont
associées depuis une source de vérité unique, ce qui garantit que la bannière, le
blocage et le bootstrap ne peuvent pas diverger. La table est donnée dans
Les catégories de cookies.
Les valeurs par défaut, avant tout consentement
L'appel default est imprimé en balisage statique, et l'update est calculé dans le
navigateur : c'est ce qui garde la page cachable.
| Signal | Régime opt-in | Régime opt-out |
|---|---|---|
ad_storage | denied | granted |
analytics_storage | denied | granted |
ad_user_data | denied | granted |
ad_personalization | denied | granted |
personalization_storage | denied | granted |
functionality_storage | denied si le site expose la catégorie functional, sinon granted | granted |
security_storage | granted toujours | granted |
Les deux jeux sont imprimés dans la même page. Le runtime choisit lequel appliquer d'après le cookie de détection régionale. Sans cela, une page mise en cache épinglerait la juridiction d'un visiteur américain sur un visiteur européen.
Les deux réglages associés
| Réglage | Défaut | Effet |
|---|---|---|
gcm_ads_data_redaction | true | Masque les identifiants publicitaires tant que ad_storage est refusé |
gcm_url_passthrough | true | Fait transiter gclid / dclid par les URLs tant que les cookies sont refusés |
À savoir
security_storage n'est jamais touché par une mise à jour : il reste accordé, comme le
prévoit la spécification. Le tableau de bord affiche une carte indiquant si Consent Mode
est actif.
IAB TCF v2.2
Le module TCF est désactivé par défaut, et il exige un identifiant que le plugin ne peut pas fournir. Lisez la section des limites avant de l'activer.
Activation
| Réglage | Défaut | Bornes |
|---|---|---|
tcf_enabled | false | booléen |
tcf_cmp_id | 0 | 0 à 4095 |
tcf_publisher_country | FR | code ISO à 2 lettres |
tcf_publisher_purposes_li | [] | liste de finalités |
tcf_special_features | [] | liste de fonctionnalités spéciales |
Le plafond de 4095 n'est pas arbitraire : le champ CmpId occupe 12 bits dans la chaîne TCF.
Trois cas où le module refuse d'émettre quoi que ce soit
- Aucun CMP ID (
tcf_cmp_id < 1) : pas de__tcfapi, pas de chaîne TC, pas de cookieeuconsent-v2, pas de route REST. Un avis d'administration l'explique. Le CMP ID doit vous être attribué par IAB Europe ; le plugin n'en fournit pas. Émettre une chaîne portant un CmpId 0 serait pire que ne rien émettre. - CMP ID supérieur à 4095 : même refus, avec un avis dédié. Une valeur tronquée sur 12 bits désignerait un autre CMP — une usurpation d'identité.
- Aucune Global Vendor List en cache : le stub est imprimé mais l'API répond
cmpStatus: 'error'avec une chaîne vide, plutôt que d'inventer un numéro de version de liste.
Un diagnostic est écrit dans une option (disabled, missing_cmp_id, no_gvl, active),
et uniquement en contexte d'administration ou de cron, jamais sur une page publique.
La Global Vendor List
- Source :
https://vendor-list.consensu.org/v3/vendor-list.json. - Jamais téléchargée pendant le rendu d'une page. Un cron quotidien s'en charge, avec une première exécution cinq minutes après l'activation ; en administration avec un cache froid, une récupération unique est mise en file.
- Requête : 5 secondes de délai maximum, 2 redirections, réponse plafonnée à 4 Mo. Un corps qui atteint le plafond est considéré comme tronqué et rejeté.
- Seuls les champs utiles sont conservés par vendeur ; les piles sont vidées. Si la sérialisation reste trop grosse, deux paliers de troncature s'appliquent : d'abord les libellés et les URLs, puis la réduction aux seules finalités.
- Conservation : une semaine dans un transient, avec stale-if-error — en cas d'échec, la dernière copie valide est conservée.
- Route publique :
GET /owc/v1/tcf/gvl, limitée à 10 requêtes par heure. Elle sert la copie en cache avec unETaget unCache-Control: public, max-age=86400, et gère les requêtes conditionnelles. Si rien n'est en cache, elle répond 503 avec unRetry-After: 300— jamais une liste synthétique. Cette route ne déclenche jamais de requête sortante.
Où la chaîne TC est calculée
Dans le navigateur, pas en PHP. Un stub entièrement statique est imprimé dans <head>
en priorité 0 : iframe locateur, implémentation de window.__tcfapi, relais postMessage,
et un encodeur de segment Core en JavaScript. La configuration publiée ne contient aucune
donnée de visiteur — c'est ce qui permet de rester compatible avec un cache de page.
Filtre : owc_tcf_stub_config.
Un miroir PHP de l'encodeur existe mais n'a aucun appelant dans le plugin : il est réservé aux intégrations et aux tests, avec l'avertissement de ne jamais l'imprimer dans du HTML cachable.
Ce qui est encodé
Segment Core uniquement, en base64url sans remplissage. La section vendeurs est encodée soit en champ de bits, soit en plages : les deux tailles sont mesurées et la plus petite gagne. L'index vendeurs transmis au navigateur utilise un format compact maison, avec un plafond dur au-delà duquel l'index est vidé (les consentements vendeurs sont alors perdus, la version de liste est conservée).
Correspondance catégories → finalités TCF
| Catégorie | Finalités |
|---|---|
necessary | aucune — hors périmètre TCF, ce qui garantit que « Tout refuser » ne peut pas produire un consentement pour la finalité 1 |
functional | 1 |
analytics | 1, 8, 9, 10 |
marketing | 1, 2, 3, 4, 7 |
preferences | 1, 5, 6, 11 |
social | 1 |
Filtre : owc_tcf_purpose_map.
Intérêt légitime de l'éditeur : seules les finalités 2, 7, 8, 9, 10 et 11 sont
retenues, parce que TCF v2.2 interdit l'intérêt légitime pour les finalités 1, 3, 4, 5 et 6.
Toute autre valeur de tcf_publisher_purposes_li est silencieusement écartée.
Un vendeur est consenti dès qu'au moins une des finalités qu'il déclare sous base « consentement » est accordée.
Les fonctionnalités spéciales
Deux entrées seulement sont supportées : 1 — usage de données de géolocalisation précises
et 2 — analyse active des caractéristiques de l'appareil. Déclarez celles que vous
utilisez dans tcf_special_features ; la liste est ramenée à ce catalogue.
Elles apparaissent comme de vraies cases à cocher dans le panneau de préférences. « Tout accepter » ne les coche pas : elles exigent un opt-in explicite propre. « Tout refuser » et « Do Not Sell » les décochent. Elles vivent dans la chaîne TC elle-même, et sont réhydratées à la réouverture du panneau depuis l'API TCF.
La couche TCF dans le panneau
Elle n'est rendue que si le module est actif, c'est-à-dire tcf_enabled et un CMP ID
utilisable. Deux blocs :
- Finalités : uniquement celles atteignables via une catégorie non forcée et nommées dans le catalogue de la liste vendeurs. Un interrupteur qui reviendrait tout seul serait pire que pas d'interrupteur. Basculer une finalité écrit dans toutes les catégories qui la déclarent, puis l'affichage est re-dérivé.
- Fonctionnalités spéciales déclarées.
Rien n'est encodé dans la chaîne qui n'ait pas été affiché.
L'API JavaScript
window.__tcfapi( command, version, callback, parameter );
// commandes : ping, getTCData, getInAppTCData,
// addEventListener, removeEventListener, getVendorList
window.__owcTcfUpdateState( tcData ); // remplace les données et notifie les auditeurs
window.__owcTcfRefresh(); // recalcule et émet 'useractioncomplete'
window.__owcTcfUiShown(); window.__owcTcfUiHidden();
window.__owcTcfSetSpecialFeatures( [ 1, 2 ] );
Versions acceptées : absente, null, 2, '2', 2.2, '2.2'. Toute autre valeur donne
callback(null, false).
gdprApplies
Décidé côté client. Si la détection régionale est désactivée, la valeur est true.
Sinon, le pays du visiteur est comparé à la liste des 31 codes où le RGPD s'applique au sens
TCF (27 pays de l'UE, plus IS, LI, NO, GB et CH). Échec fermé : un pays inconnu donne
true.
Le cookie euconsent-v2
Écrit uniquement côté client, et uniquement quand le module est prêt et que le
visiteur a agi. Sinon le cookie est effacé. Sa durée est celle du consentement, plafonnée
à 13 mois (recommandation IAB / CNIL). SameSite=Lax, Secure en HTTPS.
Limites du module TCF — à lire absolument
- Ce n'est pas un CMP enregistré auprès d'IAB Europe. Il exige votre propre CMP ID, et même avec lui, la politique TCF impose à un CMP enregistré de présenter des choix au niveau finalité et au niveau vendeur. Ici, les interrupteurs de finalités suivent les catégories et il n'y a aucun choix au niveau vendeur : les vendeurs sont fondés à rejeter ce signal. L'écran d'administration le dit et parle de « mode compatible CMP (non officiel) », accepté par la plupart des SSP en développement mais rejeté en production réelle dans l'EEE. Si un revenu publicitaire sous TCF compte pour vous, utilisez un CMP certifié.
- Segment Core uniquement : pas de segment
disclosedVendors,allowedVendorsnipublisherTC. - Aucune restriction éditeur n'est émise : le compteur correspondant vaut toujours 0 et l'objet des restrictions est vide.
purposeOneTreatmentetuseNonStandardTextsvalent toujoursfalse,isServiceSpecificvaut toujourstrue— il n'y a pas de portée globale — et l'écran de consentement vaut toujours 0.getVendorListignore le paramètre de version et renvoie toujours la liste en cache.- Les consentements éditeur recopient les finalités générales : pas de finalité personnalisée.
- Deux fonctionnalités spéciales seulement.
- Le module exige un cron fonctionnel et un HTTPS sortant. Sans liste en cache, l'API
répond
cmpStatus: 'error'et la route publique renvoie 503.
Global Privacy Control
GPC est un signal envoyé par le navigateur — l'en-tête Sec-GPC: 1 et la propriété
navigator.globalPrivacyControl. Le plugin l'honore par défaut (gpc_honor), mais son
traitement dépend du régime juridique, et c'est le point important.
Sous les profils américains : contraignant
Le signal est traité comme un opt-out universel contraignant sous les profils ccpa et
us_generic. L'Australie est explicitement exclue : régime opt-out, mais qui ne
reconnaît pas GPC.
L'écriture serveur ne se produit qu'après une chaîne complète de vérifications, dans cet ordre :
gpc_honorest activé ;- le signal est présent — l'en-tête
Sec-GPC, avec un repli surX-Sec-GPCparce que certains proxys et CDN renomment l'en-tête ; seule la valeur exacte1compte ; - le profil effectif est
ccpaouus_generic— sinon rien n'est écrit ; - c'est une simple consultation de page : pas d'administration, pas de cron, pas d'AJAX,
pas de REST, pas de XML-RPC, pas de WP-CLI, et la méthode est
GET; - les en-têtes n'ont pas encore été envoyés — un cookie qu'on ne peut pas poser est une décision qu'on ré-enregistrerait à chaque requête ;
- le marqueur de session
owc_gpcest absent : une fois par session de navigation ; - le visiteur ne ressemble pas à un robot (user-agent vide, ou contenant
bot,crawl,spider,slurp,monitor,uptime,pingdom,headless,preview,curl/,wget,python-,java/,go-http,okhttp,httpclient,libwww,facebookexternalhit) ; - le choix déjà stocké ne satisfait pas déjà le signal — sinon on marque seulement le navigateur ;
- la limite de débit partagée est respectée : 30 par heure et par IP ;
- le navigateur est marqué avant l'écriture, pour qu'un échec ne devienne pas une boucle de rejeu.
L'événement est enregistré comme gpc_opt_out avec la source auto. L'URL de page est
reconstruite à partir de l'URL du site et du chemin demandé — jamais de l'en-tête Host,
jamais du Referer.
Portée : toutes les catégories optionnelles passent à false ; les catégories forcées
restent accordées. C'est une lecture large de la notion de vente ou de partage.
Transparence : si GPC écrase un choix explicitement enregistré, un cookie de cinq minutes déclenche côté client un avis en bas d'écran qui l'explique, avec un bouton « gérer mes préférences » qui ouvre le panneau, et un bouton de fermeture.
Sous les profils RGPD et assimilés : simple indice
Hors des régimes opt-out américains, le signal est traité comme une indication, jamais comme un consentement :
- l'attribut
data-owc-gpc="1"est posé sur<html>; - Consent Mode passe
ad_storage,analytics_storage,ad_user_data,ad_personalizationetpersonalization_storageàdenied; - toutes les catégories optionnelles sont mises à
falseen mémoire seulement ; givenreste faux, la bannière reste affichée, et rien n'est enregistré.
Un drapeau interne empêche le runtime d'annoncer au visiteur qu'il est « déjà opt-out » alors que ni le cookie ni le registre ne le disent. La justification est écrite dans le code : GPC n'est pas un signal légalement reconnu sous le RGPD et ePrivacy, la nLPD suisse, la LGPD, POPIA, PIPL, DPDP ni la Loi 25.
Les deux cookies posés par ce module
| Cookie | Durée | Rôle |
|---|---|---|
owc_gpc | session | Marque que le signal a déjà été traité pour ce navigateur |
owc_gpc_notice | 300 s | Déclenche l'avis « GPC a remplacé votre choix » |
Les deux sont sur path=/, SameSite=Lax, Secure en HTTPS, et lisibles par script.
Point d'extension
add_action( 'owc_gpc_honored', function ( array $categories ) {} );
La détection régionale
La détection régionale applique à chaque visiteur le profil de sa juridiction plutôt que le profil configuré du site. Elle est désactivée par défaut.
| Réglage | Défaut |
|---|---|
geo_enabled | false |
geo_default_profile | gdpr |
geo_mmdb_enabled | false |
La cascade de détection
Premier succès gagnant :
- Cloudflare (
CF-IPCountry) — seulement si le site a déclaré être derrière Cloudflare ; - AWS CloudFront (
CloudFront-Viewer-Country) — même condition ; GEOIP_COUNTRY_CODE, la variable écrite par le serveur lui-même (mod_geoip, ngx_http_geoip), donc non forgeable par le client ; puis, si un proxy est déclaré, les variantes en en-tête HTTP ;- Base MaxMind locale, si
geo_mmdb_enabledest activé ; - Rien.
Sans déclaration explicite dans wp-config.php, aucun en-tête HTTP de pays n'est cru :
ils sont forgeables par le client. Voir Installation pour les constantes.
Le code de pays est validé contre la liste blanche ISO-3166-1 alpha-2 officiellement
assignée (environ 249 codes énumérés en dur). Les codes utilisateur ou réservés — XX,
ZZ, le T1 des sorties Tor — sont rejetés.
L'échec fermé
Sans pays détecté, le plugin lit geo_default_profile mais rétrograde vers gdpr tout
profil opt-out. Un geo_default_profile réglé sur ccpa, us_generic ou au est donc
ignoré pour les visiteurs non détectés — et l'écran d'administration ne propose que des
profils opt-in dans ce sélecteur, plutôt que d'offrir un choix silencieusement ignoré.
Le motif est direct : sans ce garde-fou, un site configuré en anglais américain placerait tout visiteur de l'EEE en régime opt-out, avec des traceurs signalés « accordés » sans aucun consentement.
Aucun repli sur la locale du site : la langue d'un site ne dit rien du lieu de son visiteur.
Le cookie owc_geo
- Durée 24 heures,
path=/,SameSite=Lax,Secureen HTTPS, lisible par script — le bootstrap front en a besoin. - Contenu : le pays, le profil, un horodatage et un code d'authentification. Jamais d'adresse IP.
- À la lecture : taille bornée, pays validé contre la liste ISO, horodatage dans la fenêtre, code vérifié en temps constant. Le profil est toujours recalculé côté serveur à partir du pays : un visiteur ne peut pas choisir son régime juridique.
- Seule une détection réelle est mise en cache. Le repli « échec fermé » n'est jamais mémorisé.
- Écrit uniquement sur une requête front-end, et seulement si la détection est activée.
La correspondance pays → profil
| Pays | Profil |
|---|---|
| Les 30 pays de l'EEE | gdpr |
| GB, JE, GG, IM | uk_pecr |
| CH | ch_nfadp |
| BR | lgpd |
| ZA | popia |
| CN, HK | pipl |
| IN | dpdp |
| AU, NZ | au |
| CA | quebec |
| US | ccpa |
| Tout le reste | Le repli, rétrogradé en opt-in |
Hong Kong relève en réalité de sa propre ordonnance locale ; il est traité comme PIPL par
sévérité. La Nouvelle-Zélande est traitée comme l'Australie. Le Canada entier reçoit
quebec — c'est le régime le plus strict qui gagne. Les États-Unis reçoivent ccpa, qui
tient lieu de profil pour les autres États.
La contrainte de cache — ce que la détection ne fait pas
Le profil géolocalisé ne change pas le HTML rendu. La réécriture du profil ne s'applique
qu'aux deux routes REST visiteur, /owc/v1/consent et /owc/v1/state. Le rendu de page,
les routes d'administration, wp-admin, le cron et WP-CLI gardent le profil configuré.
Toute réponse ayant consommé la réécriture est marquée non cachable.
C'est le runtime front qui choisit le régime, côté client, depuis le cookie. Conséquence assumée : le régime appliqué à un visiteur dépend de JavaScript, et un visiteur dont la toute première page vient du cache est traité sous le profil configuré du site tant que le cookie n'existe pas.
Le lecteur MaxMind embarqué
Le plugin embarque son propre lecteur MMDB, écrit en PHP pur, sans dépendance Composer — un plugin publié sur WordPress.org ne peut pas embarquer le paquet officiel.
- Lecture seule, pays uniquement. Pas de ville, pas d'ASN.
- Entrées/sorties bornées : lecture par petits blocs, jamais de chargement en mémoire d'un fichier de plusieurs mégaoctets.
- Ne lève jamais d'exception : fichier manquant, illisible, tronqué, corrompu ou
hostile donne
null, et la détection retombe sur son profil strict. - Garde-fous internes sur la profondeur de parcours, le budget de décodage et la taille de charge utile. Les métadonnées sont mises en cache dans un transient dont la clé intègre la taille et la date du fichier : remplacer votre téléchargement mensuel invalide le cache tout seul.
registered_countryetrepresented_countrysont délibérément ignorés. C'est la forme documentée des plages proxy anonymes, VPN et satellite, où le pays d'enregistrement désigne le lieu d'enregistrement du bloc par le fournisseur, pas le visiteur. Les utiliser placerait un visiteur de l'EEE derrière un VPN américain en régime opt-out.- Si une autre extension charge déjà un lecteur GeoIP2, il est utilisé en repli après le lecteur maison.
Où mettre le fichier : wp-content/uploads/ow-geoip/GeoLite2-Country.mmdb. Le chemin
est filtrable par owc_geo_mmdb_path, avec validation contre la traversée de répertoires.
Limite importante : le plugin ne télécharge pas la base et n'offre aucun écran de téléversement. Vous devez obtenir le fichier chez MaxMind, le déposer vous-même et le mettre à jour vous-même. En contrepartie, rien n'est envoyé à MaxMind : la recherche est intégralement locale, l'adresse IP sert au seul point de recherche et est explicitement détruite ensuite — jamais journalisée, jamais stockée, jamais écrite dans le cookie.
Limites de la détection régionale
- Désactivée par défaut, et la variante MaxMind exige un fichier que vous fournissez.
- Les en-têtes de pays sont ignorés tant que vous n'avez pas déclaré votre proxy.
- Pas de granularité état par état aux États-Unis, ni province par province au Canada.
- Le régime visiteur dépend de JavaScript et du cookie de 24 heures.
- Le profil
us_genericn'est jamais attribué automatiquement.
L'intégration OW Forms
Périmètre exact : cette intégration cible le plugin frère OW Forms, et rien d'autre. Il n'y a aucune intégration Contact Form 7, Gravity Forms ou WPForms.
Réglage : forms_integration_enabled, activé par défaut, mais l'intégration ne fait
rien si OW Forms n'est pas actif.
Ce qu'elle apporte
1. La liaison consentement ↔ soumission. À chaque soumission stockée, une ligne est écrite dans une table dédiée à OW Consent — le schéma d'OW Forms n'est jamais touché — contenant :
- le jeton pseudonyme du visiteur (32 hexadécimaux, jamais l'adresse e-mail) ;
- l'identifiant de l'entrée de registre la plus récente pour ce jeton ;
- les catégories accordées et le profil de conformité en vigueur ;
- l'empreinte complète des documents publiés à cet instant ;
- l'horodatage du consentement ;
- l'état de la case RGPD du formulaire et le libellé exact qu'elle portait.
Ce dernier point est le plus utile en pratique : vous pouvez démontrer non seulement que la case était cochée, mais ce qui était écrit à côté.
2. La cascade d'effacement. Une demande d'effacement vérifiée délègue la suppression à la routine d'OW Forms — qui supprime aussi les fichiers téléversés — puis supprime les lignes de liaison. L'intégration détecte si OW Forms a déjà branché son propre écouteur sur la même action, auquel cas elle se contente d'élaguer ses liaisons.
3. Les outils de confidentialité de WordPress. Un exportateur et un effaceur sont enregistrés, de sorte que Outils → Exporter / Effacer les données personnelles couvre les soumissions OW Forms — et le portail de droits d'OW Consent aussi, puisqu'il parcourt les mêmes crochets. L'export reprend les libellés réels des champs et ajoute les champs de consentement. Toute valeur exportée est neutralisée contre l'injection de formules de tableur et tronquée.
4. La rétention. Sur le cron quotidien, les liaisons plus anciennes que
retention_form_data_days (défaut 1095 jours, bornes 1 à 3650) sont supprimées par lots
de 500, avec un balayage des orphelines. Seules les lignes d'OW Consent sont touchées :
la rétention des soumissions elles-mêmes appartient à OW Forms.
Limites
- Aucun événement de soumission n'est écrit dans le registre chaîné. La ligne de liaison référence une entrée existante, elle n'en crée pas — la chaîne ne doit pas être écrite depuis l'extérieur.
- Impossible de lier une soumission si OW Forms est configuré pour ne pas stocker les soumissions : il n'y a aucune ligne à laquelle s'accrocher.
- Aucune récupération rétroactive : la liaison commence à la première soumission après la mise à jour.
- Le rapprochement se fait sur l'empreinte salée de l'adresse calculée par OW Forms, avec repli sur l'adresse en clair qu'OW Forms conserve aussi. Ce n'est pas un schéma à divulgation nulle : l'adresse reste en clair chez OW Forms, parce qu'il faut pouvoir répondre à la personne.
- L'intégration ne modifie jamais le plugin OW Forms, ses tables ni ses options.
- Si OW Forms est actif mais trop ancien pour exposer sa routine d'effacement, l'effaceur répond explicitement que les soumissions n'ont pas été touchées.
Le bouton flottant
Un petit bouton persistant permet de rouvrir le panneau de préférences à tout moment. C'est l'exigence de l'article 7(3) — le retrait doit rester aussi simple que le consentement — et de la délibération CNIL 2020-091, qui demande un mécanisme accessible depuis chaque page.
Conditions d'existence
Il exige les deux réglages : floating_button_enabled et banner_enabled. Le
panneau de préférences et l'API JavaScript vivent dans le runtime de la bannière ; sans
elle, le bouton serait un contrôle qui ne fait rien.
Il sort aussi en administration, sur un flux, sur robots.txt, et si wp_head n'a pas été
déclenché.
Options
| Réglage | Valeurs | Défaut |
|---|---|---|
floating_button_enabled | booléen | true |
floating_button_position | bottom-left, bottom-right, top-left, top-right | bottom-left |
floating_button_style | pill, icon | pill |
floating_button_label | texte libre | Cookies |
Le bouton n'a pas de réglage de thème propre : il reprend banner_style, pour que les
deux surfaces suivent la même palette.
Comportement et accessibilité
C'est un vrai <button>, donc accessible au clavier. Il porte aria-haspopup="dialog" et
un aria-label traduisible mais non paramétrable (« Manage my cookie choices ») : seul
le libellé visible se règle. En mode icon, le libellé est masqué visuellement mais reste
lu par les lecteurs d'écran.
Il est rendu avec l'attribut hidden ; c'est le JavaScript qui décide de l'afficher. Il est
visible dans tous les états sauf quand la bannière ou le panneau est déjà à l'écran — y
compris immédiatement après un choix sur la page courante.
Un clic ouvre le panneau de préférences. Si l'API de la bannière n'existe pas, le bouton
retombe sur le fragment #owc-preferences, en re-déclenchant l'événement à la main si c'est
déjà le fragment courant.
z-index 99990, masqué à l'impression, prise en charge du mode contrastes forcés et de
prefers-reduced-motion, décalage sous la barre d'administration en position haute.
Shortcodes
Le plugin déclare deux shortcodes, et deux seulement. Ni la bannière ni le bouton flottant n'en ont : ils s'affichent partout ou nulle part, pilotés par leurs réglages.
[owc_dsar_form] — le portail de droits
[owc_dsar_form]
[owc_dsar_form types="access,erasure,portability" title="Mes droits" submit_label="Envoyer"]
| Attribut | Défaut |
|---|---|
types | access,rectification,erasure,portability,restrict,object,optout |
title | « Exercer mes droits sur mes données personnelles » |
submit_label | « Envoyer ma demande » |
Les types demandés sont intersectés avec ceux que le serveur accepte. Le type withdraw
n'est jamais proposé par le formulaire ; voir Le portail de droits.
[owc_dnsmpi] — l'opt-out CCPA
[owc_dnsmpi]
[owc_dnsmpi label="Ne pas vendre ni partager mes informations personnelles" class="lien-pied"]
| Attribut | Défaut |
|---|---|
label | « Do Not Sell or Share My Personal Information » |
class | owc-dnsmpi |
Un clic exécute l'opt-out ; voir L'opt-out CCPA. Si vous laissez
ccpa_inject_footer activé, le lien est déjà injecté en pied de page et ce shortcode n'est
utile que pour le placer ailleurs.
Rouvrir le panneau depuis un menu ou un lien
Il n'y a pas de shortcode pour cela, parce qu'un attribut de classe suffit :
<a href="#owc-preferences" class="owc-open-preferences">Gérer mes cookies</a>
<button type="button" data-owc-open>Mes préférences</button>
<a href="#owc-dnsmpi" data-owc-dnsmpi="1">Ne pas vendre mes informations</a>
C'est la méthode recommandée pour ajouter une entrée « Cookies » à votre menu de pied de page.
API REST
Toutes les routes vivent sous le namespace owc/v1, à la racine REST habituelle
(https://exemple.com/wp-json/owc/v1/…). Treize routes au total : douze enregistrées par le
module REST, plus la route de la liste vendeurs enregistrée par le module TCF.
Routes publiques
Aucune authentification. Leur sécurité repose sur le contrôle d'origine, un jeton propre au plugin et les limites de débit décrites plus bas.
| Méthode | Chemin | Paramètres | Rôle |
|---|---|---|---|
GET | /nonce | action (défaut wp_rest) | Renvoie un jeton frais. Réponse : {nonce, action, header: "X-OWC-Nonce", ttl: 43200}, avec no-store et Vary: Cookie |
POST | /consent | event (défaut save_preferences), source (défaut banner), categories (requis), page_url (défaut ''), cookie_unreadable (booléen, défaut false) | Enregistre un choix, pose le cookie et écrit la ligne de registre |
GET | /state | — | L'état du seul appelant : {given, categories, profile, at}. Jamais le jeton visiteur, jamais l'IP, jamais l'URL de page. no-store + Vary: Cookie |
GET | /tcf/gvl | — | Sert la Global Vendor List en cache. ETag, Cache-Control: public, max-age=86400, 304 sur requête conditionnelle, 503 + Retry-After: 300 si rien n'est en cache |
POST | /dsar | type (requis), email (requis, format e-mail), message (défaut '', 2000 caractères max) | Dépose une demande d'exercice de droits. Réponse {ok, mail_sent, message} — jamais l'identifiant |
Valeurs acceptées :
event:accept_all,reject_all,save_preferences,withdraw,auto,gpc_opt_outsource:banner,preferences,footer_link,api,autotype(DSAR) :access,rectification,erasure,portability,restrict,object,optout,withdrawcategories: un objet, 32 entrées maximum, valeurs scalaires uniquementaction(nonce) :wp_restuniquement
Routes d'administration
Toutes exigent la capacité manage_options.
| Méthode | Chemin | Paramètres | Rôle |
|---|---|---|---|
GET | /ledger | page (≥1, défaut 1), per_page (1–200, défaut 50), visitor_token (32 hex), from, to | Lit le registre. Les filtres réellement appliqués sont renvoyés dans la réponse |
GET | /ledger/verify | — | Vérifie la chaîne de bout en bout |
GET | /settings | — | Les réglages effectifs |
POST | /settings | corps JSON libre | Écrit les réglages. Réponse {ok, updated, rejected, settings} |
POST | /scanner/run | — | Lance un balayage |
POST | /scanner/ingest | corps JSON {page, findings[]} | Réception des observations de la sonde |
GET | /scanner/findings | page, per_page (1–200, défaut 50), filter (slug de catégorie ou unknown) | Liste les trouvailles |
POST | /policies/generate | type (requis), publish (booléen, défaut false) | Génère un document |
GET | /policies/preview | type (requis) | Prévisualise un document |
/scanner/ingest exige en plus un nonce wp_rest valide, dans l'en-tête X-WP-Nonce
ou en paramètre _wpnonce — parce que sendBeacon ne peut pas poser d'en-tête.
/settings en écriture n'accepte que les clés déclarées comme réglages réels : un filtre
peut injecter des clés synthétiques qui n'en sont pas. Chaque valeur est assainie
individuellement. Aucune clé connue donne un 400.
/ledger re-teste la capacité dans le gestionnaire, en défense en profondeur contre un
permission_callback filtré ailleurs.
Le modèle de sécurité des écritures publiques
Le constat de départ est simple : un nonce ne peut pas vivre dans du HTML cachable.
/consent et /dsar ont donc un contrôle d'accès permissif, protégé par quatre couches
dans un ordre qui compte.
- Contrôle d'origine même-hôte. Gratuit, déterministe, ne consomme aucun budget.
- Jeton propre au plugin dans l'en-tête
X-OWC-Nonce— c'est une preuve, jamais un veto. Le choix de ne pas utiliserX-WP-Nonceest délibéré : le cœur de WordPress intercepte cet en-tête avant tout contrôle d'accès de route et refuse la requête entière s'il ne le valide pas. Un jeton périmé servi par un cache tuerait donc une écriture que l'endpoint aurait acceptée sans jeton du tout. - Échec fermé : ni origine, ni jeton vérifié donnent un 403
owc_missing_origin. - Limite de débit en dernier. Si elle passait en premier, une mauvaise configuration consommerait deux jetons par clic et finirait en 429, ce qui masquerait l'erreur de configuration derrière une limite de débit.
Hôtes acceptés : ceux que WordPress déclare (URL du site, URL WordPress, racine REST)
avec leur jumeau www./apex, plus l'hôte de l'en-tête Host de la requête courante et
son jumeau. « Même origine » veut dire « l'origine correspond à l'hôte auquel le navigateur
s'est connecté », pas « correspond à ce que WordPress a en base » — sinon tout site joint
par un domaine de prévisualisation, un alias, un nom de préproduction, un domaine mappé en
multisite ou derrière un proxy qui réécrit Host serait refusé en silence, sans possibilité
d'auto-réparation. Filtre : owc_allowed_request_hosts.
CORS est laissé au cœur de WordPress, délibérément.
/nonce refuse explicitement la forme JSONP : sans ce refus, une page tierce pourrait
moissonner le jeton vivant d'un administrateur de passage par un simple <script src>, non
soumis au contrôle d'origine.
Les limites de débit
| Compartiment | Budget | Fenêtre | Clé |
|---|---|---|---|
nonce | 120 | 5 minutes | adresse IP |
consent | 30 | 5 minutes | adresse IP |
dsar | 3 | 1 heure | adresse IP |
dsar_email | 3 | 24 heures | adresse e-mail visée |
dsar_global | 30 | 1 heure | tout le site |
gpc | 30 | 1 heure | adresse IP |
tcf_gvl | 10 | 1 heure | adresse IP |
- Fenêtre fixe, pas glissante : un compartiment qui reçoit du trafic finit toujours par expirer.
- La clé stockée est une empreinte salée : aucune adresse IP ni adresse e-mail en clair ne se trouve dans les options ou le cache objet.
- Normalisation d'IP : IPv4 conservée, IPv6 tronquée au /64 — le client contrôle chaque bit de l'identifiant d'interface, donc une clé complète permettrait de créer un budget neuf à chaque requête.
- Identité partagée : derrière un CDN ou un proxy non déclaré via
OWC_TRUSTED_PROXY, tous les visiteurs arrivent avec la même adresse. Le budget est alors fractionné par visiteur, avec un compteur élargi sur l'adresse partagée. C'est une limite plus grossière : déclarez votre proxy. - Filtre
owc_throttle_max( $max, $bucket, $window )— renvoyer0désactive la limite. - Le limiteur échoue ouvert si le cache objet est indisponible : mieux vaut ne pas limiter que d'empêcher quelqu'un d'exercer ses droits.
La route /consent en détail
headers_sent()est lu en tout premier. Si la sortie a déjà commencé, le cookie serveur ne peut pas être posé : la ligne est quand même écrite et la réponse reste 200, aveccookie_set: falseet un descripteurcookie(name,value,ttl,path,samesite,secure) que le client pose lui-même.- Les catégories que le site n'offre pas sont renvoyées dans
dropped_categories, pas ignorées en silence. page_urlest validée contre les hôtes du site, avec repli sur unReferervalidé de la même façon, sinon la chaîne vide. La clé est toujours présente, pour que le registre n'aille pas rechercher leRefererbrut.- Si le registre est activé et que l'écriture est refusée, le cookie est annulé et la
réponse est un 503
owc_ledger_write_refused. Rien n'est stocké, aucun traceur n'est libéré. cookie_unreadableest de la télémétrie pure : aucun contrôle de validation ne lui est appliqué, précisément pour qu'un champ de diagnostic ne puisse jamais refuser une écriture de consentement. Son seul effet est une note dans le journal des refus.
Réponse type : {ok, state, dropped_categories, logged, cookie_set[, cookie]}. Le champ
logged vaut null quand le registre est désactivé.
Codes d'erreur
| Code | HTTP | Signification |
|---|---|---|
owc_bad_param | 400 | Paramètre invalide |
owc_bad_categories | 400 | Objet categories malformé, trop grand ou non scalaire |
owc_bad_email | 400 | Adresse e-mail invalide |
owc_bad_dsar | 400 | Soumission DSAR malformée (piège à robots inclus) |
owc_dsar_not_attested | 400 | Attestation de l'art. 12.6 absente |
owc_no_settings | 400 | Aucune clé de réglage connue dans le corps |
owc_missing_origin | 403 | Ni origine ni jeton : échec fermé |
owc_bad_origin | 403 | L'origine déclarée n'est pas un hôte de ce site — la réponse liste jusqu'à dix hôtes acceptés |
owc_bad_nonce | 403 | Nonce invalide sur /scanner/ingest |
owc_jsonp_forbidden | 403 | Forme JSONP refusée sur /nonce |
owc_forbidden | 401/403 | Capacité insuffisante |
owc_dsar_disabled | 404 | Le portail de droits est désactivé côté serveur |
owc_no_template | 404 | Aucun gabarit pour ce type et ce profil |
owc_rate_limited | 429 | Limite de débit atteinte |
owc_consent_failed | 500 | Échec d'application du consentement |
owc_dsar_store_failed | 500 | Échec d'écriture de la demande |
owc_gen_failed | variable | Génération de document refusée (champs manquants, barrière de langue) |
owc_ledger_write_refused | 503 | Le registre a refusé d'écrire — rien n'est enregistré, rien n'est débloqué |
Le journal des refus
Les vingt derniers refus d'écriture sont conservés et affichés sur le tableau de bord — uniquement si le journal n'est pas vide. Deux natures y sont distinguées :
- une entrée avec un statut HTTP est un vrai refus : rien n'a été stocké et le visiteur a vu une erreur ;
- une entrée avec un statut 0 est un avertissement : le choix a bien été enregistré.
Le panneau affiche côte à côte l'hôte déclaré par l'origine et l'en-tête Host de la
requête. C'est exactement ce qui diffère quand vos visiteurs naviguent sur un hôte pendant
que WordPress est configuré avec un autre — la cause la plus fréquente d'un consentement
qui « ne s'enregistre pas ».
Limites de l'API
/consentet/dsaracceptent des écritures anonymes par conception ; la protection est l'origine plus la limite de débit, pas un nonce.- Le limiteur de débit échoue ouvert sans cache objet.
- Le cookie de consentement est host-only : un cookie posé pour l'apex est illisible
depuis une page
www., et aucun en-tête CORS ne peut corriger cela. Le remède est une URL d'écriture de même origine. /scanner/ingestn'accepte quemanage_options: la sonde ne peut pas venir d'un visiteur./settingsn'a pas de nonce propre : c'estmanage_optionsplus le contrôle de nonce par cookie du cœur.
Référence des réglages
Tous les réglages tiennent dans une seule option, owc_settings, chargée
automatiquement puisqu'elle est lue à chaque page front.
Lecture et écriture en PHP
$reglages = OWC_Core::settings(); // réglages effectifs (défauts + enregistrés + filtres)
$profil = OWC_Core::setting( 'compliance_profile' );
$brut = OWC_Core::stored_setting( 'compliance_profile' ); // ignore la réécriture par visiteur
OWC_Core::update_settings( array(
'consent_renewal_months' => 6,
'compliance_strict' => true,
) );
update_settings() ne fusionne que les clés soumises par-dessus l'existant, jamais
l'ensemble des défauts. C'est ce qui permet à la ligne d'option de rester vide tant que
vous n'avez rien personnalisé, et donc aux textes de suivre la langue du site.
Le résultat de settings() est mémoïsé, avec une clé fondée sur l'ensemble des rappels du
filtre owc_settings : un module qui enregistre son filtre après la première lecture
invalide le mémo au lieu d'être ignoré. Le mémo est vidé à chaque écriture de l'option.
Valeurs par défaut
[
// Bannière
'banner_enabled' => true,
'banner_position' => 'bottom-bar', // bottom-bar | bottom-card | center-modal | top-bar
'banner_style' => 'auto', // auto | light | dark
'banner_accept_all' => true,
'banner_reject_all' => true,
'banner_preferences' => true,
'banner_close_x' => false, // croix = refus implicite
'banner_show_logo' => true,
'text_title' => 'We use cookies',
'text_message' => 'We use cookies to personalise content, measure audience, '
. 'and provide social media features. You can accept or reject, '
. 'and change your choice at any time.',
'text_accept_all' => 'Accept all',
'text_reject_all' => 'Reject all',
'text_preferences' => 'Customize',
'text_save' => 'Save my choices',
// Bouton flottant
'floating_button_enabled' => true,
'floating_button_position' => 'bottom-left',
'floating_button_style' => 'pill',
'floating_button_label' => 'Cookies',
// Consentement
'consent_renewal_months' => 12, // 0 est converti en 13, pas en « jamais »
'consent_policy_hash_check' => true,
// Conformité
'compliance_profile' => 'gdpr',
'compliance_strict' => true,
'geo_enabled' => false,
'geo_default_profile' => 'gdpr',
'geo_mmdb_enabled' => false,
// IAB TCF v2.2
'tcf_enabled' => false,
'tcf_cmp_id' => 0,
'tcf_publisher_country' => 'FR',
'tcf_publisher_purposes_li' => [],
'tcf_special_features' => [],
// CCPA / GPC
'ccpa_inject_footer' => true,
'gpc_honor' => true,
// Google Consent Mode v2
'gcm_enabled' => true,
'gcm_ads_data_redaction' => true,
'gcm_url_passthrough' => true,
// Catégories
'cat_necessary_available' => true,
'cat_functional_available' => true,
'cat_analytics_available' => true,
'cat_marketing_available' => true,
'cat_preferences_available' => true,
'cat_social_available' => true,
'cat_necessary_label' => 'Necessary',
'cat_necessary_desc' => 'Strictly required for the site to function …',
'cat_functional_label' => 'Functional',
'cat_functional_desc' => 'Enhance the experience (chat, embedded videos, maps) …',
'cat_analytics_label' => 'Statistics',
'cat_analytics_desc' => 'Help us understand how you use the site (anonymously) …',
'cat_marketing_label' => 'Marketing',
'cat_marketing_desc' => 'Enable us to show you ads and content tailored …',
'cat_preferences_label' => 'Preferences',
'cat_preferences_desc' => 'Remember your interface choices (layout, saved filters).',
'cat_social_label' => 'Social & embeds',
'cat_social_desc' => 'Allow embedded social content (YouTube, Instagram, X) to load.',
// Registre
'ledger_enabled' => true,
'ledger_retention_days' => 1825, // 5 ans ; 0 = illimité
'ledger_hash_algo' => 'sha256',
// Scanner
'scanner_enabled' => false,
'scanner_frequency' => 'weekly',
'scanner_alert_email' => '',
'scanner_max_urls' => 25,
'scanner_timeout' => 8,
'scanner_probe_mode' => 'admins',
// Portail de droits
'dsar_enabled' => true,
'dsar_email' => '',
'dsar_response_days' => 30,
'dsar_notify_email' => '',
'dsar_token_ttl_days' => 7,
// Blocage
'blocker_unknown_script_policy' => 'allow',
'blocker_unknown_iframe_policy' => 'block',
'blocker_allowlist' => '', // un hôte par ligne
'blocker_block_resource_hints' => true,
// Intégration et cycle de vie
'forms_integration_enabled' => true,
'auto_footer_menu_inject' => false,
'delete_data_on_uninstall' => false,
// Identité légale
'legal_company_name' => '',
'legal_company_legal_form' => '',
'legal_company_capital' => '',
'legal_company_address' => '',
'legal_company_email' => '',
'legal_company_phone' => '',
'legal_country' => 'FR',
'legal_company_reg_number' => '',
'legal_company_reg_place' => '',
'legal_company_vat' => '',
'legal_publication_director' => '',
'legal_eu_representative' => '',
'legal_host_name' => '',
'legal_host_address' => '',
'legal_host_phone' => '',
'legal_host_url' => '',
'legal_profession_body' => '',
'legal_profession_title' => '',
'legal_profession_state' => '',
'legal_profession_rules' => '',
'legal_mediator_name' => '',
'legal_mediator_address' => '',
'legal_mediator_url' => '',
'legal_dpo_name' => '',
'legal_dpo_email' => '',
'legal_dpa_authority' => '', // vide à dessein : dérivée du pays + profil
'legal_transfer_countries' => '',
'legal_third_parties_note' => '',
'legal_automated_decisions' => false,
'legal_automated_decisions_desc' => '',
'legal_minor_age' => 15,
'legal_data_provision_note' => '',
'legal_currency' => 'EUR',
'legal_tax_label' => 'TTC',
'legal_business_type' => 'auto',
'retention_form_data_days' => 1095,
'retention_dsar_days' => 1095,
'policy_disclaimer' => true,
// Liens
'link_privacy_policy' => '',
'link_cookie_policy' => '',
'link_terms' => '',
'link_legal_notice' => '',
]
Listes fermées
Une valeur hors liste est ramenée au défaut.
| Réglage | Valeurs acceptées |
|---|---|
banner_position | bottom-bar, bottom-card, center-modal, top-bar |
banner_style | auto, light, dark |
floating_button_position | bottom-left, bottom-right, top-left, top-right |
floating_button_style | icon, pill |
compliance_profile, geo_default_profile | gdpr, ccpa, lgpd, popia, pipl, dpdp, quebec, uk_pecr, ch_nfadp, au, us_generic |
ledger_hash_algo | sha256, sha3-256 (intersectés avec ce que PHP supporte) |
scanner_frequency | hourly, twicedaily, daily, weekly |
scanner_probe_mode | admins, off |
legal_business_type | auto, vitrine, rental, ecommerce, services, saas, content |
blocker_unknown_script_policy, blocker_unknown_iframe_policy | allow, block |
Bornes des entiers
| Réglage | Bornes |
|---|---|
consent_renewal_months | 0 à 13 (0 est traité comme 13) |
dsar_response_days | 1 à 30 |
dsar_token_ttl_days | 1 à 90 |
ledger_retention_days | 0 à 3650 (0 = illimité) |
scanner_max_urls | 1 à 500 |
scanner_timeout | 1 à 60 |
legal_minor_age | 13 à 18 |
retention_form_data_days | 1 à 3650 |
retention_dsar_days | 1 à 3650 |
tcf_cmp_id | 0 à 4095 |
Assainissement
text_messageest le seul champ à HTML riche ; il accepte le HTML autorisé dans un article WordPress, et la bannière ne rend au final que<a href target rel>.- Champs multi-lignes :
legal_company_address,legal_eu_representative,legal_host_address,legal_mediator_address,legal_third_parties_note,legal_automated_decisions_desc,legal_data_provision_note,blocker_allowlist, et toute clé se terminant par_desc. - URLs : toute clé préfixée
link_ou suffixée_url, pluslegal_profession_rules. - Adresses e-mail : toute clé contenant
email. - Tout le reste : texte simple.
- Une valeur non scalaire soumise sur un réglage scalaire est ignorée, la clé n'est pas écrite.
Où se règle quoi
| Onglet | Réglages |
|---|---|
| Banner | banner_*, text_*, floating_button_*, consent_renewal_months, consent_policy_hash_check |
| Compliance | compliance_profile, compliance_strict, gcm_*, blocker_*, ccpa_inject_footer, gpc_honor, tcf_*, geo_*, auto_footer_menu_inject, forms_integration_enabled, delete_data_on_uninstall, link_* |
| Legal identity | legal_*, retention_form_data_days, retention_dsar_days, policy_disclaimer |
| Categories | cat_*_available, cat_*_label, cat_*_desc |
| Audit ledger | ledger_enabled, ledger_retention_days (l'algorithme est affiché, pas modifiable) |
| Scanner | scanner_* |
| DSAR requests | dsar_* |
Tables
| Table | Contenu |
|---|---|
{prefix}owc_ledger | Registre de consentement chaîné |
{prefix}owc_dsar | Demandes d'exercice de droits |
{prefix}owc_scanner | Trouvailles du scanner |
{prefix}owc_scripts | Catalogue de signatures du blocage |
{prefix}owc_form_links | Liaisons consentement ↔ soumission OW Forms |
Un assistant PHP owc_table( 'ledger' | 'dsar' | 'scanner' | 'scripts' ) recalcule le nom
à partir du préfixe courant : utilisez-le dans un switch_to_blog(), les constantes
étant figées pour la durée de la requête.
Autres options et transients
Options : owc_settings, owc_version, owc_pending_upgrade, owc_upgrade_lock,
owc_ledger_head, owc_ledger_trim, owc_ledger_key_source, owc_scanner_secret,
owc_scanner_lock, owc_scanner_queue, owc_scanner_status, owc_scanner_alerted,
owc_scanner_probe_status, owc_scanner_manual, owc_refusal_log, owc_tcf_status,
owc_tcf_gvl_version, owc_forms_db_version, plus les drapeaux de migration
(owc_catalog_sanitized_v2, owc_match_target_migrated_v1, owc_ledger_hmac_migrated_v1,
owc_inline_sig_labels_cleaned_v1, owc_scanner_secret_rotated_v1).
Transients : owc_catalog_<version>, owc_invalid_patterns, owc_gvl_cache,
owc_gvl_stub, owc_gvl_etag, owc_gvl_retry, owc_mmdb_<empreinte>,
owc_policies_hash, owc_site_analysis, owc_scanner_last_run,
owc_scanner_run_result, owc_scanner_pruned, owc_scanner_fail_alerted,
owc_ledger_verify_result, owc_ledger_write_error, owc_policy_gen_result,
owc_dsar_sla_alert, owc_dsar_admin_result, et les compteurs de limitation de débit.
Constantes reconnues dans wp-config.php
| Constante | Effet |
|---|---|
OWC_LEDGER_KEY | Clé HMAC du registre, et clé dérivée du cookie de détection régionale |
OWC_TRUSTED_PROXY / OWC_TRUSTED_PROXIES | Plages CIDR des proxys de confiance ; sans elles, les en-têtes d'IP client sont ignorés |
OWC_GEO_TRUSTED_HEADERS | cloudflare, cloudfront, proxy ou all |
OWC_BEHIND_CLOUDFLARE | Raccourci pour l'en-tête de pays Cloudflare |
OWC_BEHIND_CLOUDFRONT | Raccourci pour l'en-tête de pays CloudFront |
Les crochets développeur
Filtres
| Filtre | Rôle |
|---|---|
owc_settings | Réglages effectifs. La réponse doit dépendre de quelque chose de stable pour toute la requête |
owc_banner_texts | Textes de la bannière |
owc_bootstrap_config | Configuration statique imprimée dans <head> — jamais de donnée par visiteur |
owc_scripts_catalog | Lignes du catalogue avant validation |
owc_blocker_allowlist | Hôtes jamais bloqués |
owc_scanner_urls | URLs à balayer (re-restreintes à l'hôte du site ensuite) |
owc_throttle_max | Budget d'une limite de débit ; 0 la désactive |
owc_allowed_request_hosts | Hôtes acceptés pour une écriture publique |
owc_allow_headerless_write | Lève l'échec fermé sur les écritures sans origine (défaut false) |
owc_geo_mmdb_path | Chemin de la base MaxMind |
owc_tcf_purpose_map | Correspondance catégories → finalités TCF |
owc_tcf_stub_config | Configuration statique du stub TCF |
owc_policy_template | Corps brut d'un gabarit — désactive le verrou de langue |
owc_policy_vars | Variables d'un document |
owc_policy_html | HTML final d'un document |
owc_dsar_types | Types de demandes proposés et acceptés |
owc_dsar_form_notice | Mention d'information sous le formulaire |
owc_dsar_export_bundle | Contenu du bundle de portabilité |
owc_dsar_show_fulfilment_panel | Affichage du panneau d'exécution |
Actions
| Action | Signature |
|---|---|
owc_consent_updated | ( array $categories, string $event, string $source ) |
owc_consent_cookie_not_sent | ( array $cookie, string $event ) |
owc_ledger_lock_failed | ( string $event, string $token ) |
owc_ledger_write_failed | ( $error, string $event, string $token ) |
owc_ledger_anchor | ( array $anchor ) |
owc_ledger_pruned | ( int $deleted, int $days ) |
owc_catalog_updated | — |
owc_gpc_honored | ( array $categories ) |
owc_tcf_inactive | ( string $reason ) |
owc_dsar_submitted | ( string $type, string $email, string $message ) |
owc_dsar_verified | ( int $id, array $row ) |
owc_dsar_fulfilled | ( int $id, string $what, $trace ) |
owc_dsar_mail_failed | ( int $id, string $email, string $kind ) |
Crons : owc_daily_maintenance, owc_ledger_retention, owc_scanner_run,
owc_scanner_run_batch, owc_tcf_refresh_gvl, owc_run_upgrade.
Internationalisation
Le domaine de traduction est ow-consent, le chemin /languages. La langue source est
l'anglais. Le paquet livre le gabarit ow-consent.pot et une traduction française complète.
Rappel du mécanisme décrit plus haut : tant qu'un libellé ou un texte de bannière n'est pas personnalisé dans les réglages, il suit la langue du site. Dès que vous saisissez votre propre valeur, elle est servie telle quelle, quelle que soit la langue du visiteur.
Les corps des documents légaux ne passent pas par ce mécanisme : leur langue suit la juridiction, jamais la locale de l'administrateur.
Dépannage
La bannière ne s'affiche pas
Parcourez la liste dans l'ordre :
banner_enabledest-il activé ? Le badge en haut de l'écran d'administration le dit.- Votre thème appelle-t-il
wp_head()etwp_footer()? Sans eux, la bannière refuse de s'imprimer plutôt que de produire un balisage inerte. - Le visiteur a-t-il déjà fait un choix ? La bannière ne s'affiche que sur
<html data-owc="none">. Testez en navigation privée ou avecwindow.OWCBanner.reset(). - Le visiteur a-t-il JavaScript ? Sans lui, la bannière reste masquée — délibérément —
et le bloc
<noscript>prend le relais. - Un cache de page sert-il une version antérieure à l'activation ? Videz-le.
La bannière revient à chaque page alors que le choix a été enregistré
Trois causes, par ordre de fréquence.
- L'hôte parcouru diffère de l'hôte configuré dans WordPress (
www.contre apex, alias, domaine de préproduction). Le navigateur refuse alors de stocker le cookie pendant que WordPress répond 200. Ouvrez le tableau de bord : le journal des refus affiche côte à côte l'origine et l'en-têteHost. Le remède est de servir le site sur un seul hôte canonique. consent_policy_hash_checkest activé et vos documents ont changé — c'est le comportement voulu, le visiteur est re-sollicité après une modification de politique.- Vous êtes sur une version antérieure à 1.4.3. Le cookie était alors encodé deux fois et le navigateur n'arrivait pas à le relire, alors que le serveur, lui, le lisait très bien. Mettez à jour : les cookies écrits avant la correction restent lisibles.
« Votre choix n'a pas pu être enregistré » — erreur refused ou 403
Regardez data-owc-code sur l'élément de message, ou le journal des refus du tableau de
bord.
owc_bad_origin: l'origine déclarée n'est pas un hôte reconnu. La réponse liste les hôtes acceptés. Si votre site est légitimement joint par plusieurs noms, ajoutez-les avec le filtreowc_allowed_request_hosts.owc_missing_origin: ni origine, ni jeton. C'est typiquement une extension de confidentialité qui supprime les en-têtes, ou un proxy qui les réécrit.owc_bad_nonce: ne concerne que la sonde du scanner, pas l'écriture de consentement — le nonce n'est jamais un veto sur/consent.
Erreur ratelimit — 429
Les limites sont volontairement basses sur les écritures publiques. Deux causes :
- Un CDN ou un proxy non déclaré : tous vos visiteurs arrivent avec la même adresse et
partagent le même budget. Déclarez-le avec
OWC_TRUSTED_PROXYdanswp-config.php. - Un test automatisé qui envoie plus de 30 écritures en 5 minutes depuis la même adresse.
Un 503 owc_ledger_write_refused
Le registre a refusé d'écrire, donc rien n'a été enregistré et aucun traceur n'a été libéré. C'est le comportement voulu : un consentement qu'on ne peut pas prouver ne doit pas être revendiqué.
- Vérifiez que la table
{prefix}owc_ledgerexiste. L'onglet Audit ledger affiche un message lisible « pas encore installé » plutôt qu'un écran blanc. - Vérifiez les droits d'écriture de l'utilisateur MySQL.
- En dernier recours, désactivez temporairement
ledger_enabledpour rétablir le service — en sachant que vous perdez la preuve pendant ce temps.
Des traceurs se chargent encore avant le consentement
compliance_strictest-il activé ? Sans lui, seuls les scripts mis en file par WordPress sont traités.- Le traceur est-il au catalogue ? Un script tiers inconnu est autorisé par défaut. Lancez un balayage, classez la trouvaille : cela écrit la règle de blocage.
- Est-il injecté par du JavaScript first-party ? La garde dynamique ne couvre que vingt hôtes. Ajoutez une règle de catalogue sur le domaine du traceur.
- Est-ce un cookie posé par en-tête
Set-Cookie? Aucun blocage client ne peut le retenir. Il faut le traiter à la source, côté serveur. - Passez
blocker_unknown_script_policysurblock— puis testez soigneusement le site, parce que ce réglage bloque tout tiers non reconnu.
Une intégration légitime est bloquée
Ajoutez son hôte dans blocker_allowlist, un par ligne, ou par le filtre
owc_blocker_allowlist. Vérifiez aussi blocker_unknown_iframe_policy : les iframes
inconnues sont bloquées par défaut, c'est le cas le plus fréquent.
Si l'intégration doit rester conditionnée au consentement, classez-la plutôt dans la bonne catégorie : le substitut visuel porte un bouton « Accepter … » qui la débloque en un clic.
Le scanner ne trouve rien
- Est-il activé ? Il est désactivé par défaut.
- WP-Cron fonctionne-t-il ? Avec
DISABLE_WP_CRONet sans cron système, le balayage planifié ne part jamais. Lancez-en un à la main pour vérifier. - Le loopback HTTP est-il possible ? Une authentification HTTP de préproduction ou un pare-feu bloque le balayage. Le panneau de couverture affiche la première erreur.
- Aucun JavaScript n'est exécuté par le balayage serveur. Consultez une page du site en tant qu'administrateur connecté pour que la sonde rapporte ce qu'un gestionnaire de balises injecte.
Dans tous les cas, lisez le panneau « ce que ce balayage couvre réellement » avant de conclure : il distingue « rien trouvé » de « rien lu ».
La génération d'un document est refusée
Deux causes possibles, et le message le dit :
- Des champs obligatoires sont vides — la liste des clés manquantes est affichée. Complétez l'onglet Legal identity.
- Barrière de langue — vous demandez des mentions légales ou des CGU/CGV pour une
juridiction non francophone. Le bouton est masqué dans ce cas. La seule voie est de
fournir votre propre corps via le filtre
owc_policy_template.
L'e-mail de vérification DSAR n'arrive pas
C'est presque toujours la délivrabilité, pas le plugin.
- La réponse de l'API porte
mail_sent: falsequand l'envoi a échoué, et le formulaire affiche alors un message dédié renvoyant vers l'adresse de contact. - Installez une extension SMTP. La fonction
mail()de PHP est rejetée par la plupart des serveurs de réception. - Vérifiez SPF, DKIM et DMARC de votre domaine. Le plugin ne réécrit jamais l'adresse
d'expédition — c'est ce qui casserait SPF — il pose seulement un
Reply-To. - Vérifiez que
dsar_emailest une adresse valide.
Tant que l'e-mail n'est pas reçu et confirmé, la demande reste pending et le délai légal
ne démarre pas.
Le module TCF ne s'active pas
Ouvrez l'onglet Compliance : un avis d'administration nomme la cause.
missing_cmp_id:tcf_cmp_idvaut 0. Vous devez obtenir un CMP ID auprès d'IAB Europe ; le plugin n'en fournit pas et refuse d'émettre une chaîne avec un identifiant 0.- CMP ID supérieur à 4095 : la valeur ne tient pas dans le champ de 12 bits de la chaîne et désignerait un autre CMP.
no_gvl: la Global Vendor List n'a pas encore été téléchargée. Elle arrive par un cron quotidien ; vérifiez que WP-Cron tourne et que votre serveur peut joindrevendor-list.consensu.orgen HTTPS sortant.
Tous les visiteurs reçoivent le profil configuré, malgré la détection régionale
geo_enabledest-il activé ? Il est désactivé par défaut.- Avez-vous déclaré votre proxy ? Sans
OWC_BEHIND_CLOUDFLARE,OWC_BEHIND_CLOUDFRONTouOWC_GEO_TRUSTED_HEADERSdanswp-config.php, les en-têtes de pays sont ignorés, parce qu'ils sont forgeables. - Le fichier MaxMind est-il en place ? Le plugin ne le télécharge pas et n'offre pas d'écran de téléversement.
- C'est peut-être normal : le premier affichage d'un visiteur servi par le cache utilise le profil configuré tant que le cookie de 24 heures n'existe pas. Le tableau de bord affiche un bloc de diagnostic avec le pays détecté, le profil appliqué et la source.
L'administration signale que la clé du registre vit en base
C'est l'avis notice_ledger_key. Il signifie que AUTH_KEY et AUTH_SALT ne sont pas
dans wp-config.php — WordPress les stocke alors dans la base — et que OWC_LEDGER_KEY
n'est pas définie non plus. La chaîne continue d'être construite, mais quiconque a accès à
la base peut la re-signer : la revendication d'infalsifiabilité ne tient plus.
Le remède est d'ajouter OWC_LEDGER_KEY (et, tant que vous y êtes, AUTH_KEY et
AUTH_SALT) dans wp-config.php. La vérification distingue ensuite une rotation de clé
d'une réécriture, donc ce changement n'invalide pas votre historique.
Le plugin ne fait rien du tout, avec un avis rouge
Votre WordPress est antérieur à 6.2. Le plugin refuse de démarrer, et l'avis dit explicitement que rien n'est bloqué et qu'aucun consentement n'est enregistré. Mettez WordPress à jour, ou désactivez le plugin et retirez la bannière de vos pages en attendant.
Des règles de catalogue « ne peuvent pas se déclencher »
Un avis d'administration les signale, et l'onglet Tracker catalogue a un filtre dédié. Quatre motifs possibles : motif vide ou composé uniquement de caractères invisibles, expression régulière qui ne compile pas ou qui rétro-suit de façon catastrophique, motif trop court et sans point pour cibler une URL, ou catégorie qui n'existe plus sur le site. Corrigez ou supprimez ces lignes : elles donnent une fausse impression de couverture.
FAQ
Le plugin fonctionne-t-il derrière un cache de page ? Oui, et toute son architecture est bâtie autour de cette contrainte. Le HTML produit est identique pour tous les visiteurs ; le consentement est lu dans le navigateur et appliqué avant le premier rendu. Aucun nonce n'est imprimé dans du HTML cachable. Seules les réponses réellement personnelles sont marquées non cachables.
Remplace-t-il un CMP payant ? Sur la plupart des sites, il couvre le même terrain : bannière, blocage, journal d'audit, demandes de droits, routage géographique et documents générés. Deux choses qu'il ne fait pas : ce n'est pas un CMP enregistré auprès d'IAB Europe, et il ne fournit pas de conseil juridique ni de documents relus — le générateur produit des brouillons que vous devez faire valider.
Le registre est-il infalsifiable ?
Il est inviolable en évidence, sous une condition. Chaque ligne est signée par HMAC sur
l'empreinte de la précédente : une ligne modifiée ou supprimée casse la chaîne et la
vérification dit où. La garantie repose sur le fait que la clé de signature vit hors de la
base. C'est le cas si AUTH_KEY et AUTH_SALT sont dans wp-config.php, ou si vous
définissez OWC_LEDGER_KEY. Sinon, WordPress stocke les sels en base et un attaquant qui y
accède pourrait re-signer la chaîne — le plugin détecte cette situation et vous prévient.
Le scanner ralentit-il le site ? Non. Il est désactivé par défaut, et une fois activé il tourne sur WP-Cron à la fréquence que vous choisissez, en récupérant un échantillon de vos propres pages côté serveur. Il ne s'exécute jamais pendant la consultation d'un visiteur.
Le scanner trouve-t-il les cookies ?
Partiellement, et c'est important à comprendre. Le balayage serveur lit les en-têtes
Set-Cookie de vos pages : il trouve donc les cookies posés par le serveur, y compris les
HttpOnly. Il n'exécute aucun JavaScript, donc les cookies écrits par des scripts dans le
navigateur ne lui sont pas visibles ; ceux-là sont collectés par une sonde qui ne tourne que
pour un administrateur connecté. Traitez le résultat comme un inventaire de ce qui a été
vu, pas comme une liste exhaustive — les deux écrans le disent au lieu de vous laisser
supposer le contraire.
Comment une demande de droits est-elle exécutée ? La personne soumet le formulaire et reçoit un e-mail de vérification. Confirmer la demande sur la page liée démarre le délai de l'article 12.3 et ouvre une demande WordPress native. Depuis l'écran DSAR, vous téléchargez un export JSON produit par tous les exportateurs enregistrés sur le site, vous lancez tous les effaceurs, et vous clôturez avec une réponse écrite qui reste attachée à la demande. Ce que couvre l'export dépend donc des extensions installées ; celles qui n'enregistrent aucun de ces crochets doivent être traitées à la main.
Puis-je utiliser le module TCF pour AdSense ou Ad Manager ? Seulement avec votre propre CMP ID délivré par IAB Europe, et même dans ce cas avec une réserve. Sans CMP ID, le module ne se charge pas du tout. Avec un CMP ID, il émet une chaîne TC correctement encodée et le panneau affiche chaque finalité et chaque fonctionnalité spéciale que cette chaîne peut revendiquer — mais la politique TCF exige d'un CMP enregistré des choix au niveau finalité et au niveau vendeur. Ici les interrupteurs de finalités suivent les catégories et il n'y a aucun choix vendeur : ce n'est pas un CMP enregistré, et les vendeurs sont fondés à rejeter son signal. Si un revenu publicitaire sous TCF compte pour votre site, utilisez un CMP certifié.
Dans quelle langue les documents sont-ils générés ? La politique de confidentialité et la politique cookies existent pour les onze profils, dans la langue de la juridiction : anglais, français pour la France, la Belgique, le Luxembourg et le Québec, portugais pour le Brésil. Les mentions légales et les CGU/CGV n'existent qu'en français ; pour toute autre juridiction, le générateur refuse de les produire plutôt que de publier un document inadapté.
Que puis-je changer sur la bannière ?
Quatre positions, un thème clair, sombre ou automatique, un logo optionnel, les libellés,
et un bouton flottant de réouverture. Tout élément portant la classe owc-open-preferences
rouvre le panneau. « Tout refuser » est rendu avec la même prominence que « Tout accepter ».
La croix de fermeture est désactivée par défaut ; activée, elle enregistre un refus complet,
jamais une fermeture silencieuse. Le panneau est utilisable au clavier, ses interrupteurs
portent un état visible, et la modale piège puis restitue le focus. Les textes sont
traduisibles et remplaçables par le filtre owc_banner_texts.
Que bloque exactement le blocage automatique ? Les scripts tiers, les snippets de suivi en ligne, les iframes, les pixels de mesure, les feuilles de style analytics et marketing, et les sources de médias tiers sont réécrits avant consentement et libérés ensuite, dans l'ordre du document. Les indices de ressources vers un tiers catalogué sont supprimés plutôt que différés, puisqu'un indice ouvre une connexion et n'a rien à restaurer. Les feuilles et polices servies par un hôte classé fonctionnel sont laissées intactes. Les attributs de chargement paresseux des extensions de cache sont neutralisés pour qu'un chargeur ne restaure pas une URL bloquée. Le plugin livre 175 signatures, éditables depuis l'écran Tracker catalogue et extensibles par filtre.
Qu'est-ce qui est inscrit au registre ? Chaque action de consentement — acceptation, refus, enregistrement partiel, opt-out GPC, renouvellement, retrait — est ajoutée à la table chaînée. Une ligne contient une IP pseudonymisée, une empreinte de user-agent, l'URL de page, le profil de conformité, une empreinte des documents en vigueur, une empreinte de la bannière réellement affichée et la version du plugin — les éléments qui permettent de reconstituer ce que le visiteur a vu.
Quelles protections y a-t-il sur le formulaire de droits ? Un piège à robots, un délai minimum de remplissage, une attestation obligatoire vérifiée côté serveur, trois limites de débit distinctes, un jeton à usage unique stocké uniquement sous forme d'empreinte, et une confirmation qui exige une action explicite sur la page — de sorte qu'un scanner de liens de messagerie ne puisse pas confirmer une identité à la place de la personne. Une demande dont l'auteur n'a jamais confirmé son identité ne peut être ni exportée, ni effacée, ni enregistrée comme honorée, et ce contrôle est appliqué côté serveur, pas seulement en masquant des boutons.
Quels signaux Google Consent Mode v2 sont émis ?
Les sept : ad_storage, ad_user_data, ad_personalization, analytics_storage,
functionality_storage, personalization_storage et security_storage. Les six catégories
y sont associées depuis une source de vérité unique, si bien que la bannière, le blocage et
le bootstrap ne peuvent pas diverger.
Comment sont gérés l'opt-out CCPA et GPC ? Un contrôle « Do Not Sell or Share » est injecté en pied de page ou placé par shortcode. Le premier clic enregistre l'opt-out, comme l'exigent les règlements californiens, plutôt que d'ouvrir un panneau. Le signal Global Privacy Control est traité comme un opt-out contraignant sous les profils américains, une fois par session de navigation, et le visiteur est prévenu s'il a remplacé un choix qu'il avait enregistré. Sous les profils de la famille RGPD, GPC est traité comme un indice : les catégories optionnelles sont pré-refusées dans l'interface, la bannière reste affichée, et rien n'est enregistré — parce que le consentement y doit être un acte positif.
Quels cookies le plugin dépose-t-il lui-même ?
Tous sont des cookies internes, aucun ne sert au suivi, et ils sont déclarés dans la
politique cookies générée : owc_consent (les choix, le jeton pseudonyme du navigateur et
le profil ; durée suivant le réglage de renouvellement, 12 mois par défaut, plafonnée à 13),
owc_geo (pays et profil détectés, 24 heures, sans aucune adresse IP), owc_gpc (marqueur
de session), owc_gpc_notice (5 minutes), et euconsent-v2 (chaîne TCF, uniquement si le
module est activé et après une action du visiteur).
Fonctionne-t-il en multisite ? Oui. Chaque site du réseau a ses propres tables et ses propres réglages. Une activation réseau parcourt tous les sites uniquement si le réseau en compte au plus 200 ; au-delà, chaque site est provisionné à sa première requête.
Et si je supprime le plugin ? Le registre, les trouvailles du scanner, le catalogue, les liaisons OW Forms, les options et les réglages survivent par défaut. Deux choses partent toujours, quel que soit votre réglage : les six tâches planifiées, et la table des demandes de droits — c'est la seule qui contient des données personnelles directement identifiantes concernant des tiers, et une fois le plugin parti, plus rien n'en borne la rétention et aucun écran ne permet d'y répondre. Exportez vos demandes avant de supprimer le plugin. Pour un nettoyage complet du reste, activez « Delete all data on uninstall » avant la suppression.
Où trouver le support ?
- Portail de support : https://optionweb.dev/fr/addons/support/
- E-mail : support@optionweb.dev
- Documentation : https://optionweb.dev/fr/addons/ow-consent/