3

Rephrase question

I need to order posts inside the loop for my category car. What I need, if I visit the category page of car, I need all posts tagged BMW to display first, and then the other posts not tagged with the tag BMW

If my posts_per_page is set to display 10 posts, and I'm on page one, and three of the ten posts are tagged BMW, then these three posts must appear first, and the rest follows those three posts. When I click and go to page two, the same applies. If I have 5 out of these 10 posts tagged BMW, again they must appear first before the other 5 posts.

Is that possible?

  • Please show us what have you tried so far? – Nilambar Sep 16 '14 at 9:20
  • I did'nt try anything yet. – PreeT Sep 16 '14 at 9:26
  • Welcome to WPSE. If you haven't done so, please read How to Ask, help center and visit our tour page :-) – Pieter Goosen Sep 16 '14 at 10:09
  • Would you not want all posts with the tag to appear first regardless of paging, eg in your example the 8 "bmw" posts would appear on the first page of 10, then the rest of the posts? Or if there were more than 8, eg 22, then the first 2 pages would contain "bmw" posts, then 2 "bmw" posts then the rest? – bonger Sep 18 '14 at 20:13
  • @bonger no. Example, you have, like you said, 22 posts belonging to tag BMW. In normal post ordering and pagination set to ten posts per page, you have 5 posts on page one tagged BMW, 6 on page two, 3 on page three, etc. The rest of the ten posts per page are made up with other posts with other tags other than BMW. On any specific page, I would like to change the current post order. Say on page 3 I had 3 posts belonging to BMW and the other 7 not, I would like the three BMW posts on top followed by the rest. The same goes for the other pages – Pieter Goosen Sep 19 '14 at 5:11
5
+150

Here's a simplified combination of both @PieterGoosen and @ialocin fine answers by using the loop_start hook:

add_action( 'loop_start', function( $q ) {
    if( $q->is_main_query() && $q->is_category( 'car' ) )
        usort( $q->posts, function( $a, $b ){
            return -1 * has_tag( 'bmw', $a ) + 1 * has_tag( 'bmw', $b );            });
}, 10, 2 );

We could also use the same method for the the_posts filter.

Update:

To sort posts on the home page, by the first category name, we can try for example:

add_action( 'loop_start', function( $q ) {
    if( $q->is_main_query() && $q->is_home() )
        usort( $q->posts, function( $a, $b ){
            return strcasecmp( 
                get_the_category( $a->ID )[0]->name, 
                get_the_category( $b->ID )[0]->name 
            );
        });
}, 10, 2 );

where we use strcasecmp for case-insensitive string comparison.

  • Must say it is really simplified. Cannot test right now, I'm at work. Will quickly test this this afternoon when I get home, you know Fridays are braai, rugby and whiskey nights :-). I also had that flash of light this morning using loop_start – Pieter Goosen Sep 19 '14 at 5:15
  • 1
    Sure, you should keep the order as: Friday, Rugby, Whiskey, ..., Monday, WordPress ;-) – birgire Sep 19 '14 at 5:20
  • 1
    Perfect, working nicely from what I quickly tested. +1. Will wait to award the bounty to see what else comes up. As Depeche Mode sings, "working week's come to its end, PARTYTIME is here again...." Enjoy. The goose signing out – Pieter Goosen Sep 19 '14 at 14:00
  • 1
    That's a great song into the weekend and I guess any song with rock band New Order would make sense after working on this question ;-) – birgire Sep 19 '14 at 16:39
  • 2
    Great to hear it worked for you, I updated the answer with this extra category sorting. – birgire Sep 21 '14 at 9:48
3

You cannot do this type of sorting inside the loop. You can however be done by writing your own function

Here is my solution: PS! Needs PHP 5.4+

STEP 1

Create a function called wpse161553_loop_sort($tag=''). This will work with a single tag with the main query.

STEP 2

You need to get the post objects from the main query. They are already available and can just be accessed by $wp_query->posts;. (For a full list of available objects and how to access them, see WP_Post).

STEP 3

Create and start a new counter. This counter will be used to alter the array keys from $wp_query->posts; later on.

STEP 4

Get the posts_per_page option set in the back end. This value will be added to the counter for all posts that does not have the desired tag. This will advance all these posts past the desired tag posts

STEP 5

Inside the loop, use has_tag() to check each post against the desired tag. If a post have a desired tag, push the current counter value to an array ($c) as is. If a post don't have the desired tag, push the value of the current counter plus the posts_per_page value to $c

STEP 6

Use array_combine to combine the two arrays together. The newly created array $c will replace the array keys of the array of posts

STEP 7

Use ksort to sort the new array created with array_combine according to the new keys. This will now sort the array so that posts are sorted by post date, and also, post from the desired tag will appear first, then the rest of the posts

