Obtener todas las categorías y publicaciones en esas categorías
Estoy buscando una solución que me permita imprimir lo siguiente:
Cat 1 Cat 2 Cat 3
Post 1 Post 1 Post 1
Post 2 Post 2 Post 2
Post 3 Post 3
Post 4
EDICIÓN
Estoy buscando algo que solo requiera una consulta a la base de datos. Por lo tanto, si tienes un foreach
en tu código seguido de un new WP_Query
, eso no es lo que estoy buscando (planeo poner esto en la página de inicio de mi sitio web).

EDITAR REVISIÓN NÚMERO 2
Nunca había tocado la API de Transients, hasta hoy cuando vi la respuesta de @MikeSchinkel en esta publicación. Esto me inspiró a revisitar este post nuevamente. Después de algunas pruebas, llegué a lo siguiente:
El tiempo de ejecución bajó de ~0.07 segundos a ~0.002 segundos
El tiempo de consulta a la base de datos se redujo aproximadamente a la mitad
Con el transient, solo se ejecutan 2 consultas a la base de datos
Cómo funciona el código (solo voy a discutir los cambios del código original de REVISIÓN):
PASO 1
Necesitamos guardar el valor de $q
en un transient, este es el valor que contiene la lista de categorías con títulos de posts.
PASO 2
Primero debemos verificar si existe un transient, y si no existe, crearlo. Si el transient existe, recuperar su información.
PASO 3
Esta información ahora se pasa a través de un bucle foreach
para imprimir la lista con nombres de categorías y títulos de posts.
PASO 4
Actualmente, el transient se actualizará cada doce horas. Esto se puede ajustar según tus necesidades. Sin embargo, el transient deberá eliminarse y recrearse cada vez que cambie el estado de un post. Esto podría ser de borrador a publicado, un nuevo post publicado o un post que se envía a la papelera. Para hacer esto, debes usar delete_transient
que se enganchará a transition_post_status
, el cual se activará cada vez que cambie el estado de un post.
Aquí está el código completo:
En tu functions.php
add_action( 'transition_post_status', 'publish_new_post', 10, 3 );
function publish_new_post() {
delete_transient( 'category_list' );
}
En tu plantilla donde necesitas mostrar tu lista
<?php
if ( false === ( $q = get_transient( 'category_list' ) ) {
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
$query->the_post();
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
$q[$b][] = $a; // Crear un array con los nombres de categorías y títulos de posts
}
/* Restaurar los datos originales del Post */
wp_reset_postdata();
set_transient( 'category_list', $q, 12 * HOUR_IN_SECONDS );
}
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
?>
REVISIÓN
Recientemente encontré una solución muy ligera que es mucho más rápida que las otras soluciones posibles. En mi sitio de prueba obtengo un tiempo total de generación de solo ~0.07 segundos y solo 6 consultas a la base de datos según Query Monitor, mientras que los otros métodos me dan un tiempo de generación de ~0.35 segundos y 50 consultas adicionales a la base de datos.
Aquí hay un desglose de mi método:
PASO 1
Primero necesitas crear una consulta personalizada con WP_Query
para recuperar todos los posts publicados.
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
}
/* Restaurar los datos originales del Post */
wp_reset_postdata();
PASO 2
Usando get_the_category
, recupera una lista de todas las categorías a las que pertenece un post.
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
PASO 3
Asigna variables al título del post y a las categorías del post.
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
y
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
PASO 4
Combina estas dos variables para formar un array multidimensional.
$q[$b][] = $a;
Para ver qué está pasando en el array, simplemente haz un var_dump
.
?><pre><?php var_dump($q); ?></pre><?php
PASO 5
Usando bucles foreach
, crea tu lista de posts ordenada por categoría.
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
¡TODO JUNTO AHORA!
Aquí está el código completo:
<?php
$args = array(
'posts_per_page' => -1
);
$query = new WP_Query($args);
$q = array();
while ( $query->have_posts() ) {
$query->the_post();
$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';
$categories = get_the_category();
foreach ( $categories as $key=>$category ) {
$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';
}
$q[$b][] = $a; // Crear un array con los nombres de categorías y títulos de posts
}
/* Restaurar los datos originales del Post */
wp_reset_postdata();
foreach ($q as $key=>$values) {
echo $key;
echo '<ul>';
foreach ($values as $value){
echo '<li>' . $value . '</li>';
}
echo '</ul>';
}
?>

