Extrair a primeira hiperligação de um artigo é um padrão recorrente no desenvolvimento de projetos WordPress empresariais. Seja na construção de portais agregadores de notícias, feeds de curadoria editorial onde o título do post aponta diretamente para a fonte externa, ou sistemas automatizados de cartões de leitura, a necessidade de ler o atributo href da primeira tag de âncora surge com frequência.
Historicamente, muitos programadores recorreram a expressões regulares para resolver esta tarefa com duas ou três linhas de código. No entanto, o HTML dos editores visuais modernos, incluindo os blocos do Gutenberg, contém comentários de bloco, atributos formatados em múltiplas linhas e entidades escapadas que quebram facilmente qualquer padrão Regex ingénuo.
Neste guia técnico, analisamos as abordagens disponíveis no ecossistema PHP e WordPress: o WP_HTML_Tag_Processor (no núcleo desde o WordPress 6.2), a função nativa get_url_in_content(), o WP_HTML_Processor para consultas com contexto, a classe DOMDocument do PHP e o novo Dom\HTMLDocument do PHP 8.4, além da persistência em post meta para eliminar o desperdício de ciclos de processador em produção.
Porque é que regex falha ao extrair links de HTML
A tentação de usar preg_match para capturar uma hiperligação é compreensível devido à sua aparente simplicidade. Um exemplo típico frequentemente encontrado em fóruns e repositórios antigos assemelha-se ao seguinte:
// ABORDAGEM PROBLEMÁTICA: Regex ingénua
preg_match( '/<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>/siU', $content, $matches );
$first_url = $matches[2] ?? null;Embora este trecho funcione em testes com frases curtas de laboratório, ele falha silenciosamente em múltiplos cenários reais de produção:
- Comentários de bloco e rascunhos: O editor Gutenberg utiliza extensivamente comentários HTML para delimitar blocos, tais como
<!-- wp:paragraph -->. Se um comentário contiver uma tag de demonstração ou hiperligação antiga, o Regex irá capturá-la antes do conteúdo visível. - Conteúdo de
<script>e<style>: Um bloco HTML personalizado com JavaScript que monta uma string como"<a href=...>"contém, para o Regex, uma tag de âncora perfeitamente válida. Um analisador HTML sabe que o interior de<script>é texto e não marcação. - Quebras de linha e atributos alternativos: Atributos como
data-wp-interactive,target="_blank"ou classes CSS complexas divididas em várias linhas podem induzir comportamentos imprevisíveis ou falhas de correspondência na expressão. - Limite de backtracking: O motor PCRE do PHP pára quando um padrão ultrapassa
pcre.backtrack_limit, cujo valor por omissão é 1000000 segundo o manual do PHP. Nesse casopreg_match()devolvefalseem vez de 0 ou 1, e sópreg_last_error()revelaPREG_BACKTRACK_LIMIT_ERROR. Em artigos longos, com quantificadores gulosos como(.*)no padrão acima, o link simplesmente desaparece sem qualquer aviso no registo.
Por definição teórica da ciência da computação, o HTML é uma linguagem que não pertence à classe das linguagens regulares. O processamento fiável requer analisadores sintáticos dedicados. O próprio núcleo do WordPress percorreu este caminho: a função get_url_in_content() usou uma expressão regular durante mais de uma década antes de passar para o HTML API, como mostramos mais abaixo.
Como extrair o primeiro link com WP_HTML_Tag_Processor
A partir da versão 6.2, o WordPress integrou no seu núcleo o WP_HTML_Tag_Processor. A documentação da própria classe descreve-o assim: não faz o parsing completo do HTML nem desce recursivamente pela estrutura, percorre o documento de forma linear e analisa apenas as tags de abertura. É precisamente o que é preciso para ler um atributo href, sem os custos de memória associados à construção de uma árvore DOM completa.
Testámos o comportamento com o código do WordPress 7.1.3: o next_tag( 'a' ) salta por cima de comentários HTML e do conteúdo de <script>, e o get_attribute( 'href' ) devolve o valor já descodificado, de modo que & numa query string chega ao seu código como &. Se o atributo não existir, o método devolve null; num atributo booleano sem valor devolve true, por isso a verificação is_string() não é opcional.
Função para extrair o primeiro link com WP_HTML_Tag_Processor
Eis uma implementação limpa e defensiva que pode integrar no seu tema personalizado ou plugin de funcionalidades. Em vez de parar na primeira tag a, continua até encontrar um endereço http ou https:
<?php
declare(strict_types=1);
/**
* Extrai o URL do primeiro link http(s) presente num fragmento HTML.
* Utiliza o WP_HTML_Tag_Processor nativo do WordPress.
*
* @param string $content Conteúdo HTML do artigo.
* @return string|null Retorna o URL sanitizado ou null se não existir.
*/
function wppoland_obter_primeiro_url_html_processor( string $content ): ?string {
if ( '' === trim( $content ) ) {
return null;
}
$processor = new WP_HTML_Tag_Processor( $content );
// Percorre as tags âncora 'a' pela ordem do documento
while ( $processor->next_tag( 'a' ) ) {
$href = $processor->get_attribute( 'href' );
if ( ! is_string( $href ) || '' === trim( $href ) ) {
continue;
}
$href = trim( $href );
$esquema = strtolower( (string) wp_parse_url( $href, PHP_URL_SCHEME ) );
// Ignora mailto:, tel:, âncoras locais e caminhos relativos
if ( in_array( $esquema, array( 'http', 'https' ), true ) ) {
return esc_url_raw( $href );
}
}
return null;
}Vantagens do WP_HTML_Tag_Processor
- Eficiência de memória: O leitor avança de forma linear através do fluxo de caracteres sem alocar nós em memória para cada tag do documento.
- Resiliência: Comentários, o interior de
<script>e<style>e atributos sem aspas não confundem a leitura, porque o processador segue as regras de tokenização do HTML em vez de procurar padrões de texto. - Zero dependências externas: Não requer extensões compiladas adicionais além do próprio runtime do WordPress.
Como usar get_url_in_content no WordPress
Antes de escrever a sua própria função, vale a pena saber que o núcleo já tem uma: get_url_in_content( $content ), documentada como “extrai e devolve o primeiro URL do conteúdo recebido” (tradução própria da referência oficial). Existe desde o WordPress 3.6.0 e é usada no formato de post “link”: o tema Twenty Thirteen chama-a na função twentythirteen_get_link_url() e, se não houver link, regressa ao permalink do artigo.
A implementação mudou de forma importante. Comparámos o código dos ramos 6.8 e 6.9 do repositório oficial:
- Até ao WordPress 6.8 a função usava
preg_match( '/<a\s[^>]*?href=([\'"])(.+?)\1/is', ... ). Num teste com o mesmo HTML usado neste artigo, esse padrão devolveu o link que estava dentro de um comentário HTML, ou seja, um link que o leitor nunca viu. - A partir do WordPress 6.9 a função cria um
WP_HTML_Tag_Processor, percorre as tagsAe devolve o primeirohrefnão vazio passado porsanitize_url(). O link comentado deixa de ser encontrado.
$url = get_url_in_content( get_the_content() );
if ( $url ) {
printf(
'<a href="%s" rel="noopener">Ler na fonte</a>',
esc_url( $url )
);
}A função devolve false quando não encontra nada. Há duas limitações a ter em conta. Primeiro, aceita qualquer esquema: um mailto: ou um #comentarios no início do texto é devolvido antes do link editorial. Segundo, num site que ainda corre o WordPress 6.8 ou anterior, o comportamento é o da expressão regular. Se o projeto já está na 6.9 ou posterior e o primeiro href serve, use a função do núcleo; se precisa de filtrar esquemas ou de suportar versões antigas, a função da secção anterior continua a ser a escolha certa.
Como encontrar o primeiro link dentro de um parágrafo com WP_HTML_Processor
O Tag Processor não sabe onde está. Para ele, o link que envolve uma imagem num bloco core/image e o link no meio de um parágrafo são apenas duas tags a. Quando a regra de negócio é “o primeiro link escrito no texto, não o da imagem”, precisa do WP_HTML_Processor, disponível desde o WordPress 6.4, que constrói a pilha de elementos abertos e expõe-na através de get_breadcrumbs().
<?php
function wppoland_primeiro_link_em_paragrafo( string $content ): ?string {
$processor = WP_HTML_Processor::create_fragment( $content );
if ( null === $processor ) {
return null;
}
while ( $processor->next_tag( 'A' ) ) {
$trilho = $processor->get_breadcrumbs();
// Aceita apenas links dentro de <p> e fora de <figure>
if ( ! in_array( 'P', $trilho, true ) || in_array( 'FIGURE', $trilho, true ) ) {
continue;
}
$href = $processor->get_attribute( 'href' );
if ( is_string( $href ) && '' !== trim( $href ) ) {
return esc_url_raw( trim( $href ) );
}
}
return null;
}Optámos por verificar o trilho em vez de usar a consulta next_tag( array( 'breadcrumbs' => array( 'P', 'A' ) ) ). Essa consulta exige que o A seja filho direto do P; no nosso teste, um link dentro de <p><strong><a> foi ignorado por ela e encontrado pela verificação com in_array().
O WP_HTML_Processor tem uma regra que a documentação da classe deixa clara: quando encontra marcação que ainda não suporta, pára de processar em vez de arriscar uma interpretação errada. Nessa situação next_tag() devolve false e get_last_error() deixa de ser null. Se o resultado for null e o artigo tiver links visíveis, verifique get_last_error() e recorra ao Tag Processor como alternativa.
Como extrair links com DOMDocument no WordPress
Em ambientes legados de PHP puro ou situações onde o código precisa de ser executado fora do contexto do WordPress, a extensão DOMDocument do PHP (alimentada pela biblioteca libxml2) continua a ser a solução clássica.
No entanto, o analisador HTML da libxml2 segue regras anteriores ao HTML5 e exige três precauções: suprimir avisos sobre tags contemporâneas (como <article>, <section>, <nav>), forçar a descodificação correta de caracteres UTF-8 e limpar a memória interna de erros para não poluir os registos do sistema.
Função DOMDocument com tratamento de erros
<?php
declare(strict_types=1);
/**
* Analisa o HTML através da classe DOMDocument do PHP para recuperar o primeiro link http(s).
*
* @param string $html Conteúdo HTML para processar.
* @return string|null URL encontrado ou null caso não exista.
*/
function wppoland_obter_primeiro_url_dom( string $html ): ?string {
if ( '' === trim( $html ) ) {
return null;
}
$doc = new DOMDocument();
// 1. Suprimir mensagens de erro e avisos de sintaxe HTML5
$erros_anteriores = libxml_use_internal_errors( true );
// 2. Pré-fixar cabeçalho XML para forçar UTF-8 correto no motor libxml2
$html_preparado = '<?xml encoding="utf-8" ?>' . $html;
// 3. Carregar o HTML sem acesso à rede durante o carregamento
$sucesso = $doc->loadHTML(
$html_preparado,
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET
);
$url_final = null;
if ( $sucesso ) {
foreach ( $doc->getElementsByTagName( 'a' ) as $link ) {
$href = trim( $link->getAttribute( 'href' ) );
$esquema = strtolower( (string) parse_url( $href, PHP_URL_SCHEME ) );
if ( in_array( $esquema, array( 'http', 'https' ), true ) ) {
$url_final = $href;
break;
}
}
}
// 4. Limpar o buffer de erros e repor o estado prévio da biblioteca
libxml_clear_errors();
libxml_use_internal_errors( $erros_anteriores );
return $url_final;
}Esta rotina garante que carateres acentuados do idioma português (como á, ç, õ) não fiquem corrompidos durante o carregamento do documento. A versão anterior deste artigo passava o resultado por FILTER_SANITIZE_URL e FILTER_VALIDATE_URL; retirámos os dois filtros, e a secção sobre validação explica porquê.
Em código antigo encontra-se também mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) como truque de codificação. O PHP 8.2 tornou obsoleto o uso de HTML-ENTITIES em todas as funções mbstring (lista de obsolescências do PHP 8.2) e o PHP 8.4 emite um aviso Deprecated em cada chamada. A alternativa sugerida pela própria mensagem é mb_encode_numericentity().
Como usar Dom\HTMLDocument no PHP 8.4
O PHP 8.4 trouxe uma nova API DOM no namespace Dom, com “suporte conforme às normas para analisar documentos HTML5”, segundo as notas de lançamento (tradução própria). A classe Dom\HTMLDocument analisa HTML de acordo com o living standard, o que elimina os avisos sobre tags HTML5 e o truque do cabeçalho XML. A classe DOMDocument continua disponível para compatibilidade.
<?php
function wppoland_obter_primeiro_url_dom84( string $html ): ?string {
if ( '' === trim( $html ) ) {
return null;
}
$doc = Dom\HTMLDocument::createFromString( $html, LIBXML_NOERROR, 'UTF-8' );
foreach ( $doc->querySelectorAll( 'a[href]' ) as $link ) {
$href = trim( $link->getAttribute( 'href' ) );
$esquema = strtolower( (string) parse_url( $href, PHP_URL_SCHEME ) );
if ( in_array( $esquema, array( 'http', 'https' ), true ) ) {
return $href;
}
}
return null;
}Três detalhes do manual de createFromString(): o segundo argumento aceita as constantes LIBXML_* combinadas por OR; o terceiro, overrideEncoding, fixa a codificação em vez de deixar o PHP adivinhá-la; e uma codificação desconhecida lança ValueError. Com o PHP 8.4.24, esta função devolveu https://exemplo.pt/ação?x=1&y=2 intacto, com o & descodificado. Os seletores CSS permitem ainda consultas como p > a[href^="https://"], que no DOMDocument exigiriam XPath.
Qual método escolher para extrair o primeiro link
Para compreender quando escolher cada solução, comparamos as características de cada método. Não publicamos tempos de execução: dependem do servidor, da versão do PHP e do tamanho do artigo, e só valem se forem medidos no seu próprio projeto.
| Critério | Regex (preg_match) | DOMDocument (libxml2) | Dom\HTMLDocument | WP_HTML_Tag_Processor |
|---|---|---|---|---|
| Modelo de análise | Padrão de texto | Árvore DOM, regras pré-HTML5 | Árvore DOM, HTML5 | Leitura linear das tags de abertura |
| Alocação de memória | Baixa | Árvore completa | Árvore completa | Sem árvore |
Comentários e <script> | Falível | Corretos | Corretos | Corretos |
| Contexto (pai do link) | Não | Sim | Sim, com seletores CSS | Não (use WP_HTML_Processor) |
| Falha típica | false por limite de backtracking | Avisos e UTF-8 | ValueError com codificação inválida | Não conhece o contexto do link |
| Requisito mínimo | Qualquer PHP | Extensão DOM | PHP 8.4 | WordPress 6.2+ |
Para qualquer projeto moderno em WordPress, o WP_HTML_Tag_Processor é a escolha mais simples em termos de fiabilidade e economia de recursos. O Dom\HTMLDocument ganha quando o código corre fora do WordPress ou precisa de seletores.
Como obter o primeiro link externo no WordPress
Os artigos de compilação começam muitas vezes com um link interno, do tipo “saltar para os comentários” ou para um artigo relacionado. A função seguinte ignora âncoras locais e devolve o primeiro link cujo host é diferente do host do próprio site:
function wppoland_get_first_external_link( $content ) {
if ( empty( $content ) ) {
return false;
}
$doc = new DOMDocument();
libxml_use_internal_errors( true );
$doc->loadHTML( '<?xml encoding="utf-8" ?>' . $content );
libxml_clear_errors();
$site_host = wp_parse_url( home_url(), PHP_URL_HOST );
$links = $doc->getElementsByTagName( 'a' );
foreach ( $links as $link ) {
$href = trim( $link->getAttribute( 'href' ) );
if ( '' === $href || '#' === $href[0] ) {
continue;
}
$host = wp_parse_url( $href, PHP_URL_HOST );
if ( $host && $host !== $site_host ) {
return $href;
}
}
return false;
}Como obter o URL da primeira imagem do conteúdo do post
function wppoland_get_first_image_url( $content ) {
if ( empty( $content ) ) {
return false;
}
$doc = new DOMDocument();
libxml_use_internal_errors( true );
$doc->loadHTML( '<?xml encoding="utf-8" ?>' . $content );
libxml_clear_errors();
$images = $doc->getElementsByTagName( 'img' );
if ( $images->length > 0 ) {
$src = $images->item( 0 )->getAttribute( 'src' );
return $src !== '' ? $src : false;
}
return false;
}Definir a imagem de destaque automaticamente ao guardar o artigo
add_action( 'save_post_post', function( $post_id ) {
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
if ( has_post_thumbnail( $post_id ) ) {
return;
}
$post = get_post( $post_id );
if ( ! $post ) {
return;
}
$image_url = wppoland_get_first_image_url( $post->post_content );
if ( ! $image_url ) {
return;
}
$attachment_id = attachment_url_to_postid( $image_url );
if ( $attachment_id ) {
set_post_thumbnail( $post_id, $attachment_id );
}
}, 20 );O attachment_url_to_postid() só encontra ficheiros que já estão na biblioteca multimédia. Uma imagem carregada diretamente de outro servidor não passa a imagem de destaque sem um passo de descarga, e esse passo só faz sentido se tiver os direitos sobre a imagem e um processo de importação bem definido.
Que URLs rejeitam wp_http_validate_url e FILTER_VALIDATE_URL
A versão anterior deste guia validava o link com wp_http_validate_url(). O nome sugere que é a função certa, mas o código-fonte mostra outra finalidade: ela protege pedidos HTTP feitos pelo servidor. Lendo a referência da função e o seu código:
- aceita apenas
httpehttpse rejeita URLs com utilizador e palavra-passe; - rejeita URLs relativos, porque exige um host;
- para qualquer host diferente do próprio site, chama
gethostbyname(), ou seja, faz uma consulta DNS; se o nome não resolver nesse momento, o URL é rejeitado; - rejeita endereços de redes privadas, salvo se o filtro
http_request_host_is_externalos permitir; - aceita por omissão apenas as portas 80, 443 e 8080 (filtro
http_allowed_safe_ports).
Num hook save_post, isto significa uma consulta DNS por cada gravação e a perda silenciosa de links válidos para domínios temporariamente inacessíveis ou para servidores de demonstração numa porta não padrão. Use wp_http_validate_url() apenas quando o seu código vai de facto pedir esse URL ao servidor remoto.
O filtro FILTER_VALIDATE_URL do PHP tem outro problema, documentado no manual: “este filtro só funciona com URLs ASCII”, pelo que domínios internacionalizados são sempre rejeitados (tradução própria). No nosso teste com o PHP 8.4, https://exemplo.pt/ação foi rejeitado, e o FILTER_SANITIZE_URL transformou o mesmo endereço em https://exemplo.pt/ao, apagando as letras acentuadas sem aviso. Para links editoriais basta verificar o esquema com wp_parse_url() e limpar o valor com esc_url_raw() antes de o guardar e esc_url() antes de o imprimir.
Como escapar e validar os URLs extraídos
- Use sempre
esc_url()na saída. O conteúdo vem dos autores e não deve ser tratado como fiável. - Se só quer http(s), rejeite
hrefvazios, URLsjavascript:e URIsdata:. - Sempre que possível, faça a extração no painel de administração ou no momento da gravação, para que o PHP do front-end se mantenha leve.
Como aceitar apenas URLs http e https em PHP
Antes de imprimir um URL como href, aceite apenas http(s) e, se for o caso, caminhos relativos que o seu código resolve:
function wppoland_safe_http_url( $url ) {
$url = trim( $url );
if ( '' === $url ) {
return false;
}
$parsed = wp_parse_url( $url );
if ( empty( $parsed['scheme'] ) ) {
// Caminho relativo ao site.
return esc_url_raw( home_url( $url ) );
}
$scheme = strtolower( $parsed['scheme'] );
if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
return false;
}
return esc_url_raw( $url );
}Descarte javascript:, data: e vbscript: logo no início. Os redatores colam de tudo no editor, por isso trate o conteúdo como entrada não fiável.
Como guardar em cache o primeiro link em post meta
Mesmo com a velocidade do analisador nativo, executar a inspeção de strings em cada carregamento de página dentro de um loop com dezenas de publicações é uma prática ineficiente.
A solução arquitetural correta consiste em processar o conteúdo no momento em que o redator guarda o artigo (save_post), armazenando o URL resultante num metadado dedicado (post_meta). Desta forma, as consultas no front-end transformam-se numa leitura direta na base de dados, e o WP_Query já carrega os metadados dos posts do loop para a cache de objetos.
Guardar o primeiro link em post meta com save_post
<?php
/**
* Hook disparado na gravação do post para extrair e indexar o primeiro link.
*/
add_action( 'save_post', 'wppoland_indexar_primeiro_link_post', 10, 2 );
function wppoland_indexar_primeiro_link_post( int $post_id, WP_Post $post ): void {
// 1. Ignorar auto-salvamentos, revisões e rascunhos automáticos
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
// 2. Verificar permissões do utilizador
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 3. Extrair o URL utilizando a nossa função otimizada
$conteudo_bruto = $post->post_content;
$primeiro_url = wppoland_obter_primeiro_url_html_processor( $conteudo_bruto );
// 4. Atualizar ou remover o metadado conforme o resultado
if ( ! empty( $primeiro_url ) ) {
update_post_meta( $post_id, '_wppoland_primeiro_link_url', $primeiro_url );
} else {
delete_post_meta( $post_id, '_wppoland_primeiro_link_url' );
}
}A verificação current_user_can() tem um efeito secundário: gravações feitas sem utilizador autenticado, como um wp post update no WP-CLI sem --user ou uma importação por cron, não atualizam o metadado. Se esses fluxos existem no projeto, preencha o metadado com o script da secção seguinte ou retire a verificação, já que o hook não recebe dados do pedido.
Como mostrar o link guardado nos templates do tema
No ficheiro de template (por exemplo, archive.php, index.php ou numa parte de modelo Gutenberg):
<?php
$link_externo = get_post_meta( get_the_ID(), '_wppoland_primeiro_link_url', true );
if ( ! empty( $link_externo ) ) : ?>
<div class="cartao-fonte-original">
<a href="<?php echo esc_url( $link_externo ); ?>" target="_blank" rel="noopener noreferrer" class="botao-leitura-externa">
Consultar fonte oficial
</a>
</div>
<?php else : ?>
<a href="<?php the_permalink(); ?>" class="botao-leitura-interna">
Ler artigo completo
</a>
<?php endif; ?>Com este fluxo, a complexidade computacional no momento da renderização da página desce para um valor constante $O(1)$ por artigo, e o parsing deixa de contribuir para o tempo de resposta do servidor (TTFB).
Como preencher o post meta dos artigos antigos com WP-CLI
O hook save_post só trata os artigos gravados depois de o código entrar em produção. Para os artigos já publicados, o WP-CLI resolve o problema com dois comandos documentados. O wp post list --field=ID imprime um ID por linha e, segundo a documentação do comando, devolve todos os posts por omissão (--posts_per_page vale -1). O wp eval-file executa um ficheiro PHP com o WordPress carregado e coloca os argumentos posicionais na variável $args (documentação).
<?php
// preencher-primeiro-link.php
foreach ( $args as $post_id ) {
$post = get_post( (int) $post_id );
if ( ! $post ) {
continue;
}
$url = wppoland_obter_primeiro_url_html_processor( $post->post_content );
if ( $url ) {
update_post_meta( $post->ID, '_wppoland_primeiro_link_url', $url );
} else {
delete_post_meta( $post->ID, '_wppoland_primeiro_link_url' );
}
}wp post list --post_type=post --post_status=publish --field=ID \
| xargs -n 100 wp eval-file preencher-primeiro-link.phpO xargs -n 100 passa os IDs em lotes de 100, e cada lote arranca um processo PHP novo, por isso a memória não cresce com o número de artigos. Corra primeiro numa cópia de staging e confira alguns artigos à mão.
Porque é que links de blocos dinâmicos e shortcodes não aparecem
O post_content guardado na base de dados não é o HTML que o visitante recebe. Há dois casos em que o link existe na página mas não no conteúdo em bruto:
- Blocos dinâmicos. O bloco
core/navigation-linkguarda ourlcomo atributo no comentário do bloco, em JSON, e o HTML é gerado no servidor. Já ocore/buttonguarda o link no próprio HTML: noblock.jsono atributourlé lido dohrefda taga(fonteattribute, seletora). Pode confirmar o tipo de cada bloco na referência dos blocos do núcleo. - Shortcodes e embeds. Só se expandem quando o conteúdo passa pelo filtro
the_content.
Se a regra é “o primeiro link que o redator escreveu”, analise $post->post_content. Se é “o primeiro link que o visitante vê”, passe primeiro o conteúdo por do_blocks() e do_shortcode(), ou por apply_filters( 'the_content', $content ). Atenção: o filtro completo também executa o que os plugins acrescentam (botões de partilha, artigos relacionados), que pode inserir links antes do seu.
Como tratar âncoras e links internos especiais
Nem todos os atributos href contêm endereços web remotos. Ao processar dados do editor, é comum encontrar:
- Links de âncora local:
#comentarios,#seccao-introducao. - Hiperligações de comunicação:
mailto:[email protected],tel:+351210000000. - Caminhos relativos:
/sobre-nos/,./downloads/guia.pdf.
As funções deste artigo baseadas em WP_HTML_Tag_Processor, DOMDocument e Dom\HTMLDocument já descartam esquemas que não sejam http ou https, com a mesma verificação que pode reutilizar noutro código:
$esquema = strtolower( (string) wp_parse_url( $href, PHP_URL_SCHEME ) );
if ( ! in_array( $esquema, [ 'http', 'https' ], true ) ) {
// Ignorar mailto, tel, âncoras locais ou caminhos relativos e avançar para o próximo nó
}Se quiser aceitar caminhos relativos, converta-os primeiro para endereços absolutos. O núcleo tem WP_Http::make_absolute_url( $href, home_url( '/' ) ), disponível desde o WordPress 3.4.0. Não confunda com wp_make_link_relative(), que faz o contrário. No WP_HTML_Tag_Processor, pode continuar a chamar $processor->next_tag( 'a' ) num ciclo while até encontrar um endereço que satisfaça todos os critérios de validação do seu projeto.
Teste PHPUnit para a função do primeiro link
Uma asserção mínima em PHPUnit com testes de integração do WordPress evita regressões quando, mais tarde, alguém decide “melhorar” a expressão regular:
public function test_first_link_skips_hash_and_finds_external() {
$html = '<p><a href="#comments">Comments</a> <a href="https://example.com/x">X</a></p>';
$this->assertSame(
'https://example.com/x',
wppoland_get_first_external_link( $html )
);
}Prepare também um artigo de teste com texto de link em UTF-8 (o ø norueguês, o ł polaco) e confirme que o textContent não sai corrompido depois do loadHTML. Acrescente ainda um caso com um link dentro de um comentário HTML e outro com um bloco <script> que contenha <a href: ambos têm de ser ignorados.
Construir rotinas de parsing HTML fiáveis garante estabilidade contínua ao seu portal de conteúdos. Se a sua empresa necessita de arquiteturas de dados personalizadas, integrações de feeds complexas ou otimização avançada de temas, consulte os nossos serviços especializados de desenvolvimento WordPress.







