2

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? :-/

5
  • This will be your base. You need to make modifications to target the correct pages. Your challange here will be to first loop through your sticky posts according to the current term/category, get these posts and then feeding them to the_posts. filter Commented Jun 4, 2015 at 11:17
  • @PieterGoosen Thanks, not sure why my current code isn't working. Rather than just work from yours, it would be good to understand what I'm doing wrong. Thanks. Commented Jun 4, 2015 at 11:31
  • 1
    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 Commented Jun 4, 2015 at 11:39
  • @PieterGoosen Thanks, that's very helpful. It seems the main problem is WP_Query's arguments then. Commented Jun 4, 2015 at 12:06
  • My pleasure. I'm also going to post my solution as an alternative using hooks and filters just for interest sake ;-) Commented Jun 4, 2015 at 12:24

3 Answers 3

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+

10
  • Why are you using ignore_sticky_posts? Commented Jun 4, 2015 at 15:55
  • That is to ignore sticky posts. I know it makes no sense, but with post__in, the ID's supplied are used to get the posts. On top of that, all posts that are marked sticky is also returned. This is what we want to avoid, we don't want to check for stickies. For you, as a test, remove that parameter. You will notice that all stickies are returned regardless of category. :-) Commented Jun 4, 2015 at 15:59
  • As reference, check this Commented Jun 4, 2015 at 16:01
  • Hmm. As I understand it, all ignore_sticky_posts does is move stickied posts to the front the queue, or keep them in their place. Commented Jun 4, 2015 at 16:04
  • No, ignore_sticky_posts by default is set to false, which means that the query must include sticky posts in the query. When setting it to 1 or true, the query ignores/skip sticky posts and they are thus excluded Commented Jun 4, 2015 at 16:07
3

I cannot comment Pieter Goosen's answer due to reputation rules /-:

The current code has a side-effect on current wordpress 4.8 (March 2018), it shows sticky posts when accessing a non-existing page thta should give a 404 error instead. Like "www.example.com/asd" where "asd" does not exist. Same problem it "wp-admin". This is because in that case "asd" or "wp-admin" is saved in $q as category name, although that is not an existing category.

Fix: Check if the passed category really exists by adding the last line of this code into the code posted above:

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         
     && $q->is_tax(get_query_var ( 'category_name')) // Only target existing category names   
2

The answer to both issues was pretty simple.

  1. Simply add an argument to WP_Query to limit to the current category
  2. Make sure that WP_Query doesn't run unless there's actually some sticky posts

Like so:

<?php
// Get sticky posts
$sticky_ids = get_option( 'sticky_posts' );

// BEGIN Custom "sticky" loop

// If sticky posts found...
if(!empty($sticky_ids)) {
    $args = array(
        'post_type' => 'post',
        'category__in' => get_query_var('cat'), // Get current category only
        'post__in' => get_option('sticky_posts') // Get stickied posts
    );

    $sticky_posts = new WP_Query($args);

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

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

    endwhile; endif;
// END Custom "sticky" loop

    // Reset post data ready for next loop
    wp_reset_postdata();
}

if (have_posts()) :

    /* Start the Loop */
    while (have_posts()) : the_post();

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

    endwhile; endif; ?>
4
  • 1
    NO NO NO, never ever use query_posts. It breaks the main query, plugins and custom functions relies on this. It also breaks pagination Commented Jun 4, 2015 at 14:59
  • This is only for use on archive.php, Pieter. Not sure if that makes any difference. My site appears to be working fine. Although it's somewhat hamfisted, there is never a time on my site when I want the loop -- on any page -- to get something that's stickied. Commented Jun 4, 2015 at 15:13
  • Makes a huge difference. Do var_dump( $wp_query ); before and after query_posts. You will see that you broke the main query. If this is your category page, your category functionality is broken Commented Jun 4, 2015 at 15:26
  • Recheck my code, I have made two minor adjustments (which actually failed my code miserably) and tested it. It should now work as you expect it to work Commented Jun 4, 2015 at 15:54

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.