Mostrar entradas por mes
Quiero lograr algo como esto, no sé si es posible y cuál sería la mejor manera de hacerlo:
La forma en que consulto las entradas es así:
<div class="post">
<?php global $wp_query;
// Consulta de entradas del tipo 'lecturas-post' ordenadas por fecha descendente
query_posts( array('post_type' => array( 'lecturas-post' ),'showposts' => 15, 'paged'=>$paged, 'order' => 'DESC' ));?>
<?php while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?>
<div><?php the_title() ?></a>
<?php endwhile; // fin del bucle. ?>
</div>
¿Alguien puede darme un consejo sobre cómo o cuál es la mejor manera de hacerlo?

Como se mencionó en un comentario, puedes hacer esto en una sola consulta. El principio aquí es mostrar el encabezado de fecha solo si el mes de la fecha de publicación del post anterior no coincide con el del post actual
ALGUNAS NOTAS
Antes de comenzar, algunas notas:
Nunca uses
query_posts
, excepto si realmente necesitas romper todo en una página. No solo vuelve a ejecutar la consulta principal y además la rompe, también desordena la paginación y tus variables globales, y afecta las funciones de objeto consultado. Si realmente necesitas ejecutar una consulta personalizada, usaWP_Query
oget_posts
showposts
fue eliminado hace años en favor deposts_per_page
No es necesario usar la variable global
$wp_query
.query_posts
la desordena de todas formas
EL PLAN
Los posts se muestran en orden cronológico con el post más reciente primero, y el más antiguo al final, por lo que ya están en el orden correcto. Solo es cuestión de mostrar la fecha en el lugar apropiado.
Para hacer esto, todo lo que necesitas es obtener el mes y año actual de la fecha de publicación del post actual, y luego compararlo con el mes de la fecha de publicación del post anterior, mostrando la fecha si los meses no coinciden o no mostrándola si coinciden
Como explicación, usaré el bucle principal con la consulta principal.
Para lograr esto, necesitas:
Obtener el mes de la fecha de publicación del post actual. Para esto, usa
get_the_date( 'F' )
Obtener el post anterior en el bucle con
$wp_query->posts['este será el post actual -1 ']->post
.Obtener y comparar los meses entre los dos posts
Mostrar o no mostrar la fecha de acuerdo a la comparación
EL CÓDIGO
Este código va dentro de tu bucle, justo después de tu declaración while()
.
$current_month = get_the_date('F');
if( $wp_query->current_post === 0 ) {
the_date( 'F Y' );
}else{
$f = $wp_query->current_post - 1;
$old_date = mysql2date( 'F', $wp_query->posts[$f]->post_date );
if($current_month != $old_date) {
the_date( 'F Y' );;
}
}
CONSULTA PERSONALIZADA
Si necesitas ejecutar una consulta personalizada, prueba esto
$q = new WP_Query( array('post_type' => array( 'lecturas-post' ),'posts_per_page' => 15, 'paged'=>$paged, 'order' => 'DESC' ));
if( $q->have_posts() ) {
while( $q->have_posts() ) {
$q->the_post();
$current_month = get_the_date('F');
if( $q->current_post === 0 ) {
the_date( 'F Y' );
}else{
$f = $q->current_post - 1;
$old_date = mysql2date( 'F', $q->posts[$f]->post_date );
if($current_month != $old_date) {
the_date( 'F Y' );;
}
}
the_title();
}
}

Buena respuesta, y un concepto similar para tu sección de consulta personalizada como en mi respuesta actualizada, solo que con una forma diferente de verificar si el título ya se ha mostrado. Supongo que depende del OP si quieren incorporar el date_query
o no :)

Una cosa que diré es que un mes puede dividirse en varias páginas. Si al OP no le importa, entonces genial, solo pensé que valía la pena señalarlo.

@DavidGard gracias. La fecha se mostrará en cada página en la primera publicación sin importar qué. Si no necesitas eso, añade la condición is_paged()
.

