I think the cleanest method here is just run a custom query, get the ID and then remove that post from the main query with post__not_in
. You can additionally run a query inside a function and then call the function in your featured section and in pre_get_posts
. There is another way, using a global to pass the ID from your custom query to your main query, but you should never go this route. You must avoid creating globals at all cost, they are evil
Just a note on globals and variables, avoid using $posts
, $post
and $wp_query
as variables and assinging custom values to it. These three are globals variables used by Wordpress whichholds the main query object and post objects from the main query. Assigning any other custom value to it breaks these globals. The only time you should use any of these globals is when you use get_posts()
with setup_postdata()
. setup_postdata()
only works with the $post
global, any other global name will not work. That is why you should use wp_reset_postdata()
after your foreach
loop when you used setup_postdata()
to reset the $post
global to the last post in the main query
Example:
$q = get_posts( 'posts_per_page=1' );
if ( $q ) {
foreach ( $q as $post ) {
setup_postdata( $post );
// Run loop as normal with template tags
}
wp_reset_postdata();
}
Now, lets look at the real problem at hand. What we will do here is create a function which we can use in our featured section and pre_get_posts
.
(The following code is untested and requires PHP 5.4+. Feel free to modify as needed)
function get_featured_custom_post( $main_query = false )
{
/*
* For the purpose of performance, I have created a variable called $main_query with a value of false
* When we run this function in pre_get_posts, we just need the post id, nothing more. For this, our argumaents will change
* The default is meant to return the complete post object, that is for the featured section
*/
if ( $main_query == true ) {
$fields = ['fields' => 'ids'];
} else {
$fields = ['fields' => 'all'];
}
// Build our query arguments
$defaults = [
'numberposts' => 1,
'post_type' => 'post',
'meta_key' => 'featured_post',
'meta_value' => true
];
$args = wp_parse_args( $defaults, $fields );
$q = get_posts( $args );
return $q;
}
The value from our function get_featured_custom_post
will either hold an array with the featured post id or the complete post object. Now, lets put that to use
IN pre_get_posts
add_action( 'pre_get_posts', function ( $q )
{
if ( $q->is_home() // Targets the home page only
&& $q->is_main_query() // Targts the main query only
) {
// Get the post to exclude, remeber, we need id only, so set $main_query to true
$exclude = get_featured_custom_post( true );
if ( $exclude ) // Only set post__not_in if we have a post to exclude, otherwise the result might be unexpected
$q->set( 'post__not_in', (array) $exclude );
}
});
FEATURED SECTION
In our featured section we can use the function as follow
$q = get_featured_custom_post(); // We need the complete post object, so leave $main_query at default which is false
if ( $q ) {
foreach ( $q as $post ) {
setup_postdata( $post );
// Run loop as normal with template tags
}
wp_reset_postdata();
}