We could try to use the the_posts
filter, that’s applied on the queried posts, if the suppress_filters
query variable is false, to collect relevant post IDs.
We can then use the pre_get_posts
hook, in each query, to exclude the accumulated array of post IDs.
Here’s a schema example for the process:
// Main query
$wp_query = new WP_Query( $args );
$wp_query->posts filtered and collected post IDs [1,2] from the 'post' post type
// Sub query #1
$q1 = new WP_Query( [ 'wpse_exclude' => true, ... ] );
pre_get_posts excludes accumulated [1,2]
$q1->posts filtered and collected [3,4]
// Sub query #2
$q2 = new WP_Query( [ 'wpse_exclude' => true, ... ] );
pre_get_posts excludes accumulated [1,2,3,4]
$q2->posts filtered and collected [5,6]
// Sub query #3
$q3 = new WP_Query( $args );
No exclusion because 'wpse_exclude' argument is missing
$q3->posts filtered and collected [7]
// Sub query #4
$q4 = new WP_Query( [ 'wpse_exclude' => true, ... ] );
pre_get_posts excludes accumulated [1,2,3,4,5,6,7]
$q4->posts filtered and collected [8,9]
Here are possible building blocks for a demo class that could help with that:
The init()
method:
public function init( $collect_post_type="post" )
{
// Collect post IDs
add_filter( 'the_posts', [ $this, 'the_posts' ] );
// Exclude accumulated post IDs in queries
add_action( 'pre_get_posts', [ $this, 'pre_get_posts' ] );
// Initialize accumulated post IDs for exclusion
$this->exclude_pids = [];
// Collect post IDs only from this post type
$this->collect_post_type = $collect_post_type;
}
The pre_get_posts()
method:
public function pre_get_posts( \WP_Query $q )
{
if(
$q->is_home() // Target the home page
&& $q->get( 'wpse_exclude' ) // Target queries with wpse_exclude set as true
// && ... etc
) {
// Exclude accumulated set of post IDs
if( ! empty( $this->exclude_pids ) )
$q->set( 'post__not_in', $this->exclude_pids );
}
}
Here we should also consider preserving any previous post__not_in
arguments.
The the_posts()
method:
public function the_posts( array $posts )
{
// Collect post IDs from 'post' post type and merge to the $pids array
$this->exclude_pids = array_merge(
$this->exclude_pids ,
(array) wp_filter_object_list(
$posts,
[ 'post_type' => $this->collect_post_type ],
'AND',
'ID'
)
);
return $posts;
}
Hope you can adjust this to your needs.