Step 8

Use array-values to reset the keys back to start at 0 and increment numerically by one

STEP 9

Rewind the loop so that we can rerun the loop

STEP 10

Unset the original array inside $wp_query->posts and replace it with the new array created. $wp_query->posts will now hold an array with the newly ordered post order

ALL TOGETHER NOW!!

The following code goes into your functions.php

function wpse161553_loop_sort($tag='') {
    global $wp_query;
    $posts = $wp_query->posts; // Gets all post data from the main query

    $c = []; // Going to hold an array of new keys for later use
    if ( have_posts() ) {
        $count = 0; //Start the counter
        $ppp = get_option('posts_per_page'); // Gets the backend posts per page option set. Will be used in conjustion with the counter

        while ( have_posts() ) {
            the_post();
                if( '' != $tag && has_tag($tag)) { // This will be the tag to test against, your desired tag
                    $c[] = $count++;
                }else{
                    $c[] = $ppp + $count++; // Adds posts per page value to each count to advance posts without desired tag past desired tag
                }
        }
    } 

    $posts_reordered  = array_combine( $c, $posts ); // Reset each post from main query's key with the new keys created by $c
        $posts_sorted = ksort($posts_reordered); // Sort the new array according to key
    $posts_reordered  = array_values($posts_reordered); // Reset keys to start at zero and increment by one

    rewind_posts(); // Reset the loop so we can run the loop again

    unset($wp_query->posts); //unset the original $wp_query->posts object array
    $wp_query->posts = $posts_reordered; // Set $wp_query->posts to the new reordered array

}

HOW IT WILL BE USED

In your category.php, or for that matter, any template, paste the following just above the loop. You don't need to change anything else

wpse161553_loop_sort( 'NAME OF THE TAG TO APPEAR FIRST' );

where NAME OF THE TAG TO APPEAR FIRST is the name of the desired tag which posts will appear first

2

Just a quick outlined draft of an idea, so beware I haven't thought and tested this through entirely. I'm certain though that you can perform sorting actions this way. Ok, what I was thinking is, why not hooking into the_posts and use usort to do the sorting. All the rest, especially the conditionals are just to make sure that this isn't performed on every query and depends on the actual use case, so that has to be fit accordingly.

add_action(
    'the_posts',
    'wpse161553_posts_with_tags_on_top_of_category',
    10,
    2
);
function wpse161553_posts_with_tags_on_top_of_category(
    $posts,
    $wp_query
) {
    // we want this for one specific category
    $the_category = 'abc';
    // we want the post with this tag on top 
    $the_tag      = 'xyz';

    if (
        $wp_query->is_main_query()
        && is_category( $the_category )
    ) {
        // getting an array of post ids from category with tag
        $posts_with_tag = new WP_Query(
            array(
                'category_name' => $the_category,
                'tag'           => $the_tag,
                'fields'        => 'ids'
            )
        );  
        // performing usort on referenced posts object
        usort(
            $posts,
            function (
                $post
            ) use (
                $posts_with_tag
            ) {
                // puts posts from the $posts_with_tag->posts array to the top
                if(
                    in_array (
                        $post->ID,
                        (array) $posts_with_tag->posts 
                     )
                ) {
                    return -1;
                } else {
                    return 1;
                }
            }
        );
    }

    return $posts;
} 
  • It might be possible to persuite it this way, but it is going to take some work though. This just currently sorts the current posts in the loop from last to first, so basically it inverts the order :-) – Pieter Goosen Sep 18 '14 at 14:23
  • Well, the bounty is for seven days, and I think your idea is valid. I would like to see what comes out of this, even if it just invert the posts. The choice is up to you as well if you want to work with it :-) – Pieter Goosen Sep 18 '14 at 15:30
  • 1
    I have formatted your code to be a bit more readable, :-). Also, I have changed (if you don't mind) $wp_query->query['category_name'] == $the_category to is_category( $the_category ) as the original broke my back end and frond end queries. Your code targets the correct page, it is just the sorting issue now. – Pieter Goosen Sep 18 '14 at 18:01
  • 1
    Not only do I not mind, I do appreciate it! And if I'm doing things in a hurry, I still do all the wrong stuff, so good thing you replaced my tempering with $wp_query. About the readability, I guess thats kind of subjective, I personally like to separate parameters on extra lines and find it quite readable, but I also don't mind that change. @PieterGoosen – Nicolai Sep 18 '14 at 18:19
  • Changed it up a little, gave it a quick test, seems to work even with pagination. Sorry about the "wrong" formatting again, thats just how I've set up my IDE to do it for me. I'm too lazy for reformatting, but feel free to do it. @PieterGoosen – Nicolai Sep 18 '14 at 19:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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