Ao vivo · status OK
Documentação · OW Consent v1.4.3

OW Consent
Documentação

O consentimento WordPress que bloqueia os rastreadores antes do clique, não depois.

v1.4.3GPL-2.0-or-laterDocumentação

OW Consent — Documentação

A gestão completa do consentimento no WordPress: banner multijurisdição, bloqueio de rastreadores, scanner, documentos legais, registo de prova e portal de direitos. Autor: OptionWeb — Julien Daniel Página do plugin: https://optionweb.dev/pt/addons/ow-consent/ Licença: GPL-2.0-or-later Versão coberta por este documento: 1.4.3


Índice

  1. Visão geral
  2. Instalação
  3. Início rápido
  4. Os onze perfis de conformidade
  5. As categorias de cookies
  6. O banner
  7. O bloqueio automático
  8. O scanner de rastreadores
  9. O gerador de documentos legais
  10. O registo de consentimento
  11. O portal de direitos (DSAR)
  12. O opt-out CCPA «Do Not Sell or Share»
  13. Google Consent Mode v2
  14. IAB TCF v2.2
  15. Global Privacy Control
  16. A deteção regional
  17. A integração com o OW Forms
  18. O botão flutuante
  19. Shortcodes
  20. API REST
  21. Referência das definições
  22. Resolução de problemas
  23. FAQ

Visão geral

O OW Consent é uma suite de gestão do consentimento para WordPress. Cobre a cadeia inteira: mostrar uma escolha, aplicá-la realmente aos rastreadores, guardar a prova, publicar os documentos que a explicam e receber os pedidos dos titulares dos dados.

Vêm incluídos onze perfis de conformidade — do RGPD à CCPA, passando pela Lei 25 do Quebec, pela LGPD brasileira ou pela DPDP indiana. O perfil ativo decide o modelo de consentimento (opt-in ou opt-out), os valores por defeito do Google Consent Mode, o texto dos documentos gerados, os direitos publicados e a autoridade de controlo citada.

Fica tudo dentro do seu site. O plugin contacta um único serviço externo, a Global Vendor List do IAB Europe, e apenas se ativar o módulo TCF, que vem desativado por defeito. Sem telemetria, sem conta, sem subscrição.

Compatível com uma cache de página completa, por construção

É este o ponto de arquitetura que governa todo o resto. Nada do que o servidor produz depende do cookie de consentimento. O HTML é idêntico para todos os visitantes: os rastreadores são reescritos em etiquetas inertes para toda a gente, e é um runtime JavaScript que os liberta no browser, categoria a categoria, lendo o cookie antes da primeira renderização.

Consequência prática: LiteSpeed Cache, WP Rocket, Varnish ou um CDN não podem servir as escolhas de um visitante a outro. Só as respostas realmente pessoais — a página de confirmação de identidade de um pedido de direitos, as respostas REST que contêm dados do visitante — são explicitamente marcadas como não armazenáveis em cache. Também não há nenhum nonce impresso em HTML armazenável: o banner vai buscar um fresco a um endpoint no-store mesmo antes de cada escrita.

O que vem incluído

  • Banner de consentimento: quatro posições, tema claro / escuro / automático, «Recusar tudo» tão visível quanto «Aceitar tudo», painel de preferências acessível por teclado, totalmente traduzível.
  • Bloqueio automático de rastreadores: scripts, snippets inline, iframes, pixels, sugestões de recursos (preconnect, dns-prefetch, preload), folhas de estilo de analytics/marketing e media de terceiros, a partir de 175 assinaturas incluídas e editáveis.
  • Scanner de rastreadores: varrimento das suas próprias páginas por WP-Cron, leitura dos cabeçalhos Set-Cookie, sonda de browser reservada aos administradores, e um painel que diz sempre o que o varrimento cobriu realmente.
  • Gerador de documentos legais: política de privacidade e política de cookies para os onze perfis, na língua da jurisdição; aviso legal e termos e condições em francês.
  • Registo de consentimento: cada ação acrescentada a uma tabela encadeada por HMAC, com o perfil em vigor, o hash dos documentos publicados e o hash do banner realmente mostrado.
  • Portal de direitos (DSAR): formulário por shortcode, verificação por e-mail, prazo do artigo 12.º, n.º 3 accionado na confirmação de identidade, ligação ao exportador e ao apagador nativos do WordPress.
  • Google Consent Mode v2, IAB TCF v2.2, Global Privacy Control, deteção regional, integração com o OW Forms: cada um ativável de forma independente.
  • API REST completa sob o namespace owc/v1.

O que não faz — leia antes de se comprometer

Num tema jurídico, uma promessa excessiva expõe-o a si tanto quanto expõe o editor. Aqui ficam as limitações, tal como estão escritas no código.

  • Os documentos gerados são modelos, não aconselhamento jurídico. Cada documento termina com um aviso que o diz, e esse aviso está ativo por defeito. Mande rever os seus documentos antes de os publicar.
  • O módulo TCF não é um CMP registado junto do IAB Europe. Exige um CMP ID que tem de obter por si, não oferece qualquer escolha ao nível do fornecedor, e os fornecedores têm fundamento para rejeitar o sinal dele. Se a receita publicitária sob TCF conta para si, use um CMP certificado.
  • O registo só está à prova de adulteração sob condição. A cadeia só é uma prova se a chave de assinatura viver fora da base de dados. O plugin deteta o caso contrário, reporta-o ele próprio e diz-lho no backoffice, em vez de fingir o oposto.
  • O scanner não executa JavaScript nenhum. Vê o que o seu HTML contém e o que os seus cabeçalhos Set-Cookie depositam; o que um gestor de tags injeta em runtime só é visto por uma sonda reservada aos administradores com sessão iniciada.
  • O bloqueio por defeito não é «tudo o que é de terceiros». Um script de terceiros desconhecido é autorizado por defeito; são as iframes desconhecidas que ficam bloqueadas por defeito. O bloqueio assenta no catálogo de assinaturas, que pode enriquecer.
  • Sem JavaScript, nenhuma escolha pode ser registada. O visitante vê um bloco <noscript> e nada de não essencial é carregado, mas também nada fica registado.
  • Sem granularidade estado a estado nos Estados Unidos nem província a província no Canadá na deteção regional: todo o território norte-americano recebe o perfil ccpa, todo o Canadá recebe quebec.
  • Aviso legal e termos e condições só existem em francês. Para qualquer jurisdição cuja língua-alvo não seja o francês, o gerador recusa produzir estes dois documentos em vez de publicar um texto desadequado.
  • Sem papel dedicado: toda a administração exige a capacidade manage_options.

Esta lista é uma opção assumida, não um roadmap disfarçado. Em conformidade, uma ferramenta que enuncia os seus buracos vale mais do que uma que os esconde.


Instalação

A partir do .zip

  1. Descarregue ow-consent-1.4.3.zip de https://optionweb.dev/pt/addons/ow-consent/
  2. Plugins → Adicionar novo → Carregar plugin
  3. Escolha o ficheiro, clique em Instalar, depois em Ativar

Por FTP

Descompacte o arquivo e coloque a pasta ow-consent em /wp-content/plugins/, depois ative o plugin a partir de Plugins.

Requisitos

  • WordPress 6.2 ou posterior — é uma recusa de arranque, não uma recomendação (ver abaixo)
  • PHP 7.4 ou posterior
  • MySQL 5.7+ / MariaDB 10.2+
  • WP-Cron funcional se usar o scanner, a retenção do registo, os lembretes de prazo DSAR ou o módulo TCF

A salvaguarda do WordPress 6.2. Desde a 1.3.0, cada identificador de tabela é ligado pelo marcador %i de wpdb::prepare(), que o WordPress só entende a partir da 6.2. Num core mais antigo, prepare() devolveria uma string vazia, o catálogo de rastreadores resolveria silenciosamente para um conjunto vazio, e o banner continuaria a prometer ao visitante um bloqueio que não aconteceria. Por isso o plugin recusa arrancar e mostra uma mensagem de erro que diz explicitamente que nada está bloqueado e que nenhum consentimento está a ser registado. O cabeçalho Requires at least já impede a ativação abaixo da 6.2, mas não cobre nem uma despromoção do core numa instalação em serviço, nem uma cópia colocada por FTP.

O que é instalado

Na ativação, o OW Consent cria quatro tabelas:

TabelaConteúdo
{prefix}owc_ledgerO registo de consentimento, encadeado por hash
{prefix}owc_dsarOs pedidos de exercício de direitos
{prefix}owc_scannerAs deteções do scanner de rastreadores
{prefix}owc_scriptsO catálogo de assinaturas usado pelo bloqueio

Uma quinta tabela, {prefix}owc_form_links, é criada à parte pela integração com o OW Forms se o plugin irmão estiver ativo (ver A integração com o OW Forms).

A ativação acrescenta ainda:

  • a opção owc_settings, semeada vazia — de propósito: enquanto não tiver gravado nada, todos os textos vêm dos valores por defeito ingleses, traduzidos ao momento para a língua do site, em vez de fixar na base de dados a locale de quem ativou o plugin;
  • a opção owc_version;
  • o catálogo de rastreadores incluído (data/tracker-catalog.json), inserido em lotes de 100 com INSERT IGNORE, com uma flag de seed por versão (owc_catalog_seeded_1.4.3);
  • a tarefa diária owc_daily_maintenance, agendada uma hora depois da ativação.

Outros cinco eventos de cron são armados pelos módulos que os usam. Os seis hooks de cron do plugin são: owc_scanner_run, owc_scanner_run_batch, owc_ledger_retention, owc_tcf_refresh_gvl, owc_run_upgrade e owc_daily_maintenance.

Em multisite, as tabelas são criadas por site, nunca partilhadas (wp_2_owc_ledger, etc.). Uma ativação de rede percorre todos os sites apenas se a rede tiver no máximo 200 sites; acima disso, cada site é provisionado preguiçosamente no seu primeiro pedido. Um subsite criado depois de uma ativação de rede é provisionado pelo hook wp_initialize_site.

A definir no wp-config.php

Nenhuma destas constantes é obrigatória, mas duas delas mudam o valor probatório daquilo que o plugin produz.

// Recomendado: tira a chave de assinatura do registo de dentro da base de dados.
define( 'OWC_LEDGER_KEY', 'uma string longa e aleatória, própria deste site' );

// Recomendado: os salts padrão do WordPress. Sem eles, o WordPress guarda-os na base de
// dados, e o registo assinala-se a si próprio como não estando à prova de adulteração.
define( 'AUTH_KEY',  '…' );
define( 'AUTH_SALT', '…' );

// Se o site estiver atrás de um CDN, de um balanceador de carga ou de um reverse proxy.
// Sem esta constante, CF-Connecting-IP, X-Forwarded-For e X-Real-IP são IGNORADOS
// e só REMOTE_ADDR é usado — o que dá a mesma identidade a todos os seus visitantes
// para efeitos de limite de taxa.
define( 'OWC_TRUSTED_PROXY', '198.51.100.0/24, 2001:db8::/32' );
// `OWC_TRUSTED_PROXIES` é aceite como alias; serve uma string ou um array.

// Só para a deteção regional: declara que cabeçalhos de país são dignos de confiança.
define( 'OWC_GEO_TRUSTED_HEADERS', 'cloudflare' ); // 'cloudflare'|'cloudfront'|'proxy'|'all'
define( 'OWC_BEHIND_CLOUDFLARE', true );
define( 'OWC_BEHIND_CLOUDFRONT', true );

OWC_LEDGER_KEY serve também para derivar a chave do cookie de deteção regional. Se não estiver definida, a chave do registo recai sobre wp_salt('auth').

Quem tem acesso

Toda a administração do plugin e todas as rotas REST de administração exigem a capacidade manage_options. Não existe nem papel dedicado, nem capacidade mais fina: abrir o ecrã do plugin a alguém equivale a dar-lhe as definições do site.

Caminho de atualização

O plugin nunca migra o esquema em linha numa página anónima. Quando a versão muda, owc_version e uma flag owc_pending_upgrade são escritas de imediato, e depois:

  • a migração corre inline se o pedido for um pedido de administração fora de AJAX, uma execução de cron ou um comando WP-CLI;
  • caso contrário, é agendado um evento owc_run_upgrade cinco segundos mais tarde.

admin-ajax.php é tratado como um pedido anónimo: é um ponto de entrada público. A migração corre sob bloqueio (owc_upgrade_lock, roubado ao fim de 300 segundos), e uma rede de segurança em admin_init apanha os sites onde o WP-Cron está desativado. Numa versão igual, o custo total é um único get_option().

Desativação e eliminação

Desativar mantém todos os dados e limpa apenas os seis eventos de cron. Numa desativação de rede, todos os sites são percorridos em lotes de 200 — ao contrário da ativação — porque um cron deixado armado nunca mais desapareceria.

Apagar o plugin dispara o uninstall.php, que procede em dois tempos:

  1. Sempre, seja qual for a sua definição: os seis crons são limpos e a tabela {prefix}owc_dsar é eliminada, juntamente com os transients de limite de taxa. É a única tabela que contém dados pessoais diretamente identificáveis relativos a terceiros (endereço de e-mail, nome, texto livre); uma vez o plugin fora, deixa de haver limite à retenção dela e nenhum ecrã permite responder-lhes, exportá-los ou apagá-los. Exporte os pedidos que tem de conservar antes de apagar o plugin.
  2. Só se delete_data_on_uninstall estiver explicitamente ativo: eliminação das tabelas owc_ledger, owc_dsar, owc_scanner, owc_scripts e owc_form_links, das opções nomeadas, de todas as opções com prefixo owc_ (transients incluídos) e dos usermeta com prefixo owc_. Em multisite, as opções de rede seguem a decisão do site principal.

Esta definição está desativada por defeito: a prova de consentimento, exigida pelo artigo 7.º, n.º 1 do RGPD, sobrevive à eliminação do plugin.


Início rápido

Depois da ativação, abra a entrada OW Consent na barra lateral do backoffice. O plugin inteiro cabe neste ecrã único, em dez separadores: Dashboard, Banner, Compliance, Legal identity, Categories, Policies, Scanner, Tracker catalogue, Audit ledger, DSAR requests.

Uma gravação só diz respeito ao separador aberto. É deliberado: cada booleano tem um campo oculto gémeo, e uma chave ausente do formulário significa «este campo está noutro separador», nunca «desmarcado». Sem isso, gravar um separador esmagaria as definições de todos os outros.

1. Escolha o seu perfil de conformidade

Separador Compliance. O perfil decide o modelo de consentimento, os valores por defeito do Consent Mode, os documentos gerados e os direitos publicados. Por defeito: gdpr.

compliance_strict está ativo por defeito: é o que estende o bloqueio para além dos scripts registados pelo WordPress. Deixe-o ativo se quiser que as iframes, os pixels e os scripts escritos à mão no seu tema sejam tratados.

2. Preencha a identidade legal

Separador Legal identity. Estes campos são a matéria-prima dos documentos gerados, e a geração é recusada enquanto houver um campo obrigatório vazio — com a lista das chaves em falta, não uma falha silenciosa.

Mínimo para todos os documentos: legal_company_name e legal_company_email. Para tudo menos a política de cookies, acrescente a morada e o país. Para o aviso legal, acrescente também o telefone, o diretor de publicação e os dados completos do alojamento; em França, na Bélgica e no Luxemburgo, a forma jurídica e o número de registo passam também a ser obrigatórios.

Deixe legal_dpa_authority vazio: a autoridade de controlo é derivada do seu país e do seu perfil. Preenchê-la à mão num site multijurisdição equivale a nomear o regulador errado.

3. Verifique as suas categorias

Separador Categories. As seis categorias estão disponíveis por defeito. Desative as que o seu site não usa: uma categoria ausente da interface não é um ganho de conformidade, é um recurso bloqueado para sempre sem interruptor que o liberte — o bloqueio despeja-a então em marketing.

Deixe as etiquetas vazias enquanto lhe servirem: assim seguem a língua do site. A partir do momento em que personaliza um texto, esse texto deixa de seguir a língua.

4. Afine o banner

Separador Banner. Posição, tema, botões, etiquetas, prazo de renovação.

Dois pontos a não perder: deixe banner_reject_all ativo («Recusar» deve ser tão simples e tão visível como «Aceitar»), e deixe a cruz de fecho desativada — está ausente por defeito porque um fecho sem escolha equivale a uma recusa implícita. Se a ativar, um clique na cruz executa o caminho «Recusar tudo» completo, nunca um fecho silencioso.

5. Gere os seus documentos

Separador Policies. Quatro documentos: política de cookies, política de privacidade, aviso legal, termos e condições. Cada um torna-se uma página WordPress versionada, cujo link é reinjetado nas definições.

Gere primeiro em pré-visualização, releia, e só depois publique. E mande rever por um profissional: a faixa de aviso no fundo de cada documento não é decorativa.

6. Abra o portal de direitos

Crie uma página e cole lá [owc_dsar_form]. Preencha dsar_email no separador DSAR requests: é o endereço de contacto publicado por baixo do formulário e o destinatário das notificações. Sem ele, o plugin recai sobre legal_dpo_email e depois sobre admin_email — mas admin_email nunca é publicado numa página pública.

7. Verifique antes de abrir ao público

O dashboard executa quinze verificações de conformidade e distingue erros de avisos: banner desativado, «Recusar tudo» ausente, cruz de fecho ativa, identidade legal incompleta, portal de direitos fechado, registo desativado, nenhuma política ligada, autoridade de controlo em contradição com o perfil ativo (com um botão «Fix this» que a repõe), site sem HTTPS, rastreadores não categorizados, pedidos DSAR fora de prazo, regras de catálogo que não conseguem disparar.

Ponha estas quinze linhas a verde antes de anunciar que o seu site está conforme.


Os onze perfis de conformidade

O perfil de conformidade não é cosmético. Comanda o modelo legal, a interface, os sinais técnicos e o conteúdo dos documentos publicados.

A lista

PerfilRegime visadoModelo
gdprRGPD + ePrivacy (UE/EEE)Opt-in
uk_pecrUK GDPR + PECR (Reino Unido)Opt-in
ch_nfadpnLPD suíçaOpt-in
quebecLei 25 (Quebec)Opt-in
lgpdLGPD (Brasil)Opt-in
popiaPOPIA (África do Sul)Opt-in
piplPIPL (China)Opt-in
dpdpDPDP Act 2023 (Índia)Opt-in
ccpaCCPA / CPRA (Califórnia)Opt-out
us_genericLeis estaduais norte-americanas genéricasOpt-out
auPrivacy Act (Austrália)Opt-out

Definição: compliance_profile, por defeito gdpr.

