diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index cf07b07d977c3..c8fdf4a6b1769 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -1054,7 +1054,22 @@ public function parse_query( $query = '' ) { unset( $_query['embed'] ); - if ( empty( $_query ) || ! array_diff( array_keys( $_query ), array( 'preview', 'page', 'paged', 'cpage' ) ) ) { + $_query_keys = array_keys( $_query ); + if ( isset( $GLOBALS['wp'], $GLOBALS['wp_the_query'] ) + && $GLOBALS['wp_the_query'] === $this ) { + $_routing_query_vars = array(); + + if ( ! empty( $GLOBALS['wp']->matched_query ) ) { + parse_str( $GLOBALS['wp']->matched_query, $_routing_query_vars ); + } + + $_query_keys = array_intersect( + $_query_keys, + array_merge( WP::CORE_PUBLIC_QUERY_VARS, array_keys( $_routing_query_vars ) ) + ); + } + + if ( empty( $_query_keys ) || ! array_diff( $_query_keys, array( 'preview', 'page', 'paged', 'cpage' ) ) ) { $this->is_page = true; $this->is_home = false; $query_vars['page_id'] = get_option( 'page_on_front' ); diff --git a/src/wp-includes/class-wp.php b/src/wp-includes/class-wp.php index f1664747d4042..b6e16e64a11c1 100644 --- a/src/wp-includes/class-wp.php +++ b/src/wp-includes/class-wp.php @@ -7,6 +7,17 @@ */ #[AllowDynamicProperties] class WP { + /** + * Core public query variables. + * + * Long list of public query variables before plugins and themes can extend + * them. + * + * @since x.x.x + * @var string[] + */ + const CORE_PUBLIC_QUERY_VARS = array( 'm', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'pagename', 'page_id', 'error', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'favicon', 'taxonomy', 'term', 'cpage', 'post_type', 'embed' ); + /** * Public query variables. * @@ -15,7 +26,7 @@ class WP { * @since 2.0.0 * @var string[] */ - public $public_query_vars = array( 'm', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'pagename', 'page_id', 'error', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'favicon', 'taxonomy', 'term', 'cpage', 'post_type', 'embed' ); + public $public_query_vars = self::CORE_PUBLIC_QUERY_VARS; /** * Private query variables. diff --git a/tests/phpunit/tests/query/conditionals.php b/tests/phpunit/tests/query/conditionals.php index 4b473178897ae..6f950b561e01a 100644 --- a/tests/phpunit/tests/query/conditionals.php +++ b/tests/phpunit/tests/query/conditionals.php @@ -72,6 +72,148 @@ public function test_page_on_front() { delete_option( 'page_for_posts' ); } + /** + * A custom query var registered via the 'query_vars' filter must not prevent + * the static front page from loading. + * + * @ticket 40521 + */ + public function test_custom_query_var_via_filter_does_not_break_static_front_page() { + $page_on_front = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'show_on_front', 'page' ); + update_option( 'page_on_front', $page_on_front ); + + $filter = static function ( $vars ) { + $vars[] = 'custom_test_var'; + return $vars; + }; + add_filter( 'query_vars', $filter ); + + try { + $this->go_to( '/?custom_test_var=yep' ); + + $this->assertQueryTrue( 'is_front_page', 'is_page', 'is_singular' ); + } finally { + remove_filter( 'query_vars', $filter ); + update_option( 'show_on_front', 'posts' ); + delete_option( 'page_on_front' ); + } + } + + /** + * A custom query var registered via WP::add_query_var() must not prevent + * the static front page from loading. + * + * @ticket 40521 + */ + public function test_custom_query_var_via_add_query_var_does_not_break_static_front_page() { + global $wp; + + $page_on_front = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'show_on_front', 'page' ); + update_option( 'page_on_front', $page_on_front ); + + // Register the var the way a plugin would outside the query_vars filter. + $wp->add_query_var( 'custom_test_var' ); + + try { + $this->go_to( '/?custom_test_var=yep' ); + + $this->assertQueryTrue( 'is_front_page', 'is_page', 'is_singular' ); + } finally { + $wp->public_query_vars = array_diff( $wp->public_query_vars, array( 'custom_test_var' ) ); + update_option( 'show_on_front', 'posts' ); + delete_option( 'page_on_front' ); + } + } + + /** + * A secondary WP_Query constructed with only a custom var must not be + * coerced into querying page_on_front. + * + * @ticket 40521 + */ + public function test_secondary_query_with_custom_var_not_coerced_to_front_page() { + $page_on_front = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'show_on_front', 'page' ); + update_option( 'page_on_front', $page_on_front ); + + $filter = static function ( $vars ) { + $vars[] = 'custom_test_var'; + return $vars; + }; + add_filter( 'query_vars', $filter ); + + try { + // Establish a proper main-request context. + $this->go_to( '/' ); + $secondary = new WP_Query( array( 'custom_test_var' => 'yep' ) ); + + $this->assertFalse( $secondary->is_page(), 'Secondary query must not be flagged as is_page().' ); + $this->assertFalse( $secondary->is_front_page(), 'Secondary query must not be flagged as is_front_page().' ); + $this->assertNotEquals( $page_on_front, $secondary->get_queried_object_id(), 'Secondary query must not load page_on_front.' ); + } finally { + remove_filter( 'query_vars', $filter ); + update_option( 'show_on_front', 'posts' ); + delete_option( 'page_on_front' ); + } + } + + /** + * A custom query var from a matched rewrite rule must still prevent the + * static front page correction. + * + * @ticket 40521 + */ + public function test_custom_query_var_from_rewrite_rule_prevents_static_front_page_correction() { + $page_on_front = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'show_on_front', 'page' ); + update_option( 'page_on_front', $page_on_front ); + + $filter = static function ( $vars ) { + $vars[] = 'custom_route'; + return $vars; + }; + add_filter( 'query_vars', $filter ); + add_rewrite_rule( '^custom-route/([^/]+)/?$', 'index.php?custom_route=$matches[1]', 'top' ); + flush_rewrite_rules(); + + try { + $this->go_to( '/custom-route/matched/' ); + + $this->assertSame( 'matched', get_query_var( 'custom_route' ) ); + $this->assertFalse( is_page(), 'Matched custom rewrite route must not be flagged as is_page().' ); + $this->assertFalse( is_front_page(), 'Matched custom rewrite route must not be flagged as is_front_page().' ); + $this->assertNotEquals( $page_on_front, get_queried_object_id(), 'Matched custom rewrite route must not load page_on_front.' ); + } finally { + remove_filter( 'query_vars', $filter ); + update_option( 'show_on_front', 'posts' ); + delete_option( 'page_on_front' ); + flush_rewrite_rules(); + } + } + + /** + * Core routing vars such as 's' must still prevent the static front page + * correction so that a search result is not replaced by the front page. + * + * @ticket 40521 + */ + public function test_core_routing_var_prevents_static_front_page_correction() { + $page_on_front = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'show_on_front', 'page' ); + update_option( 'page_on_front', $page_on_front ); + + try { + $this->go_to( '/?s=test' ); + + $this->assertQueryTrue( 'is_search' ); + } finally { + update_option( 'show_on_front', 'posts' ); + delete_option( 'page_on_front' ); + } + } + public function test_404() { $this->go_to( '/notapage' ); $this->assertQueryTrue( 'is_404' );