0

I have a custom post type for business locations that I am trying to add an additional sorting filter to by using a custom meta value. The custom field specifies whether or not the location is open or closed. The default sorting is being done alphabetically by post title, but I would like to add this additional sort so that any location with the value "Closed" will be moved to the very end of the query results.

Edit: Some additional context based on a comment: currently there are only 2 location statuses; "Open" and "Closed," but there is a possibility of there being another added in the future (ie "Temporarily Closed"). In that scenario those posts would remain in alphabetical order, I really only want the ones that are "Closed" to be at the end of the results.

I found this answer which has gotten me most of the way there, the only problem I am facing now is that the query only shows 9 posts per page, and while the "Closed" post does get moved to the bottom of the results on initial load, it's only on a per-page basis currently. For instance, I have a "Closed" location that, alphabetically, is #3 in the results, and with this filter enabled it is getting moved to the 9th position, but the query itself has 30 results overall, so when I start loading more posts, "Open" locations end up loading in after this initial "Closed" one. Is there an easy way to make this filter work while keeping the pagination intact? I would prefer not to query all the posts at once and fake the pagination with JavaScript/CSS.

function order_by_closed($posts, $query) {
    $postType = $query->get('post_type');

    if ($postType == 'location') {
        $open = array();
        $closed = array();

        // Separate posts by open/closed
        foreach ($posts as $post) {
            if (get_field('location_status', $post->ID) == 'Closed') {
                $closed[] = $post;
            } else {
                $open[] = $post;
            }
        }

        // Merge arrays with closed locations at the end
        $posts = array_merge($open, $closed);
    }

    return $posts;
}
add_filter( 'posts_results', 'order_by_closed', PHP_INT_MAX, 2 );
4
  • The question you've linked to talks about adding an order by to the SQL query (which I think is the way to do this). Have you done that, or have you just copied the post_results filter part of the answer?
    – Rup
    Dec 13, 2023 at 23:51
  • @Rup Thank you, yes, I did try adding that but it didn't seem to have any affect. I think that was in regard to something else. Other commenters said that the solution does not support pagination.
    – user13286
    Dec 14, 2023 at 0:29
  • 1
    If the meta_key=location_status has only two values for all posts in cpt: Open or Close, why don't you order by meta_value with desc order in the WP_Query arguments?
    – birgire
    Dec 14, 2023 at 1:06
  • @birgire because I need the posts to be in alphabetical order by title and only the "Closed" locations should be at the very bottom. There's also a possibility that an additional location status will be added in the future.
    – user13286
    Dec 14, 2023 at 15:55

1 Answer 1

2
+75

Okay, so why doesn't this work:

The Filter you are using is the posts_results, which is a filter that is applied AFTER the Results are retrieved from the database. Which has the problem that you are describing: Ordering happens on a page-per-page basis.

What you want to do is add an order TO the SQL-Query, so that the order is applied to the complete result and your Locations are retrieved first open, then closed.

The way to do that is to remove the posts_results filter, and then use the pre_get_posts action:

function wpse_420312_order_locations_by_open_closed( $query ) {
    //First we need to check that this is the "main" query and that we're not on a backend page
    if ( $query->is_main_query() && !is_admin() ) {
        $post_type = $query->get('post_type');
        if ( $post_type === 'location' ) {
            //we need to build a meta query. To learn more, look here: https://developer.wordpress.org/reference/classes/wp_meta_query/
            $meta_query = array(
                //to sort by a meta query, we have to name it
                'location_status' => array(
                    //luckily, ACF saved custom fields with their name as the meta_key
                    'key' => 'location_status',
                    //we only want to see locations that have the status information
                    'compare' => 'EXISTS'
                )
            );
            $query->set( 'meta_query', $meta_query );
            //sort by: location Status DESC (first "Open", then "Closed"), then post title ASC
            $query->set( 'orderby', array( 'location_status' => 'DESC', 'title' => 'ASC', 'ID' => 'ASC' ) );
        }
    }
}
add_action( 'pre_get_posts', 'wpse_420312_order_locations_by_open_closed' );

Happy Coding!

9
  • Thank you for the explanation! This is definitely doing something, but it doesn't seem to give the intended result. The posts are no longer returning in order of post title on page load and I am still seeing the "closed" location in the initial 9 results. I can't really figure out what order they're being returned in on load, but the first post in the list starts with a "T" now and when I click "Load More" I am getting the 2nd page of results in alphabetical order, but I am getting duplicate posts since the initial 9 posts are not in alphabetical order.
    – user13286
    Dec 14, 2023 at 15:53
  • 1
    @HU is Sebastian: Shouldn't $query->set( 'order', ... ) instead be $query->set( 'orderby', ... )? See the 'orderby' with multiple 'meta_key's example in the WordPress documentation for WP_Query.
    – kofeigen
    Dec 14, 2023 at 17:59
  • @kofeigen that seemed to have helped. The posts are now returning in alphabetical order again on load and my "Closed" location is no longer showing up in the initial 9 results. The only remaining issue is that I am still getting duplicate posts when I load more (the last 3 posts in the initial results are also the first 3 posts when I load more). The "Closed" location is never returned, I'm assuming because the 3 duplicate posts are throwing off the count. Do I maybe have to make adjustments to my load more posts function as well?
    – user13286
    Dec 14, 2023 at 18:51
  • 1
    @user13286 A completely different approach might be needed though. Is the list of posts displayed by a default theme template (e.g. archive.html) or your own custom-made template? Why do you need a load more posts function? Why can't default pagination (functions) be used? Will you ever require translation? Could you ever want to accommodate additional location status options that could influence sorting? Right now, sort order is based on "chance". There are only two English-language status labels. If 3 labels ever required a specific sequence the current suggested solutions might not work.
    – kofeigen
    Dec 15, 2023 at 1:26
  • 1
    @kofeigen Damn, you're right. I fixed the "orderby" typo and added the sorting by ID to prevent duplicates when using "normal" pagination in case there are locations with the same title. Dec 15, 2023 at 9:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.