wp_kses() na HTML API: o que testar antes do WordPress 7.2

wp_kses() na HTML API: o que testar antes do WordPress 7.2

Última verificação: 15 de outubro de 2026
17 min de leitura
Tutorial
500+ projetos WP
Desenvolvedor full-stack

O wp_kses() está por trás do wp_kses_post(), da limpeza de comentários, do texto de widgets e de cada chamada wp_kses( $html, $allowed ) no seu plugin. No trunk, passa agora a correr sobre a HTML API do WordPress em vez de expressões regulares. Segundo o relatório de progresso de Dennis Snell, a alteração deve chegar ao WordPress 7.2 ou 7.3, consoante o que os testes revelarem. Este artigo explica o que muda no resultado, como encontrar o código seu que é afetado e para que serve o opt-out temporário.

#O que muda, exatamente, no wp_kses()

A implementação antiga desmontava o markup com expressões regulares. A nova lê-o com o tokenizador da HTML API, decide por etiqueta e atributo o que é permitido e volta a escrever o resultado. O pull request chama-se “KSES: Reimplement with Tag Processor” (PR 13271) e está ligado ao ticket Trac 66208. Segundo o PR, o código novo vive numa função chamada wp_sanitize_html_kses().

A intenção do relatório é clara: o wp_kses() não deve ficar pior do que era. O relatório diz também que plugins e temas não deverão precisar de alterações. As duas frases podem ser verdadeiras e os seus testes ficarem vermelhos na mesma, porque “igualmente seguro” não quer dizer “os mesmos bytes”. A nova serialização é o centro da mudança, e altera strings.

Fonte: Progress Report: wp_kses(), 7 de outubro de 2026.

Estado a 15 de outubro de 2026: a alteração já está no trunk. Entrou a 7 de outubro de 2026 como changeset 64233 (commit no GitHub), e a mensagem do commit diz que o código que chama a função não deverá precisar de alterações. O docblock de wp_kses() no trunk já tem a anotação @since 7.2.0, mas o relatório continua a falar em 7.2 ou 7.3, e quem decide o que entra na versão final é o período de testes, não a anotação. A 9 de outubro entraram dois commits de seguimento em kses.php: um normaliza todos os delimitadores de bloco e o outro só serializa os atributos JSON de bloco que não estejam vazios (histórico do ficheiro).

#Que entradas produzem resultado diferente

O relatório apresenta pares de antes e depois. O bloco abaixo inspira-se neles (o meu array de etiquetas permitidas, as entradas do relatório). Corra os casos no seu próprio build trunk antes de confiar na minha transcrição.

<?php
// Modelled on the examples in the Make Core progress report.
$allowed = array(
	'a'    => array( 'href' => true ),
	'code' => true,
	'img'  => array( 'src' => true, 'class' => true ),
);

wp_kses( 'Click on my <script>alert(1)</script>!', $allowed );
// legacy: Click on my alert(1)!
// new:    Click on my !

wp_kses( 'The <IMG class=bike class="bo&#x0061;t" src=\'vehicle.png\' /> & the wheels&hellip;', $allowed );
// legacy: The <IMG class="bike" src="vehicle.png" /> &amp; the wheels&hellip;
// new:    The <img class="bike" src="vehicle.png"> &amp; the wheels…

wp_kses( 'Add <code>/?preview=true&section=grilling</code>.', $allowed );
// legacy: Add <code>/?preview=true&amp;section=grilling</code>.
// new:    Add <code>/?preview=true§ion=grilling</code>.

wp_kses( 'Click on the <butto', $allowed );
// legacy: Click on the &lt;butto
// new:    Click on the (trailing space, nothing else)