@Pieter-Goosen ¡Excelente código! En lugar de usar un <ul>, quiero usar una plantilla de un archivo. Intenté incluir el archivo en el `foreach ($values as $value) {include(template.php);} pero luego establece los nombres de las publicaciones como título de la página, en lugar de los títulos de las publicaciones. ¿Alguna idea de cómo puedo solucionar esto?

Quería compartir mi solución que derivé de esta pregunta que tenía. Esto almacena en caché la consulta para las categorías y también almacena en caché las publicaciones, incluyendo el contenido de cada publicación, en cada categoría. La primera vez que se llena la caché hay consultas normales a la base de datos, pero una vez llenas, se sirven las categorías y publicaciones en caché, por lo que no hay más consultas a la base de datos.
// API de Transients para todas las categorías y todas las publicaciones
$query_categories = get_transient('cached_categories');
if ( false === $query_categories){
$args_cat = array(
// ordenar por nombre de categoría ascendente
'orderby' => 'name',
'order' => 'ASC',
// obtener solo categorías de nivel superior
'parent' => 0
);
// En lugar de almacenar en caché una WP Query, almaceno 'get_categories()'.
$query_categories = get_categories($args_cat);
// var_dump($query_categories);
set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );
}
// Consulta de publicaciones completas
// si hay categorías con publicaciones
if (!empty ($query_categories) && !is_wp_error( $query_categories )) {
foreach ($query_categories as $category) {
// var_dump($category);
$query_category_posts = get_transient('cached_posts_' . $category->slug );
if ( false === $query_category_posts ){
// Consultar todas las publicaciones por slug dentro de cada categoría
$args_category_posts = array(
'post_type' => 'post',
// El slug y nombre de la categoría los obtenemos del foreach sobre todas las categorías
'category_name' => $category->slug
);
// Aquí almaceno en caché la WP_Query, aunque esto se ejecuta para todas las categorías.
// Debido a esto, se usa '$category->slug' para servir una cadena y no un objeto.
$query_category_posts = new WP_Query($args_category_posts);
set_transient( 'cached_posts_' . $category->slug , $query_category_posts, DAY_IN_SECONDS );
}
if ($query_category_posts->have_posts()) {
while ($query_category_posts->have_posts()) {
$query_category_posts->the_post(); ?>
<article class="<?php echo $category->slug ?>-article">
<h2 class="<?php echo $category->slug ?>-article-title">
<a href="<?php echo get_permalink() ?>"><?php echo get_the_title() ?></a>
</h2>
<p class="<?php echo $category->slug ?>-post-info">
<?php the_time('d. m. Y') ?>
</p>
<div <?php post_class() ?> >
<?php the_content(); ?>
</div>
</article> <?php
}
} // fin del bucle
} // fin del foreach
wp_reset_postdata() ;
} // fin del if hay categorías con publicaciones

Aquí está mi solución para obtener las categorías y las publicaciones dentro de esas categorías como un solo resultado.
Mi ejemplo está basado en la taxonomía de categoría de tipo de publicación personalizada document_category
y el tipo de publicación personalizada documents
. Pero estoy seguro de que captarás la idea.
$result = [];
$categories = get_terms( [
'taxonomy' => 'document_category'
] );
foreach ( $categories as $index => $category ) {
$args = [
'post_type' => 'documents',
'posts_per_page' => - 1,
'public' => true,
'tax_query' => [
[
'taxonomy' => 'document_category',
'field' => 'term_id',
'terms' => $category->term_id,
'include_children' => true
]
]
];
$documents = get_posts( $args );
$result[ $index ]['category'] = $category;
$result[ $index ]['documents'] = $documents;
}
return $result;

Prueba ahora este código
$cat_ids=array();
foreach (get_categories() as $cat)
{
array_push($cat_ids, $cat->cat_ID);
}
$the_query = new WP_Query(array('post_type'=>'post', array('category__and' => $cat_ids) ) );
if( $the_query->have_posts() ):
while ( $the_query->have_posts() ) : $the_query->the_post();
echo the_title();
endwhile;
endif;
wp_reset_query();

Puedes usar esto... Establece la cantidad de publicaciones que necesites...
También lo he puesto todo dentro de un div, para que puedas hacer la estructura y el diseño que buscas.
<?php
$allcats = get_categories('child_of=0');
foreach ($allcats as $cat) :
$args = array(
'posts_per_page' => 3, // establece el número de publicaciones por categoría aquí
'category__in' => array($cat->term_id)
);
$customInCatQuery = new WP_Query($args);
if ($customInCatQuery->have_posts()) :
echo '<div>';
echo '<h3>'.$cat->name.'</h3>';
echo '<ul>';
while ($customInCatQuery->have_posts()) : $customInCatQuery->the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
endwhile;
echo '</ul></div>';
?>
<?php
else :
echo 'No hay publicaciones en: '.$cat->name;
endif;
wp_reset_query();
endforeach;
?>
Espero que esto ayude.

Gracias, pero toma el ejemplo de digamos 10 categorías. En ese caso, tu código genera 11 consultas SQL. Estoy buscando 1 sola consulta SQL. Lo mejor probablemente sería obtener todas las publicaciones, ordenadas por categoría, pero no sé cómo hacerlo (no encontré order_by = cat
en el codex) !?

no creo que importe en absoluto si son 10 categorías o 20... (lo probé con 20) a menos que estés intentando cargar un número enorme de publicaciones, lo cual en cualquier caso podría ralentizar la carga de la página... pruébalo - verás que es muy fluido - intentar reorganizar después de cargar por categorías significaría construir un código enorme y en su mayoría inútil (en mi humilde opinión :) )

No es así, solo necesitas modificar ligeramente la salida (es decir, cerrar la lista para la categoría n y comenzar la lista para la categoría n+1) cada vez que una publicación tenga una categoría diferente a la de la publicación anterior... Mucho más simple que bombardear tu pobre base de datos con 20+ consultas por carga de página... (en mi humilde opinión :)

Creo que estaríamos preguntando lo mismo a la base de datos pero solo ordenando los resultados de manera diferente en la salida... No veo la diferencia... Además - es una consulta lógica... "dame el primer * de esta categoría"... "dame el primer * de esa categoría" en lugar de darme ese post, otra vez, otra vez, otra vez... Es la misma forma pero estoy extrayendo los datos como los necesito... A menos que me equivoque..

La diferencia está en el número de consultas a la base de datos. Una grande vs muchas pequeñas... ;)

Mmm... no sé - no lo entiendo a ese nivel... Me refiero a cómo WordPress actúa en esta consulta... Pero lo intenté y creo que funciona bastante rápido. Espero al menos haber acortado el camino hacia tu solución deseada. ;)

He construido algo para mí que uso bastante, aquí está el código, puedes usar slugs, ids o el objeto de término en el array $categories
, si quieres obtener todas las categorías puedes usar get_terms()
, y alimentarlo con un montón de objetos de término, pero ten cuidado, no hay tratamiento de jerarquía en este código.
$categories = array( 1, 'slug', 3 );
echo '<ul>';
foreach($categories as $category) {
$term = ( is_numeric($category) || is_object($category) ? get_term( $category, 'category' ) : get_term_by( 'slug', $category, 'category' ) );
$args = array(
'cat' => $term->term_id
// Añade cualquier otro argumento que se ajuste a tus necesidades
);
$q = new WP_Query( $args );
if( $q->have_posts() ) {
echo '<li><a href="' . get_term_link( $term->term_id, 'category' ) . '">' . $term->name . '</a><ul>';
while( $q->the_post() ) {
$q->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
echo '</ul></li>';
} else {
}
}
echo '</ul>';

No probado, pero uno de los enfoques más simples que probaría es el siguiente:
<?php
$category_ids = get_all_category_ids();
foreach ($category_ids as $values) {
$args = array('category' => $value);
$posts_array = get_posts($args);
foreach ($posts_array as $post) : setup_postdata($post);
?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
endforeach;
wp_reset_query();
}
?>

Ok. No hay forma de hacer una sola consulta. Cargar cualquier página de WordPress tiene múltiples consultas de por sí, antes de añadir código personalizado. Todas las consultas integradas usan la clase WPDB. La única forma de hacerlo en una sola consulta es con tu propio SQL transaccional o haciendo que WPDB sea una clase padre de otra clase para este propósito y llamándola de esa manera.

Si estás buscando un plugin, List Category Posts podría funcionar para ti.
Para una consulta, echa un vistazo a get_posts

o esto también: http://codex.wordpress.org/Function_Reference/get_all_category_ids
