Como extrair o primeiro link do conteúdo do post no WordPress (PHP)

Como extrair o primeiro link do conteúdo do post no WordPress (PHP)

Última verificação: 14 de outubro de 2026
21 min de leitura
Tutorial
Desenvolvedor full-stack

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.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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 caso preg_match() devolve false em vez de 0 ou 1, e só preg_last_error() revela PREG_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.

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 &amp; 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.

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 tags A e devolve o primeiro href não vazio passado por sanitize_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.

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.

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 &amp; descodificado. Os seletores CSS permitem ainda consultas como p > a[href^="https://"], que no DOMDocument exigiriam XPath.

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érioRegex (preg_match)DOMDocument (libxml2)Dom\HTMLDocumentWP_HTML_Tag_Processor
Modelo de análisePadrão de textoÁrvore DOM, regras pré-HTML5Árvore DOM, HTML5Leitura linear das tags de abertura
Alocação de memóriaBaixaÁrvore completaÁrvore completaSem árvore
Comentários e <script>FalívelCorretosCorretosCorretos
Contexto (pai do link)NãoSimSim, com seletores CSSNão (use WP_HTML_Processor)
Falha típicafalse por limite de backtrackingAvisos e UTF-8ValueError com codificação inválidaNão conhece o contexto do link
Requisito mínimoQualquer PHPExtensão DOMPHP 8.4WordPress 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.

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 http e https e 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_external os 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 href vazios, URLs javascript: e URIs data:.
  • 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.

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.php

O 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.

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-link guarda o url como atributo no comentário do bloco, em JSON, e o HTML é gerado no servidor. Já o core/button guarda o link no próprio HTML: no block.json o atributo url é lido do href da tag a (fonte attribute, seletor a). 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.

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.

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.

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.

Quer implementar isto no seu site?

Se quer transformar o artigo em melhorias concretas, redesign ou num plano de implementação, posso fechar o escopo e executar.

Cluster relacionado

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

FAQ do artigo

Perguntas frequentes

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

SEO-readyGEO-readyAEO-ready4 Q&A
Posso usar a função nativa wp_extract_urls() do WordPress?#
A função wp_extract_urls() recolhe todas as cadeias de carateres que se assemelham a URLs. Não distingue se o endereço está no atributo href, no src de uma imagem ou simplesmente escrito no meio de uma frase, por isso não serve para encontrar a primeira hiperligação clicável.
O WP_HTML_Tag_Processor funciona com blocos interativos do Gutenberg?#
Sim. O processador opera sobre o HTML serializado e lê atributos como data-wp-interactive sem os alterar, porque apenas lê a tag de abertura.
O get_url_in_content() devolve links mailto ou âncoras?#
Devolve o primeiro href não vazio, seja qual for o esquema. Um mailto: ou um #comentarios no início do texto ganha ao link editorial; se precisa só de http ou https, escreva a sua própria função.
Preciso de WordPress para usar Dom\HTMLDocument?#
Não. Dom\HTMLDocument faz parte da extensão DOM do PHP 8.4 e funciona em qualquer script PHP, por exemplo num importador que corre fora do WordPress.

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

Fale connosco

Artigos Relacionados