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.
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="boat" src=\'vehicle.png\' /> & the wheels…', $allowed );
// legacy: The <IMG class="bike" src="vehicle.png" /> & the wheels…
// new: The <img class="bike" src="vehicle.png"> & the wheels…
wp_kses( 'Add <code>/?preview=true§ion=grilling</code>.', $allowed );
// legacy: Add <code>/?preview=true&section=grilling</code>.
// new: Add <code>/?preview=true§ion=grilling</code>.
wp_kses( 'Click on the <butto', $allowed );
// legacy: Click on the <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
<$500ou>5yoera engolido. Agora passa a<e>, e o texto sobrevive. - E comercial ambíguo. Uma referência nomeada sem ponto e vírgula, como
§ionnum 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. - 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
NOSCRIPTe 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.
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.
- Arrays que permitem
scriptoustyleinline. Um shortcode que imprime um script inline e o passa porwp_kses()comscriptpermitido 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 porwp_add_inline_script(), em vez de passar pelo sanitizador. - Arrays que permitem
svgoumath. 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. - Arrays para
<title>e semelhantes. O<title>é um elemento especial e o conteúdo é tratado como texto. Daí o exemplo do<sneaky>escapado.
Se o array permitir apenas a, strong, em, img e poucos atributos, o efeito provável é cosmético: <IMG ... /> passa a <img ...>.
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 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. Não consegui confirmar se a reescrita já vem na Beta 1, por isso verifique 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.txtwp-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.
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.
Não encontrei uma data confirmada para remover o parser antigo. Trate o opt-out como algo que vai desaparecer e planeie em conformidade.
Qual é o calendário do WordPress 7.2?
Do artigo do calendário da 7.2, sempre às 15:00 UTC:
| Marco | Data |
|---|---|
| Beta 1 | 20 de outubro de 2026 |
| Beta 2 | 27 de outubro de 2026 |
| Beta 3 | 3 de novembro de 2026 |
| Beta 4 | 10 de novembro de 2026 |
| RC1 | 17 de novembro de 2026 |
| RC2 | 24 de novembro de 2026 |
| RC3 | 1 de dezembro de 2026 |
| Lançamento final | 8 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?
- Pesquisar o código por
wp_kses(,wp_kses_post(,wp_kses_data(e arrays$allowedpróprios. Listar quais recebem HTML de utilizadores ou editores. - Montar um staging em trunk por um dos caminhos acima e confirmar que o filtro existe.
- Correr o script de diff contra uma cópia do conteúdo de produção. Triar por causa, não por artigo.
- Reescrever os testes frágeis para verificarem estrutura e gerar uma vez os fixtures de texto, com revisão humana.
- Verificar shortcodes e blocos que emitem script, style, SVG ou MathML inline.
- 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
- Progress Report: wp_kses(), Make Core, 7 de outubro de 2026. Exemplos, nome do filtro, afirmação sobre 7.2 ou 7.3.
- Calendário da release party 7.2, Make Core, 6 de outubro de 2026.
- Pull request 13271, KSES: Reimplement with Tag Processor. Nome da função, filtro, lista de alterações de comportamento. Os tickets Trac 66208 e 65984 são aí referidos.
- Ticket Trac 66208. Não consegui abrir o ticket diretamente, por isso os dados sobre ele vêm da página do PR.
Verificado a 10 de outubro de 2026. O comportamento do trunk pode mudar antes do lançamento.







