“Sticky” posts for each category (archive.php)

I need to have the ability to have Sticky posts for each category. The simplest way to do this appeared to be to just create two loops on the page. This is what I wrote:

<?php
//Custom "sticky" loop
$sticky_posts = new WP_Query(array(
    'post__in' => get_option('sticky_posts')
));

if ($sticky_posts->have_posts()) :
    while ($sticky_posts->have_posts()) : $sticky_posts->the_post();

        get_template_part('post-formats/content', 'sticky');

    endwhile; endif;

// CLEAR DATA
wp_reset_postdata();

if (have_posts()) : 
// Normal loop

while (have_posts()) : the_post(); 

    $format = get_post_format();
    if (false === $format) {
        $format="standard";
    }
    get_template_part('post-formats/content', $format);

endwhile; 

Unfortunately it doesn’t work as expected:

  1. This places stickied items at the top of ALL categories, whether they belong to that category or not.
  2. Most of strangely of all: If there’s no stickied posts at, all posts are displayed by the “sticky” loop — and then repeated below by the normal loop. Weird!

What have I done wrong? I realise that stickied posts will appear twice (once at the top, and again in their normal postition) but other than that, what’s causing these issues? :-/

3 Answers
3

To make this more complete, here is what I have said in comments in reply to the question on hand

Just to quickly explain, WP_Query fails catastrophically in some cases where empty arrays are passed to some of its parameters, instead of also returning an empty array as we should expect, WP_Query returns all posts. As for getting the correct stickies, as I said before, you need to get the current category id and use that to filter the sticky posts. Remember, with your approach, you need remove sticky posts from the main query otherwise you will get duplicates

As an alternative solution using hooks and filters on the main query, and working from a similar question/answer, this is what I have come up with: (Code is well commented so it can be followed. CAVEAT: This is untested though, and needs at least PHP 5.4+)

function get_term_sticky_posts()
{
    // First check if we are on a category page, if not, return false
    if ( !is_category() )
        return false;

    // Secondly, check if we have stickies, return false on failure
    $stickies = get_option( 'sticky_posts' );

    if ( !$stickies )
        return false;

    // OK, we have stickies and we are on a category page, continue to execute. Get current object (category) ID
    $current_object = get_queried_object_id();

    // Create the query to get category specific stickies, just get post ID's though
    $args = [
        'nopaging' => true,
        'post__in' => $stickies,
        'cat' => $current_object,
        'ignore_sticky_posts' => 1,
        'fields' => 'ids'
    ];
    $q = get_posts( $args );

    return $q;
}

add_action( 'pre_get_posts', function ( $q )
{
    if (    !is_admin() // IMPORTANT, make sure to target front end only
         && $q->is_main_query() // IMPORTANT, make sure we only target the main query
         && $q->is_category() // Only target category archives
    ) {
        // Check if our function to get term related stickies exists to avoid fatal errors
        if ( function_exists( 'get_term_sticky_posts' ) ) {
            // check if we have stickies
            $stickies = get_term_sticky_posts();

            if ( $stickies ) {
                // Remove stickies from the main query to avoid duplicates
                $q->set( 'post__not_in', $stickies );

                // Check that we add stickies on the first page only, remove this check if you need stickies on all paged pages
                if ( !$q->is_paged() ) {

                    // Add stickies via the the_posts filter
                    add_filter( 'the_posts', function ( $posts ) use ( $stickies )
                    {   
                        $term_stickies = get_posts( ['post__in' => $stickies, 'nopaging' => true] );

                        $posts = array_merge( $term_stickies, $posts );

                        return $posts;
                    }, 10, 1 );
                }
            }
        }
    }
});

FEW NOTES:

  • This only works with default category taxonomy. The code can be easily modified (please do that, modify to your needs) to use any taxonomy and its relevant terms

  • You just add this to functions.php. No need to alter your template files or using custom queries. All you need is the main query with default loop

EDIT

The above code is now tested and working on WordPress 4.2.1 and PHP 5.4+

Leave a Comment