Respuesta Actualizada
Después de considerar el comentario de @PieterGoosen a continuación, he añadido un método para lograr tu objetivo usando una sola consulta.
Comparé los métodos y la consulta única es más rápida, con el método de múltiples consultas siendo aproximadamente un 15% más lento. No es una gran diferencia, pero todo ayuda, y para ser honesto el método probablemente pueda refinarse aún más.
He dejado el método de múltiples consultas en la respuesta para la posteridad, pero recomiendo que se use el método de consulta única.
Método de Consulta Única
$time_start = microtime(true);
/** Configurar un objeto de intervalo de fecha para hace 6 meses (puedes cambiarlo según sea necesario) */
$interval = new DateInterval('P6M');
$interval->invert = 1;
/** Obtener la fecha como era hace 6 meses */
$date = new DateTime(date('Y-m-d'));
$date->add($interval);
/** Consultar la base de datos para todos los posts más nuevos que la fecha del intervalo dado */
$args = array(
'nopaging' => true,
'posts_per_page' => -1,
'post_type' => 'post',
'post_status' => 'publish',
'order_by' => 'date',
'date_query' => array(
'after' => $date->format('Y-m-d')
)
);
$month_query = new WP_Query($args);
/** Verificar que haya artículos para este mes... */
if($month_query->have_posts()) :
$month_titles = array();
$close_ul = false;
//echo '<ul style="padding-left: 250px;" id="monthly-posts">';
/** Configurar los atributos para mostrar el título como un atributo */
$title_attribute_args = array(
'before' => 'Visitar artículo \'',
'after' => '\' ',
'echo' => false
);
/** Recorrer cada post para este mes... */
while($month_query->have_posts()) : $month_query->the_post();
/** Verificar el mes/año del post actual */
$month_title = date('F Y', strtotime(get_the_date('Y-m-d H:i:s')));
/** Mostrar una fecha legible, si aún no se ha mostrado */
if(!in_array($month_title, $month_titles)) :
if($close_ul) echo '</ul>'; // Verificar si la lista desordenada de posts debe cerrarse (no debería para el primer '$month_title')
echo '<h1 style="padding-left: 250px;" id="monthly-title">' . $month_title . '</h1>'; // Mostrar el '$month_title'
echo '<ul style="padding-left: 250px;" id="monthly-posts">'; // Abrir una lista desordenada para los posts que vendrán
$month_titles[] = $month_title; // Añadir este `$month_title' al array de `$month_titles` para que no se repita
$close_ul = true; // Indicar que la lista desordenada debe cerrarse en la próxima oportunidad
endif;
/** Mostrar cada artículo para este mes */
printf(
'<li id="monthly-post-%1$s">%2$s <a href="%3$s" title="%4$s">%3$s</a></li>',
get_the_ID(), /** %1$s - El ID del post */
get_the_title(), /** %2$s - El título del artículo */
get_permalink(get_the_ID()), /** %3$s - El enlace del artículo */
the_title_attribute($title_attribute_args) /** %4$s - El título para usar como atributo */
);
endwhile;
if($close_ul) echo '</ul>'; // Cerrar la última lista desordenada de posts (si se mostró alguna)
endif;
/** Restablecer la consulta para que WP no haga cosas raras */
wp_reset_query();
Respuesta Original
Prueba esto. Lo he configurado para que solo se elijan los últimos 6 meses y solo los últimos 5 posts de cada mes, pero puedes modificarlo como desees.
Esencialmente, el código primero verificará qué meses tienen posts y luego mostrará los últimos cinco posts de ese mes, junto con un enlace.
Método de Múltiples Consultas
global $wpdb, $wp_locale;
/** Consultar los meses individuales a mostrar (he elegido los últimos 6 meses) */
$query = $wpdb->prepare('
SELECT DISTINCT YEAR(`%1$s`.`post_date`) AS year, MONTH(`%1$s`.`post_date`) AS month
FROM `%1$s`
WHERE `%1$s`.`post_type` = "post"
ORDER BY `%1$s`.`post_date` DESC
LIMIT 6',
$wpdb->posts
);
$months = $wpdb->get_results($query);
/** Contar el número de meses */
$month_count = count($months);
/** Asegurarse de que hay meses para mostrar... */
if($month_count || ($month_count === 1 && $months[0]->month > 0)) :
/** Recorrer cada mes... */
foreach($months as $month) :
if($month->year === 0) :
continue;
endif;
/** Obtener el mes y año individuales, y construir una fecha legible (para el título) */
$m = zeroise($month->month, 2);
$y = $month->year;
$human_date = sprintf(__('%1$s %2$d'), $wp_locale->get_month($m), $y);
/** Obtener los posts para este mes (he elegido solo los últimos 5 posts) */
$args = array(
'nopaging' => true,
'posts_per_page' => 5,
'post_type' => 'post',
'post_status' => 'publish',
'order_by' => 'date',
'year' => $y,
'monthnum' => $m
);
$month_query = new WP_Query($args);
/** Verificar que haya artículos para este mes... */
if($month_query->have_posts()) :
/** Mostrar una fecha legible */
echo '<h1 id="monthly-title">' . $human_date . '</h1>';
echo '<ul id="monthly-posts">';
/** Configurar los atributos para mostrar el título como un atributo */
$title_attribute_args = array(
'before' => 'Visitar artículo \'',
'after' => '\' ',
'echo' => false
);
/** Recorrer cada post para este mes... */
while($month_query->have_posts()) : $month_query->the_post();
/** Mostrar cada artículo para este mes */
printf(
'<li id="monthly-post-%1$s">%2$s <a href="%3$s" title="%4$s">%3$s</a></li>',
get_the_ID(), /** %1$s - El ID del post */
get_the_title(), /** %2$s - El título del artículo */
get_permalink(get_the_ID()), /** %3$s - El enlace del artículo */
the_title_attribute($title_attribute_args) /** %4$s - El título para usar como atributo */
);
endwhile;
echo '</ul>';
endif;
/** Restablecer la consulta para que WP no haga cosas raras */
wp_reset_query();
endforeach;
endif;