O relatório agrupa as diferenças assim:

  • Normalização. Os nomes das etiquetas passam a minúsculas, os atributos ficam com aspas duplas, num atributo repetido vence o primeiro e as referências de caracteres são reescritas.
  • Chavetas angulares soltas. Texto como <$500 ou >5yo era engolido. Agora passa a &lt; e &gt;, e o texto sobrevive.
  • E comercial ambíguo. Uma referência nomeada sem ponto e vírgula, como &section num URL dentro de um nó de texto, é descodificada como o browser a descodifica. Por isso o terceiro caso dá o sinal de parágrafo.
  • Script e style. Quando estes elementos não são permitidos, o conteúdo desaparece juntamente com as etiquetas. O código antigo deixava o conteúdo como texto visível.
  • Entrada truncada. Uma última etiqueta incompleta é descartada, não escapada.
  • Elementos especiais. Texto com aspeto de etiqueta dentro de um <title> permitido é escapado, enquanto o mesmo texto dentro de um <div> desaparece com a etiqueta desconhecida.
  • CDATA, PLAINTEXT e elementos especiais incompletos. As secções CDATA e o conteúdo de PLAINTEXT passam a texto escapado normal. Um <title> ou outro elemento especial que chegue incompleto é removido por inteiro.
  • SVG e MathML. Conteúdo que exige parsing complexo faz a função parar e devolver apenas o que vinha antes. O relatório mostra um <li> com MathML e um <span> aninhado cortado nesse ponto.
  • Limites rígidos. Alguns comentários malformados, o NOSCRIPT e elementos confundíveis passam a ser rejeitados.
  • Etiquetas desequilibradas. Mantêm-se de propósito, porque o código existente depende disso.

Numa loja WooCommerce típica, os primeiros sítios onde olhar são descrições de produto com medidas como “<5 cm” coladas da folha de cálculo do fornecedor, HTML importado de fornecedores com etiquetas em maiúsculas e construtores de páginas que imprimem SVG inline. É uma suposição sobre onde procurar, não uma medição.

Nas fontes que li não encontrei confirmado como o <script> se comporta quando o permite expressamente no array. Teste esse caso por si mesmo, em vez de assumir um resultado.

#Porque é que o wp_kses() usa o Tag Processor e não o HTML Processor

A HTML API tem duas peças. O WP_HTML_Tag_Processor existe desde o WordPress 6.2 e percorre o documento token a token: etiquetas, comentários e nós de texto (referência da classe). O WP_HTML_Processor chegou na 6.4, constrói a árvore do documento e, quando encontra marcação que ainda não suporta, termina cedo para não estragar o documento (referência da classe).

Segundo uma nota de rodapé do relatório, o novo wp_kses() usa o Tag Processor. Analisa cada etiqueta, comentário e nó de texto, mas não conhece a estrutura em árvore do documento. O HTML Processor constrói essa árvore, à custa de bastante mais tempo e esforço. A descrição do PR acrescenta porque é que os elementos abertos não são fechados: muito código do núcleo chama o wp_kses() com entradas incompletas de propósito, por exemplo uma etiqueta de abertura de um contentor com parte do conteúdo, separada do fecho. Isolar o conteúdo fechando esses elementos estragaria sites. É por isso que as etiquetas desequilibradas da lista anterior continuam desequilibradas.

#O meu array de allowed html continua a funcionar

Na maioria dos casos sim, porque a lógica de permissões é a mesma: uma etiqueta ou um atributo está no array ou não está. O que muda é o que sai à volta. Três padrões merecem atenção.

  1. Arrays que permitem script ou style inline. Um shortcode que imprime um script inline e o passa por wp_kses() com script permitido está exatamente na zona que o relatório toca. Compare o resultado antigo com o novo. Pergunte também se o script não deveria antes passar por wp_add_inline_script(), em vez de passar pelo sanitizador.
  2. Arrays que permitem svg ou math. O caso comum são conjuntos de ícones inline. Se a regra do relatório se aplicar, o resultado pode ser cortado na primeira construção que o parser não consiga tratar. Compare cada ícone que entrega.
  3. Arrays para <title> e semelhantes. O <title> é um elemento especial e o conteúdo é tratado como texto. Daí o exemplo do &lt;sneaky&gt; escapado.

Se o array permitir apenas a, strong, em, img e poucos atributos, o efeito provável é cosmético: <IMG ... /> passa a <img ...>.

#Onde o WordPress executa o wp_kses() sem que o chame