Opt-in significa que, antes de qualquer escolha, só as categorias forçadas estão autorizadas. Opt-out significa que tudo está autorizado até haver recusa. Os três perfis opt-out são ccpa, us_generic e au. O runtime JavaScript aplica exatamente a mesma regra que o PHP, de modo que servidor e browser não podem reportar dois estados diferentes.

O que o perfil muda realmente

O que mudaDetalhe
Modelo de consentimentoOpt-in, exceto ccpa, us_generic, au
Valores por defeito do Consent ModeOs sete sinais passam a granted sob um perfil opt-out
Link «Do Not Sell or Share» obrigatórioccpa e us_generic apenas
GPC juridicamente vinculativoccpa e us_generic apenas — a Austrália está explicitamente excluída: regime opt-out, mas que não reconhece o GPC
Perímetro «o RGPD aplica-se» para o TCF27 países da UE + IS, LI, NO + GB + CH, ou seja 31 códigos
Língua do documento geradoquebec → francês; lgpd → português; gdpr com país FR, BE ou LU → francês; todo o resto → inglês
Autoridade de controlo citadaTabela por perfil, afinada por país no caso do RGPD
Lista de direitos publicadaUma lista redigida por perfil, com citação de artigo
Corpo «regime de cookies» do documento inglêsTexto, perímetro de transferência e garantias distintos por perfil

Os documentos gerados por perfil

A política de privacidade e a política de cookies existem para os onze perfis. Os modelos são resolvidos por esta ordem, ganhando o primeiro encontrado: <tipo>_<perfil>_<língua>, depois <tipo>_<perfil>, depois <tipo>_<língua>, depois <tipo>.

Os corpos ingleses da política de cookies são conscientes do perfil: regra aplicável, perímetro de transferência, garantias e texto do painel diferem para gdpr, uk_pecr, ch_nfadp, au, pipl, dpdp, ccpa e us_generic, com as citações correspondentes (art. 5.º, n.º 3 da diretiva 2002/58, PECR reg. 6, art. 45c(b) LTC e art. 19/6(7)(b) nLPD, APP 8, art. 24 PIPL, secções 5/6/7/9(3)/16 do DPDP Act 2023, §1798.121 e Cal. Code Regs. tit. 11 §7025, VCDPA/CPA/CTDPA/UCPA/TDPSA).

Autoridade de controlo citada

PerfilAutoridade nomeada
gdpr, país FRCNIL
gdpr, país BEAPD-GBA
gdpr, país LUCNPD
gdpr, país DEBfDI
gdpr, outro país do EEEFormulação genérica («a autoridade de controlo competente»)
uk_pecrICO
ch_nfadpPFPDT / FDPIC
quebecCommission d'accès à l'information
lgpdANPD
ccpaCalifornia Privacy Protection Agency
popiaInformation Regulator (South Africa)
piplCyberspace Administration of China
dpdpData Protection Board of India
auOAIC
us_genericAttorney General do seu estado

Se tiver alterado legal_dpa_authority, o que escreveu prevalece sobre esta derivação.

Limitações da cobertura jurisdicional

  • Só quatro países do EEE têm autoridade nomeada (FR, BE, LU, DE). Um site RGPD em Espanha, em Itália ou nos Países Baixos publica uma formulação genérica.
  • Sem granularidade estado a estado nos Estados Unidos. O perfil us_generic existe e traz os seus próprios textos VCDPA/CPA/CTDPA/UCPA/TDPSA, mas nenhuma deteção automática lho atribui: escolhe-se à mão.
  • Sem granularidade provincial no Canadá.
  • Só três línguas de documentos: francês, inglês, português. Os corpos não passam pelo mecanismo de tradução do WordPress — é deliberado: um documento legal deve ser monolingue e a língua dele segue a jurisdição, nunca a locale do administrador.
  • Aviso legal e termos e condições: modelos só em francês. O gerador recusa liminarmente para qualquer outra língua-alvo, a menos que forneça o seu próprio corpo através do filtro owc_policy_template.

As categorias de cookies

A lista canónica

Seis categorias, por esta ordem de apresentação, com necessary sempre à cabeça:

SlugEtiqueta incluídaDescrição incluída
necessaryNecessaryStrictly required for the site to function (cart, login, language preferences). Cannot be disabled.
functionalFunctionalEnhance the experience (chat, embedded videos, maps). Without them some features may not work.
analyticsStatisticsHelp us understand how you use the site (anonymously). No personal data is shared for commercial purposes.
marketingMarketingEnable us to show you ads and content tailored to your interests on other sites.
preferencesPreferencesRemember your interface choices (layout, saved filters).
socialSocial & embedsAllow embedded social content (YouTube, Instagram, X) to load.

Os valores acima são os valores por defeito ingleses, que podem ser guardados. Num site em português, o visitante vê a tradução — ver o mecanismo de tradução mais abaixo.

Disponibilidade

Definição cat_<slug>_available, uma por categoria. As seis estão disponíveis por defeito. A antiga chave cat_<slug>_enabled continua a ser lida como recurso, sem migração.

preferences e social vêm disponíveis porque o catálogo de seed classifica o YouTube, o Spotify, o SoundCloud, o Instagram, o X e o Facebook em social: sem a categoria na interface, estas integrações ficariam bloqueadas definitivamente, sem forma de dar opt-in.

necessary é forçada: não pode ser desativada (art. 5.º, n.º 3 da ePrivacy). Por isso não tem interruptor de disponibilidade no backoffice, apenas uma etiqueta e uma descrição.

O mecanismo de tradução

Definições cat_<slug>_label e cat_<slug>_desc. O valor guardado é devolvido tal e qual apenas se não estiver vazio e for diferente do valor por defeito inglês; caso contrário, sai a tradução.

Consequência a conhecer: enquanto não personalizar nada, mudar a língua do site muda a língua das categorias. A partir do momento em que escreve o seu próprio texto, esse texto fica fixado na língua em que o escreveu. A mesma regra se aplica aos textos do banner.

A descrição traduzida de necessary é construída a partir de duas strings: a segunda nomeia os cookies do próprio plugin, owc_consent (até 13 meses) e owc_geo (24 horas, escrito apenas quando a deteção regional está ativa).

Correspondência com o Google Consent Mode v2 — vinculativa

É a fonte de verdade única do plugin: o banner, o bloqueio e o bootstrap leem todos esta tabela, por isso não podem divergir.

CategoriaSinais do Consent Mode v2
necessarysecurity_storage, functionality_storage
functionalfunctionality_storage, personalization_storage
analyticsanalytics_storage
marketingad_storage, ad_user_data, ad_personalization
preferencespersonalization_storage
socialad_storage, ad_user_data

Nuance importante: se o seu site expuser a categoria functional, functionality_storage é retirado da lista de necessary. Caso contrário, ficaria autorizado antes de qualquer consentimento, apesar de pertencer a uma categoria opcional.

Correspondência TCF — apenas descritiva

functionalfunctional, analyticsmeasurement, marketingadvertising, preferencespersonalization, socialsocial_media.

São etiquetas de apresentação. A correspondência vinculativa do lado do TCF é a tabela dos identificadores numéricos de finalidades do IAB, descrita na secção IAB TCF v2.2. Nunca ligue um comportamento TCF a estas cinco etiquetas.

O que acontece se uma categoria desaparecer

Se uma regra do catálogo apontar para uma categoria que o seu site já não expõe — desativou social, por exemplo — o bloqueio despeja o recurso em marketing ou, na falta dela, na primeira categoria opcional disponível. Sem esta normalização, o recurso ficaria bloqueado para sempre, sem qualquer interruptor que o libertasse.


O banner

Quando aparece

O banner é renderizado em wp_footer com prioridade 5, e os recursos dele são enfileirados em wp_enqueue_scripts. Sai de imediato se uma destas condições for verdadeira: contexto de administração, feed RSS, robots.txt, ou banner_enabled desativado. Sai também se o wp_head nunca tiver sido disparado — um tema que não chame wp_head() obteria, de outra forma, uma marcação inerte e sem estilos.

O HTML é idêntico para todos os visitantes. Todas as caixas do painel são renderizadas a OFF no servidor e depois hidratadas no cliente a partir do cookie. É o JavaScript que coloca <html data-owc="given|none"> de forma síncrona, antes da primeira renderização; o CSS só mostra o banner em data-owc="none".

Corolário a conhecer: um visitante sem JavaScript nunca vê o banner, logo nunca fica preso atrás de uma modal que não consegue fechar. Vê, em vez disso, um bloco <noscript> que explica que as preferências dele não podem ser registadas e que nenhum cookie não essencial é carregado enquanto não houver escolha.

Posições e tema

DefiniçãoValoresPor defeito
banner_positionbottom-bar, bottom-card, center-modal, top-barbottom-bar
banner_styleauto, light, darkauto

center-modal recebe role="dialog", aria-modal="true", um fundo escurecido, uma armadilha de foco e a tecla Esc. As outras três posições são um role="region": o Esc não é intercetado (o tema mantém a gestão dele) e o foco não é roubado na primeira renderização, o que empurraria um utilizador de teclado para além de todos os links de salto.

A paleta escura aplica-se sob prefers-color-scheme: dark, com guardas para não esmagar um tema que declare explicitamente light. O tema do site pode comandar a paleta por variáveis CSS: --owc-paper-tint, --owc-ink, --owc-smoke, --owc-fog, --owc-ui, --owc-accent, --owc-accent-strong, --owc-accent-darker.

Detalhes de implementação: z-index 99998 para o banner e 99997 para o fundo, suporte de env(safe-area-inset-*) para os entalhes do iOS, desvio automático por baixo da barra de administração do WordPress na posição de topo.

Os botões

DefiniçãoPor defeitoEfeito
banner_accept_alltrueMostra «Aceitar tudo»
banner_reject_alltrueMostra «Recusar tudo»
banner_preferencestrueMostra «Personalizar»
banner_close_xfalseMostra a cruz de fecho
banner_show_logotrueMostra o logótipo do site

«Aceitar tudo» e «Recusar tudo» partilham a mesma classe de formatação: mesmo fundo, mesmo contorno, mesmo peso, mesmo preenchimento. É a resposta à exigência da CNIL / do EDPB (orientações 03/2022): recusar deve ser tão simples e tão visível como aceitar.

A cruz de fecho

Está desativada por defeito, porque uma cruz de fecho equivale a uma recusa implícita. Três comportamentos a conhecer:

  1. Quando banner_close_x está off, a cruz está presente na marcação mas tem o atributo hidden, e o CSS retira-a por completo: nem visível, nem focável, nem anunciada aos leitores de ecrã.
  2. Quando está ativa, clicar na cruz executa o caminho «Recusar tudo» completo, nunca um fecho silencioso.
  3. É revelada pelo runtime, e reetiquetada, num único caso: quando uma recusa não pôde ser registada de todo. Aí já não recusa nada, apenas arruma o aviso — e esse fecho não conta como escolha. Um visitante cujo servidor recusa a decisão não fica preso perante um banner que não consegue fechar.

A primeira camada

  • O título (text_title) e a mensagem (text_message). A mensagem é o único campo com HTML rico do plugin; é renderizada com um wp_kses limitado a <a href target rel>.
  • O link para a política de privacidade: link_privacy_policy, com recurso à página de política declarada no WordPress se a definição estiver vazia.
  • O link para a política de cookies: link_cookie_policy, sem recurso alternativo.
  • O nome do responsável pelo tratamento: legal_company_name ou, na falta dele, o nome do site, apresentado como «Responsável pelo tratamento: …» (art. 13.º, n.º 1, alínea a)).
  • O logótipo: o logótipo personalizado do tema em tamanho medium ou, na falta dele, o ícone do site.

O painel de preferências

Uma linha por categoria disponível, dentro de um role="group" nomeado.

  • Uma categoria forçada mostra um selo textual «Always active», sem interruptor.
  • Uma categoria opcional mostra um <button role="switch"> com aria-checked, aria-labelledby e aria-describedby. O estado é transmitido pelo aria-checked, pela posição do cursor e por uma palavra visível On/Off — nunca apenas pela cor.
  • Alvo tátil aumentado para 44 px em ecrã pequeno (WCAG 2.2 AA, critério 2.5.8).
  • A barra de ações do painel é sticky em baixo, para que «Guardar as minhas escolhas» continue alcançável enquanto a lista rola.

Se o módulo TCF estiver ativo, aparecem dois blocos adicionais: as finalidades TCF alcançáveis e as funcionalidades especiais declaradas. Ver IAB TCF v2.2.

Renovação do consentimento

DefiniçãoPor defeitoLimites
consent_renewal_months120 a 13
consent_policy_hash_checktrue

0 não quer dizer «nunca voltar a perguntar». O valor 0 é convertido em 13 meses, e 13 meses é o teto rígido (deliberação CNIL 2020-091). A duração efetiva está portanto sempre entre 1 e 13 meses, e é esse mesmo valor que governa a duração do cookie, o controlo de caducidade no servidor e a duração publicada nos documentos gerados. A constante OWC_COOKIE_TTL visível no código-fonte é apenas um recurso alternativo.

consent_policy_hash_check volta a pedir o consentimento quando os seus documentos mudam: o cookie leva um hash das páginas de política ligadas, e uma diferença reabre o banner. O hash vale a string vazia quando nenhuma página está ligada — é isso que permite distinguir «desconhecido» de «alterado» e nunca voltar a interpelar um site inteiro com base numa comparação vazia.

Os textos

Sete slots, todos vazios por defeito e por isso traduzidos automaticamente: text_title, text_message, text_accept_all, text_reject_all, text_preferences, text_save, e floating_button_label para o botão flutuante.

No backoffice, cada campo mostra o valor por defeito traduzido como placeholder: deixar vazio conserva o valor por defeito que segue a língua do visitante.

O filtro owc_banner_texts permite substituir estes textos por código. São aceites slots adicionais; os valores não escalares ou vazios são descartados, para que um callback desastrado não possa esvaziar o banner.

Como a escolha é realmente registada

O runtime JavaScript é ES5 estrito — sem arrow functions, sem template literals — para funcionar nos browsers integrados em aplicações e nas WebViews antigas. Vale a pena conhecer a sequência de escrita dele, porque é ela que explica a maioria das mensagens de erro.

  1. Reancoragem da origem. Os URLs REST vêm da configuração do WordPress. Se os seus visitantes navegam noutro host (www contra apex, alias, domínio de pré-visualização, proxy que reescreve o Host), esse URL é cross-origin e o browser recusa guardar o Set-Cookie enquanto o WordPress responde 200. Por isso o runtime reancora o caminho fornecido pelo PHP na origem realmente percorrida.
  2. Nonce. Obtido de fresco em GET /owc/v1/nonce mesmo antes da escrita, nunca embutido em HTML armazenável em cache, enviado no cabeçalho X-OWC-Nonce. A obtenção tem um teto de 4 segundos, e um nonce ausente, vazio ou inalcançável nunca impede a escrita nem é reportado ao visitante.
  3. page_url explícito no corpo do pedido, para que a linha de registo não dependa do cabeçalho Referer, que uma extensão, uma meta-referrer ou um proxy podem retirar.
  4. Guarda antibloqueio de 15 segundos com AbortController: o pedido é cancelado, não apenas ignorado, para que um POST tardio não escreva uma segunda linha.
  5. Definição de sucesso. O POST é um sucesso se, e só se: a resposta for ok, o corpo for JSON analisável, json.ok === true, e o cookie for relido no browser. Uma página de cache, um desafio de edge ou um WAF servido em 200 não contam.
  6. Se o servidor responder cookie_set: false, o runtime escreve ele próprio o cookie com os parâmetros devolvidos e depois relê. Uma falha continua a ser uma falha: nada é publicado, nada é desbloqueado.
  7. Uma única reemissão, e apenas num 403 cujo código seja owc_bad_nonce, rest_cookie_invalid_nonce ou rest_nonce_invalid, e apenas se o nonce obtido for realmente diferente.
  8. O que o runtime publica é o que o servidor guardou: as categorias rejeitadas pelo servidor são subtraídas ao estado local.

Enquanto o POST não tiver tido êxito, nada é desmascarado, nada é desbloqueado, e nenhum sinal «granted» do Consent Mode é emitido.

A taxonomia de erros

O runtime distingue nove causas, cada uma com a sua mensagem visível: network, refused, ratelimit, unexpected, timeout, browser, config, cookie, owc_cookie_not_persisted. O estado HTTP e o código do servidor são colocados no elemento (data-owc-status, data-owc-code) e registados uma vez em console.warn — nunca apresentados como texto visível. Uma captura de ecrã de suporte nomeia, assim, a causa sem ser preciso um traço de rede.

A API JavaScript do banner

window.OWCBanner.show();              // abre em modo banner
window.OWCBanner.hide();              // fecha
window.OWCBanner.openPreferences();   // abre o painel de preferências
window.OWCBanner.openDnsmpi();        // abre o painel com marketing, social
                                      // e preferences já a OFF (entrada CCPA)
window.OWCBanner.reset();             // apaga o cookie no cliente, envia uma retirada,
                                      // e depois recarrega a página
window.OWCBanner.acceptCategory( 'social' );  // devolve uma promessa resolvida a true
                                              // só se o servidor tiver mesmo guardado

Um bootstrap mais leve é impresso no <head> com prioridade 1 e expõe window.OWConsent: .config, .categories, .geo, .profile, .optOut, .state, .has(cat), .refresh(), .paint(), .gcmSignals(state). A configuração dele é filtrável por owc_bootstrap_config — mas tudo o que lá estiver é público e partilhado pela cache: nunca lá ponha um dado próprio de um visitante.

Abrir o painel a partir das suas páginas

Três formas, todas equivalentes:

  • o fragmento de URL #owc-preferences (ou #owc-dnsmpi), tido em conta no carregamento e em hashchange;
  • qualquer elemento com a classe owc-open-preferences ou o atributo data-owc-open;
  • o atributo data-owc-dnsmpi="1" para forçar o comportamento «Do Not Sell».

Um evento owc:consent-changed é difundido a cada alteração, com o payload enviado em detalhe. É escutado pelo bootstrap, pelo botão flutuante, pelo runtime CCPA e pelo módulo TCF; também o pode escutar.

Limitações do banner

  • Tudo depende de wp_head() e wp_footer(): um tema que não os chame não obtém nada.
  • Sem window.fetch e window.Promise, nenhuma escrita é possível e é mostrado o erro browser.
  • Um consentimento pode ser registado no servidor sem ficar guardado no browser (endereço do site diferente do host percorrido, armazenamento bloqueado, jar cheio). O runtime deteta este caso, recusa contá-lo como sucesso, mostra uma mensagem dedicada e transmite o facto ao servidor na escrita seguinte.
  • O banner não tem shortcode: aparece em todo o lado ou em lado nenhum.

