Mostrar entradas por mes

27 ene 2015, 11:52:21
Vistas: 27K
Votos: 3

Quiero lograr algo como esto, no sé si es posible y cuál sería la mejor manera de hacerlo:

ejemplo de entradas organizadas por mes

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?

2
Comentarios

Para lograr esta estructura necesitarías múltiples consultas. Primero tendrías que identificar los meses a mostrar, y luego recorrer los resultados para obtener las publicaciones relevantes. Intentaré dar una respuesta en breve...

David Gard David Gard
27 ene 2015 13:47:13

Puedes lograr esto con una sola consulta sin usar query_posts

Pieter Goosen Pieter Goosen
27 ene 2015 15:18:55
Todas las respuestas a la pregunta 3
5
11

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, usa WP_Query o get_posts

  • showposts fue eliminado hace años en favor de posts_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();

    }

}
27 ene 2015 16:44:06
Comentarios

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 :)

David Gard David Gard
27 ene 2015 17:37:37

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.

David Gard David Gard
27 ene 2015 17:39:25

@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().

Pieter Goosen Pieter Goosen
27 ene 2015 17:50:13

@wpuser un placer. Disfruta :-)

Pieter Goosen Pieter Goosen
27 ene 2015 17:50:51

@PieterGoosen - Me doy cuenta de eso, solo pensé que valía la pena mencionarlo para cualquier otra persona que pueda encontrarse con esta respuesta en el futuro. Sé que me confundo fácilmente, ¡así que asumo que los demás también!

David Gard David Gard
27 ene 2015 17:56:38
7

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;
27 ene 2015 14:29:12
Comentarios

Puedes hacer esto en una sola consulta :-)

Pieter Goosen Pieter Goosen
27 ene 2015 14:33:14

¿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 :-/

David Gard David Gard
27 ene 2015 14:36:49

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

Pieter Goosen Pieter Goosen
27 ene 2015 15:18:14

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 ;-)

David Gard David Gard
27 ene 2015 15:29:51

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

David Gard David Gard
27 ene 2015 16:39:16

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 :-)

Pieter Goosen Pieter Goosen
27 ene 2015 18:15:08

Gracias :) Tiendo a esforzarme mucho en mis respuestas, siempre asumo que el usuario está empezando desde cero (a menos que se proporcione algún código).

David Gard David Gard
27 ene 2015 18:36:53
Mostrar los 2 comentarios restantes
4

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... :)

15 ago 2017 11:59:30
Comentarios

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 birgire
15 ago 2017 16:45:52

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

Luca Reghellin Luca Reghellin
16 ago 2017 16:05:42

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

birgire birgire
16 ago 2017 17:52:27

Ok gracias, agregué sugerencias de seguridad, creo que es suficiente, la pregunta original no es sobre problemas de seguridad :)

Luca Reghellin Luca Reghellin
16 ago 2017 19:09:10