O núcleo liga o sanitizador à gravação. A kses_init() está ligada aos hooks init e set_current_user em default-filters.php e só ativa os filtros quando o utilizador atual não tem a capacidade unfiltered_html (referência de kses_init). Nesse caso, a kses_init_filters() acrescenta wp_filter_kses a title_save_pre, wp_filter_post_kses a content_save_pre, excerpt_save_pre e content_filtered_save_pre, e filtra pre_comment_content com a variante básica (referência de kses_init_filters).

A página de referência do wp_kses() lista ainda dezenas de chamadas diretas no núcleo, entre elas Walker_Comment::filter_comment_text(), wp_filter_oembed_result(), img_caption_shortcode(), filter_block_kses_value() e os ecrãs de instalação de plugins e temas (referência de wp_kses). A reescrita afeta comentários, resultados oEmbed e legendas de imagem mesmo que o seu código nunca chame o wp_kses().

Para os testes há uma consequência prática. Numa instalação simples, administradores e editores têm unfiltered_html; em multisite só os super administradores (funções e capacidades). O conteúdo deles não passa pelo kses ao ser gravado. Teste a gravação com uma conta com a função Autor ou Contribuidor, que é onde os filtros atuam, e não só com a sua conta de administrador.

#O que quebra nos testes que comparam HTML limpo

Tudo o que verifica uma string exata. Um teste como a primeira asserção abaixo passa hoje e pode falhar depois da reescrita, embora o markup signifique o mesmo. A segunda asserção sobrevive à normalização.

<?php
// Brittle: pins the exact string the sanitizer happens to emit.
$this->assertSame( '<a href="/x">Read</a>', wp_kses_post( "<A HREF='/x'>Read</A>" ) );

// Sturdier: asserts on what the markup means.
$p = new WP_HTML_Tag_Processor( wp_kses_post( "<A HREF='/x'>Read</A>" ) );
$this->assertTrue( $p->next_tag( 'a' ) );
$this->assertSame( '/x', $p->get_attribute( 'href' ) );

Fixe o comportamento, não os bytes. Nos fixtures que têm de ficar iguais à string, volte a gerá-los uma vez no trunk, leia o diff como pessoa e faça commit do novo valor esperado com um comentário a indicar a versão em que mudou. Um “atualizar snapshots” em bloco é a forma como uma regressão real fica aprovada.

Os testes visuais e os ficheiros de referência pedem o mesmo cuidado. Os modelos de email que limpa antes do envio são um sítio típico onde uma string guardada é comparada com uma recém-gerada.

#Como usar o assertEqualHTML() nos testes do wp_kses()

Nos comentários ao relatório, Jon Surrell recomenda o assertEqualHTML() a quem tenha testes a falhar por causa das diferenças do kses. O método chegou no WordPress 6.9 como parte de WP_UnitTestCase (ticket 63527). Segundo o artigo de JuanMa Garrido no Developer Blog, de 26 de fevereiro de 2026, analisa as duas strings com WP_HTML_Processor e compara as árvores, não os bytes.

Ignora exatamente o tipo de diferença que a reescrita traz: maiúsculas nos nomes de etiqueta, estilo de aspas, ordem de atributos e de classes, as várias formas de uma referência de caracteres e atributos duplicados, em que ganha o primeiro. Continua a falhar se mudar o conteúdo, o valor de um atributo ou uma classe, ou se faltar ou sobrar um atributo.

<?php
$this->assertEqualHTML(
	'<a href="/x">Read</a>',
	wp_kses_post( "<A HREF='/x'>Read</A>" )
);

O mesmo artigo marca o limite: quando a serialização exata importa, como o conteúdo de um <script> ou um valor concreto de data-wp-context, mantenha o assertSame(). No kses isso significa assertSame() para cada caso de script ou style que permita e assertEqualHTML() para o resto. Se a sua suite corre contra o WordPress 6.9 ou posterior, o método já está disponível.

#Como testo no trunk sem tocar na produção

Três caminhos, todos em staging ou localmente. São sugestões minhas, não fazem parte do relatório oficial.

Plugin WordPress Beta Tester. Pode mover um site de staging para as versões noturnas e depois para as betas da 7.2. Segundo o calendário, a Beta 1 sai a 20 de outubro de 2026. A alteração entrou no trunk a 7 de outubro, mas verifique na mesma se o filtro wp_kses_force_legacy_parser existe no código que instalou.

