diff --git a/inc/namespace.php b/inc/namespace.php index f327cb50..d4c3a214 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -665,26 +665,38 @@ function action_pre_get_posts( WP_Query $query ) : void { * @param WP_Post[]|null $posts Array of post objects. Passed by reference. * @param WP_Query $query The WP_Query instance. */ - add_filter( 'posts_pre_query', function( ?array $posts, WP_Query $query ) use ( &$stored_values, $user_ids ) : ?array { + $restore_query_vars = static function( ?array $posts, WP_Query $filtered_query ) use ( &$restore_query_vars, &$stored_values, $user_ids, $query ) : ?array { + if ( $filtered_query !== $query ) { + return $posts; + } + + remove_filter( 'posts_pre_query', $restore_query_vars, 999 ); + if ( empty( $stored_values ) ) { return $posts; } // Reset the query vars to their original values. foreach ( $stored_values as $concern => $value ) { - $query->set( $concern, $value ); + $filtered_query->set( $concern, $value ); } // Specifically set `author` when `author_name` is in use as WP_Query also sets `author` internally. - if ( ! empty( $stored_values['author_name'] ) ) { - $query->set( 'author', $user_ids[0] ); + if ( + array_key_exists( 'author_name', $stored_values ) + && is_string( $stored_values['author_name'] ) + && '' !== $stored_values['author_name'] + ) { + $filtered_query->set( 'author', $user_ids[0] ); } // Clear the recorded values so subsequent queries are not affected. $stored_values = []; return $posts; - }, 999, 2 ); + }; + + add_filter( 'posts_pre_query', $restore_query_vars, 999, 2 ); } /** diff --git a/tests/phpunit/test-wp-query.php b/tests/phpunit/test-wp-query.php index 63b33d34..7f1f1089 100644 --- a/tests/phpunit/test-wp-query.php +++ b/tests/phpunit/test-wp-query.php @@ -336,4 +336,51 @@ public function testSubsequentQueriesAreUnaffected() : void { $query2->get( 'author' ) ); } + + public function testAuthorFilteredQueriesDoNotAccumulatePostsPreQueryCallbacks() : void { + $factory = self::factory()->post; + + $factory->create_and_get( [ + POSTS_PARAM => [ + self::$users['editor']->ID, + ], + ] ); + + $before = $this->countHookCallbacks( 'posts_pre_query' ); + + for ( $i = 0; $i < 3; $i++ ) { + $query = new WP_Query(); + $query->query( [ + 'post_type' => 'post', + 'author' => self::$users['editor']->ID, + 'fields' => 'ids', + ] ); + } + + $after = $this->countHookCallbacks( 'posts_pre_query' ); + + $this->assertSame( $before, $after ); + } + + /** + * Count total callbacks registered for a hook across all priorities. + * + * @param string $hook Hook name. + * @return int + */ + private function countHookCallbacks( string $hook ) : int { + global $wp_filter; + + if ( ! isset( $wp_filter[ $hook ] ) || ! $wp_filter[ $hook ] instanceof \WP_Hook ) { + return 0; + } + + $count = 0; + + foreach ( $wp_filter[ $hook ]->callbacks as $callbacks ) { + $count += count( $callbacks ); + } + + return $count; + } }