How to bring specific post to front of wordpress loop?

So, I have a WordPress loop going on. The below code is the template page for the woocommerce product archive page. Here’s the use case for what I am trying to find out how to achieve:

  • User is displayed with product archive page (product thumbnails wrapped in links are listed).
  • User would click on a thumbnail (which is a link) and it takes him to another page that is basically the same as the current, except that a slider is shown of the same products that appeared on the previous page (you know, the number of posts on page that appeared on page 1, page 2, or page 3, whichever the post clicked was on).
  • This page will contain a slider with a bigger thumbnail of all of the products, and the selected product will be at the beginning of the slider (I need to move it to the beginning of the loop of products for that page and rearrange the other posts accordingly).

TL;DR: To sum it up, how can I go to another page that has the exact same posts at the exact same page number (page as in pagination location), but with the selected post in the beginning of the loop of the page while keeping the order of the posts still relevant (as in the posts before the chosen post will still be before it and the ones after it will still be after it)?

PS: I would prefer not to use a plugin solution, nor a client-side solution(I want it all done on the server in PHP).

For example:

I am on page 5:

Posts are as follows: 41, 42, 43, 44, 45, 46, 47, 48, 49, 50.

Say I clicked on 44.

The page I’m taken to has the same loops but with 44 in the beginning, and the others rearranged accordingly.

The loop would read as follows: 44, 45, 46, 47, 48, 49, 50, 41, 42, 43.

Then if I clicked on the next page. Since I’m on page 5 and clicked on 6, the loop would be normal again: 51, 52, 53…

<?php get_template_part('templates/page', 'header');

    /**
    * woocommerce_before_main_content hook
    *
    * @hooked woocommerce_output_content_wrapper - 10 (outputs opening divs for the content)
    * @hooked woocommerce_breadcrumb - 20
    */
    do_action('woocommerce_before_main_content');

    do_action( 'woocommerce_archive_description' );

    if ( have_posts() ) {

        /**
         * woocommerce_before_shop_loop hook
         *
         * @hooked woocommerce_result_count - 20
         * @hooked woocommerce_catalog_ordering - 30
         */
        do_action( 'woocommerce_before_shop_loop' );

        woocommerce_product_loop_start();

        woocommerce_product_subcategories();

        while ( have_posts() ) {

            the_post();

        ?>

        <li>

            <?php get_template_part( 'woocommerce/content', 'product' );

        </li>

        <?php
        }
        woocommerce_product_loop_end();
        /**
         * woocommerce_after_shop_loop hook
         *
         * @hooked woocommerce_pagination - 10
         */
        do_action( 'woocommerce_after_shop_loop' );

    } elseif ( ! woocommerce_product_subcategories( array( 'before' => woocommerce_product_loop_start( false ), 'after' => woocommerce_product_loop_end( false ) ) ) ) {

    wc_get_template( 'loop/no-products-found.php' );
    }
    /**
    * woocommerce_after_main_content hook
    *
    * @hooked woocommerce_output_content_wrapper_end - 10 (outputs closing divs for the content)
    */
    do_action('woocommerce_after_main_content');

    ?>

1 Answer
1

The 'the_posts' filter hook allows you to edit posts that are going to be displayed in a loop.

It’s fired for all queries (the main and the secondaries) so you need to check that the query you’re acting on is the right one.

That said in your case you can:

  1. send a query variable to individuate the selected post
  2. use 'the_posts' filter to move selected post on beginning of post array

1. Send a query variable to individuate the selected post

Post thumbnails should be printed using something like this:

<a href="https://wordpress.stackexchange.com/questions/174491/<?php esc_url( add_query_arg( array("psel' => get_the_ID() ) ) ) ?>">
  <?php the_thumbnail() ?>
</a>

add_query_arg() add a query variable to current url, it means that if you are on the page that has the url example.com/some/path/page/5 by clicking on the post thumbnail for the post with ID 44 you are sent to the url example.com/some/path/page/5?psel=44.

Once the url is the same posts that are shown will be the same, but thanks to the psel url variable you can reorder posts to have the selected post on beginning of posts array.

2. Use 'the_posts' filter to move selected post on beginning of post array

Once you have the selected post id in a url variable, to put related post object on top of posts array is just a matter of a couple of PHP functions

function get_selected_post_index() {
  $selID = filter_input(INPUT_GET, 'psel', FILTER_SANITIZE_NUMBER_INT);
  if ($selID) {
    global $wp_query;
    return array_search($selID, wp_list_pluck($wp_query->posts, 'ID'), true);
  }
  return false;
}

add_filter('the_posts', function($posts, $wp_query) {

  // nothing to do if not main query or there're no posts or no post is selected
  if ($wp_query->is_main_query() && ! empty($posts) && ($i = get_selected_post_index())) {
      $sel = $posts[$i]; // get selected post object
      unset($posts[$i]); // remove it from posts array
      array_unshift($posts, $sel); // put selected post to the beginning of the array
  }

  return $posts;

}, 99, 2);

Previous code will assure that posts are ordered like you want.

The get_selected_post_index() function can be also used inside your templates to know if there is a selected post or not (and modify your template accordingly), because it returns false when no post is selected (or if a wrong Id is sent via psel url variable).

Leave a Comment