A veces construimos temas tipo “Agregador de Noticias”, donde una entrada no tiene su propio contenido, sino que solo enlaza a un artículo externo. O queremos que la primera imagen del contenido se convierta automáticamente en la “Imagen Destacada” si el editor olvida establecerla.
Descubre más sobre desarrollo profesional WordPress en WPPoland.
En ambos casos, necesitamos “escanear” el contenido de la entrada (the_content) y extraer la primera etiqueta <a> o <img> de el.
Método 1: Clase DOMDocument (recomendado)
Muchos desarrolladores usan Expresiones Regulares (Regex) para esto, pero analizar HTML con Regex es una mala practica. Es mejor usar la clase integrada DOMDocument de PHP.
Aqui tienes una función lista para pegar en functions.php:
function get_first_link_url( $content ) {
// Si el contenido esta vacio, devolver false
if ( empty( $content ) ) return false;
$doc = new DOMDocument();
// Suprimir errores de HTML5 (DOMDocument es antiguo y a veces se queja de <section> etc.)
libxml_use_internal_errors(true);
// Cargar HTML (con hack UTF-8)
$doc->loadHTML( mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8') );
$links = $doc->getElementsByTagName('a');
if ( $links->length > 0 ) {
// Devolver href del primer enlace
return $links->item(0)->getAttribute('href');
}
return false;
}
Uso en el Loop
$link = get_first_link_url( get_the_content() );
if ( $link ) {
echo '<a href="' . esc_url($link) . '" class="read-more-external">Leer original</a>';
}
Esta solución es solida, segura y maneja errores en la estructura HTML mejor que cualquier Regex.
Método 2: Extraer la primera imagen
El mismo enfoque funciona para imágenes. Esto es útil para temas de “lista de posts” donde necesitas una miniatura automática:
function get_first_image_url( $content ) {
if ( empty( $content ) ) return false;
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML( mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8') );
$images = $doc->getElementsByTagName('img');
if ( $images->length > 0 ) {
return $images->item(0)->getAttribute('src');
}
return false;
}
Uso como imagen destacada automática
/**
* Establecer automáticamente la primera imagen del contenido como imagen destacada
* si no se ha establecido una manualmente.
*/
add_action( 'save_post', function( $post_id ) {
// Evitar autoguardados y revisiones
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
// Si ya tiene imagen destacada, no hacer nada
if ( has_post_thumbnail( $post_id ) ) {
return;
}
$post = get_post( $post_id );
$image_url = get_first_image_url( $post->post_content );
if ( $image_url ) {
// Buscar el attachment por URL
$attachment_id = attachment_url_to_postid( $image_url );
if ( $attachment_id ) {
set_post_thumbnail( $post_id, $attachment_id );
}
}
});
Método 3: Regex (solo como referencia)
Aunque no se recomienda, aquí esta el enfoque con Regex para comparación:
function get_first_link_url_regex( $content ) {
if ( empty( $content ) ) return false;
// Patron para encontrar la primera etiqueta <a>
$pattern = '/<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>/i';
if ( preg_match( $pattern, $content, $matches ) ) {
return $matches[1];
}
return false;
}
Por que no usar Regex:
- Falla con HTML malformado
- No maneja correctamente atributos en diferente orden
- Problemás con HTML anidado
- Dificil de mantener y depurar
- No entiende la estructura del DOM
Versión avanzada: Extraer multiples datos del primer enlace
A veces necesitas más que solo la URL. Aqui hay una versión que extrae toda la información del enlace:
function get_first_link_data( $content ) {
if ( empty( $content ) ) return false;
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML( mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8') );
$links = $doc->getElementsByTagName('a');
if ( $links->length > 0 ) {
$first_link = $links->item(0);
return array(
'url' => $first_link->getAttribute('href'),
'text' => $first_link->textContent,
'title' => $first_link->getAttribute('title'),
'target' => $first_link->getAttribute('target'),
'rel' => $first_link->getAttribute('rel'),
'class' => $first_link->getAttribute('class'),
);
}
return false;
}
Uso:
$link_data = get_first_link_data( get_the_content() );
if ( $link_data ) {
printf(
'<a href="%s" title="%s" target="%s" rel="noopener noreferrer">%s</a>',
esc_url( $link_data['url'] ),
esc_attr( $link_data['title'] ),
esc_attr( $link_data['target'] ?: '_blank' ),
esc_html( $link_data['text'] )
);
}
Filtrar enlaces por tipo
Para temas más complejos, puedes necesitar filtrar enlaces por tipo (internos vs externos):
function get_first_external_link( $content ) {
if ( empty( $content ) ) return false;
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML( mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8') );
$links = $doc->getElementsByTagName('a');
$site_url = home_url();
foreach ( $links as $link ) {
$href = $link->getAttribute('href');
// Saltar enlaces vacios, anclas y enlaces internos
if ( empty( $href ) || strpos( $href, '#' ) === 0 ) {
continue;
}
// Comprobar si es externo
if ( strpos( $href, $site_url ) === false && strpos( $href, 'http' ) === 0 ) {
return $href;
}
}
return false;
}
Consideraciones de rendimiento
Cache de resultados
Si extraes enlaces frecuentemente, almacena en cache los resultados:
function get_cached_first_link( $post_id = null ) {
$post_id = $post_id ?: get_the_ID();
$cache_key = 'first_link_' . $post_id;
$result = get_transient( $cache_key );
if ( false === $result ) {
$post = get_post( $post_id );
$result = get_first_link_url( $post->post_content );
$result = $result ?: 'none'; // Almacenar 'none' para evitar consultas repetidas
set_transient( $cache_key, $result, HOUR_IN_SECONDS );
}
return $result === 'none' ? false : $result;
}
// Invalidar cache al actualizar la entrada
add_action( 'save_post', function( $post_id ) {
delete_transient( 'first_link_' . $post_id );
});
Uso con Object Cache
Para sitios con Redis o Memcached:
function get_cached_first_link_object_cache( $post_id = null ) {
$post_id = $post_id ?: get_the_ID();
$cache_key = 'first_link_' . $post_id;
$result = wp_cache_get( $cache_key, 'post_links' );
if ( false === $result ) {
$post = get_post( $post_id );
$result = get_first_link_url( $post->post_content ) ?: '';
wp_cache_set( $cache_key, $result, 'post_links', 3600 );
}
return $result ?: false;
}
Seguridad: Validación de URLs
Siempre válida las URLs extraidas antes de usarlas:
function get_safe_first_link( $content ) {
$url = get_first_link_url( $content );
if ( ! $url ) return false;
// Validar que es una URL valida
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
return false;
}
// Solo permitir protocolos seguros
$allowed_protocols = array( 'http', 'https' );
$parsed = wp_parse_url( $url );
if ( ! isset( $parsed['scheme'] ) || ! in_array( $parsed['scheme'], $allowed_protocols ) ) {
return false;
}
return esc_url( $url );
}
Integración con formatos de entrada de WordPress
WordPress tiene formatos de entrada como “Link” que se benefician de esta funcionalidad:
// En functions.php - Soporte para formatos de entrada
add_theme_support( 'post-formats', array( 'link', 'image', 'video', 'quote' ) );
// En el template - Obtener URL para formato 'link'
if ( has_post_format( 'link' ) ) {
$link = get_first_link_url( get_the_content() );
if ( $link ) {
echo '<a href="' . esc_url( $link ) . '" class="post-link-format" target="_blank" rel="noopener">';
the_title();
echo '</a>';
}
} else {
echo '<a href="' . get_permalink() . '">';
the_title();
echo '</a>';
}
Aplicación práctica: Tema de agregador de noticias
Un caso de uso completo para un tema estilo “agregador de noticias”:
// En archive.php o index.php
while ( have_posts() ) : the_post();
$external_link = get_first_link_url( get_the_content() );
$target_url = $external_link ?: get_permalink();
$is_external = (bool) $external_link;
?>
<article class="news-item <?php echo $is_external ? 'external' : 'internal'; ?>">
<?php if ( has_post_thumbnail() ) : ?>
<a href="<?php echo esc_url( $target_url ); ?>"
<?php echo $is_external ? 'target="_blank" rel="noopener noreferrer"' : ''; ?>>
<?php the_post_thumbnail( 'medium' ); ?>
</a>
<?php endif; ?>
<h2>
<a href="<?php echo esc_url( $target_url ); ?>"
<?php echo $is_external ? 'target="_blank" rel="noopener noreferrer"' : ''; ?>>
<?php the_title(); ?>
<?php if ( $is_external ) : ?>
<span class="external-icon" aria-label="Enlace externo">↗</span>
<?php endif; ?>
</a>
</h2>
<div class="meta">
<?php if ( $is_external ) : ?>
<span class="source"><?php echo esc_html( wp_parse_url( $external_link, PHP_URL_HOST ) ); ?></span>
<?php endif; ?>
<time datetime="<?php echo get_the_date('c'); ?>"><?php echo get_the_date(); ?></time>
</div>
</article>
<?php endwhile; ?>
Resumen
La extraccion de enlaces del contenido es una técnica fundamental para desarrolladores WordPress. DOMDocument es siempre la opción correcta sobre Regex para analizar HTML. Recuerda siempre:
- Usar
DOMDocumenten lugar de Regex para analizar HTML - Manejar correctamente la codificacion UTF-8
- Validar y sanitizar las URLs extraidas
- Almacenar en cache los resultados para rendimiento
- Considerar la seguridad al mostrar enlaces externos