WP-CLI nightly. Numa cópia descartável:

# Staging only, never production.
wp core update --version=nightly --force
wp core version
wp eval-file bin/kses-diff.php > kses-diff.txt
tail -n 1 kses-diff.txt

wp-env. Aponte o core para o espelho do trunk e monte o plugin:

{
  "core": "WordPress/WordPress#master",
  "plugins": [ "." ]
}

Qualquer que seja o caminho, termine por confirmar que o filtro existe, por exemplo com grep -rn wp_kses_force_legacy_parser wp-includes/. Se não existir, não está a testar a reescrita.

#Como comparo o resultado limpo de conteúdo real

Casos sintéticos só encontram os erros que já imaginou. O teste útil é a sua própria base de dados. O script abaixo limpa cada artigo publicado duas vezes, uma com o parser antigo forçado e outra sem, e imprime só os artigos que diferem.

<?php
// bin/kses-diff.php
// Run on a trunk build: wp eval-file bin/kses-diff.php > kses-diff.txt
$ids  = get_posts( array(
	'post_type'   => 'any',
	'post_status' => 'publish',
	'numberposts' => -1,
	'fields'      => 'ids',
) );
$diff = 0;

foreach ( $ids as $id ) {
	$raw = get_post_field( 'post_content', $id );

	add_filter( 'wp_kses_force_legacy_parser', '__return_true' );
	$old = wp_kses_post( $raw );
	remove_filter( 'wp_kses_force_legacy_parser', '__return_true' );

	$new = wp_kses_post( $raw );

	if ( $old !== $new ) {
		++$diff;
		printf( "== %d %s\n--- old\n%s\n+++ new\n%s\n", $id, get_permalink( $id ), $old, $new );
	}
}

printf( "%d of %d posts differ\n", $diff, count( $ids ) );

Corra-o numa cópia da base de dados de produção, porque os editores colam coisas estranhas. Leia os primeiros vinte diffs à mão. Conte com um punhado de causas repetidas (etiquetas auto-fechadas, um < solto numa medida, SVG inline). Corrigir uma causa corrige muitos artigos de uma vez.

Alargue a rede com o mesmo padrão: troque wp_kses_post() pela chamada que o seu plugin faz com o seu próprio array e alimente-a com valores de opções, texto de widgets e comentários, não só com artigos.

Tenha presente o que o script mede. Chama wp_kses_post() sobre todo o conteúdo, incluindo artigos escritos por utilizadores com unfiltered_html, que nunca passaram pelo kses ao serem gravados. Nesses artigos o diff não descreve uma alteração visível hoje. Mostra o que aconteceria se alguém sem essa capacidade os voltasse a gravar.

#Quando devo usar o opt-out do parser antigo

O filtro chama-se wp_kses_force_legacy_parser. Devolver true repõe o parser antigo. O relatório descreve-o como forma de os donos de sites não executarem o código de substituição, e a palavra que conta é temporário.

<?php
// wp-content/mu-plugins/kses-legacy-parser.php
// Stopgap only. Delete it once your output diff is clean.
add_filter( 'wp_kses_force_legacy_parser', '__return_true' );

Há duas situações. A primeira: encontrou uma regressão num site de cliente durante a janela de testes e precisa de manter o site estável enquanto corrige o código ou reporta o erro. A segunda: um plugin que não controla deixa de funcionar e o autor ainda não lançou correção. Ponha o filtro num mu-plugin, para ficar visível num só sítio, e associe-lhe uma tarefa com data de remoção.

Não o entregue como predefinição permanente. A implementação antiga tem problemas conhecidos que a reescrita quer resolver, entre eles falhas de PCRE com valores de atributo grandes e conteúdo de scripts apresentado como texto visível, segundo a descrição do PR. Um opt-out silencioso conserva esses problemas e esconde a migração do próximo programador.

O relatório não promete um prazo longo. Diz que a implementação original fica por agora por razões de compatibilidade e que vai desaparecer em versões futuras, talvez logo no WordPress 7.2.0. Planeie como se o parser antigo pudesse não existir na próxima versão principal.

