0

I have a partial that I call around the website at various points. This partial simply displays the latest Post.

It looks like so:

<?php
$args = array(
    'posts_per_page'    => 1,
    'order'             => 'desc'
);
query_posts($args);

if ( have_posts() ) :
    while ( have_posts() ) : the_post();
        if (has_post_thumbnail( $post->ID ) ){
            $featured_image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'home-news-thumbnail' );
        }
        ?>
        <a href="<?php echo get_the_permalink(); ?>"><h3><?php echo get_the_title(); ?></h3></a>
        <?php if(!empty($featured_image)): ?>
        <a href="<?php echo get_the_permalink(); ?>">
            <img src="<?php echo $featured_image[0]; ?>" alt="" width="250" class="pull-left">
        </a>
        <?php endif; ?>
        <p>
            <?php echo get_the_excerpt(); ?>
        </p>
        <a href="<?php echo get_the_permalink(); ?>" class="btn btn-brand-dark">more</a>
        <div class="clearfix"></div>
<?php
    endwhile;
endif;

wp_reset_postdata();
?>

This works fine until I 'Sticky' a post. Then I get 2 posts instead of 1.

How can I amend all queries so that posts_per_page has the requested number of posts, regardless of sticky posts?

So in the example above, I'm currently getting 2 posts (despite requesting 1), but I want the latest post, whether that's a sticky post or not.

I know about ignore_sticky_posts parameter, but that will ignore the sticky post, which I don't want to do. If there's a sticky post it should be first.

4 Answers 4

4

Here's a way to force the exact posts_per_page value in WP_Query, regardless of sticky posts or custom post injects:

$args = [
    'posts_per_page'        => 1,
    '_exact_posts_per_page' => true   // <-- our custom input argument
];

by using our custom _exact_posts_per_page input argument.

I'm sure this has been implemented many times before, but I didn't find it at the moment, so let us try to implement it with this demo plugin:

<?php
/**
 *  Plugin Name:   Exact Posts Per Pages 
 *  Description:   Activated through the '_exact_posts_per_page' bool argument of WP_Query 
 *  Plugin URI:    http://wordpress.stackexchange.com/a/257523/26350 
 */

add_filter( 'the_posts', function( $posts, \WP_Query $q )
{
    if( 
        wp_validate_boolean( $q->get( '_exact_posts_per_page' ) )
        && ! empty( $posts ) 
        && is_int( $q->get( 'posts_per_page' ) ) 
    )
        $posts = array_slice( $posts, 0, absint( $q->get( 'posts_per_page' ) ) );

    return $posts;

}, 999, 2 ); // <-- some late priority here!

Here we use the the_posts filter to slice the array, no matter if it contains sticky posts or not.

The the_posts filter is not applied if the suppress_filters input argument of WP_Query is true.

Note that query_posts() is not recommended in plugins or themes. Check out the many warnings in the Code reference here.

Hope you can test it further and adjust to your needs!

2
  • Awesome, thank you. I find it strange this isn't default behaviour
    – mikemike
    Commented Feb 22, 2017 at 15:45
  • In the core code it can be seen pagination is not honored for sticky posts, they go to the top regardless. And well that is the idea behind the status 'sticky'
    – David Lee
    Commented Feb 22, 2017 at 16:51
1

If you want your query to ignore if a post is sticky and dont have it at the top of your ordered query:

//$args = array(
//    'posts_per_page' => 1,
//    'order'          => 'desc'
//    'ignore_sticky_posts' => 1 //set this
//);

with this the sticky posts will not be ignored, what will be ignored is their status of sticky, the sticky posts will still be in your result, what 'ignore_sticky_posts' does is tell the query to not bring the posts that are sticky to the top if you are ordering them.

EDIT:

If you want just 1, remove the While loop logic:

<?php
$args = array(
    'posts_per_page'    => 1,
    'order'             => 'desc'
);
query_posts($args);

if ( have_posts() ) :

        if (has_post_thumbnail( $post->ID ) ){
            $featured_image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'home-news-thumbnail' );
        }
        ?>
        <a href="<?php echo get_the_permalink(); ?>"><h3><?php echo get_the_title(); ?></h3></a>
        <?php if(!empty($featured_image)): ?>
        <a href="<?php echo get_the_permalink(); ?>">
            <img src="<?php echo $featured_image[0]; ?>" alt="" width="250" class="pull-left">
        </a>
        <?php endif; ?>
        <p>
            <?php echo get_the_excerpt(); ?>
        </p>
        <a href="<?php echo get_the_permalink(); ?>" class="btn btn-brand-dark">more</a>
        <div class="clearfix"></div>
<?php

endif;

wp_reset_postdata();
?>

you can even remove the 'posts_per_page' => 1 the output will be the first one.

