Search code examples
phpwordpressmeta-query

Wordpress wp_query: Order by meta_query key & then everything else by date (pinned / sticky)


I've been racking my brain over this for hours and now it's time to take it to Stack overflow.

I'm aware there's no easy way to do this and probably there is not a way to do this at all.

Using Wordpress, I'm trying to query posts but order them in a particular way whilst keeping pagination (as it's done via AJAX so a two query solution wouldn't work in my case - to my knowledge).

I'm trying to get an ordered by date list of posts where if the meta key set_as_pinned is set to true return this as post 1 always, then the rest can be ordered by date. Thus giving me a "Sticky" feat.

After playing about with many different solutions, I'm at the following query, which

$args = array(
    'post_type'         => $post_type,
    'posts_per_page'    => $post_type,
    'post_status'       => 'publish',
    'search_prod_title' => $s_query,
    'orderby'           => array(
        'pinned', 'not_pinned'
    ),
    'paged'             => $paged,
    'tax_query'         => $tax_query,
    'meta_query'      => array(
        'relation'    => 'or',
        'pinned' => array(
            'key'     => 'set_as_pinned',
            'value'   => true,
            'compare' => '=',
            'orderby' => 'date',
            'order' => 'DESC'
        ),
        'not_pinned' =>  array(
            'key'     => 'set_as_pinned',
            'value'    => true,
            'compare' => '!=',
            'orderby' => 'date',
            'order' => 'DESC'
        ),
    )

);

Currently, all this returns is the 1 pinned post that I have. It's missing everything else in DATE order.


Solution

  • I have managed to achieve this not by using the meta query method but actually by using 2 queries, which I didn't think was possible. I'll do my best to explain for others who may be having the same issue.

    This solution will help people trying to solve

    • AJAX Pagination with pinned or sticky posts
    • Sticky Posts with custom post types

    A comment-less version can be found here https://github.com/Sandy-Garrido/wordpress-ajax-pagination-sticky-pinned

    Pre-req - Setting meta data for pinned / sticky

    I will presume that you have already (with ACF or other means) set meta data to retrieve pinned posts. In my example, I have a field type of Boolean for a Key named set_as_pinned

    Step 1 - Get your pinned/stick posts

    $paged = $_POST['pageRequested'];
    
    // declare an empty array for your pinned posts
    // (this helps us exclude it from our query when we need to get all the other posts)
    $pinned_post_arr = array(); 
    
    // Define your args for pinned or sticky posts
    $pinned_args = array(
        'post_type' => 'insight',
        'posts_per_page' => -1,
        'meta_key' => 'set_as_pinned',
        'meta_value' => true
    ); 
    
    // Query list of pinned posts using wp_query or get_posts()
    $pinned_posts = new \WP_Query($pinned_args);
    
    
        while ($pinned_posts->have_posts()){
            $pinned_posts->the_post();
    
    //Send this post ID to array so we don't duplicate this in our list
            array_push( $pinned_post_arr,  get_the_ID()); 
    
            $Posts = new Posts;
    
            //add each post to the query response data
            if($paged == 1){
    
               // DO STUFF WITH YOUR PINNED POSTS HERE.
               // Generate HTML or cards, whatever you need
               // My use case was to generate HTML and pass it back via AJAX
    
    // this is relative to my project, so copying won't work
              query_response['data'] .= $Posts->render_posts(); 
    
            }
        }
        wp_reset_postdata(); // ensure you include this line
    

    Step 2 - Set variables for the second query

    (and calculate an offset for page 2 and up)

    What is offset?

    An argument for get_posts() & wp_query

    offset (int) – number of post to displace or pass over. Warning: Setting the offset parameter overrides/ignores the paged parameter and breaks pagination. The 'offset' parameter is ignored when 'posts_per_page'=>-1 (show all posts) is used.

    Take note of the warning Wordpress has given us of the paged and pagination issues.

    if($paged == 1){
    
        // if the request from the browser ajax request wants page 1,
        // we need the second query to return the right amount of posts. 
        // This is done by deducting the count of our pinned post array
        $post_per_page = 10 - count($pinned_post_arr);
        $offset = false; // this is not req but I like to add for sanity
    
    
    } else {
    
        // If the browser did not request page 1 but requested another page.
        // We need to counter the pinned posts using an offset.
        // This stops page 2 missing items which page 2 thinks is on page 1.
    
    
        $post_per_page = 10;
    
    // here, we calculate, based on the page we're on and how many pinned posts there were
    // the number 10 is my defined posts per page that I personally wanted.
        $offset = ((10-count($pinned_post_arr) ) + (10 * ($paged - 2)));
    
    }
    

    Step 3 - The second query

    $args = array(
        'post__not_in'      => $pinned_post_arr, // required -exclude already pinned posts
        'post_type'         => $post_type,
        'posts_per_page'    => $post_per_page, // required
        'post_status'       => 'publish',
        'orderby'           => $orderby,
        'order'             =>  $order,
        'offset'            => $offset, // required
        'paged'             => $paged, // required
        'tax_query'         => $tax_query, 
    );
    
    
    $wp_query = new \WP_Query($args);
    
    
    // I used a library called SimplePagination
    // https://github.com/flaviusmatis/simplePagination.js
    // This required me to send back the number of items.
    // I can query found posts as an int but I MUST add the count from pinned posts too
    
    $query_response['numOfItems'] = $wp_query->found_posts + count($pinned_post_arr);
    
    $query_response['postsPerPage'] = 10;
    
    
    // Get all found posts (not including pinned) and add it to the data query
    while ($wp_query->have_posts()) {
    
        $wp_query->the_post();
        $Posts = new Posts;
    
    // Here I'm appending more to the AJAX response. Which will follow seamlessly from the pinned posts (if any)
        $query_response['data'] .= $Posts->render_posts();
    
    }
    wp_reset_postdata(); // reset for safe measures
    

    I hope this helps you as it did me..