I want to achieve something like this, I don’t know if it is possible and what would be the best way to do it:

The way I query the posts is like this:
<div class="post">
<?php global $wp_query;
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; // end of the loop. ?>
</div>
Anyone can give me a tip on how or best way to do it?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
As said in a comment, you can do this in one query. The principle here is to only display the date heading if the post date’s month of the previous post does not match that of the previous post
FEW NOTES
Before I start, a few notes:
-
Never use
query_posts, except if you really need to break everything on a page. It not just reruns the main query and also breaks it, it messes up pagination and your globals, and also messes with your queried object functions. If you really have to run a custom query, useWP_Queryorget_posts -
showpostswas dropped years ago in favor ofposts_per_page -
No need to make use of the global
$wp_query.query_postsmesses that up anyway
THE PLAN
Post are served up in chronological order with the newest post first, and the oldest post last, so they are already in the correct order. It is just a matter of displaying the date in the appropriate place.
To do this, all you need to do is to get the current month and year from the current post’s post date, and then comparing that with the previous post’s post date month and then either displaying the date if the months don’t match or not display it if they match
As explanation, I will use the main loop with the main query.
To accomplish this, you need to:
-
Get the month from the current post’s post date. To achieve this, use
get_the_date( 'F' ) -
Get the previous post in the loop with
$wp_query->posts['this will be current post -1 ']->post. - Get and compare the months between the two posts
- Display or do not display the date according to the comparison
THE CODE
This code goes inside your loop, just after your while() statement.
$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' );;
}
}
CUSTOM QUERY
If you need to run a custom query, try this
$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();
}
}
Method 2
Updated Answer
After thinking about the comment from @PieterGoosen below I’ve added a method of achieving the you goal by using a single query.
I benchmarked the methods and the single query is faster, with the multiple query method being about 15% slower. Not a hugh margin, but every little helps, and to be honest the method can probably be refined further.
I’ve left the multiple query method in the answer for prosperity, but I recommend that the single query method be used.
Single Query Method
$time_start = microtime(true);
/** Set up a date interval object for 6 monts ago (you can change as required) */
$interval = new DateInterval('P6M');
$interval->invert = 1;
/** Grab the date as it was 6 months ago */
$date = new DateTime(date('Y-m-d'));
$date->add($interval);
/** Query the database for all posts newer than the the given date interval */
$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);
/** Check to ensure that there are articles for this month... */
if($month_query->have_posts()) :
$month_titles = array();
$close_ul = false;
//echo '<ul style="padding-left: 250px;" id="monthly-posts">';
/** Set the attributes for displaying the title as an attribute */
$title_attribute_args = array(
'before' => 'Visit article '',
'after' => '' ',
'echo' => false
);
/** Loop through each post for this month... */
while($month_query->have_posts()) : $month_query->the_post();
/** Check the month/year of the current post */
$month_title = date('F Y', strtotime(get_the_date('Y-m-d H:i:s')));
/** Maybe output a human friendly date, if it's not already been output */
if(!in_array($month_title, $month_titles)) :
if($close_ul) echo '</ul>'; // Check if the unordered list of posts should be closed (it shouldn't for the first '$monthe_title')
echo '<h1 style="padding-left: 250px;" id="monthly-title">' . $month_title . '</h1>'; // Output the '$month_title'
echo '<ul style="padding-left: 250px;" id="monthly-posts">'; // Open an unordered lists for the posts that are to come
$month_titles[] = $month_title; // Add this `$month_title' to the array of `$month_titles` so that it is not repeated
$close_ul = true; // Indicate that the unordered list should be closed at the next oppurtunity
endif;
/** Output each article for this month */
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 - The ID of the post */
get_the_title(), /** %2$s - The article title */
get_permalink(get_the_ID()), /** %3$s - The article link */
the_title_attribute($title_attribute_args) /** %4$s - The title for use as an attribute */
);
endwhile;
if($close_ul) echo '</ul>'; // Close the last unordered list of posts (if there are any shown)
endif;
/** Reset the query so that WP doesn't do funky stuff */
wp_reset_query();
Original Answer
Give this a try. I’ve set it up so that only the last 6 months are chosen and only the last 5 posts from each month, but you can alter that as you please.
Essentially the code will first check which months have posts in them and then output the last five posts from that month, along with a link.
Multiple Query Method
global $wpdb, $wp_locale;
/** Query the individual months to display (I've chosen the last 6 months) */
$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);
/** Count the number of months */
$month_count = count($months);
/** Ensure that there are months to display... */
if($month_count || ($month_count === 1 && $months[0]->month > 0)) :
/** Loop through each month... */
foreach($months as $month) :
if($month->year === 0) :
continue;
endif;
/** Grab the individual month and year, and construct a human friendly date (for the title) */
$m = zeroise($month->month, 2);
$y = $month->year;
$human_date = sprintf(__('%1$s %2$d'), $wp_locale->get_month($m), $y);
/** Grab any posts for this month (I've chosedn only the last 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);
/** Check to ensure that there are articles for this month... */
if($month_query->have_posts()) :
/** Output a human friendly date */
echo '<h1 id="monthly-title">' . $human_date . '</h1>';
echo '<ul id="monthly-posts">';
/** Set the attributes for displaying the title as an attribute */
$title_attribute_args = array(
'before' => 'Visit article '',
'after' => '' ',
'echo' => false
);
/** Loop through each post for this month... */
while($month_query->have_posts()) : $month_query->the_post();
/** Output each article for this month */
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 - The ID of the post */
get_the_title(), /** %2$s - The article title */
get_permalink(get_the_ID()), /** %3$s - The article link */
the_title_attribute($title_attribute_args) /** %4$s - The title for use as an attribute */
);
endwhile;
echo '</ul>';
endif;
/** Reset the query so that WP doesn't do funky stuff */
wp_reset_query();
endforeach;
endif;
Method 3
This is a function I made to use for general needs, to retrieve post or custom post types data before or after a certain year/month, or current year, in whichever order you need:
// you could change the name in case it collides with some other plugin
function get_posts_by_date($user_options = array()){
$options = array(
'year_limit' => '1980'
,'month_limit' => '01'
,'operator' => '>=' // date comparison operator
,'current_year' => true // limit data to current year
,'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){ // will be after [previous year]/12/31
$year_limit = date('Y', strtotime('-1 year'));
$month_limit = '12';
$day = "31";
$operator == '>=';
}
// warning: if your parameters come from user input/forms,
// pass them using $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; // year count
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
Usage example:
$posts_by_date = get_posts_by_date(array( 'year_limit' => '2016' ,'operator' => '<=' ,'current_year' => false ,'post_type' => 'product' ,'month_order' => 'ASC' ,'raw_output' => true ));
If raw_output option is true, the default output will be something like this:
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"
}
}
The “months” string contains values formatted as:
month:Display posts by month-month:Display posts by month-ecc
If raw_output option is false, you get a list of posts like this:
array (array of objects)
object
-> year (ex. '2017')
-> count (total year's posts)
-> months (array of objects)
month
-> number (of month)
-> name (localized)
-> posts (array of post objects)
Happy coding… 🙂
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0