¿En serio? ¿Qué tienes en mente? Pensé en usar GROUP BY
, pero el OP quiere encabezados para el mes/año, así que mi pequeño cerebro no logra ver más allá de múltiples consultas :-/

Veré si puedo publicar una solución más tarde. Solo que estoy un poco ocupado :-)

Mmm, me intriga… Supongo que podrías obtener todas las publicaciones más recientes que X usando date_query
, y luego separarlas en un array multidimensional. Puede que lo intente, pero estoy esperando una respuesta súper simple de ti más tarde ;-)

@PieterGoosen He intentado un método de consulta única, ¡pero agradecería cualquier crítica (constructiva) que puedas tener!

Tu versión actualizada se ve bien. Definitivamente otro método válido, aunque quizás sea un poco excesivo. Tendrás mi voto a favor por la versión actualizada :-)

Esta es una función que hice para uso general, para recuperar datos de posts o custom post types antes o después de un año/mes determinado, o del año actual, en el orden que necesites:
// podrías cambiar el nombre en caso de colisión con algún otro plugin
function get_posts_by_date($user_options = array()){
$options = array(
'year_limit' => '1980'
,'month_limit' => '01'
,'operator' => '>=' // operador de comparación de fecha
,'current_year' => true // limitar datos al año actual
,'post_type' => 'post'
,'year_order' => 'DESC'
,'month_order' => 'DESC'
,'post_ids_order' => 'DESC'
,'raw_output' => false
);
extract(array_merge($options, $user_options));
global $wpdb;
if($operator == '>=' || $operator == '=='){
$day = "01";
} elseif($mode == '<='){
$day = "31";
}
if($current_year){ // será después del 31/12/[año anterior]
$year_limit = date('Y', strtotime('-1 year'));
$month_limit = '12';
$day = "31";
$operator == '>=';
}
// advertencia: si tus parámetros vienen de entrada de usuario/formularios,
// pásalos usando $wpdb::prepare()
// https://developer.wordpress.org/reference/classes/wpdb/prepare/
$results = $wpdb->get_results("
SELECT tbl.y year, group_concat(month_posts ORDER BY tbl.m " . $month_order . " SEPARATOR '-') months
FROM (
SELECT YEAR(p.post_date) y, MONTH(p.post_date) m, concat(MONTH(p.post_date), ':', group_concat(p.id ORDER BY p.post_date " . $post_ids_order . " )) month_posts
FROM $wpdb->posts p
WHERE (p.post_status = 'publish' OR p.post_status = 'future')
AND p.post_type = '" . $post_type . "'
AND p.post_date " . $operator . " DATE('" . $year_limit . "-" . $month_limit . "-" . $day . " 00:00:00')
GROUP BY y, m
) tbl
GROUP BY tbl.y
ORDER BY tbl.y " . $year_order
);
if($raw_output) return $results;
global $wp_locale;
foreach ($results as $data){
$months_data = explode('-',$data->months);
$months = array();
$data->count = 0; // conteo anual
foreach ($months_data as $month_data){
$month_obj = new stdClass;
$splitted = explode(':',$month_data);
$raw_month = $splitted[0];
$month_obj->number = $raw_month;
$month_obj->name = $wp_locale->get_month($raw_month);
$month_obj->posts = array();
$post_ids = explode(',',$splitted[1]);
$data->count += count($post_ids);
foreach($post_ids as $post_id){
$month_obj->posts[] = get_post($post_id);
$months[$raw_month] = $month_obj;
}// foreach
}// foreach
$data->months = $months;
}// foreach
return $results;
}// get_posts_by_date
Ejemplo de uso:
$posts_by_date = get_posts_by_date(array(
'year_limit' => '2016'
,'operator' => '<='
,'current_year' => false
,'post_type' => 'product'
,'month_order' => 'ASC'
,'raw_output' => true
));
Si la opción raw_output
es true, la salida por defecto será algo así:
array(2) {
[0]=>
object(stdClass)#6645 (2) {
["year"]=>
string(4) "2017"
["months"]=>
string(65) "8:386,401-7:406,373,380,377,408,399,362-6:1,391,404-5:367,397,394"
}
[1]=>
object(stdClass)#6644 (2) {
["year"]=>
string(4) "2016"
["months"]=>
string(5) "6:429"
}
}
El string "months" contiene valores formateados como:
mes:[ids de posts]-mes:[ids de posts]-etc
Si la opción raw_output
es false, obtendrás una lista de posts así:
array (array de objetos)
object
-> year (ej. '2017')
-> count (total de posts del año)
-> months (array de objetos)
mes
-> number (del mes)
-> name (localizado)
-> posts (array de objetos post)
Feliz codificación... :)

Cuidado con posibles inyecciones SQL y evita extract
(más difícil de depurar) y no uses nombres de funciones genéricos para evitar posibles colisiones de nombres (o usa namespaces).

@birgire: bah... sé todo esto, pero depende del desarrollador evaluar el nombre de la función en relación a su entorno, y eventualmente sanitizar las entradas de usuario/formulario. Esto es solo para compartir código útil, honestamente creo que esta función ayudará mucho. Luego, realmente espero que cualquier desarrollador -- (solo) si es necesario -- sea perfectamente capaz de cambiar el nombre de la función, y/o sanitizar la entrada del usuario o añadir una sentencia prepare en lugar de escritura directa...

Ojalá lo hagan, pero me temo que muchos solo copian/pegan código de este sitio y lo olvidan, si funcionó la primera vez ;-)