8
  • I don't want to ignore it at all. I want the sticky post. I just don't want it along with another post. I'm asking for 1 post currently and getting 2.
    – mikemike
    Commented Feb 21, 2017 at 18:59
  • @mikemike: his answer is correct, the "ignore" in 'ignore_sticky_posts' simply means "ignore the special processing of sticky posts" (i.e., don't automatically move stickies to the front), see WP_Query, Pagination Parameters Commented Feb 21, 2017 at 19:07
  • But I want it at the front...
    – mikemike
    Commented Feb 21, 2017 at 19:31
  • @Paul'SparrowHawk'Biron I've updated the question to be more clear. I just want the latest post, whether that is sticky or not. Limiting number of posts to 1 currently gives me 2 posts, which is obviously unexpected, and setting ignore_sticky_posts will hide the sticky one, but I want the sticky posts, just not more than I'm asking for
    – mikemike
    Commented Feb 21, 2017 at 19:40
  • oh i got it now
    – David Lee
    Commented Feb 21, 2017 at 20:00
0

Try using if(! is_sticky())

<?php
$args = array(
    'posts_per_page'    => 1,
    'order'             => 'desc'
);
query_posts($args);

if ( have_posts() ) :
    while ( have_posts() ) : the_post();
    if(! is_sticky()) {
        if (has_post_thumbnail( $post->ID ) ){
            $featured_image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'home-news-thumbnail' );
        }
        ?>
        <a href="<?php echo get_the_permalink(); ?>"><h3><?php echo get_the_title(); ?></h3></a>
        <?php if(!empty($featured_image)): ?>
        <a href="<?php echo get_the_permalink(); ?>">
            <img src="<?php echo $featured_image[0]; ?>" alt="" width="250" class="pull-left">
        </a>
        <?php endif; ?>
        <p>
            <?php echo get_the_excerpt(); ?>
        </p>
        <a href="<?php echo get_the_permalink(); ?>" class="btn btn-brand-dark">more</a>
        <div class="clearfix"></div>
<?php } 
    endwhile;
endif;

wp_reset_postdata();
?>

Please note, this will completely prevent any sticky posts from appearing.

1
  • I want sticky posts, but just 1 in this case. I also wanted a global method (using a filter), this is just an illustration
    – mikemike
    Commented Feb 21, 2017 at 20:12
0

I just noticed your comment that you wanted to accomplish this via a filter, so here you go (this basically expounds on the comments I've made on your original question and @DavidLee's answer).

The following will result in 1 and only 1 post, regardless of stickies. If the most recently published post is sticky, you will get it. If the most recently published post is not sticky, you will still get it. And, you will get no other posts, sticky or otherwise.

add_filter ('pre_get_posts', 'get_only_one_post') ;

function
get_only_one_post ($wp_query)
{
    if (is_admin () || !$wp_query->is_main_query ()) {
        // we're not in the Main Loop, so return the query unmodified
        return ($wp_query) ;
        }

    // order by most recently published 1st
    // this isn't really necesary (since it is the default),
    // but include it just in case some other filter has modified that default
    $wp_query->set ('orderby', 'date') ;
    $wp_query->set ('order', 'DESC') ;

    // limit to 1 post, not counting stickies
    $wp_query->set ('posts_per_page', 1) ;

    // disable the special processing of sticky posts.
    // without this, ALL sticky posts will be returned, whether they match
    // the rest of the query_vars or not
    $wp_query->set ('ignore_sticky_posts', true) ;

    return ($wp_query) ;
}

Explanation

Without 'ignore_sticky_posts' => true

The following is done:

  1. retrieve posts according to query vars, including respecting posts_per_page and orderby
    • any sticky posts that match query vars will already be included in the results
  2. move sticky posts that are included in the results to the front of the list
  3. retreive all sticky posts
    1. those not already in the results are preprend to the list

With 'ignore_sticky_posts' => true

The following is done:

  1. retrieve posts according to query vars, including respecting posts_per_page and orderby
    • any sticky posts that match query vars will already be included in the results

Edit: Additional answer

OK, try this:

add_filter ('pre_get_posts', 'set_posts_per_page_on_front_end_main_query') ;
add_filter ('the_posts', 'respect_posts_per_page') ;

function
respect_posts_per_page ($posts, $wp_query)
{
    return (array_slice ($posts, 0, $wp_query->get ('posts_per_page', count ($posts)))) ;
}

function
set_posts_per_page_on_front_end_main_query ($wp_query)
{
    if ( is_admin () || !$wp_query->is_main_query ()) {
        return ($wp_query) ;
        }

    $wp_query->set ('posts_per_page', 1) ;

    return ($wp_query) ;
}
1
  • I don't want to remove sticky posts. I just want the number I'm asking for returned. I'm asking for 1 post, so I want the latest post, or if the are sticky posts the latest sticky post.
    – mikemike
    Commented Feb 22, 2017 at 8:13

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.