Como funciona no trunk: o wp_kses() aplica o filtro com true como valor por omissão, e o default-filters.php regista __return_false nele, que é o que ativa o código novo. O wp-settings.php carrega os mu-plugins depois do default-filters.php, por isso o seu __return_true com a prioridade por omissão corre mais tarde e prevalece (kses.php no trunk). Não chame wp_sanitize_html_kses() diretamente: o docblock marca-a como privada e de transição.

#Desempenho do novo wp_kses() face ao parser antigo

Não há números publicados. Questionado sobre benchmarks nos comentários ao relatório, Dennis Snell respondeu que esta alteração não inclui nenhum e que a expectativa é que o código novo seja mais rápido e use menos memória. Dá três razões:

  • O código antigo parte o documento num grande array de tokens e mantém-no em memória. O novo avança token a token sem guardar esse array.
  • O código antigo faz várias passagens de processamento de texto sobre o documento inteiro. O novo elimina a maior parte e faz quase sempre uma única passagem para a frente.
  • O código antigo chama sempre serialize_blocks( parse_blocks() ) sobre o documento inteiro. O novo só analisa um bloco quando ele existe de facto e apoia-se no WP_Block_Parser para adiar ainda mais o trabalho.

Snell admite também que parte do processamento de HTML é mais exigente na implementação nova. Se o seu site limpa muito conteúdo em cada pedido, meça em staging com o filtro ligado e desligado e publique os números nos comentários ao relatório, que é exatamente o que o autor pede.

#Qual é o calendário do WordPress 7.2

Do artigo do calendário da 7.2, sempre às 15:00 UTC:

MarcoData
Beta 120 de outubro de 2026
Beta 227 de outubro de 2026
Beta 33 de novembro de 2026
Beta 410 de novembro de 2026
RC117 de novembro de 2026
RC224 de novembro de 2026
RC31 de dezembro de 2026
Lançamento final8 de dezembro de 2026

O relatório diz 7.2 ou 7.3. Não pode contar com nenhuma das duas, mas também não pode excluir a 7.2. Um plano sensato: correr o script de diff em staging antes da Beta 1, de novo depois da RC1, e reportar no ticket Trac tudo o que surpreender enquanto a janela está aberta. É exatamente esse retorno que o autor pede.

#O que deve uma agência fazer esta semana

  1. Pesquisar o código por wp_kses(, wp_kses_post(, wp_kses_data( e arrays $allowed próprios. Listar quais recebem HTML de utilizadores ou editores.
  2. Montar um staging em trunk por um dos caminhos acima e confirmar que o filtro existe.
  3. Correr o script de diff contra uma cópia do conteúdo de produção. Triar por causa, não por artigo.
  4. Reescrever os testes frágeis para verificarem estrutura e gerar uma vez os fixtures de texto, com revisão humana.
  5. Verificar shortcodes e blocos que emitem script, style, SVG ou MathML inline.
  6. Decidir por cliente se o opt-out é necessário, documentá-lo e fixar uma data de remoção.

Se o projeto tem anos de limpeza própria à volta de um ecossistema de plugins, esta auditoria encaixa no nosso serviço de desenvolvimento WordPress à medida.

#Fontes

Verificado a 15 de outubro de 2026 contra o trunk. O comportamento do trunk pode mudar antes do lançamento.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Perguntas frequentes

Respostas práticas para aplicar o tema na execução real.

O novo wp_kses() entra no WordPress 7.2#
Ainda não está decidido. O relatório no Make Core indica 7.2 ou 7.3, consoante o que o período de testes revelar.
Como volto ao parser antigo#
Devolva true no filtro wp_kses_force_legacy_parser. É uma saída temporária, enquanto corrige o código que depende do resultado antigo.
Os meus arrays de allowed html vão deixar de funcionar#
O relatório prevê que plugins e temas não precisem de alterações, mas o resultado de alguns inputs muda. Compare conteúdo real num build trunk.
O que acontece ao conteúdo de script e style#
Quando script ou style não são permitidos, o código novo remove também o conteúdo, não só as etiquetas. O código antigo deixava-o como texto visível.

Precisa de FAQ adaptado ao setor e mercado? Crio uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados