How can I allow sticky posts but cap the query to 1 post?
-
24-01-2021 - |
Domanda
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.
Soluzione
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!
Altri suggerimenti
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.
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.
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:
- retrieve posts according to query vars, including respecting
posts_per_page
andorderby
- any sticky posts that match query vars will already be included in the results
- move sticky posts that are included in the results to the front of the list
- retreive all sticky posts
- those not already in the results are preprend to the list
- it is this step that potentially causes the results to contain more than
posts_per_page
posts being returned- See Lines 2966-2979, of the source for WP_Query
With 'ignore_sticky_posts' => true
The following is done:
- retrieve posts according to query vars, including respecting
posts_per_page
andorderby
- 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) ;
}