O bloqueio automático

É o módulo que transforma uma escolha em efeito real. Reescreve as etiquetas portadoras de rastreadores antes de qualquer consentimento, para toda a gente, e deixa o runtime libertá-las no browser.

Dois modos, uma única definição

compliance_strict, ativo por defeito.

  • Desativado (modo flexível): só o filtro script_loader_tag fica ligado. Por outras palavras, só os scripts registados por wp_enqueue_script() são reescritos. Um <script> escrito à mão no tema, um embed, uma iframe, um pixel <img>: nada é tocado.
  • Ativado (modo estrito): além do filtro, um buffer de saída captura o documento inteiro, e a marcação renderizada através da API REST é tratada também. No backoffice, se o pedido for um admin-ajax.php, um buffer separado cobre as respostas que servem também o front-end («carregar mais», arquivos filtrados) — mas apenas se o handler tiver ele próprio declarado Content-Type: text/html.

O que nunca é passado por buffer

O buffer estrito sai de imediato para: o backoffice, os pedidos AJAX, os feeds, robots.txt, os trackbacks, o cron, os favicons, os pedidos REST, os pedidos JSON, a pré-visualização do personalizador, os sitemaps, e os pontos de entrada wp-login.php, wp-register.php, wp-signup.php, xmlrpc.php, wp-cron.php, wp-trackback.php — testados pelo nome exato do script, nunca como substring do URL. Sai também para os pedidos do scanner, que precisam do HTML em bruto.

As cinco passagens

A reescrita é feita por expressões regulares calibradas, nunca por um parser de DOM. Três salvaguardas enquadram o conjunto: um documento com mais de 8 MB é devolvido intacto, uma falha do motor PCRE devolve o documento intacto, e se o resultado tiver menos de metade do tamanho original, é devolvido o original. Por outras palavras, o modo de falha do bloqueio é «página servida sem bloqueio», nunca «página partida».

Passagem 0 — mascaramento. Comentários HTML, <style> e <textarea> são substituídos por marcadores feitos de caracteres de controlo, para que nenhum padrão posterior possa corresponder lá dentro.

Passagem 1 — <script>. Decisão, por esta ordem:

  1. já tratado → intacto;
  2. script do próprio plugin → intacto;
  3. type="text/plain" ou type não-JS (ld+json, importmap, x-template) → intacto;
  4. URL procurado em src, data-src, data-rocket-src, data-lazy-src, data-litespeed-src, data-cfsrc — os atributos dos plugins de desempenho ficam assim cobertos;
  5. host na lista de permissões → intacto; recurso first-party que não se pareça com um rastreador → intacto;
  6. correspondência com o catálogo na forma host + caminho; um URL data: ou javascript: é descodificado e julgado como um corpo inline;
  7. uma regra classificada como necessary nunca é bloqueada (Stripe.js, reCAPTCHA, Turnstile, cdnjs…): bloqueá-la não ganha conformidade nenhuma e parte o snippet que a chama;
  8. nenhuma correspondência → blocker_unknown_script_policy, por defeito allow;
  9. caso contrário, reescrita para type="text/plain" com data-owc-cat, data-owc-vendor, data-owc-src e, se o type de origem fosse especial (module, por exemplo), data-owc-type, para não degradar o script restaurado.

