Cómo crear post relacionados sin usar plugins
Existe la idea de que mientras más plugins tenga instalado un blog, peor será su desempeño. En realidad no es el número de extensiones lo que determina el rendimiento de un sitio, sino la calidad de su implementación. Un solo plugin mal diseñado puede consumir más recursos que diez bien escritos.
Por esa razón nunca he tenido problema en utilizar plugins cuando resuelven una necesidad concreta. Ahorran tiempo, reducen la cantidad de código que tenemos que mantener y, en muchos casos, están mucho más probados que una solución desarrollada desde cero.
Sin embargo, hay ocasiones en las que ninguna extensión hace exactamente lo que necesitamos.
Ése fue mi caso con Cyberia.MX.
Quería mostrar una lista de artículos relacionados al final de cada entrada para ofrecer al lector contenido que realmente tuviera relación con el tema que acababa de leer. El conocido plugin Related Posts, de René Ade, hace un excelente trabajo, pero debido a la forma en que está construido este sitio no podía aprovechar una de las características que más me interesaban: utilizar las imágenes generadas por Genesis Framework y mezclar en las recomendaciones mis tipos de contenido personalizados.
Buscando una alternativa encontré un excelente ejemplo publicado por Nick the Geek. Su código resolvía la mayor parte del problema, así que decidí adaptarlo a las necesidades de este sitio.
Entre otras modificaciones:
- agregué soporte para los tipos de contenido personalizados películas, libros y apps;
- eliminé de la búsqueda la etiqueta
code, ya que prácticamente todos los artículos técnicos la utilizan y terminaba generando recomendaciones poco relevantes; - excluí algunas categorías que ya no utilizo;
- mejoré los atributos
titleyaltde las imágenes y enlaces para ofrecer un poco más de contexto tanto al usuario como a los navegadores.
Como ocurre con frecuencia, escribir el código desde cero habría sido más costoso que comprender una buena solución existente y adaptarla al problema real.
La consulta por etiquetas
Lo primero que hace la función es comprobar que nos encontramos viendo una entrada individual.
No tendría sentido ejecutar la consulta en la portada, en una página estática o en los resultados de una búsqueda.
Después obtiene todas las etiquetas del artículo actual y construye una consulta utilizando WP_Query.
En mi caso fue necesario modificar algunos parámetros para que la consulta incluyera también los tipos de contenido personalizados.
'post_type' => array( 'post', 'peliculas', 'libros', 'apps'),
'tag__not_in' => array( 70 ),
El parámetro post_type indica qué tipos de contenido participarán en la búsqueda de relacionados.
Por otra parte, tag__not_in excluye determinadas etiquetas de la comparación. En mi instalación la etiqueta con identificador 70 corresponde a code, una etiqueta demasiado común para resultar útil como criterio de recomendación.
Limitando los resultados
La consulta incorpora además algunos parámetros adicionales.
'showposts' => 5,
'ignore_sticky_posts' => 1,
Con ellos limitamos la salida a cinco recomendaciones e ignoramos los artículos marcados como destacados (sticky posts).
También se utiliza una consulta sobre taxonomías para excluir algunos formatos de entrada que no tienen sentido dentro de una lista de artículos relacionados.
'tax_query' => array(
array(
'taxonomy' => 'post_format',
'field' => 'slug',
'terms' => array(
'post-format-link',
'post-format-status',
'post-format-aside',
'post-format-quote'
),
'operator' => 'NOT IN'
)
)
Si tu sitio no utiliza formatos de entrada puedes eliminar completamente esta parte de la consulta.
Construyendo la lista
Una vez obtenidos los resultados comienza la parte visual.
Para cada artículo recuperamos la imagen destacada mediante genesis_get_image(). Si el artículo no tiene una imagen asociada utilizamos una imagen genérica.
Además aproveché para generar atributos title y alt más descriptivos.
$img = genesis_get_image() ? genesis_get_image(
array(
'size' => 'cyberia_related',
'attr' => array(
title => 'Si te gustó “' .
single_post_title('', false) .
'”, tal vez te pueda interesar «' .
get_the_title() . '»'
)
)
) : '<img src="' .
get_bloginfo( 'stylesheet_directory' ) .
'/images/related.png"
alt="Si te gustó “' .
single_post_title('', false) .
'”, tal vez te pueda interesar «' .
get_the_title() . '»" />';
El mismo texto se utiliza posteriormente en el enlace.
$related .= '<li><a href="' .
get_permalink() .
'" rel="bookmark"
title="Si te gustó “' .
single_post_title('', false) .
'”, tal vez te pueda interesar «' .
get_the_title() .
'»">' .
$img .
get_the_title() .
'</a></li>';
Puede parecer un detalle menor, pero considero que pequeños cambios como éste mejoran la experiencia de navegación sin aumentar significativamente la complejidad del código.
Completando la lista con categorías
No siempre existen suficientes artículos relacionados mediante etiquetas.
Para evitar mostrar listas demasiado pequeñas, el código realiza una segunda consulta utilizando las categorías del artículo actual.
En esta ocasión también excluyo algunas categorías que no quiero utilizar como criterio de recomendación.
'category__not_in' => array( 1, 4, 72, 34, 236 ),
Además, los resultados se presentan en orden aleatorio (orderby => 'rand') para evitar que las recomendaciones sean exactamente las mismas en cada visita.
Mostrando el resultado
Una vez terminadas ambas consultas únicamente queda comprobar que realmente existan artículos relacionados.
if ( $related ) {
printf(
'<div class="breadcrumb" id="related">
<h3 class="related-title">Artículos Recomendados</h3>
<ul class="related-list">%s</ul>
</div>',
$related
);
}
Si la lista está vacía simplemente no se imprime ningún bloque adicional, evitando ocupar espacio innecesario dentro del artículo.
Código completo
A continuación dejo la función completa con todas las modificaciones realizadas para Cyberia.MX.
add_action( 'genesis_after_post_content', 'cyberia_related_posts' );
/**
* Outputs related posts with thumbnail
*
* @author Nick the Geek
* @url http://designsbynickthegeek.com/tutorials/related-posts-genesis
* @global object $post
*/
function cyberia_related_posts() {
if ( is_single ( ) ) {
global $post;
$count = 0;
$postIDs = array( $post->ID );
$related = '';
$tags = wp_get_post_tags( $post->ID );
$cats = wp_get_post_categories( $post->ID );
if ( $tags ) {
foreach ( $tags as $tag ) {
$tagID[] = $tag->term_id;
}
$args = array(
'post_type' => array( 'post','peliculas', 'libros', 'apps'),
'tag__in' => $tagID,
'tag__not_in' => array ( 70 ),
'post__not_in' => $postIDs,
'showposts' => 5,
'ignore_sticky_posts' => 1,
'tax_query' => array(
array(
'taxonomy' => 'post_format',
'field' => 'slug',
'terms' => array(
'post-format-link',
'post-format-status',
'post-format-aside',
'post-format-quote'
),
'operator' => 'NOT IN'
)
)
);
$tag_query = new WP_Query( $args );
if ( $tag_query->have_posts() ) {
while ( $tag_query->have_posts() ) {
$tag_query->the_post();
$img = genesis_get_image()
? genesis_get_image(
array(
'size' => 'cyberia_related',
'attr' => array(
title => 'Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»'
)
)
)
: '<img src="'.
get_bloginfo('stylesheet_directory').
'/images/related.png" alt="Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»" />';
$related .= '<li><a href="'.
get_permalink().
'" rel="bookmark" title="Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»">'.
$img.
get_the_title().
'</a></li>';
$postIDs[] = $post->ID;
$count++;
}
}
}
if ( $count <= 4 ) {
$catIDs = array();
foreach ( $cats as $cat ) {
if ( 3 == $cat )
continue;
$catIDs[] = $cat;
}
$showposts = 5 - $count;
$args = array(
'post_type' => array( 'post','peliculas', 'libros', 'apps'),
'category__in' => $catIDs,
'category__not_in' => array( 1,4,72,34,236 ),
'post__not_in' => $postIDs,
'showposts' => $showposts,
'ignore_sticky_posts' => 1,
'orderby' => 'rand',
'tax_query' => array(
array(
'taxonomy' => 'post_format',
'field' => 'slug',
'terms' => array(
'post-format-link',
'post-format-status',
'post-format-aside',
'post-format-quote'
),
'operator' => 'NOT IN'
)
)
);
$cat_query = new WP_Query($args);
if ($cat_query->have_posts()) {
while ($cat_query->have_posts()) {
$cat_query->the_post();
$img = genesis_get_image()
? genesis_get_image(
array(
'size' => 'cyberia_related',
'attr' => array(
title => 'Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»'
)
)
)
: '<img src="'.
get_bloginfo('stylesheet_directory').
'/images/related.png" alt="Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»" />';
$related .= '<li><a href="'.
get_permalink().
'" rel="bookmark" title="Si te gustó “'.
single_post_title('', false).
'”, tal vez te pueda interesar «'.
get_the_title().'»">'.
$img.
get_the_title().
'</a></li>';
}
}
}
if ($related) {
printf(
'<div class="breadcrumb" id="related"><h3 class="related-title">Artículos Recomendados</h3><ul class="related-list">%s</ul></div>',
$related
);
}
wp_reset_query();
}
}
Con este enfoque conseguí exactamente el comportamiento que buscaba sin depender de una extensión adicional y, sobre todo, manteniendo el control completo sobre la consulta y la presentación de los resultados.
No siempre vale la pena reemplazar un plugin por código propio. Sin embargo, cuando el comportamiento que necesitamos es muy específico, adaptar una buena solución existente suele ser más sencillo que intentar forzar una extensión para hacer algo para lo que nunca fue diseñada.
Usa Genesis Framework
Genesis facilita la creación de sitios web rápidos, seguros y optimizados para motores de búsqueda. Si ya utilizas WordPress y necesitas una base sólida sobre la cual construir un tema personalizado, Genesis sigue siendo una excelente plataforma para hacerlo.