Support » Developing with WordPress » Help to order and group posts by month (but not year)

  • Resolved tictok

    (@tictok)



    I’ve been struggling with this for ages but can’t figure out how to order or group posts on a category page by month, ignoring the year completely.

    I have a category ‘videos’

    When viewing the category page/archive for ‘videos’ I need the posts to be grouped into months, with the current month’s posts displayed first (the videos are seasonal / time sensitive). The year needs to be ignored, so that in May (for example), the page will show all posts published in May of ALL years at the top.

    I can do this for the current month using pre_get_posts and monthnum $query->set('monthnum', $month);, to modify the query on the category.php page, but as expected, ‘monthnum’ only shows posts from a month then stops. It doesn’t show the previous posts.

    I then need to show all posts from previous month ($month-1), and the month before that ($month-2) and the month before that etc etc

    I guess I could do it by creating 11 additional queries and loops (for each previous month), but that can’t be the way to do it?

    Unless I’m missing something I don’t think theres a way to do anything like orderby -> monthnum

    What’s the smart way? Hope someone can help!

    Many thanks

Viewing 11 replies - 1 through 11 (of 11 total)
  • Only thing that comes to mind for ordering by month is to store the month number ( 1-12 ) in post meta and then order by meta_value_num.

    Hi Codismo –

    I’ve been thinking about different approaches a lot over the past 12 hours. I think that something like your suggestion is probably the easiest to implement, but I was hoping not to have to modify existing posts by adding new post meta.

    The only other option I came up with, is that as I already have all the post data within an array $posts (as the query for category.php is generated before the page is rendered), I believe post dates are stored in the array in something similar to a DD/MM/YY hh-mm-ss format. I could potentially search within that $posts array for a partial match to a string (including month number) by using PHP to search the array with preg_match. I could hopefully then return post IDs, or add those items / posts to a new array for each month and display those? Could work? Sounds like a more fragile method to me, doesn’t feel like the ‘wordpress way’…

    Moderator keesiemeijer

    (@keesiemeijer)

    moderator

    Try it with a date query.
    https://codex.wordpress.org/Function_Reference/WP_Query#Date_Parameters

    This will get all posts from the current month, regardless of year, on category pages. Put it in your themes functions.php file.

    
    function my_theme_month_query( $query ) {
    
    	// not an admin page and is the main query.
    	if ( ! is_admin() && $query->is_main_query() ) {
    
    		// Only on category pages.
    		if ( is_category() ) {
    			$date_query = array(
    				array(
    					'month' => date( 'n' ),
    				),
    			);
    
    			$query->set( 'date_query', $date_query );
    		}
    	}
    }
    
    add_action( 'pre_get_posts', 'my_theme_month_query' );

    btw:
    consider creating a child theme instead of editing your theme directly – if you upgrade the theme all your modifications will be lost. Or create a plugin with the code above.

    Many thanks keesiemeijer –

    If I understand correctly, from reading your snippet, that shows all posts from current month, regardless of year (as you state), but just for the current month.

    Unfortunately it doesn’t then go on to show all posts from the previous month (regardless of year), or the month before that etc. which is what I really need. I need to see all posts, but grouped by month – current month first (regardless of year).

    I was playing around using pre_get_posts along with $query->set(‘monthnum’, $month); instead of a date_query, which I think returns exactly the same results as your suggestion.

    Good tip on the child theme btw – thanks. I’m modifying a theme I created from scratch (using _S) a couple of years ago, so no need at the moment, but invaluable advice non-the-less!

    Moderator keesiemeijer

    (@keesiemeijer)

    moderator

    Aha, yes I see.

    Remove that code and try it with this in your child theme’s functions.php

    
    function my_theme_month_query( $pieces, $query ) {
    	global $wpdb;
    	// not an admin page and is the main query.
    	if ( ! is_admin() && $query->is_main_query() ) {
    
    		// Only on category pages.
    		if ( is_category() ) {
    			$pieces['orderby'] = " Month($wpdb->posts.post_date) DESC, $wpdb->posts.post_date DESC";
    		}
    	}
    
    	return $pieces;
    }
    
    add_action( 'posts_clauses', 'my_theme_month_query', 10, 2 );

    W0W! Many thanks @keesiemeijer !

    That gets me soo close. It does indeed change the query to display all posts, grouped by month, regardless of year – which is great!

    It’s so much more elegant and efficient than I’m sure the bloated mess (along with hundereds of DB queries) I would’ve otherwise ended up with would have been.

    Interestingly it keeps posts within any month in chronological order (i.e. all posts from May 2017, then May 2016, May 2015, April 2017, April 2016, April 2015 etc) then which is fine in this instance.

    As I’m learning as I go, can I check that I understand what it’s doing correctly please?
    $pieces['orderby'] = " Month($wpdb->posts.post_date) DESC, $wpdb->posts.post_date DESC";

    So, that line is making a new array ($pieces), of posts ordered by month in DESCending order, but you’ve done this via a custom query to the database to look at just the Month within the post_date. You’ve then ordered those results (within each month) by descending date order too?

    I’ve not looked into the $wpdb database class much before. Seems there’s a lot of potential for custom or otherwise difficult queries using that.

    Thanks again – I’d give you a hug if you were close enough!

    Oh, one more thing. I’m very close, but not quite across the finish line yet.

    I need to alter this to show the current months posts (regardless of year) at the top, and then preceding months posts (regardless of year) follows on after that.

    Would the best approach here be:
    – modify category.php in my child theme? (I can kind of see a way of doing this)
    – or should I stick with altering the query at source via pre_get_posts? (not so sure how I would this)

    Thanks again, again!

    Moderator keesiemeijer

    (@keesiemeijer)

    moderator

    The $pieces variable is an array of the database query that’s been made by the WP_Query class (split in pieces).

    We can add or alter these pieces to create a custom query. The ordering you want is getting quite complex, but try it with this.

    
    function my_theme_month_query( $pieces, $query ) {
    	global $wpdb;
    	// not an admin page and is the main query.
    	if ( ! is_admin() && $query->is_main_query() ) {
    
    		// Only on category pages.
    		if ( is_category() ) {
    			$pieces['fields'] .= " ,month($wpdb->posts.post_date), if(month($wpdb->posts.post_date)>month(now()), month($wpdb->posts.post_date), month($wpdb->posts.post_date)+12) month_order";
    			$pieces['orderby'] = " month_order DESC, $wpdb->posts.post_date DESC";
    		}
    	}
    
    	return $pieces;
    }
    
    add_action( 'posts_clauses', 'my_theme_month_query', 10, 2 );
    • This reply was modified 1 year, 4 months ago by  keesiemeijer. Reason: wrong conditional is_home()

    Awesome – can’t thank you enough. That does the job perfectly and I feel like I’m learning a lot too!

    The $pieces variable is an array of the database query that’s been made by the WP_Query class (split in pieces).

    So you defined $pieces in your function? It could’ve been called anything? i.e. $pieces isn’t reserved for WordPress?

    $pieces['fields'] .= " ,month($wpdb->posts.post_date), if(month($wpdb->posts.post_date)>month(now()), month($wpdb->posts.post_date), month($wpdb->posts.post_date)+12) month_order";
    			$pieces['orderby'] = " month_order DESC, $wpdb->posts.post_date DESC";
    		}

    Reading the above snippet I can understnad what it’s doing – not sure I’d be able to get anywhere close from scratch though. Not yet anyway!

    You’ve made my weekend!

    • This reply was modified 1 year, 4 months ago by  tictok.
    Moderator keesiemeijer

    (@keesiemeijer)

    moderator

    Aarg, it is a filter. Change this

    
    add_action( 'posts_clauses', 'my_theme_month_query', 10, 2 );

    To this

    
    add_filter( 'posts_clauses', 'my_theme_month_query', 10, 2 );

    add_action and add_filter are basically the same. It’s better to use the correct one though.

    I’ve called it $pieces in the first function parameter (you can call it whatever you want). The posts_clauses filter calls your my_theme_month_query function with the first parameter containing the database query pieces. If you would print it inside the function it would display something similar (depending on the page query) to this:

    
    Array
    (
        [where] =>  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')
        [groupby] => 
        [join] => 
        [orderby] => wp_posts.post_date DESC
        [distinct] => 
        [fields] => wp_posts.*
        [limits] => LIMIT 0, 10
    )

    You change the query by adding or changing a piece. Because it’s a filter you return the first parameter ($pieces) at the end.

    I hope this clears things up

    I’m glad you’ve got it solved πŸ™‚

    • This reply was modified 1 year, 4 months ago by  keesiemeijer. Reason: typo

    Thank you, you’ve been a great help.

    It all makes sense but I think I’ve still got plenty to read up on and experiment with filters and tweaking the query this way before I can easily put it into practice.

    Today’s one of those days where my WordPress knowledge and understandinh made a little leap forwards. πŸ˜€

Viewing 11 replies - 1 through 11 (of 11 total)
  • The topic ‘Help to order and group posts by month (but not year)’ is closed to new replies.