Os corpos inline são comparados primeiro com as regras de catálogo do tipo inline_signature, e depois com catorze assinaturas ancoradas escritas no código: fbq(, _fbq.push, gtag(, dataLayer.push(, ga('…, _gaq.push, _paq.push, hjid, clarity(, ttq.load|track|page, snaptr(, twq(, lintrk(, pintrk(. Exceção explícita: um gtag('consent', …) sozinho, ou seja, uma declaração de valores por defeito do Consent Mode sem outro marcador, continua a ser executado.

Passagem 2 — <iframe>. Mesmos atributos de URL, mais data-original e data-srcset. First-party ou lista de permissões → intacto. necessary → intacto, senão um formulário com captcha ficaria impossível de submeter. Sem correspondência → blocker_unknown_iframe_policy, por defeito block. A iframe bloqueada passa a src="about:blank" e é envolvida num substituto visual com um botão «Aceitar » que desbloqueia a categoria correspondente.

Passagem 3 — <img>, apenas pixels de tracking. Regra importante: uma linha de catálogo, sozinha, nunca chega para neutralizar uma imagem. Se a imagem corresponder ao catálogo mas a categoria dela não for analytics nem marketing — um CDN de imagens, um serviço de gravatares, um alojamento de tipos de letra — fica intacta, senão estar-se-ia a apagar os media do site em vez de um rastreador. Só a heurística de etiqueta pode bloquear: onze pontos de recolha conhecidos (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 uma imagem de terceiros de 1×1 pixel. O src é então substituído por um GIF transparente, e todos os atributos portadores de URL são removidos, para que um lazy loader não restaure o original.

Passagem 4 — <link>. Condicionada a blocker_block_resource_hints, por defeito true. Trata apenas os hosts já presentes no catálogo, nunca um desconhecido.

  • As sugestões de recursos (preconnect, dns-prefetch, prefetch, prerender, preload, modulepreload) para um terceiro catalogado são removidas, não adiadas: uma sugestão abre uma ligação TCP+TLS e divulga o IP e a impressão TLS do visitante, e não há nada para restaurar depois.
  • Uma folha de estilo classificada como analytics ou marketing é neutralizada.
  • O Google Fonts e os outros hosts de tipos de letra classificados como functional nunca são tocados: neutralizá-los dá texto em tipo de letra alternativo em todo o site, sem qualquer ganho de conformidade.

Passagem 5 — <object>, <embed>, <source>, <video>, <audio>. Nunca há bloqueio por defeito: só um terceiro já catalogado é tratado. Os atributos autoplay e preload são retirados.

O que conta como «first party»

O URL do site, o URL do WordPress, o URL de conteúdo, o URL dos includes, os equivalentes de rede em multisite, e a base dos uploads. O www. é retirado na comparação. Qualquer caminho que comece por /wp-content/ ou /wp-includes/ é first-party, seja qual for o host, para cobrir as reescritas de CDN. Os esquemas não-HTTP (data:, blob:, javascript:) nunca são first-party.

Contra-exceção: um URL que contenha gtag, gtm.js, analytics, pixel, fbevents, hotjar, matomo, piwik ou clarity é tratado mesmo no host do site. É isso que apanha um GTM ou um Matomo auto-alojado, e os proxies first-party.

O runtime de desbloqueio

Impresso no <head> com prioridade 2. A configuração JSON dele contém apenas o nome do cookie e a lista de hosts vigiados — nada que dependa do visitante.

  • Leitura de estado: o cookie owc_consent é descodificado até estabilizar, com um máximo de três passagens, porque os cookies escritos antes da 1.4.3 vinham codificados duas vezes.
  • Guarda dos scripts injetados dinamicamente: o setter HTMLScriptElement.prototype.src e o setAttribute são envolvidos. Um loader first-party que atribua s.src = 'https://www.googletagmanager.com/gtm.js?id=…' é intercetado e o elemento é carimbado antes de executar. A lista vigiada tem vinte hosts: 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. Tudo o que lá não figure passa sem ser incomodado. Os hosts da sua lista de permissões são retirados desta lista antes da impressão.
  • Reinjeção pela ordem do documento. Um script externo restaurado bloqueia a fila até ao onload ou onerror dele, com um prazo máximo de 5 segundos para que um fornecedor inalcançável não bloqueie o resto. O atributo async só é aplicado se estivesse presente de origem — sem isso, um script criado por createElement seria forçado a assíncrono e o snippet de configuração correria antes da biblioteca dele. Para um script inline restaurado, o document.write é temporariamente redirecionado, para não apagar o documento.
  • Retirada de consentimento: se uma categoria já aplicada passar explicitamente a false, o runtime recarrega a página. Um script já executado não pode ser descarregado (art. 7.º, n.º 3 do RGPD). Uma categoria simplesmente ausente do payload não é uma retirada.
  • Um MutationObserver volta a analisar a marcação injetada depois do carregamento (AJAX, secções lazy).
  • Um clique no botão de um embed bloqueado chama OWCBanner.acceptCategory(); se a API do banner não existir, o embed é desbloqueado localmente, sem persistir nada.

O catálogo de assinaturas

Tabela {prefix}owc_scripts. O ficheiro incluído, data/tracker-catalog.json, contém 175 linhas que cobrem 71 fornecedores distintos:

DistribuiçãoDetalhe
Por alvo110 padrões de URL, 65 nomes de cookies
Por tipo172 fragmentos de texto, 3 expressões regulares
Por categorianecessary 55, marketing 37, functional 37, analytics 36, social 10

Colunas: 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.

Dois comportamentos a conhecer:

  • As linhas match_target = 'cookie' nunca são usadas pelo bloqueio. Servem apenas para a tabela de cookies dos documentos gerados. Compará-las como substring de URL era precisamente o que partia o JavaScript dos sites.
  • Um padrão do tipo host que vise um URL, com menos de seis caracteres e sem ponto, é recusado: «fr» ou «IDE» corresponderiam a «frame.js» e a «provider.js».

O catálogo é guardado na cache de objetos (1 hora) e num transient (12 horas). Um resultado vazio nunca é guardado em cache. Qualquer escrita dispara a ação owc_catalog_updated, que esvazia estas caches.

O separador Tracker catalogue permite acrescentar, alterar, apagar e procurar regras, com um filtro «só as regras que não conseguem disparar»: padrão vazio ou invisível, expressão regular que não compila ou que faz backtracking catastrófico, padrão demasiado curto e sem ponto para um URL, categoria que já não existe. Este diagnóstico é feito em PHP, porque só o motor PCRE pode dizer se uma expressão compila.

Definições do bloqueio

DefiniçãoPor defeitoValores
compliance_stricttruebooleano
blocker_unknown_script_policyallowallow, block
blocker_unknown_iframe_policyblockallow, block
blocker_block_resource_hintstruebooleano
blocker_allowlist''um host por linha

Pontos de extensão

// Hosts nunca bloqueados, além de blocker_allowlist.
add_filter( 'owc_blocker_allowlist', function ( array $hosts ) {
    $hosts[] = 'cdn.o-meu-parceiro.example';
    return $hosts;
} );

// Linhas do catálogo antes da validação.
add_filter( 'owc_scripts_catalog', function ( array $rows ) {
    $rows[] = array(
        'pattern'      => 'tracker.example.com',
        'pattern_type' => 'host',
        'match_target' => 'url',
        'name'         => 'Exemplo',
        'vendor'       => 'Exemplo SA',
        'category'     => 'analytics',
    );
    return $rows;
} );

O método público OWC_Blocker::block_html_fragment( $html ) aplica o bloqueio a um fragmento que não tenha passado pelo buffer de saída.

Limitações do bloqueio

  1. Sem compliance_strict, só os scripts enfileirados pelo WordPress são tratados.
  2. Um script de terceiros desconhecido é autorizado por defeito. O bloqueio real assenta no catálogo e nas assinaturas inline — enriqueça-os.
  3. A guarda dos scripts injetados por JavaScript cobre apenas vinte hosts; um rastreador fora da lista, injetado por código first-party, passa.
  4. As folhas de estilo classificadas como functional (tipos de letra web) são deliberadamente deixadas passar.
  5. Um rastreador catalogado em <img> mas classificado de forma diferente de analytics ou marketing não é neutralizado.
  6. Os media (object, embed, source, video, audio) nunca são bloqueados por defeito: só um terceiro já catalogado é tratado.
  7. Um documento com mais de 8 MB, uma falha do PCRE ou uma perda de mais de 50 % do conteúdo dão uma página servida sem qualquer bloqueio, em silêncio.
  8. A retirada de consentimento provoca um recarregamento completo da página.

O scanner de rastreadores

O scanner inventaria o que as suas páginas carregam realmente. Está desativado por defeito: ative-o no separador Scanner.

Três fontes de deteção

Cada deteção guarda a proveniência dela e a informação «o bloqueio consegue agir sobre isto».

1. O HTML servido. O scanner vai buscar uma amostra dos URLs do seu próprio site e lê a marcação: <script src> e <script data-owc-src> — os scripts já bloqueados são, portanto, vistos na mesma —, assinaturas inline, <iframe>, folhas de estilo externas e imagens cujo URL contenha 1x1, pixel, track, beacon ou impression. Nenhum JavaScript é executado: o que um gestor de tags injeta em runtime é invisível para esta passagem.

2. Os cabeçalhos Set-Cookie. São os únicos cookies que o varrimento do servidor consegue provar, incluindo os cookies HttpOnly que uma sonda de browser nunca verá. Ficam marcados como não bloqueáveis: nada no bloqueio do lado do cliente pode reter um cookie emitido pelo servidor. Os cookies do próprio plugin são ignorados.

3. A sonda de browser. Impressa no rodapé apenas para um utilizador com sessão iniciada que tenha manage_options, e só se o scanner estiver ativo. Fotografa o document.cookie no carregamento, observa as mutações do DOM — é isso que apanha o que um gestor de tags injeta —, consulta o document.cookie de 5 em 5 segundos para os cookies escritos no cliente, e transmite as observações dela de 2 em 2 segundos e em beforeunload. A resposta que leva a sonda é marcada como não armazenável em cache.

Limitação reconhecida no código: um administrador já aceitou tudo, em geral. A sonda descreve portanto o estado pós-consentimento, não o estado pré-consentimento.

O varrimento

A fila de URLs é construída para cobrir templates, não páginas: primeiro os URLs essenciais (página inicial, página inicial estática, página dos artigos, e as quatro páginas de políticas ligadas), e depois um entrelaçamento em rotação por família — até 30 páginas, 15 artigos, 5 por tipo de conteúdo público personalizado, 3 categorias e 3 etiquetas. Sem este entrelaçamento, trinta páginas quase idênticas comiam todo o orçamento e os templates do WooCommerce nunca eram alcançados.

O filtro owc_scanner_urls permite acrescentar URLs, mas o resultado é novamente restringido ao host do site: o scanner nunca sai do seu domínio.

Cada pedido é feito com os redirecionamentos seguidos à mão (dois saltos no máximo, cada salto novamente testado contra o host do site), uma resposta limitada a 2 MB, um cabeçalho Cache-Control: no-cache, no-store e um parâmetro de URL único para não ler uma página de cache, e um user-agent OW-Consent-Scanner/<versão>. Uma resposta não-HTML é contada como ignorada, não como falha.

Um segredo de contorno é enviado no cabeçalho X-OWC-Scanner para que o bloqueio se retire e deixe ver o HTML em bruto. É comparado em tempo constante, e o cabeçalho é testado primeiro, para que um visitante comum nem sequer desencadeie a leitura da opção.

Orçamento, bloqueio, retoma

RestriçãoValor
URLs por execuçãoscanner_max_urls, por defeito 25, limites 1 a 500
Tempo limite por pedidoscanner_timeout, por defeito 8 s, limites 1 a 60
Orçamento de tempo real por lotemax_execution_time − 10 s, senão 45 s, limitado entre 5 e 60 s
Bloqueio de execução15 minutos
Tempo de vida da fila6 horas

Se o orçamento se esgotar, é agendada uma retoma um minuto mais tarde e o varrimento continua onde tinha parado. Um segundo arranque enquanto um varrimento corre responde «já está um scan em curso». Curto-circuito: se três pedidos falharem sem que nenhuma página tenha sido lida, o varrimento para em vez de queimar o tempo dele em mais vinte e dois URLs — é o caso típico de um site cujo loopback HTTP está bloqueado.

As assinaturas inline reconhecidas

Vinte e três agulhas, agrupadas em vinte e uma etiquetas: 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.

A chave de desduplicação é a assinatura, nunca um hash do corpo: um verdadeiro snippet do gtag ou do Pixel contém valores próprios da página, o que daria uma linha por página.

O ciclo de retorno: deteção → catálogo → bloqueio

É o ponto que torna o scanner útil. Classificar um rastreador escreve uma regra no catálogo de bloqueio. Um clique na categoria de uma deteção faz três coisas: escreve a categoria na linha, memoriza a sua decisão manual, e cria a regra correspondente. No fim de cada varrimento completo, a mesma operação é feita em massa para aquilo que o catálogo ainda não sabe classificar.

Regras de conversão:

  • uma deteção do tipo cookie torna-se uma regra match_target = cookie sobre o nome do cookie;
  • uma deteção do tipo inline_script só escreve uma regra se a agulha de origem for reencontrada. Sem ela, nenhuma regra é escrita: uma regra construída a partir da etiqueta «Google gtag (config)» estaria morta para sempre e corresponderia ao nome da deteção, o que a faria passar por «coberta» para sempre;
  • caso contrário, uma regra host sobre o domínio, recusada se o domínio estiver vazio.

As suas decisões manuais são guardadas à parte (500 no máximo) e reaplicadas a cada nova observação, porque a flag «confirmado» significa também «uma regra de catálogo correspondeu» e não pode, por isso, transportar sozinha a informação «o administrador decidiu». Reclassificar como «não categorizado» apaga a entrada: é uma verdadeira marcha atrás.

O catálogo é comparado do padrão mais longo para o mais curto, para que uma regra ampla (google-analytics.com) não esmague a regra mais precisa que escreveu (www.google-analytics.com).

Agendamento, alertas, retenção

scanner_frequency aceita hourly, twicedaily, daily e weekly — mas o backoffice só propõe as recorrências que a sua instalação conhece realmente. Por defeito: weekly. Desativar o scanner desagenda os dois eventos.

Dois e-mails distintos, nunca os dois ao mesmo tempo:

  1. Rastreadores não categorizados — enviado apenas para identificadores realmente novos, com uma memória limitada a 500 entradas.
  2. «O varrimento não conseguiu ler este site» — limitado a uma mensagem por semana e por assinatura de erro. É o modo de falha mais grave, porque um varrimento que não lê nada não produz nenhuma deteção e, portanto, nenhum alerta do primeiro tipo.

Destinatário: scanner_alert_email ou, na falta dele, o endereço de administração do site.

Retenção: 90 dias. As deteções não revistas há 90 dias são apagadas, assim que pelo menos uma página tenha sido lida — não apenas quando um varrimento termina.

O painel de cobertura

É a parte mais importante do separador Scanner, e alimenta também a caixa de aviso dos documentos gerados. Responde sempre a estas perguntas:

  • já foi lançado algum varrimento?
  • o último terminou, ou foi interrompido pelo orçamento de tempo dele?
  • quantas páginas foram realmente obtidas (não retiradas da fila)?
  • quantas não puderam sê-lo, e qual foi o primeiro erro?
  • algum browser reportou alguma coisa, ou nunca foi observado JavaScript nenhum?
  • qual é a janela para lá da qual um rastreador não revisto desaparece?

Um varrimento que não alcançou página nenhuma é apresentado como uma falha, não como um resultado limpo. É este painel que é preciso ler antes de publicar uma política de cookies construída sobre estas deteções.

O separador Scanner

Três cartões (rastreadores detetados, não categorizados, último varrimento e próxima execução), o painel de cobertura, um botão «lançar um scan agora», o formulário de agendamento, e depois a lista das deteções: filtros por categoria, pesquisa de texto, filtro por tipo de elemento (script, inline_script, iframe, stylesheet, pixel, cookie, link, preconnect), filtro por estado, exportação CSV, paginação de 25. Classificação individual ou em massa — o seletor de massa tem um primeiro elemento vazio desativado, para que uma submissão acidental não se transforme numa desclassificação geral.

Definições do scanner

DefiniçãoPor defeitoLimites
scanner_enabledfalsebooleano
scanner_frequencyweeklyhourly, twicedaily, daily, weekly
scanner_max_urls251 a 500
scanner_timeout81 a 60
scanner_probe_modeadminsadmins, off
scanner_alert_email''endereço de e-mail

A saber: scanner_probe_mode não é consumido nesta versão. O código assinala-o explicitamente — a condição de impressão da sonda já não lê esta definição e baseia-se apenas em scanner_enabled. Gravar off guarda o valor sem desativar a sonda. Para parar realmente a sonda, desative o scanner.

Limitações do scanner

  1. Nenhum JavaScript é executado pelo varrimento do servidor. Sem a sonda, o que um gestor de tags injeta está ausente do inventário.
  2. A sonda está reservada aos administradores com sessão iniciada e não existe modo nenhum que a execute para um visitante comum: isso exigiria informação prévia própria.
  3. Um cookie colocado por cabeçalho Set-Cookie é detetado mas não é bloqueável.
  4. O varrimento nunca sai do domínio e está limitado a 25 URLs por defeito: um site grande nunca é coberto na íntegra.
  5. Num alojamento onde o loopback HTTP esteja bloqueado (autenticação HTTP de pré-produção, firewall), o varrimento não lê nada.
  6. Uma deteção não revista há 90 dias desaparece e, portanto, desaparece também da política de cookies.
  7. O scanner depende do WP-Cron: num site com DISABLE_WP_CRON e sem cron de sistema, o varrimento agendado não dispara.

O gerador de documentos legais

Os quatro documentos

TipoConteúdoLínguas disponíveis
cookie_policyPolítica de cookiesfrancês, inglês, português
privacy_policyPolítica de privacidadefrancês, inglês, português
legal_noticeAviso legalsó francês
termsTermos e condiçõessó francês

Cada documento gerado é uma página WordPress, versionada por metadados: _owc_policy_type, _owc_policy_version (incrementada a cada geração), _owc_policy_hash (hash SHA-256 do HTML), _owc_policy_generated_at, _owc_policy_profile, _owc_policy_lang, _owc_policy_manual_edit.

Depois da geração, o link é reinjetado nas definições correspondentes (link_cookie_policy, link_privacy_policy, link_legal_notice, link_terms), e a publicação de uma política de privacidade atualiza a página de política declarada no WordPress.

Dois travões antes da publicação

Travão 1 — os campos obrigatórios. A geração é recusada com a lista das chaves vazias, em vez de publicar cláusulas com buracos.

DocumentoCampos exigidos
Todoslegal_company_name, legal_company_email
Exceto a política de cookies+ legal_company_address, legal_country
Aviso legal+ legal_company_phone, legal_publication_director, legal_host_name, legal_host_address, legal_host_phone
Aviso legal, países FR / BE / LU+ legal_company_legal_form, legal_company_reg_number

Travão 2 — a língua. O gerador recusa publicar um documento redigido numa língua que a jurisdição não usa. Em pré-visualização, o documento sai com uma faixa vermelha de aviso; na publicação, é uma recusa liminar. No separador Policies, o botão de geração fica escondido quando não existe modelo para o tipo e o perfil ativos, em vez de aparecer e falhar sistematicamente.

Resolução da língua

PerfilLíngua do documento
quebecfrancês
lgpdportuguês
gdpr com legal_country ∈ {FR, BE, LU}francês
Todo o restoinglês

O aviso obrigatório

Enquanto policy_disclaimer estiver ativo — e está por defeito — cada documento termina com um bloco que diz que se trata de um modelo gerado automaticamente, que deve ser revisto por um profissional qualificado antes de ser publicado, e que traz a data de geração e a versão do plugin. Só o desative com plena consciência do que faz.

A tabela de rastreadores — as regras de honestidade

A tabela publicada na política de cookies vem da sua tabela de scanner, não de uma base de terceiros. Cinco regras governam o que ela mostra.

  1. Janela de frescura: só são publicadas as deteções revistas nos últimos 90 dias. Se a coluna de última observação ainda não existir porque uma migração não passou, a janela é ignorada em vez de publicar «nenhum rastreador» num site que os tem — a subdivulgação é a única direção em que um documento legal nunca deve falhar.
  2. Caixa de aviso à cabeça da tabela, derivada do painel de cobertura do scanner: varrimento nunca lançado, nenhuma página lida, varrimento interrompido, N URLs em falha, nenhuma observação com JavaScript ativo, janela de N dias. Um varrimento incompleto é divulgado, não publicado como um inventário terminado.
  3. Os rastreadores não classificados não são escondidos: têm a secção própria deles. São aqueles que ninguém examinou.
  4. Um cookie observado num cabeçalho Set-Cookie é marcado com um sinal distintivo, com uma nota a explicar que é depositado pelo servidor e que nenhum bloqueio do lado do cliente o consegue reter. Não deve, por isso, ser apresentado como condicionado ao consentimento, e a cláusula que afirma que os rastreadores não classificados só são depositados depois do consentimento leva a exceção correspondente.
  5. O nome do responsável e o link para a política dele vêm do catálogo, apenas para as regras que visam um URL. Os padrões de nome de cookie ficam excluídos: não se afirma num documento legal quem trata dados sem prova.

Os cookies do site e as durações publicadas

A secção «cookies depositados por este site» lista os cookies do próprio plugin: owc_consent (duração derivada da definição de renovação), euconsent-v2 se o TCF estiver ativo, owc_geo se a deteção regional estiver ativa, owc_gpc (duração de sessão), owc_gpc_notice (5 minutos), mais os cookies de sessão e de definições do WordPress.

A tabela dos prazos de conservação tira os números das definições reaisledger_retention_days, retention_form_data_days, retention_dsar_days e a duração de renovação do consentimento. Nenhum valor decorativo é publicado. A duração do cookie de consentimento é derivada do mesmo cálculo que o próprio cookie, o que garante que uma definição de 0 meses publica mesmo «13 meses» e não «nunca».

Deteção do tipo de atividade (termos e condições)

Os termos e condições têm 21 secções, com alternância vendedor/prestador, produtos/serviços, orçamento/encomenda, e cláusulas específicas por tipo de atividade.

legal_business_type (auto, vitrine, rental, ecommerce, services, saas, content) é a fonte de verdade. Quando vale auto, uma heurística examina o site — presença do WooCommerce, registo aberto, plugins de subscrição, página de preços, páginas de serviços, e depois densidade de palavras-chave nos últimos cinquenta conteúdos publicados — mas o veredicto dela nunca é impresso num documento publicado: serve apenas para escolher as cláusulas opcionais.

As citações de artigos franceses só são inseridas se legal_country valer FR. A Bélgica tem as citações próprias dela (CDE art. VI.45 §1.º, art. VI.47, Serviço de mediação para o consumidor); o Luxemburgo e o Quebec recebem uma formulação neutra. Citar um texto não verificado seria o defeito, não o remédio.

Outros comportamentos

  • A data do documento é formatada na locale do documento. Se o pacote de tradução correspondente não estiver instalado, o formato passa para dd/mm/aaaa: um documento francês não pode abrir com «4 September 2026».
  • Edição manual detetada: se o conteúdo guardado já não corresponder ao hash dele, é registada uma revisão antes de sobrescrever e o ecrã mostra o selo «alterações manuais substituídas», com um link para as revisões.
  • O gerador nunca despromove: uma página publicada continua publicada mesmo que a caixa «publicar» seja desmarcada, e um título ou uma ligação permanente que tenha renomeado sobrevive a uma regeneração.
  • Injeção no menu de rodapé: auto_footer_menu_inject, desativado por defeito. O plugin não modifica o seu site público sem pedido explícito. Localizações reconhecidas quando o ativa: footer, footer-menu, footer_menu, footer-1, footer_1, secondary, legal.

Pontos de extensão

// Corpo em bruto do modelo, com as {{variáveis}} ainda por substituir.
add_filter( 'owc_policy_template', function ( $html, $type, $profile ) {
    return $html;
}, 10, 3 );

// As variáveis injetadas no modelo.
add_filter( 'owc_policy_vars', function ( array $vars, $type, $profile ) {
    return $vars;
}, 10, 3 );

// O HTML final, já com as variáveis substituídas.
add_filter( 'owc_policy_html', function ( $html, array $vars ) {
    return $html;
}, 10, 2 );

Fornecer o seu próprio corpo através de owc_policy_template desativa o travão de língua: um site que fornece o texto dele assume a língua desse texto. É a via oficial para publicar um aviso legal que não seja em francês.

Estão disponíveis quatro blocos pré-gerados como variáveis reservadas: {{__trackers_table__}}, {{__categories_list__}}, {{__retention_table__}}, {{__jurisdictional_rights__}}. Todas as variáveis vêm já escapadas no contexto de uso delas. Três métodos públicos são reutilizáveis por um modelo de terceiros: OWC_Policies::build_cookie_table(), build_data_retention_table() e render_first_party_cookies().

Rotas REST

MétodoCaminhoParâmetrosAcesso
POST/owc/v1/policies/generatetype (obrigatório), publish (booleano, por defeito false)manage_options
GET/owc/v1/policies/previewtype (obrigatório)manage_options

O HTML da pré-visualização passa por wp_kses_post() antes de ser devolvido.

Limitações do gerador

  1. São modelos, não aconselhamento jurídico. Mande-os rever.
  2. Aviso legal e termos e condições só existem em francês. Qualquer outra jurisdição esbarra no travão de língua, a não ser que forneça o seu próprio corpo.
  3. Só três línguas: francês, inglês, português.
  4. Quatro países do EEE têm autoridade nomeada; nos restantes, uma formulação genérica.
  5. A tabela de rastreadores vale o que valer o seu varrimento — e o documento di-lo.
  6. A classe do gerador pesa cerca de 440 KB de modelos jurídicos: é carregada preguiçosamente, apenas quando um dos três pontos de entrada é realmente solicitado.

O registo de consentimento

O registo é a resposta ao artigo 7.º, n.º 1 do RGPD: poder demonstrar que a pessoa consentiu. Está ativo por defeito.

A tabela

{prefix}owc_ledger, com todas as datas em UTC:

ColunaTipoConteúdo
idbigintChave primária
created_atdatetimeData/hora UTC
visitor_tokenchar(32)Token pseudónimo do browser, 32 caracteres hexadecimais
eventvarchar(20)accept_all, reject_all, save_preferences, withdraw, auto, gpc_opt_out
categoriesvarchar(255)Lista das categorias autorizadas
profilevarchar(20)Perfil de conformidade em vigor no momento da ação
sourcevarchar(60)banner, preferences, footer_link, api, auto
ip_pseudonymousvarchar(45)IP truncado
ua_hashchar(64)Hash com sal do user-agent
page_urlvarchar(500)Página onde a ação ocorreu
prev_hashvarchar(128)Hash da linha anterior
row_hashvarchar(128)Hash desta linha
policies_hashchar(64)Hash dos documentos em vigor
banner_revisionvarchar(40)Hash do banner realmente mostrado
plugin_versionvarchar(20)Versão do plugin no momento da escrita

As três últimas colunas permitem reconstituir o que o visitante viu, não apenas o que ele assinalou.

A cadeia

Cada linha é assinada por HMAC sobre uma serialização canónica prefixada em comprimento do conteúdo dela e do hash da linha anterior. A génese é uma sequência de 64 zeros. A escrita faz-se dentro de uma transação, com um bloqueio de linha sobre a última entrada: é esse bloqueio que serializa realmente as escritas concorrentes e impede a cadeia de bifurcar. Um bloqueio nomeado do MySQL é usado como segunda barreira, na medida do possível: se for recusado, a inserção prossegue e uma ação é disparada para que possa segui-la.

Os valores são truncados antes da assinatura, para que o valor assinado seja exatamente o valor guardado.

A honestidade sobre a prova de adulteração

O plugin determina ele próprio a proveniência da chave de assinatura dele:

ProveniênciaCondiçãoVeredicto
constantOWC_LEDGER_KEY está definidaÀ prova de adulteração
wp-configAUTH_KEY e AUTH_SALT estão definidas, não vazias, diferentes uma da outra e sem a frase por defeitoÀ prova de adulteração
databaseCaso contrárioNão está à prova de adulteração

No terceiro caso, o WordPress guarda os salts na base de dados: quem tenha acesso à base de dados pode voltar a assinar a cadeia. O plugin reporta-o no resultado da verificação e mostra um aviso de administração que o diz por palavras claras. É conservado um hash não reversível da chave, o que permite distinguir uma rotação de salts de uma reescrita.

É por esta razão que a secção Instalação recomenda definir OWC_LEDGER_KEY.

A verificação

O botão «Verify the chain now» do separador Audit ledger — ou a rota GET /owc/v1/ledger/verify — percorre toda a cadeia em lotes de 500 linhas.

São reconhecidos três esquemas de assinatura: o esquema canónico atual, um esquema histórico anterior, e um esquema sem chave vindo das primeiras versões. Este último produz um hash que qualquer pessoa com a base de dados pode recalcular: nunca é considerado válido, é contado à parte e assinalado como uma quebra. Um resumo sem chave não é uma prova.

Quebras reportadas: hash_mismatch, chain_break, bad_genesis, unkeyed_rows, table_emptied, tail_truncated, head_mismatch, count_mismatch.

O resultado traz, entre outros: ok, total, checked, table_total, broken_at, breaks, break_count, partial, legacy_rows, unverifiable_rows, anchor, anchor_ok, key_source, tamper_evident, key_rotated.

A âncora de cabeça

É conservada uma âncora fora da tabela: identificador, hash, número de linhas, data/hora. Sem ela, apagar as linhas mais recentes ou esvaziar a tabela não deixaria rasto nenhum. É disparada uma ação a cada deslocação da âncora, e o código convida explicitamente a replicá-la fora da base de dados — ficheiro, syslog, endpoint externo — para tornar detetável um reencenamento global.

O plugin recusa sobrescrever uma âncora existente e assinala honestamente que uma âncora derivada da própria tabela só torna detetáveis os truncamentos posteriores.

A retenção

ledger_retention_days, por defeito 1825 dias (5 anos), limites 0 a 3650. O valor 0 significa conservação ilimitada.

A poda corre no cron diário próprio dela e retira apenas um prefixo contíguo: nunca um buraco a meio da cadeia. O hash da última linha apagada é memorizado para que o resto continue verificável. Teto por execução: 20 000 linhas (40 lotes de 500). Um atraso muito grande de poda resolve-se, portanto, ao longo de vários dias.

Minimização de dados

  • Endereço IP: IPv4 com o último octeto a zero (o /24 é conservado), IPv6 truncado ao /48 com os 80 bits restantes a zero. O mascaramento é feito sobre a forma binária; um endereço IPv6 que mapeie um IPv4 é tratado como IPv4.
  • User-agent: só é guardado um hash com sal, nunca a string.
  • page_url: validado contra os hosts do site. Quem chama é a autoridade — se fornecer o valor, mesmo vazio, é essa a resposta; o Referer só é consultado se nada tiver sido dito de todo, e é validado da mesma forma.
  • Token de visitante: 32 caracteres hexadecimais vindos de um gerador criptográfico, sem ligação a uma identidade.

O algoritmo

ledger_hash_algo aceita sha256 (por defeito) e sha3-256, intersetados com os algoritmos que o seu PHP suporta realmente. A definição é mostrada mas não modificável no backoffice: mudar de algoritmo faria falhar a verificação de todas as linhas existentes. As colunas estão dimensionadas mais largas, mas a escolha real limita-se a estes dois algoritmos.

O separador Audit ledger

Lista paginada de 25: identificador, data local e data/hora UTC em bruto, token de visitante, evento, categorias, perfil, IP pseudonimizado, e o elo de cadeia (hash anterior → hash atual, truncados na apresentação, valor completo na dica).

Filtros: token de visitante (32 hexadecimais), evento, perfil, intervalo de datas — introduzidos no fuso horário do site e comparados com as datas/horas UTC realmente guardadas.

Exportações CSV e JSON. O JSON leva um envelope (formato, algoritmo, proveniência da chave) para que uma autoridade possa voltar a verificar o extrato sem dispor do resto da cadeia. Todas as células CSV são neutralizadas contra a injeção de fórmulas de folha de cálculo.

Consultar o registo pela API

GET /owc/v1/ledger aceita page, per_page (1 a 200, por defeito 50), visitor_token, from e to. Os três últimos permitem responder a um pedido de acesso (art. 15.º) sem percorrer toda a cadeia. Os limites são lidos em UTC, e uma data no formato AAAA-MM-DD é alargada ao dia inteiro. Os filtros realmente aplicados são devolvidos na resposta: um filtro rejeitado pelas verificações não deve ler-se como «aqui está o registo todo».

O que acontece quando o registo recusa uma escrita

É o comportamento mais importante de todo o plugin. Se o registo estiver ativo e a escrita da linha falhar, o cookie de consentimento é anulado e a resposta é um 503. Nada é guardado, nenhum rastreador é libertado.

O raciocínio é direto: um consentimento que não se consegue provar não deve ser reivindicado. Ver Resolução de problemas para o procedimento a seguir.

Pontos de extensão

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 é o gancho a usar para replicar a âncora fora da base de dados.

Limitações do registo

  1. A prova de adulteração é condicional, e o plugin di-lo ele próprio.
  2. As linhas anteriores à 1.2.0 trazem um resumo sem chave: nunca são voltadas a assinar — a reassinatura deixaria um atacante fazer reassinar um histórico falsificado — e impedem o veredicto «cadeia intacta» enquanto subsistirem.
  3. A retenção está limitada a 20 000 linhas por execução diária.
  4. O registo não é apagado pelo apagador de dados pessoais do WordPress: é uma cadeia de hashes, retirá-la destruiria a prova que ela existe para transportar, e contém apenas um token pseudónimo e um IP truncado. Uma mensagem explica esta opção ao titular dos dados.

O portal de direitos (DSAR)

O portal permite a uma pessoa exercer os direitos dela a partir de uma página do seu site. Está ativo por defeito, mas só aparece onde colocar o shortcode.

O formulário

[owc_dsar_form]

Atributos:

AtributoPor defeitoFunção
typesaccess,rectification,erasure,portability,restrict,object,optoutTipos propostos, separados por vírgulas
title«Exercer os meus direitos sobre os meus dados pessoais»Título do bloco
submit_label«Enviar o meu pedido»Texto do botão

Se dsar_enabled estiver desativado, o shortcode mostra «o portal está desativado» e a rota REST responde 404: a desativação é mesmo do lado do servidor.

A lista que pedir é intersetada com a lista que o servidor aceita: uma opção recusada pelo endpoint nunca é proposta ao visitante.

Os oito tipos de pedidos

access, rectification, erasure, portability, restrict, object, optout, withdraw.

withdraw foi deliberadamente retirado do que o formulário propõe. Retirar o consentimento aos cookies é instantâneo no painel de preferências; fazê-lo passar por um procedimento escrito de 30 dias tornaria a retirada mais difícil do que o consentimento (art. 7.º, n.º 3 do RGPD). Um link por baixo do formulário abre diretamente o painel de preferências para esse caso.

O filtro owc_dsar_types é a fonte de verdade única: a lista de permissões do endpoint deriva dela.

O ciclo completo

1. Submissão. POST /owc/v1/dsar. Três proteções no servidor, por esta ordem:

  • Armadilha para robôs: um campo oculto preenchido leva a uma rejeição 400 com a mensagem genérica de uma submissão malformada — um robô não aprende nada sobre a razão da recusa. Do lado do browser, o formulário mostra até a mensagem de sucesso sem enviar nada.
  • Declaração (art. 12.º, n.º 6): a caixa «confirmo que estou a exercer um direito sobre OS MEUS próprios dados pessoais» tem de estar assinalada, e isso é verificado no servidor, não apenas no browser. Fica registada com o pedido.
  • Prazo mínimo de 3 segundos entre a apresentação do formulário e o envio, do lado do cliente.

Limites de taxa: 3 por hora e por endereço IP, 3 por dia e por endereço de e-mail visado — o endereço é escolhido pelo atacante, é ele que tem de ser limitado — e 30 por hora para todo o site.

A linha é inserida com o estado pending, um token de 64 caracteres do qual só o hash SHA-256 é guardado, uma data de expiração do token e um prazo de resposta. O identificador não é devolvido: sem enumeração, sem fuga de volumetria. A resposta vale {ok, mail_sent, message}, e mail_sent reflete a falha real de envio.

O DPO não é notificado nesta fase. Caso contrário, um chamador anónimo faria sair dois e-mails por submissão a partir do seu domínio.

2. E-mail de verificação. O link aponta para a sua página inicial com o token em claro como parâmetro. Validade: dsar_token_ttl_days, por defeito 7 dias, limites 1 a 90. O e-mail leva um cabeçalho Reply-To mas nunca um From reescrito — reescrever o envelope é o que parte o SPF. O endereço de resposta é dsar_email ou, na falta dele, legal_dpo_email; nunca o endereço de administração do site, que não deve ser publicado.

3. Confirmação de identidade. O link abre uma página que não executa nada: a confirmação faz-se por um POST protegido por um nonce. É isso que impede um scanner de links de correio (Safe Links, URL Defense, pré-visualização de caixa de entrada) de confirmar uma identidade em vez da pessoa.

A página é um documento HTML autónomo, servido fora do tema, com noindex, nofollow e cabeçalhos não armazenáveis em cache. As tentativas estão limitadas a 30 por hora e por IP. As respostas são distintas: 404 para um link desconhecido, 200 para um link já confirmado, 410 para um link expirado, 403 para um nonce caducado, 500 para uma falha de escrita, 200 para uma confirmação.

4. O que a confirmação desencadeia.

  • O estado passa a verified.
  • O prazo é recalculado a partir da verificação. Artigo 12.º, n.º 3: o prazo corre desde que o pedido está completo, não desde uma submissão nunca confirmada.
  • O token é queimado. O hash dele é conservado de propósito, para que uma pessoa que volte a abrir o link dela leia «já confirmado» em vez de um 404 a pedir-lhe que recomece tudo. O uso único é garantido pela verificação de estado.
  • Ficam registados: a data de confirmação, o IP truncado, o token pseudónimo do browser — a única ponte possível entre o registo de consentimento e o pedido — e o identificador do pedido WordPress associado.
  • É aberto um pedido nativo do WordPress com a identidade já provada, portanto diretamente na sua fila Ferramentas → Exportar / Apagar dados pessoais. Isso faz com que a execução cubra todos os plugins do site, não apenas o OW Consent. Correspondência: access e portability → exportação; erasure → eliminação; os outros tipos não abrem pedido nativo.
  • O responsável é notificado em dsar_email e dsar_notify_email, recaindo sobre o endereço de administração se nenhum for válido.

5. Execução. Um painel por cima do separador DSAR requests lista os 20 pedidos verificados, ordenados por prazo, com o número de dias que faltam ou de atraso.

AçãoComportamento
Descarregar os dados (JSON)Recusa 409 se a identidade nunca tiver sido confirmada. O ficheiro chama-se dsar-<id>-<AAAAMMDD>.json
Apagar os dadosOferecido apenas para um pedido do tipo erasure, e só depois da confirmação de identidade. O pedido é encerrado antes do apagamento, senão o texto livre do pedido em curso sobreviveria à execução dele próprio
+2 mesesProrrogação do art. 12.º, n.º 3, uma única vez, com um motivo de 500 caracteres no máximo. O prazo recua 60 dias, os lembretes são rearmados, e o titular dos dados recebe um e-mail com o novo prazo e os motivos
EncerrarEstado resolved ou rejected, nota obrigatória. Impossível reescrever um pedido já encerrado, e impossível inscrever «satisfeito» sobre uma identidade nunca confirmada
Reenviar o linkEmite um novo token, o que invalida o anterior

Cinco estados: pending, verified, resolved, rejected, expired.

O ecrã nunca carrega o token nem o hash dele, e não mostra o nome do requerente.

6. Vigilância do prazo. No cron diário: até 200 pedidos verificados percorridos por prazo, um lembrete sete dias antes, um lembrete quando o prazo é ultrapassado, uma única vez cada um. Uma faixa no backoffice assinala os pedidos fora de prazo e os que estão a menos de sete dias.

7. Purga. As submissões nunca verificadas são apagadas depois de o token delas caducar, e os pedidos encerrados são apagados para lá de retention_dsar_days. A contagem parte do encerramento, não da submissão.

O pacote de portabilidade

A exportação produz um documento no formato ow-consent/dsar-export, com a data/hora, o site, o titular, o pedido e os grupos de dados. Chama todos os exportadores registados no site, pagina até 50 páginas por exportador e para aos 20 segundos. O apagamento faz o mesmo do lado dos apagadores, com os mesmos limites.

Filtro: owc_dsar_export_bundle( $bundle, $email, $id ).

O que a exportação e o apagamento cobrem depende, portanto, dos plugins instalados no seu site: um plugin que não registe nenhum destes ganchos tem de ser tratado à mão.

Integração com as ferramentas nativas do WordPress

  • Exportador registado sob a chave ow-consent, com dois grupos: os pedidos de direitos e o registo de consentimento.
  • Apagador:
    1. as submissões nunca confirmadas são apagadas — sem valor probatório, só dados pessoais;
    2. os pedidos encerrados veem o texto livre e o nome apagados, ficando a linha mínima (data, tipo, estado) conservada como prova (art. 5.º, n.º 2) até retention_dsar_days;
    3. um pedido ainda aberto é conservado, com uma mensagem a explicar que tem de ser respondido antes de ser apagado;
    4. o registo de consentimento não é apagado, pela razão explicada acima.
  • O plugin alimenta também o rascunho de política de privacidade do WordPress.

A informação por baixo do formulário

Por baixo do formulário, um bloco expansível traz a informação do artigo 13.º: identidade e morada do responsável, contacto de privacidade, finalidade e fundamento legal (art. 6.º, n.º 1, alínea c)), dados recolhidos, destinatários e prazo de conservação, prazo de resposta e possibilidade de prorrogação, autoridade de controlo competente, link para a política. Filtro: owc_dsar_form_notice( $html, $context ).

A autoridade mostrada vem da derivação descrita em Os onze perfis de conformidade, e só é usada se começar por maiúscula: ler a definição em bruto nomearia a autoridade francesa num site sul-africano, indiano, australiano ou californiano.

Sem JavaScript, o botão de envio fica escondido e uma mensagem propõe o endereço de contacto — para que o browser nunca faça uma submissão nativa que colocaria o endereço do requerente no URL e, portanto, nos registos de todos os servidores e proxies atravessados.

Definições DSAR

DefiniçãoPor defeitoLimites
dsar_enabledtruebooleano
dsar_email''contacto público e destinatário das notificações
dsar_notify_email''destinatário adicional
dsar_response_days301 a 30 — nunca mais de um mês
dsar_token_ttl_days71 a 90
retention_dsar_days1095 (3 anos)1 a 3650

Pontos de extensão

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 );

Limitações do portal

  1. A identidade assenta num único fator: a ida e volta por e-mail. Qualquer prova adicional ao abrigo do art. 12.º, n.º 6 escreve-se à mão nas notas do pedido.
  2. Uma falha no envio de e-mail bloqueia o ciclo: a resposta expõe mail_sent: false e o formulário mostra uma mensagem que remete para o endereço de contacto, mas o pedido continua pending e o prazo legal não arranca.
  3. Os lembretes de prazo e a purga dependem do WP-Cron.
  4. A tabela dos pedidos é sempre apagada na desinstalação do plugin, seja qual for a sua definição. Exporte antes de apagar.

O opt-out CCPA «Do Not Sell or Share»

Sob os perfis norte-americanos, a lei exige um controlo nomeado, visível, e que execute realmente o opt-out.

Duas formas de o colocar

Shortcode, onde quiser:

[owc_dnsmpi]
[owc_dnsmpi label="Não vender nem partilhar as minhas informações pessoais" class="o-meu-link"]
AtributoPor defeito
label«Do Not Sell or Share My Personal Information»
classowc-dnsmpi

Injeção automática no rodapé: definição ccpa_inject_footer, ativa por defeito, renderizada em wp_footer com prioridade 20.

O que faz um clique

Um clique executa o opt-out. Não abre um painel. O runtime vai buscar um nonce fresco e depois envia um reject_all com a origem footer_link e todas as categorias opcionais a false. O link passa a aria-busy="true" e uma zona role="status" mostra o estado: «a registar», e depois «o seu opt-out foi registado neste browser», ou a mensagem de erro.

O código cita a razão: segundo os regulamentos de aplicação da CCPA (§7026(a)(1)), um link que se limite a abrir um painel não é um mecanismo conforme.

Alternativa sem fetch nem Promise (WebView antiga, browser integrado): o clique abre o painel de preferências com as categorias publicitárias já posicionadas em off.

A restrição de cache

O HTML servido é idêntico para todos. O link é, por isso, renderizado visível se o perfil configurado do site for ccpa ou us_generic; caso contrário é renderizado escondido e revelado do lado do cliente para os visitantes cuja jurisdição o exija, com base no cookie de deteção regional.

Se a deteção regional estiver desativada e o seu perfil configurado não for norte-americano, o bloco nem sequer é impresso: sem marcação morta.

Nenhum dado de visitante viaja na configuração JavaScript deste módulo — apenas as listas de perfis, nunca o booleano já resolvido.

Os recursos deste módulo são impressos no <head>, não no rodapé, para que a recusa do Consent Mode chegue ao gtag antes de um gestor de tags disparar. O JavaScript é ES5 apenas.

Limitações

  1. O controlo num clique exige fetch e Promise.
  2. O link só é obrigatório sob os perfis ccpa e us_generic; sob qualquer outro perfil, não é mostrado nem revelado.
  3. A revelação do lado do cliente depende do cookie de deteção regional e, portanto, do JavaScript.

Google Consent Mode v2

O módulo está ativo por defeito (gcm_enabled).

Os sete sinais

São emitidos os sete sinais do Consent Mode v2: ad_storage, ad_user_data, ad_personalization, analytics_storage, functionality_storage, personalization_storage e security_storage. As seis categorias do plugin são-lhes associadas a partir de uma fonte de verdade única, o que garante que o banner, o bloqueio e o bootstrap não podem divergir. A tabela está em As categorias de cookies.

Os valores por defeito, antes de qualquer consentimento

A chamada default é impressa como marcação estática, e o update é calculado no browser: é isso que mantém a página armazenável em cache.

SinalRegime opt-inRegime opt-out
ad_storagedeniedgranted
analytics_storagedeniedgranted
ad_user_datadeniedgranted
ad_personalizationdeniedgranted
personalization_storagedeniedgranted
functionality_storagedenied se o site expuser a categoria functional, senão grantedgranted
security_storagegranted sempregranted

Os dois conjuntos são impressos na mesma página. O runtime escolhe qual aplicar com base no cookie de deteção regional. Sem isso, uma página em cache fixaria a jurisdição de um visitante norte-americano num visitante europeu.

As duas definições associadas

DefiniçãoPor defeitoEfeito
gcm_ads_data_redactiontrueOculta os identificadores publicitários enquanto o ad_storage estiver recusado
gcm_url_passthroughtrueFaz passar gclid / dclid pelos URLs enquanto os cookies estiverem recusados

A saber

O security_storage nunca é tocado por uma atualização: continua autorizado, como a especificação prevê. O dashboard mostra um cartão a indicar se o Consent Mode está ativo.


IAB TCF v2.2

O módulo TCF está desativado por defeito, e exige um identificador que o plugin não pode fornecer. Leia a secção das limitações antes de o ativar.

Ativação

DefiniçãoPor defeitoLimites
tcf_enabledfalsebooleano
tcf_cmp_id00 a 4095
tcf_publisher_countryFRcódigo ISO de 2 letras
tcf_publisher_purposes_li[]lista de finalidades
tcf_special_features[]lista de funcionalidades especiais

O teto de 4095 não é arbitrário: o campo CmpId ocupa 12 bits na string TCF.

Três casos em que o módulo recusa emitir seja o que for

  1. Nenhum CMP ID (tcf_cmp_id < 1): sem __tcfapi, sem string TC, sem cookie euconsent-v2, sem rota REST. Um aviso de administração explica-o. O CMP ID tem de lhe ser atribuído pelo IAB Europe; o plugin não fornece nenhum. Emitir uma string com um CmpId 0 seria pior do que não emitir nada.
  2. CMP ID superior a 4095: mesma recusa, com um aviso dedicado. Um valor truncado a 12 bits designaria outro CMP — uma usurpação de identidade.
  3. Nenhuma Global Vendor List em cache: o stub é impresso mas a API responde cmpStatus: 'error' com uma string vazia, em vez de inventar um número de versão de lista.

Um diagnóstico é escrito numa opção (disabled, missing_cmp_id, no_gvl, active), e apenas em contexto de administração ou de cron, nunca numa página pública.

A Global Vendor List

  • Origem: https://vendor-list.consensu.org/v3/vendor-list.json.
  • Nunca descarregada durante a renderização de uma página. Um cron diário trata disso, com uma primeira execução cinco minutos depois da ativação; no backoffice com a cache fria, é enfileirada uma obtenção única.
  • Pedido: 5 segundos de tempo limite, 2 redirecionamentos, resposta limitada a 4 MB. Um corpo que atinja o teto é considerado truncado e rejeitado.
  • Só os campos úteis são conservados por fornecedor; as pilhas são esvaziadas. Se a serialização continuar demasiado grande, aplicam-se dois patamares de truncamento: primeiro as etiquetas e os URLs, depois a redução apenas às finalidades.
  • Conservação: uma semana num transient, com stale-if-error — em caso de falha, a última cópia válida é mantida.
  • Rota pública: GET /owc/v1/tcf/gvl, limitada a 10 pedidos por hora. Serve a cópia em cache com um ETag e um Cache-Control: public, max-age=86400, e trata os pedidos condicionais. Se não houver nada em cache, responde 503 com um Retry-After: 300 — nunca uma lista sintética. Esta rota nunca desencadeia um pedido de saída.

Onde a string TC é calculada

No browser, não em PHP. Um stub inteiramente estático é impresso no <head> com prioridade 0: iframe localizadora, implementação de window.__tcfapi, retransmissão postMessage, e um codificador de segmento Core em JavaScript. A configuração publicada não contém nenhum dado de visitante — é isso que permite continuar compatível com uma cache de página. Filtro: owc_tcf_stub_config.

Existe um espelho PHP do codificador, mas não tem nenhum chamador dentro do plugin: está reservado a integrações e a testes, com o aviso de nunca o imprimir em HTML armazenável em cache.

O que é codificado

Segmento Core apenas, em base64url sem preenchimento. A secção de fornecedores é codificada em campo de bits ou em intervalos: os dois tamanhos são medidos e ganha o mais pequeno. O índice de fornecedores transmitido ao browser usa um formato compacto próprio, com um teto rígido acima do qual o índice é esvaziado (perdem-se então os consentimentos de fornecedor, e a versão da lista é conservada).

Correspondência categorias → finalidades TCF

CategoriaFinalidades
necessarynenhuma — fora do perímetro TCF, o que garante que «Recusar tudo» não pode produzir um consentimento para a finalidade 1
functional1
analytics1, 8, 9, 10
marketing1, 2, 3, 4, 7
preferences1, 5, 6, 11
social1

Filtro: owc_tcf_purpose_map.

Interesse legítimo do editor: só são retidas as finalidades 2, 7, 8, 9, 10 e 11, porque o TCF v2.2 proíbe o interesse legítimo nas finalidades 1, 3, 4, 5 e 6. Qualquer outro valor de tcf_publisher_purposes_li é descartado em silêncio.

Um fornecedor fica consentido assim que pelo menos uma das finalidades que declara sob fundamento «consentimento» seja autorizada.

As funcionalidades especiais

Só são suportadas duas entradas: 1 — uso de dados de geolocalização precisos e 2 — análise ativa das características do dispositivo. Declare as que usa em tcf_special_features; a lista é reduzida a este catálogo.

Aparecem como verdadeiras caixas de seleção no painel de preferências. «Aceitar tudo» não as assinala: exigem um opt-in explícito próprio. «Recusar tudo» e «Do Not Sell» desmarcam-nas. Vivem dentro da própria string TC, e são re-hidratadas quando o painel volta a abrir, a partir da API TCF.

A camada TCF no painel

Só é renderizada se o módulo estiver ativo, ou seja, tcf_enabled e um CMP ID utilizável. Dois blocos:

  • Finalidades: apenas as alcançáveis através de uma categoria não forçada e nomeadas no catálogo da lista de fornecedores. Um interruptor que voltasse atrás sozinho seria pior do que nenhum interruptor. Alternar uma finalidade escreve em todas as categorias que a declaram, e depois a apresentação é novamente derivada.
  • Funcionalidades especiais declaradas.

Nada é codificado na string que não tenha sido mostrado.

A API JavaScript

window.__tcfapi( command, version, callback, parameter );
// comandos: ping, getTCData, getInAppTCData,
//           addEventListener, removeEventListener, getVendorList

window.__owcTcfUpdateState( tcData );        // substitui os dados e notifica os ouvintes
window.__owcTcfRefresh();                    // recalcula e emite 'useractioncomplete'
window.__owcTcfUiShown(); window.__owcTcfUiHidden();
window.__owcTcfSetSpecialFeatures( [ 1, 2 ] );

Versões aceites: ausente, null, 2, '2', 2.2, '2.2'. Qualquer outro valor dá callback(null, false).

gdprApplies

Decidido do lado do cliente. Se a deteção regional estiver desativada, o valor é true. Caso contrário, o país do visitante é comparado com a lista dos 31 códigos onde o RGPD se aplica no sentido do TCF (27 países da UE, mais IS, LI, NO, GB e CH). Falha fechada: um país desconhecido dá true.

O cookie euconsent-v2

Escrito apenas do lado do cliente, e apenas quando o módulo está pronto e o visitante agiu. Caso contrário, o cookie é apagado. A duração dele é a do consentimento, limitada a 13 meses (recomendação IAB / CNIL). SameSite=Lax, Secure em HTTPS.

Limitações do módulo TCF — leitura obrigatória

  1. Não é um CMP registado junto do IAB Europe. Exige o seu próprio CMP ID e, mesmo com ele, a política do TCF impõe a um CMP registado que apresente escolhas ao nível da finalidade e ao nível do fornecedor. Aqui, os interruptores de finalidades seguem as categorias e não há qualquer escolha ao nível do fornecedor: os fornecedores têm fundamento para rejeitar este sinal. O ecrã de administração di-lo e fala de «modo compatível com CMP (não oficial)», aceite pela maioria dos SSP em desenvolvimento mas rejeitado em produção real no EEE. Se a receita publicitária sob TCF conta para si, use um CMP certificado.
  2. Segmento Core apenas: sem segmento disclosedVendors, allowedVendors nem publisherTC.
  3. Nenhuma publisher restriction é emitida: o contador correspondente vale sempre 0 e o objeto das restrições está vazio.
  4. purposeOneTreatment e useNonStandardTexts valem sempre false, isServiceSpecific vale sempre true — não há âmbito global — e o ecrã de consentimento vale sempre 0.
  5. getVendorList ignora o parâmetro de versão e devolve sempre a lista em cache.
  6. Os consentimentos do editor copiam as finalidades gerais: sem finalidades personalizadas.
  7. Só duas funcionalidades especiais.
  8. O módulo exige um cron funcional e HTTPS de saída. Sem lista em cache, a API responde cmpStatus: 'error' e a rota pública devolve 503.

Global Privacy Control

O GPC é um sinal enviado pelo browser — o cabeçalho Sec-GPC: 1 e a propriedade navigator.globalPrivacyControl. O plugin honra-o por defeito (gpc_honor), mas o tratamento dele depende do regime jurídico, e esse é o ponto importante.

Sob os perfis norte-americanos: vinculativo

O sinal é tratado como um opt-out universal vinculativo sob os perfis ccpa e us_generic. A Austrália está explicitamente excluída: regime opt-out, mas que não reconhece o GPC.

A escrita no servidor só ocorre depois de uma cadeia completa de verificações, por esta ordem:

  1. gpc_honor está ativo;
  2. o sinal está presente — o cabeçalho Sec-GPC, com recurso a X-Sec-GPC porque alguns proxies e CDN renomeiam o cabeçalho; só o valor exato 1 conta;
  3. o perfil efetivo é ccpa ou us_genericcaso contrário, nada é escrito;
  4. trata-se de uma simples consulta de página: sem administração, sem cron, sem AJAX, sem REST, sem XML-RPC, sem WP-CLI, e o método é GET;
  5. os cabeçalhos ainda não foram enviados — um cookie que não se consegue colocar é uma decisão que se voltaria a registar a cada pedido;
  6. o marcador de sessão owc_gpc está ausente: uma vez por sessão de navegação;
  7. o visitante não se parece com um robô (user-agent vazio, ou que contenha bot, crawl, spider, slurp, monitor, uptime, pingdom, headless, preview, curl/, wget, python-, java/, go-http, okhttp, httpclient, libwww, facebookexternalhit);
  8. a escolha já guardada ainda não satisfaz o sinal — caso contrário, apenas se marca o browser;
  9. o limite de taxa partilhado é respeitado: 30 por hora e por IP;
  10. o browser é marcado antes da escrita, para que uma falha não se transforme num ciclo de repetição.

O evento é registado como gpc_opt_out com a origem auto. O URL da página é reconstruído a partir do URL do site e do caminho pedido — nunca do cabeçalho Host, nunca do Referer.

Âmbito: todas as categorias opcionais passam a false; as categorias forçadas continuam autorizadas. É uma leitura ampla da noção de venda ou partilha.

Transparência: se o GPC substituir uma escolha explicitamente registada, um cookie de cinco minutos desencadeia no cliente um aviso no fundo do ecrã a explicá-lo, com um botão «gerir as minhas preferências» que abre o painel, e um botão de fecho.

Sob os perfis RGPD e equivalentes: mera indicação

Fora dos regimes opt-out norte-americanos, o sinal é tratado como uma indicação, nunca como um consentimento:

  • o atributo data-owc-gpc="1" é colocado em <html>;
  • o Consent Mode passa ad_storage, analytics_storage, ad_user_data, ad_personalization e personalization_storage a denied;
  • todas as categorias opcionais são postas a false apenas em memória;
  • given continua falso, o banner continua visível, e nada fica registado.

Uma flag interna impede o runtime de anunciar ao visitante que ele já está «em opt-out» quando nem o cookie nem o registo o dizem. A justificação está escrita no código: o GPC não é um sinal legalmente reconhecido sob o RGPD e a ePrivacy, a nLPD suíça, a LGPD, a POPIA, a PIPL, a DPDP nem a Lei 25.

Os dois cookies colocados por este módulo

CookieDuraçãoFunção
owc_gpcsessãoMarca que o sinal já foi tratado para este browser
owc_gpc_notice300 sDesencadeia o aviso «o GPC substituiu a sua escolha»

Ambos estão em path=/, SameSite=Lax, Secure em HTTPS, e são legíveis por script.

Ponto de extensão

add_action( 'owc_gpc_honored', function ( array $categories ) {} );

A deteção regional

A deteção regional aplica a cada visitante o perfil da jurisdição dele, em vez do perfil configurado do site. Está desativada por defeito.

DefiniçãoPor defeito
geo_enabledfalse
geo_default_profilegdpr
geo_mmdb_enabledfalse

A cascata de deteção

Ganha o primeiro que tiver êxito:

  1. Cloudflare (CF-IPCountry) — só se o site tiver declarado estar atrás da Cloudflare;
  2. AWS CloudFront (CloudFront-Viewer-Country) — mesma condição;
  3. GEOIP_COUNTRY_CODE, a variável escrita pelo próprio servidor (mod_geoip, ngx_http_geoip), logo não forjável pelo cliente; e depois, se houver um proxy declarado, as variantes em cabeçalho HTTP;
  4. Base MaxMind local, se geo_mmdb_enabled estiver ativo;
  5. Nada.

Sem declaração explícita no wp-config.php, nenhum cabeçalho HTTP de país é acreditado: são forjáveis pelo cliente. Ver Instalação para as constantes.

O código de país é validado contra a lista de permissões ISO-3166-1 alpha-2 oficialmente atribuída (cerca de 249 códigos enumerados no código). Os códigos de utilizador ou reservados — XX, ZZ, o T1 das saídas Tor — são rejeitados.

A falha fechada

Sem país detetado, o plugin lê geo_default_profile mas despromove para gdpr qualquer perfil opt-out. Um geo_default_profile definido como ccpa, us_generic ou au é, portanto, ignorado para os visitantes não detetados — e o ecrã de administração só propõe perfis opt-in neste seletor, em vez de oferecer uma escolha silenciosamente ignorada.

O motivo é direto: sem esta salvaguarda, um site configurado em inglês norte-americano colocaria todos os visitantes do EEE em regime opt-out, com rastreadores assinalados como «autorizados» sem qualquer consentimento.

Sem recurso à locale do site: a língua de um site não diz nada sobre o local do visitante dele.

O cookie owc_geo

  • Duração de 24 horas, path=/, SameSite=Lax, Secure em HTTPS, legível por script — o bootstrap do front-end precisa dele.
  • Conteúdo: o país, o perfil, uma data/hora e um código de autenticação. Nunca um endereço IP.
  • Na leitura: tamanho limitado, país validado contra a lista ISO, data/hora dentro da janela, código verificado em tempo constante. O perfil é sempre recalculado no servidor a partir do país: um visitante não pode escolher o regime jurídico dele.
  • Só uma deteção real é guardada em cache. O recurso «falha fechada» nunca é memorizado.
  • Escrito apenas num pedido de front-end, e só se a deteção estiver ativa.

A correspondência país → perfil

PaísPerfil
Os 30 países do EEEgdpr
GB, JE, GG, IMuk_pecr
CHch_nfadp
BRlgpd
ZApopia
CN, HKpipl
INdpdp
AU, NZau
CAquebec
USccpa
Todo o restoO recurso alternativo, despromovido para opt-in

Hong Kong está, na verdade, sujeita a um regulamento local próprio; é tratada como PIPL por severidade. A Nova Zelândia é tratada como a Austrália. Todo o Canadá recebe quebec — ganha o regime mais estrito. Os Estados Unidos recebem ccpa, que faz as vezes de perfil para os restantes estados.

A restrição de cache — o que a deteção não faz

O perfil geolocalizado não muda o HTML renderizado. A reescrita do perfil só se aplica às duas rotas REST de visitante, /owc/v1/consent e /owc/v1/state. A renderização de página, as rotas de administração, o wp-admin, o cron e o WP-CLI mantêm o perfil configurado. Qualquer resposta que tenha consumido a reescrita é marcada como não armazenável em cache.

É o runtime do front-end que escolhe o regime, do lado do cliente, a partir do cookie. Consequência assumida: o regime aplicado a um visitante depende de JavaScript, e um visitante cuja primeira página venha da cache é tratado sob o perfil configurado do site enquanto o cookie não existir.

O leitor MaxMind embutido

O plugin traz o leitor MMDB próprio dele, escrito em PHP puro, sem dependência do Composer — um plugin publicado no WordPress.org não pode incorporar o pacote oficial.

  • Só leitura, só país. Sem cidade, sem ASN.
  • Entradas/saídas limitadas: leitura por pequenos blocos, nunca o carregamento em memória de um ficheiro de vários megabytes.
  • Nunca lança exceções: ficheiro em falta, ilegível, truncado, corrompido ou hostil dá null, e a deteção recai sobre o perfil estrito dela.
  • Salvaguardas internas sobre a profundidade de travessia, o orçamento de descodificação e o tamanho de payload. Os metadados são guardados num transient cuja chave integra o tamanho e a data do ficheiro: substituir o seu download mensal invalida a cache sozinho.
  • registered_country e represented_country são deliberadamente ignorados. É a forma documentada dos intervalos de proxy anónimo, VPN e satélite, em que o país de registo designa o local de registo do bloco pelo fornecedor, não o visitante. Usá-los colocaria um visitante do EEE atrás de um VPN norte-americano em regime opt-out.
  • Se outro plugin já carregar um leitor GeoIP2, esse é usado como alternativa depois do leitor próprio.

Onde pôr o ficheiro: wp-content/uploads/ow-geoip/GeoLite2-Country.mmdb. O caminho é filtrável por owc_geo_mmdb_path, com validação contra a travessia de diretórios.

Limitação importante: o plugin não descarrega a base de dados e não oferece nenhum ecrã de carregamento. Tem de obter o ficheiro junto da MaxMind, colocá-lo você mesmo e atualizá-lo você mesmo. Em contrapartida, nada é enviado para a MaxMind: a pesquisa é integralmente local, o endereço IP serve apenas para o ponto de pesquisa e é explicitamente destruído a seguir — nunca registado, nunca guardado, nunca escrito no cookie.

Limitações da deteção regional

  1. Desativada por defeito, e a variante MaxMind exige um ficheiro que é você a fornecer.
  2. Os cabeçalhos de país são ignorados enquanto não declarar o seu proxy.
  3. Sem granularidade estado a estado nos Estados Unidos, nem província a província no Canadá.
  4. O regime do visitante depende de JavaScript e do cookie de 24 horas.
  5. O perfil us_generic nunca é atribuído automaticamente.

A integração com o OW Forms

Perímetro exato: esta integração destina-se ao plugin irmão OW Forms, e a mais nada. Não existe qualquer integração com o Contact Form 7, o Gravity Forms ou o WPForms.

Definição: forms_integration_enabled, ativa por defeito, mas a integração não faz nada se o OW Forms não estiver ativo.

O que traz

1. A ligação consentimento ↔ submissão. A cada submissão guardada, é escrita uma linha numa tabela dedicada ao OW Consent — o esquema do OW Forms nunca é tocado — que contém:

  • o token pseudónimo do visitante (32 hexadecimais, nunca o endereço de e-mail);
  • o identificador da entrada de registo mais recente para esse token;
  • as categorias autorizadas e o perfil de conformidade em vigor;
  • o hash completo dos documentos publicados nesse momento;
  • a data/hora do consentimento;
  • o estado da caixa RGPD do formulário e o texto exato que ela tinha ao lado.

Este último ponto é o mais útil na prática: pode demonstrar não só que a caixa estava assinalada, mas o que estava escrito ao lado dela.

2. A cascata de apagamento. Um pedido de apagamento verificado delega a eliminação na rotina do OW Forms — que apaga também os ficheiros carregados — e depois apaga as linhas de ligação. A integração deteta se o OW Forms já ligou o ouvinte próprio dele à mesma ação, caso em que se limita a podar as ligações dela.

3. As ferramentas de privacidade do WordPress. São registados um exportador e um apagador, de modo que Ferramentas → Exportar / Apagar dados pessoais cubra as submissões do OW Forms — e o portal de direitos do OW Consent também, já que percorre os mesmos ganchos. A exportação usa os textos reais dos campos e acrescenta os campos de consentimento. Qualquer valor exportado é neutralizado contra a injeção de fórmulas de folha de cálculo e truncado.

4. A retenção. No cron diário, as ligações mais antigas do que retention_form_data_days (por defeito 1095 dias, limites 1 a 3650) são apagadas em lotes de 500, com um varrimento das órfãs. Só as linhas do OW Consent são tocadas: a retenção das próprias submissões pertence ao OW Forms.

Limitações

  1. Nenhum evento de submissão é escrito no registo encadeado. A linha de ligação referencia uma entrada existente, não cria nenhuma — a cadeia não deve ser escrita a partir de fora.
  2. Impossível ligar uma submissão se o OW Forms estiver configurado para não guardar as submissões: não há linha nenhuma onde agarrar.
  3. Sem recuperação retroativa: a ligação começa na primeira submissão depois da atualização.
  4. A reconciliação é feita sobre o hash com sal do endereço calculado pelo OW Forms, com recurso ao endereço em claro que o OW Forms também conserva. Não é um esquema de divulgação nula: o endereço fica em claro no OW Forms, porque é preciso poder responder à pessoa.
  5. A integração nunca modifica o plugin OW Forms, nem as tabelas ou as opções dele.
  6. Se o OW Forms estiver ativo mas for demasiado antigo para expor a rotina de apagamento dele, o apagador responde explicitamente que as submissões não foram tocadas.

O botão flutuante

Um pequeno botão persistente permite reabrir o painel de preferências a qualquer momento. É a exigência do artigo 7.º, n.º 3 — a retirada deve continuar tão simples como o consentimento — e da deliberação CNIL 2020-091, que pede um mecanismo acessível a partir de todas as páginas.

Condições de existência

Exige as duas definições: floating_button_enabled e banner_enabled. O painel de preferências e a API JavaScript vivem no runtime do banner; sem ele, o botão seria um controlo que não faz nada.

Sai também no backoffice, num feed, em robots.txt, e se o wp_head não tiver sido disparado.

Opções

DefiniçãoValoresPor defeito
floating_button_enabledbooleanotrue
floating_button_positionbottom-left, bottom-right, top-left, top-rightbottom-left
floating_button_stylepill, iconpill
floating_button_labeltexto livreCookies

O botão não tem definição de tema própria: retoma banner_style, para que as duas superfícies sigam a mesma paleta.

Comportamento e acessibilidade

É um <button> a sério, portanto acessível por teclado. Tem aria-haspopup="dialog" e um aria-label traduzível mas não configurável («Manage my cookie choices»): só o texto visível é que se ajusta. No modo icon, o texto fica visualmente escondido mas continua a ser lido pelos leitores de ecrã.

É renderizado com o atributo hidden; é o JavaScript que decide mostrá-lo. Está visível em todos os estados exceto quando o banner ou o painel já está no ecrã — incluindo imediatamente depois de uma escolha na página atual.

Um clique abre o painel de preferências. Se a API do banner não existir, o botão recai sobre o fragmento #owc-preferences, voltando a disparar o evento à mão se já for o fragmento atual.

z-index 99990, escondido na impressão, suporte do modo de contrastes forçados e de prefers-reduced-motion, desvio por baixo da barra de administração na posição de topo.


Shortcodes

O plugin declara dois shortcodes, e só dois. Nem o banner nem o botão flutuante têm um: aparecem em todo o lado ou em lado nenhum, comandados pelas definições deles.

[owc_dsar_form] — o portal de direitos

[owc_dsar_form]
[owc_dsar_form types="access,erasure,portability" title="Os meus direitos" submit_label="Enviar"]
AtributoPor defeito
typesaccess,rectification,erasure,portability,restrict,object,optout
title«Exercer os meus direitos sobre os meus dados pessoais»
submit_label«Enviar o meu pedido»

Os tipos pedidos são intersetados com os que o servidor aceita. O tipo withdraw nunca é proposto pelo formulário; ver O portal de direitos.

[owc_dnsmpi] — o opt-out CCPA

[owc_dnsmpi]
[owc_dnsmpi label="Não vender nem partilhar as minhas informações pessoais" class="link-rodape"]
AtributoPor defeito
label«Do Not Sell or Share My Personal Information»
classowc-dnsmpi

Um clique executa o opt-out; ver O opt-out CCPA. Se deixar ccpa_inject_footer ativo, o link já está injetado no rodapé e este shortcode só é útil para o colocar noutro sítio.

Reabrir o painel a partir de um menu ou de um link

Não há shortcode para isso, porque basta um atributo de classe:

<a href="#owc-preferences" class="owc-open-preferences">Gerir os meus cookies</a>
<button type="button" data-owc-open>As minhas preferências</button>
<a href="#owc-dnsmpi" data-owc-dnsmpi="1">Não vender as minhas informações</a>

É o método recomendado para acrescentar uma entrada «Cookies» ao seu menu de rodapé.


API REST

Todas as rotas vivem sob o namespace owc/v1, na raiz REST habitual (https://exemplo.com/wp-json/owc/v1/…). Treze rotas ao todo: doze registadas pelo módulo REST, mais a rota da lista de fornecedores registada pelo módulo TCF.

Rotas públicas

Sem autenticação. A segurança delas assenta na verificação de origem, num token próprio do plugin e nos limites de taxa descritos mais abaixo.

MétodoCaminhoParâmetrosFunção
GET/nonceaction (por defeito wp_rest)Devolve um token fresco. Resposta: {nonce, action, header: "X-OWC-Nonce", ttl: 43200}, com no-store e Vary: Cookie
POST/consentevent (por defeito save_preferences), source (por defeito banner), categories (obrigatório), page_url (por defeito ''), cookie_unreadable (booleano, por defeito false)Regista uma escolha, coloca o cookie e escreve a linha de registo
GET/stateO estado apenas de quem chama: {given, categories, profile, at}. Nunca o token de visitante, nunca o IP, nunca o URL da página. no-store + Vary: Cookie
GET/tcf/gvlServe a Global Vendor List em cache. ETag, Cache-Control: public, max-age=86400, 304 em pedido condicional, 503 + Retry-After: 300 se não houver nada em cache
POST/dsartype (obrigatório), email (obrigatório, formato de e-mail), message (por defeito '', 2000 caracteres no máximo)Regista um pedido de exercício de direitos. Resposta {ok, mail_sent, message}nunca o identificador

Valores aceites:

  • event: accept_all, reject_all, save_preferences, withdraw, auto, gpc_opt_out
  • source: banner, preferences, footer_link, api, auto
  • type (DSAR): access, rectification, erasure, portability, restrict, object, optout, withdraw
  • categories: um objeto, 32 entradas no máximo, apenas valores escalares
  • action (nonce): apenas wp_rest

Rotas de administração

Todas exigem a capacidade manage_options.

MétodoCaminhoParâmetrosFunção
GET/ledgerpage (≥1, por defeito 1), per_page (1–200, por defeito 50), visitor_token (32 hex), from, toLê o registo. Os filtros realmente aplicados são devolvidos na resposta
GET/ledger/verifyVerifica a cadeia de ponta a ponta
GET/settingsAs definições efetivas
POST/settingscorpo JSON livreEscreve as definições. Resposta {ok, updated, rejected, settings}
POST/scanner/runLança um varrimento
POST/scanner/ingestcorpo JSON {page, findings[]}Receção das observações da sonda
GET/scanner/findingspage, per_page (1–200, por defeito 50), filter (slug de categoria ou unknown)Lista as deteções
POST/policies/generatetype (obrigatório), publish (booleano, por defeito false)Gera um documento
GET/policies/previewtype (obrigatório)Pré-visualiza um documento

/scanner/ingest exige ainda um nonce wp_rest válido, no cabeçalho X-WP-Nonce ou no parâmetro _wpnonce — porque o sendBeacon não consegue pôr cabeçalhos.

O /settings em escrita só aceita as chaves declaradas como definições reais: um filtro pode injetar chaves sintéticas que não o são. Cada valor é higienizado individualmente. Nenhuma chave conhecida dá um 400.

O /ledger volta a testar a capacidade dentro do handler, como defesa em profundidade contra um permission_callback filtrado noutro sítio.

O modelo de segurança das escritas públicas

A constatação de partida é simples: um nonce não pode viver em HTML armazenável em cache. /consent e /dsar têm, por isso, um controlo de acesso permissivo, protegido por quatro camadas numa ordem que conta.

  1. Verificação de origem do mesmo host. Gratuita, determinista, não consome orçamento nenhum.
  2. Token próprio do plugin no cabeçalho X-OWC-Nonce — é uma prova, nunca um veto. A opção de não usar X-WP-Nonce é deliberada: o core do WordPress interceta esse cabeçalho antes de qualquer controlo de acesso da rota e recusa o pedido inteiro se não o validar. Um token caducado servido por uma cache mataria, assim, uma escrita que o endpoint teria aceite sem token nenhum.
  3. Falha fechada: nem origem, nem token verificado dão um 403 owc_missing_origin.
  4. Limite de taxa em último lugar. Se viesse primeiro, uma má configuração consumiria dois tokens por clique e acabaria em 429, o que esconderia o erro de configuração atrás de um limite de taxa.

Hosts aceites: os que o WordPress declara (URL do site, URL do WordPress, raiz REST) com o gémeo www./apex deles, mais o host do cabeçalho Host do pedido atual e o gémeo dele. «Mesma origem» quer dizer «a origem corresponde ao host a que o browser se ligou», não «corresponde ao que o WordPress tem na base de dados» — senão qualquer site alcançado por um domínio de pré-visualização, um alias, um nome de pré-produção, um domínio mapeado em multisite ou atrás de um proxy que reescreva o Host seria recusado em silêncio, sem possibilidade de se reparar sozinho. Filtro: owc_allowed_request_hosts.

O CORS fica a cargo do core do WordPress, deliberadamente.

O /nonce recusa explicitamente a forma JSONP: sem essa recusa, uma página de terceiros poderia colher o token vivo de um administrador de passagem com um simples <script src>, não sujeito à verificação de origem.

Os limites de taxa

BaldeOrçamentoJanelaChave
nonce1205 minutosendereço IP
consent305 minutosendereço IP
dsar31 horaendereço IP
dsar_email324 horasendereço de e-mail visado
dsar_global301 horatodo o site
gpc301 horaendereço IP
tcf_gvl101 horaendereço IP
  • Janela fixa, não deslizante: um balde que receba tráfego acaba sempre por expirar.
  • A chave guardada é um hash com sal: nenhum endereço IP nem endereço de e-mail em claro fica nas opções ou na cache de objetos.
  • Normalização de IP: IPv4 conservado, IPv6 truncado ao /64 — o cliente controla cada bit do identificador de interface, por isso uma chave completa permitiria criar um orçamento novo a cada pedido.
  • Identidade partilhada: atrás de um CDN ou de um proxy não declarado via OWC_TRUSTED_PROXY, todos os visitantes chegam com o mesmo endereço. O orçamento é então fracionado por visitante, com um contador alargado sobre o endereço partilhado. É um limite mais grosseiro: declare o seu proxy.
  • Filtro owc_throttle_max( $max, $bucket, $window ) — devolver 0 desativa o limite.
  • O limitador falha aberto se a cache de objetos estiver indisponível: mais vale não limitar do que impedir alguém de exercer os direitos dele.

A rota /consent em detalhe

  • headers_sent() é lido logo em primeiro lugar. Se a saída já tiver começado, o cookie do servidor não pode ser colocado: a linha é escrita na mesma e a resposta continua 200, com cookie_set: false e um descritor cookie (name, value, ttl, path, samesite, secure) que o cliente coloca ele próprio.
  • As categorias que o site não oferece são devolvidas em dropped_categories, não ignoradas em silêncio.
  • O page_url é validado contra os hosts do site, com recurso a um Referer validado da mesma forma e, na falta disso, à string vazia. A chave está sempre presente, para que o registo não vá procurar o Referer em bruto.
  • Se o registo estiver ativo e a escrita for recusada, o cookie é anulado e a resposta é um 503 owc_ledger_write_refused. Nada é guardado, nenhum rastreador é libertado.
  • cookie_unreadable é telemetria pura: não lhe é aplicada nenhuma verificação de validação, precisamente para que um campo de diagnóstico nunca possa recusar uma escrita de consentimento. O único efeito dele é uma nota no registo de recusas.

Resposta típica: {ok, state, dropped_categories, logged, cookie_set[, cookie]}. O campo logged vale null quando o registo está desativado.

Códigos de erro

CódigoHTTPSignificado
owc_bad_param400Parâmetro inválido
owc_bad_categories400Objeto categories malformado, demasiado grande ou não escalar
owc_bad_email400Endereço de e-mail inválido
owc_bad_dsar400Submissão DSAR malformada (armadilha para robôs incluída)
owc_dsar_not_attested400Declaração do art. 12.º, n.º 6 em falta
owc_no_settings400Nenhuma chave de definição conhecida no corpo
owc_missing_origin403Nem origem nem token: falha fechada
owc_bad_origin403A origem declarada não é um host deste site — a resposta lista até dez hosts aceites
owc_bad_nonce403Nonce inválido em /scanner/ingest
owc_jsonp_forbidden403Forma JSONP recusada em /nonce
owc_forbidden401/403Capacidade insuficiente
owc_dsar_disabled404O portal de direitos está desativado no servidor
owc_no_template404Nenhum modelo para este tipo e este perfil
owc_rate_limited429Limite de taxa atingido
owc_consent_failed500Falha ao aplicar o consentimento
owc_dsar_store_failed500Falha ao escrever o pedido
owc_gen_failedvariávelGeração de documento recusada (campos em falta, barreira de língua)
owc_ledger_write_refused503O registo recusou escrever — nada é registado, nada é desbloqueado

O registo de recusas

As últimas vinte recusas de escrita são conservadas e mostradas no dashboard — apenas se o registo não estiver vazio. Distinguem-se lá duas naturezas:

  • uma entrada com estado HTTP é uma recusa a sério: nada foi guardado e o visitante viu um erro;
  • uma entrada com estado 0 é um aviso: a escolha ficou mesmo registada.

O painel mostra lado a lado o host declarado pela origem e o cabeçalho Host do pedido. É exatamente isso que difere quando os seus visitantes navegam num host enquanto o WordPress está configurado com outro — a causa mais frequente de um consentimento que «não fica registado».

Limitações da API

  1. /consent e /dsar aceitam escritas anónimas por conceção; a proteção é a origem mais o limite de taxa, não um nonce.
  2. O limitador de taxa falha aberto sem cache de objetos.
  3. O cookie de consentimento é host-only: um cookie colocado para o apex é ilegível a partir de uma página www., e nenhum cabeçalho CORS consegue corrigir isso. O remédio é um URL de escrita da mesma origem.
  4. /scanner/ingest só aceita manage_options: a sonda não pode vir de um visitante.
  5. /settings não tem nonce próprio: é o manage_options mais a verificação de nonce por cookie do core.

Referência das definições

Todas as definições cabem numa única opção, owc_settings, carregada automaticamente visto que é lida em todas as páginas do front-end.

Leitura e escrita em PHP

$definicoes = OWC_Core::settings();               // definições efetivas (defaults + gravadas + filtros)
$perfil     = OWC_Core::setting( 'compliance_profile' );
$bruto      = OWC_Core::stored_setting( 'compliance_profile' ); // ignora a reescrita por visitante

OWC_Core::update_settings( array(
    'consent_renewal_months' => 6,
    'compliance_strict'      => true,
) );

update_settings() só funde as chaves submetidas por cima do que já existe, nunca o conjunto dos valores por defeito. É isso que permite que a linha de opção continue vazia enquanto não tiver personalizado nada e, portanto, que os textos sigam a língua do site.

O resultado de settings() é memoizado, com uma chave baseada no conjunto dos callbacks do filtro owc_settings: um módulo que registe o filtro dele depois da primeira leitura invalida o memo em vez de ser ignorado. O memo é esvaziado a cada escrita da opção.

Valores por defeito

[
    // Banner
    '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,          // cruz de fecho = recusa implícita
    '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',

    // Botão flutuante
    'floating_button_enabled'   => true,
    'floating_button_position'  => 'bottom-left',
    'floating_button_style'     => 'pill',
    'floating_button_label'     => 'Cookies',

    // Consentimento
    'consent_renewal_months'    => 12,             // 0 é convertido em 13, não em «nunca»
    'consent_policy_hash_check' => true,

    // Conformidade
    '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,

    // Categorias
    '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.',

    // Registo
    'ledger_enabled'            => true,
    'ledger_retention_days'     => 1825,           // 5 anos; 0 = ilimitado
    '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',

    // Portal de direitos
    'dsar_enabled'              => true,
    'dsar_email'                => '',
    'dsar_response_days'        => 30,
    'dsar_notify_email'         => '',
    'dsar_token_ttl_days'       => 7,

    // Bloqueio
    'blocker_unknown_script_policy' => 'allow',
    'blocker_unknown_iframe_policy' => 'block',
    'blocker_allowlist'             => '',         // um host por linha
    'blocker_block_resource_hints'  => true,

    // Integração e ciclo de vida
    'forms_integration_enabled' => true,
    'auto_footer_menu_inject'   => false,
    'delete_data_on_uninstall'  => false,

    // Identidade legal
    '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'        => '',            // vazio de propósito: derivada do país + perfil
    '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,

    // Links
    'link_privacy_policy'        => '',
    'link_cookie_policy'         => '',
    'link_terms'                 => '',
    'link_legal_notice'          => '',
]

Listas fechadas

Um valor fora da lista é reposto no valor por defeito.

DefiniçãoValores aceites
banner_positionbottom-bar, bottom-card, center-modal, top-bar
banner_styleauto, light, dark
floating_button_positionbottom-left, bottom-right, top-left, top-right
floating_button_styleicon, pill
compliance_profile, geo_default_profilegdpr, ccpa, lgpd, popia, pipl, dpdp, quebec, uk_pecr, ch_nfadp, au, us_generic
ledger_hash_algosha256, sha3-256 (intersetados com o que o PHP suporta)
scanner_frequencyhourly, twicedaily, daily, weekly
scanner_probe_modeadmins, off
legal_business_typeauto, vitrine, rental, ecommerce, services, saas, content
blocker_unknown_script_policy, blocker_unknown_iframe_policyallow, block

Limites dos inteiros

DefiniçãoLimites
consent_renewal_months0 a 13 (0 é tratado como 13)
dsar_response_days1 a 30
dsar_token_ttl_days1 a 90
ledger_retention_days0 a 3650 (0 = ilimitado)
scanner_max_urls1 a 500
scanner_timeout1 a 60
legal_minor_age13 a 18
retention_form_data_days1 a 3650
retention_dsar_days1 a 3650
tcf_cmp_id0 a 4095

Higienização

  • text_message é o único campo com HTML rico; aceita o HTML autorizado num artigo WordPress, e o banner acaba por renderizar apenas <a href target rel>.
  • Campos multilinha: 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, e qualquer chave terminada em _desc.
  • URLs: qualquer chave com prefixo link_ ou sufixo _url, mais legal_profession_rules.
  • Endereços de e-mail: qualquer chave que contenha email.
  • Todo o resto: texto simples.
  • Um valor não escalar submetido numa definição escalar é ignorado, e a chave não é escrita.

Onde se define o quê

SeparadorDefinições
Bannerbanner_*, text_*, floating_button_*, consent_renewal_months, consent_policy_hash_check
Compliancecompliance_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 identitylegal_*, retention_form_data_days, retention_dsar_days, policy_disclaimer
Categoriescat_*_available, cat_*_label, cat_*_desc
Audit ledgerledger_enabled, ledger_retention_days (o algoritmo é mostrado, não modificável)
Scannerscanner_*
DSAR requestsdsar_*

Tabelas

TabelaConteúdo
{prefix}owc_ledgerRegisto de consentimento encadeado
{prefix}owc_dsarPedidos de exercício de direitos
{prefix}owc_scannerDeteções do scanner
{prefix}owc_scriptsCatálogo de assinaturas do bloqueio
{prefix}owc_form_linksLigações consentimento ↔ submissão do OW Forms

Um auxiliar PHP owc_table( 'ledger' | 'dsar' | 'scanner' | 'scripts' ) recalcula o nome a partir do prefixo atual: use-o dentro de um switch_to_blog(), já que as constantes ficam fixadas durante o pedido.

Outras opções e transients

Opções: 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, mais as flags de migração (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_<versão>, owc_invalid_patterns, owc_gvl_cache, owc_gvl_stub, owc_gvl_etag, owc_gvl_retry, owc_mmdb_<hash>, 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, e os contadores de limite de taxa.

Constantes reconhecidas no wp-config.php

ConstanteEfeito
OWC_LEDGER_KEYChave HMAC do registo, e chave derivada do cookie de deteção regional
OWC_TRUSTED_PROXY / OWC_TRUSTED_PROXIESIntervalos CIDR dos proxies de confiança; sem elas, os cabeçalhos de IP do cliente são ignorados
OWC_GEO_TRUSTED_HEADERScloudflare, cloudfront, proxy ou all
OWC_BEHIND_CLOUDFLAREAtalho para o cabeçalho de país da Cloudflare
OWC_BEHIND_CLOUDFRONTAtalho para o cabeçalho de país do CloudFront

Os ganchos para programadores

Filtros

FiltroFunção
owc_settingsDefinições efetivas. A resposta deve depender de algo estável durante todo o pedido
owc_banner_textsTextos do banner
owc_bootstrap_configConfiguração estática impressa no <head>nunca dados por visitante
owc_scripts_catalogLinhas do catálogo antes da validação
owc_blocker_allowlistHosts nunca bloqueados
owc_scanner_urlsURLs a varrer (novamente restringidos ao host do site depois)
owc_throttle_maxOrçamento de um limite de taxa; 0 desativa-o
owc_allowed_request_hostsHosts aceites para uma escrita pública
owc_allow_headerless_writeLevanta a falha fechada nas escritas sem origem (por defeito false)
owc_geo_mmdb_pathCaminho da base MaxMind
owc_tcf_purpose_mapCorrespondência categorias → finalidades TCF
owc_tcf_stub_configConfiguração estática do stub TCF
owc_policy_templateCorpo em bruto de um modelo — desativa o travão de língua
owc_policy_varsVariáveis de um documento
owc_policy_htmlHTML final de um documento
owc_dsar_typesTipos de pedidos propostos e aceites
owc_dsar_form_noticeInformação por baixo do formulário
owc_dsar_export_bundleConteúdo do pacote de portabilidade
owc_dsar_show_fulfilment_panelApresentação do painel de execução

Ações

AçãoAssinatura
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.

Internacionalização

O domínio de tradução é ow-consent, com o caminho /languages. A língua de origem é o inglês. O pacote inclui o modelo ow-consent.pot e uma tradução francesa completa.

Lembrete do mecanismo descrito acima: enquanto uma etiqueta ou um texto de banner não for personalizado nas definições, segue a língua do site. A partir do momento em que escreve o seu próprio valor, ele é servido tal e qual, seja qual for a língua do visitante.

Os corpos dos documentos legais não passam por este mecanismo: a língua deles segue a jurisdição, nunca a locale do administrador.


Resolução de problemas

O banner não aparece

Percorra a lista por ordem:

  1. banner_enabled está ativo? O selo no topo do ecrã de administração di-lo.
  2. O seu tema chama wp_head() e wp_footer()? Sem eles, o banner recusa imprimir-se em vez de produzir uma marcação inerte.
  3. O visitante já fez uma escolha? O banner só aparece em <html data-owc="none">. Teste em navegação privada ou com window.OWCBanner.reset().
  4. O visitante tem JavaScript? Sem ele, o banner fica escondido — deliberadamente — e o bloco <noscript> assume o lugar.
  5. Há alguma cache de página a servir uma versão anterior à ativação? Limpe-a.

O banner volta em todas as páginas apesar de a escolha ter sido registada

Três causas, por ordem de frequência.

  1. O host percorrido difere do host configurado no WordPress (www. contra apex, alias, domínio de pré-produção). O browser recusa então guardar o cookie enquanto o WordPress responde 200. Abra o dashboard: o registo de recusas mostra lado a lado a origem e o cabeçalho Host. O remédio é servir o site num único host canónico.
  2. consent_policy_hash_check está ativo e os seus documentos mudaram — é o comportamento pretendido: o visitante é novamente interpelado depois de uma alteração de política.
  3. Está numa versão anterior à 1.4.3. O cookie vinha então codificado duas vezes e o browser não conseguia relê-lo, apesar de o servidor o ler muito bem. Atualize: os cookies escritos antes da correção continuam legíveis.

«A sua escolha não pôde ser registada» — erro refused ou 403

Veja o data-owc-code no elemento da mensagem, ou o registo de recusas do dashboard.

  • owc_bad_origin: a origem declarada não é um host reconhecido. A resposta lista os hosts aceites. Se o seu site é legitimamente alcançado por vários nomes, acrescente-os com o filtro owc_allowed_request_hosts.
  • owc_missing_origin: nem origem, nem token. É tipicamente uma extensão de privacidade que retira os cabeçalhos, ou um proxy que os reescreve.
  • owc_bad_nonce: só diz respeito à sonda do scanner, não à escrita de consentimento — o nonce nunca é um veto em /consent.

Erro ratelimit — 429

Os limites são propositadamente baixos nas escritas públicas. Duas causas:

  1. Um CDN ou um proxy não declarado: todos os seus visitantes chegam com o mesmo endereço e partilham o mesmo orçamento. Declare-o com OWC_TRUSTED_PROXY no wp-config.php.
  2. Um teste automatizado que envia mais de 30 escritas em 5 minutos a partir do mesmo endereço.

Um 503 owc_ledger_write_refused

O registo recusou escrever, portanto nada foi registado e nenhum rastreador foi libertado. É o comportamento pretendido: um consentimento que não se consegue provar não deve ser reivindicado.

  1. Verifique que a tabela {prefix}owc_ledger existe. O separador Audit ledger mostra uma mensagem legível de «ainda não instalado» em vez de um ecrã em branco.
  2. Verifique as permissões de escrita do utilizador MySQL.
  3. Em último recurso, desative temporariamente ledger_enabled para repor o serviço — sabendo que perde a prova durante esse período.

Continuam a carregar-se rastreadores antes do consentimento

  1. compliance_strict está ativo? Sem ele, só os scripts enfileirados pelo WordPress são tratados.
  2. O rastreador está no catálogo? Um script de terceiros desconhecido é autorizado por defeito. Lance um varrimento e classifique a deteção: isso escreve a regra de bloqueio.
  3. Está a ser injetado por JavaScript first-party? A guarda dinâmica cobre apenas vinte hosts. Acrescente uma regra de catálogo sobre o domínio do rastreador.
  4. É um cookie colocado por cabeçalho Set-Cookie? Nenhum bloqueio do lado do cliente o consegue reter. Tem de ser tratado na origem, no servidor.
  5. Ponha blocker_unknown_script_policy em block — e depois teste o site com cuidado, porque essa definição bloqueia todos os terceiros não reconhecidos.

Uma integração legítima está bloqueada

Acrescente o host dela em blocker_allowlist, um por linha, ou através do filtro owc_blocker_allowlist. Verifique também blocker_unknown_iframe_policy: as iframes desconhecidas são bloqueadas por defeito, é o caso mais frequente.

Se a integração tiver de continuar condicionada ao consentimento, classifique-a antes na categoria certa: o substituto visual traz um botão «Aceitar …» que a desbloqueia num clique.

O scanner não encontra nada

  1. Está ativo? Vem desativado por defeito.
  2. O WP-Cron funciona? Com DISABLE_WP_CRON e sem cron de sistema, o varrimento agendado nunca arranca. Lance um à mão para verificar.
  3. O loopback HTTP é possível? Uma autenticação HTTP de pré-produção ou uma firewall bloqueiam o varrimento. O painel de cobertura mostra o primeiro erro.
  4. Nenhum JavaScript é executado pelo varrimento do servidor. Visite uma página do site como administrador com sessão iniciada, para que a sonda reporte o que um gestor de tags injeta.

Em qualquer dos casos, leia o painel «o que este varrimento cobre realmente» antes de concluir: distingue «não encontrou nada» de «não leu nada».

A geração de um documento é recusada

Duas causas possíveis, e a mensagem di-lo:

  • Há campos obrigatórios vazios — a lista das chaves em falta é mostrada. Complete o separador Legal identity.
  • Barreira de língua — está a pedir um aviso legal ou uns termos e condições para uma jurisdição não francófona. O botão fica escondido neste caso. A única via é fornecer o seu próprio corpo através do filtro owc_policy_template.

O e-mail de verificação DSAR não chega

É quase sempre a entregabilidade, não o plugin.

  1. A resposta da API traz mail_sent: false quando o envio falhou, e o formulário mostra então uma mensagem dedicada que remete para o endereço de contacto.
  2. Instale um plugin de SMTP. A função mail() do PHP é rejeitada pela maioria dos servidores de receção.
  3. Verifique o SPF, o DKIM e o DMARC do seu domínio. O plugin nunca reescreve o endereço de expedição — é isso que partiria o SPF — limita-se a pôr um Reply-To.
  4. Verifique que dsar_email é um endereço válido.

Enquanto o e-mail não for recebido e confirmado, o pedido continua pending e o prazo legal não arranca.

O módulo TCF não se ativa

Abra o separador Compliance: um aviso de administração nomeia a causa.

  • missing_cmp_id: tcf_cmp_id vale 0. Tem de obter um CMP ID junto do IAB Europe; o plugin não fornece nenhum e recusa emitir uma string com o identificador 0.
  • CMP ID superior a 4095: o valor não cabe no campo de 12 bits da string e designaria outro CMP.
  • no_gvl: a Global Vendor List ainda não foi descarregada. Chega por um cron diário; verifique que o WP-Cron corre e que o seu servidor consegue alcançar vendor-list.consensu.org por HTTPS de saída.

Todos os visitantes recebem o perfil configurado, apesar da deteção regional

  1. geo_enabled está ativo? Vem desativado por defeito.
  2. Declarou o seu proxy? Sem OWC_BEHIND_CLOUDFLARE, OWC_BEHIND_CLOUDFRONT ou OWC_GEO_TRUSTED_HEADERS no wp-config.php, os cabeçalhos de país são ignorados, porque são forjáveis.
  3. O ficheiro MaxMind está no sítio? O plugin não o descarrega e não oferece ecrã de carregamento.
  4. Pode ser normal: a primeira visualização de um visitante servido pela cache usa o perfil configurado enquanto o cookie de 24 horas não existir. O dashboard mostra um bloco de diagnóstico com o país detetado, o perfil aplicado e a origem.

O backoffice assinala que a chave do registo vive na base de dados

É o aviso notice_ledger_key. Significa que AUTH_KEY e AUTH_SALT não estão no wp-config.php — o WordPress guarda-os então na base de dados — e que OWC_LEDGER_KEY também não está definida. A cadeia continua a ser construída, mas quem tenha acesso à base de dados pode voltar a assiná-la: a alegação de estar à prova de adulteração deixa de se sustentar.

O remédio é acrescentar OWC_LEDGER_KEY (e, já agora, AUTH_KEY e AUTH_SALT) ao wp-config.php. A verificação passa depois a distinguir uma rotação de chave de uma reescrita, por isso esta alteração não invalida o seu histórico.

O plugin não faz absolutamente nada, com um aviso vermelho

O seu WordPress é anterior à 6.2. O plugin recusa arrancar, e o aviso diz explicitamente que nada está bloqueado e que nenhum consentimento está a ser registado. Atualize o WordPress, ou desative o plugin e retire o banner das suas páginas entretanto.

Há regras de catálogo que «não conseguem disparar»

Um aviso de administração assinala-as, e o separador Tracker catalogue tem um filtro dedicado. Quatro motivos possíveis: padrão vazio ou composto apenas por caracteres invisíveis, expressão regular que não compila ou que faz backtracking catastrófico, padrão demasiado curto e sem ponto para visar um URL, ou categoria que já não existe no site. Corrija ou apague estas linhas: dão uma falsa impressão de cobertura.


FAQ

O plugin funciona atrás de uma cache de página? Sim, e toda a arquitetura dele foi construída à volta dessa restrição. O HTML produzido é idêntico para todos os visitantes; o consentimento é lido no browser e aplicado antes da primeira renderização. Nenhum nonce é impresso em HTML armazenável em cache. Só as respostas realmente pessoais são marcadas como não armazenáveis.

Substitui um CMP pago? Na maioria dos sites, cobre o mesmo terreno: banner, bloqueio, registo de auditoria, pedidos de direitos, encaminhamento geográfico e documentos gerados. Duas coisas que não faz: não é um CMP registado junto do IAB Europe, e não presta aconselhamento jurídico nem entrega documentos revistos — o gerador produz rascunhos que tem de mandar validar.

O registo está à prova de adulteração? Está, sob uma condição: qualquer adulteração fica detetável. Cada linha é assinada por HMAC sobre o hash da anterior: uma linha alterada ou apagada parte a cadeia e a verificação diz onde. A garantia assenta no facto de a chave de assinatura viver fora da base de dados. É o que acontece se AUTH_KEY e AUTH_SALT estiverem no wp-config.php, ou se definir OWC_LEDGER_KEY. Caso contrário, o WordPress guarda os salts na base de dados e um atacante que lá chegue poderia voltar a assinar a cadeia — o plugin deteta esta situação e avisa-o.

O scanner torna o site mais lento? Não. Vem desativado por defeito e, uma vez ativo, corre em WP-Cron com a frequência que escolher, indo buscar uma amostra das suas próprias páginas pelo lado do servidor. Nunca corre durante a visita de um visitante.

O scanner encontra os cookies? Parcialmente, e é importante perceber isto. O varrimento do servidor lê os cabeçalhos Set-Cookie das suas páginas: encontra portanto os cookies colocados pelo servidor, incluindo os HttpOnly. Não executa JavaScript nenhum, por isso os cookies escritos por scripts no browser não lhe são visíveis; esses são recolhidos por uma sonda que só corre para um administrador com sessão iniciada. Trate o resultado como um inventário do que foi visto, não como uma lista exaustiva — os dois ecrãs dizem-no, em vez de o deixar supor o contrário.

Como é executado um pedido de direitos? A pessoa submete o formulário e recebe um e-mail de verificação. Confirmar o pedido na página ligada arranca o prazo do artigo 12.º, n.º 3 e abre um pedido nativo do WordPress. A partir do ecrã DSAR, descarrega uma exportação JSON produzida por todos os exportadores registados no site, lança todos os apagadores, e encerra com uma resposta escrita que fica anexada ao pedido. O que a exportação cobre depende, portanto, dos plugins instalados; os que não registarem nenhum destes ganchos têm de ser tratados à mão.

Posso usar o módulo TCF para o AdSense ou o Ad Manager? Só com o seu próprio CMP ID emitido pelo IAB Europe, e mesmo assim com uma reserva. Sem CMP ID, o módulo não carrega de todo. Com um CMP ID, emite uma string TC corretamente codificada e o painel mostra cada finalidade e cada funcionalidade especial que essa string pode reivindicar — mas a política do TCF exige de um CMP registado escolhas ao nível da finalidade e ao nível do fornecedor. Aqui, os interruptores de finalidades seguem as categorias e não há escolha ao nível do fornecedor: não é um CMP registado, e os fornecedores têm fundamento para rejeitar o sinal dele. Se a receita publicitária sob TCF conta para o seu site, use um CMP certificado.

Em que língua são gerados os documentos? A política de privacidade e a política de cookies existem para os onze perfis, na língua da jurisdição: inglês, francês para a França, a Bélgica, o Luxemburgo e o Quebec, português para o Brasil. O aviso legal e os termos e condições só existem em francês; para qualquer outra jurisdição, o gerador recusa produzi-los em vez de publicar um documento desadequado.

O que posso mudar no banner? Quatro posições, um tema claro, escuro ou automático, um logótipo opcional, os textos, e um botão flutuante de reabertura. Qualquer elemento com a classe owc-open-preferences reabre o painel. «Recusar tudo» é renderizado com a mesma proeminência que «Aceitar tudo». A cruz de fecho está desativada por defeito; se a ativar, regista uma recusa completa, nunca um fecho silencioso. O painel é utilizável por teclado, os interruptores dele têm um estado visível, e a modal prende e devolve o foco. Os textos são traduzíveis e substituíveis pelo filtro owc_banner_texts.

O que é que o bloqueio automático bloqueia, exatamente? Os scripts de terceiros, os snippets de tracking inline, as iframes, os pixels de medição, as folhas de estilo de analytics e de marketing, e as fontes de media de terceiros são reescritos antes do consentimento e libertados depois, pela ordem do documento. As sugestões de recursos para um terceiro catalogado são removidas em vez de adiadas, já que uma sugestão abre uma ligação e não tem nada para restaurar. As folhas e os tipos de letra servidos por um host classificado como funcional ficam intactos. Os atributos de lazy loading dos plugins de cache são neutralizados, para que um loader não restaure um URL bloqueado. O plugin inclui 175 assinaturas, editáveis a partir do ecrã Tracker catalogue e extensíveis por filtro.

O que é inscrito no registo? Cada ação de consentimento — aceitação, recusa, gravação parcial, opt-out por GPC, renovação, retirada — é acrescentada à tabela encadeada. Uma linha contém um IP pseudonimizado, um hash do user-agent, o URL da página, o perfil de conformidade, um hash dos documentos em vigor, um hash do banner realmente mostrado e a versão do plugin — os elementos que permitem reconstituir o que o visitante viu.

Que proteções tem o formulário de direitos? Uma armadilha para robôs, um tempo mínimo de preenchimento, uma declaração obrigatória verificada no servidor, três limites de taxa distintos, um token de uso único guardado apenas sob a forma de hash, e uma confirmação que exige uma ação explícita na página — de modo que um scanner de links de correio não possa confirmar uma identidade em vez da pessoa. Um pedido cujo autor nunca confirmou a identidade não pode ser exportado, nem apagado, nem registado como satisfeito, e esta verificação é aplicada no servidor, não apenas escondendo botões.

Que sinais do Google Consent Mode v2 são emitidos? Os sete: ad_storage, ad_user_data, ad_personalization, analytics_storage, functionality_storage, personalization_storage e security_storage. As seis categorias são-lhes associadas a partir de uma fonte de verdade única, de tal forma que o banner, o bloqueio e o bootstrap não podem divergir.

Como são geridos o opt-out CCPA e o GPC? Um controlo «Do Not Sell or Share» é injetado no rodapé ou colocado por shortcode. O primeiro clique regista o opt-out, como exigem os regulamentos californianos, em vez de abrir um painel. O sinal Global Privacy Control é tratado como um opt-out vinculativo sob os perfis norte-americanos, uma vez por sessão de navegação, e o visitante é avisado se ele tiver substituído uma escolha que já tinha registado. Sob os perfis da família RGPD, o GPC é tratado como uma indicação: as categorias opcionais ficam pré-recusadas na interface, o banner continua visível, e nada fica registado — porque aí o consentimento tem de ser um ato positivo.

Que cookies é que o plugin deposita? Todos são cookies internos, nenhum serve para tracking, e estão declarados na política de cookies gerada: owc_consent (as escolhas, o token pseudónimo do browser e o perfil; duração conforme a definição de renovação, 12 meses por defeito, limitada a 13), owc_geo (país e perfil detetados, 24 horas, sem qualquer endereço IP), owc_gpc (marcador de sessão), owc_gpc_notice (5 minutos), e euconsent-v2 (string TCF, apenas se o módulo estiver ativo e depois de uma ação do visitante).

Funciona em multisite? Sim. Cada site da rede tem as tabelas próprias e as definições próprias. Uma ativação de rede percorre todos os sites apenas se a rede tiver no máximo 200; acima disso, cada site é provisionado no primeiro pedido dele.

E se apagar o plugin? O registo, as deteções do scanner, o catálogo, as ligações do OW Forms, as opções e as definições sobrevivem por defeito. Duas coisas vão-se sempre, seja qual for a sua definição: as seis tarefas agendadas, e a tabela dos pedidos de direitos — é a única que contém dados pessoais diretamente identificáveis relativos a terceiros e, uma vez o plugin fora, nada limita a retenção dela e nenhum ecrã permite responder-lhes. Exporte os seus pedidos antes de apagar o plugin. Para uma limpeza completa do resto, ative «Delete all data on uninstall» antes de apagar.

Onde encontrar apoio?