Skip to content
23 changes: 22 additions & 1 deletion src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,13 @@ class WP_Query {
*/
public $is_favicon = false;

/**
* Signifies whether the current query is for a sitemap.
*
* @since 7.1.0
*/
public bool $is_sitemap = false;

/**
* Signifies whether the current query is for the page_for_posts page.
*
Expand Down Expand Up @@ -519,6 +526,7 @@ private function init_query_flags() {
$this->is_singular = false;
$this->is_robots = false;
$this->is_favicon = false;
$this->is_sitemap = false;
$this->is_posts_page = false;
$this->is_post_type_archive = false;
}
Expand Down Expand Up @@ -817,6 +825,8 @@ public function parse_query( $query = '' ) {
$this->is_robots = true;
} elseif ( ! empty( $query_vars['favicon'] ) ) {
$this->is_favicon = true;
} elseif ( ! empty( $query_vars['sitemap'] ) ) {
$this->is_sitemap = true;
}

if ( ! is_scalar( $query_vars['p'] ) || (int) $query_vars['p'] < 0 ) {
Expand Down Expand Up @@ -1040,7 +1050,7 @@ public function parse_query( $query = '' ) {

if ( ! ( $this->is_singular || $this->is_archive || $this->is_search || $this->is_feed
|| ( wp_is_serving_rest_request() && $this->is_main_query() )
|| $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon ) ) {
|| $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon || $this->is_sitemap ) ) {
$this->is_home = true;
}

Expand Down Expand Up @@ -4638,6 +4648,17 @@ public function is_favicon() {
return (bool) $this->is_favicon;
}

/**
* Determines whether the query is for a sitemap.
*
* @since 7.1.0
*
* @return bool Whether the query is for a sitemap.
*/
public function is_sitemap(): bool {
return $this->is_sitemap;
}

/**
* Determines whether the query is for a search.
*
Expand Down
20 changes: 20 additions & 0 deletions src/wp-includes/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,26 @@ function is_favicon() {
return $wp_query->is_favicon();
}

/**
* Is the query for a sitemap?
*
* @since 7.1.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a sitemap.
*/
function is_sitemap(): bool {
global $wp_query;

if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '7.1.0' );
return false;
}

return $wp_query->is_sitemap();
}

/**
* Determines whether the query is for a search.
*
Expand Down
1 change: 1 addition & 0 deletions tests/phpunit/includes/abstract-testcase.php
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,7 @@ public function assertQueryTrue( ...$prop ) {
'is_preview',
'is_robots',
'is_favicon',
'is_sitemap',
'is_search',
'is_single',
'is_singular',
Expand Down
213 changes: 213 additions & 0 deletions tests/phpunit/tests/query/isSitemap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php

/**
* Tests for the is_sitemap() conditional tag and the WP_Query::$is_sitemap property.
*
* This exercises both query.php and class-wp-query.php: query vars are fed through
* WP_Query, then the effects on the wp_query object are tested.
*
* @group query
* @group sitemaps
*
* @ticket 51543
*/
class Tests_Query_IsSitemap extends WP_UnitTestCase {

/**
* Post IDs created for the shared fixture.
*
* @var int[]
*/
public static $post_ids;

/**
* Set up the shared fixture.
*
* Published posts are required so that a sitemap request does not turn into a
* 404 in WP::handle_404(), which would reset the is_sitemap flag via set_404().
*
* @param WP_UnitTest_Factory $factory Factory instance.
*/
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$post_ids = $factory->post->create_many( 3 );
}

public function set_up() {
parent::set_up();

$this->set_permalink_structure( '/%year%/%monthnum%/%day%/%postname%/' );

create_initial_taxonomies();
}

/**
* The property defaults to false on a freshly initialized query.
*
* @covers WP_Query::init
*/
public function test_is_sitemap_defaults_to_false() {
$query = new WP_Query();

$this->assertFalse( $query->is_sitemap, 'The $is_sitemap property should default to false.' );
$this->assertFalse( $query->is_sitemap(), 'WP_Query::is_sitemap() should return false by default.' );
}

/**
* The flag is set when the "sitemap" query var is present (sitemap index route).
*
* @covers WP_Query::parse_query
* @covers WP_Query::is_sitemap
*/
public function test_is_sitemap_true_for_sitemap_index() {
$query = new WP_Query( array( 'sitemap' => 'index' ) );

$this->assertTrue( $query->is_sitemap, 'The $is_sitemap property should be true for a sitemap query.' );
$this->assertTrue( $query->is_sitemap(), 'WP_Query::is_sitemap() should return true for a sitemap query.' );
}

/**
* The flag is set for a sitemap subtype route (e.g. wp-sitemap-posts-post-1.xml).
*
* @covers WP_Query::parse_query
* @covers WP_Query::is_sitemap
*/
public function test_is_sitemap_true_for_sitemap_subtype() {
$query = new WP_Query(
array(
'sitemap' => 'posts',
'sitemap-subtype' => 'post',
'paged' => 1,
)
);

$this->assertTrue( $query->is_sitemap(), 'WP_Query::is_sitemap() should return true for a sitemap subtype query.' );
}

/**
* is_sitemap() must return a boolean.
*
* @covers WP_Query::is_sitemap
*/
public function test_is_sitemap_return_type_is_bool() {
$query = new WP_Query( array( 'sitemap' => 'index' ) );

$this->assertIsBool( $query->is_sitemap(), 'WP_Query::is_sitemap() should return a boolean.' );
}

/**
* An empty "sitemap" query var must not set the flag.
*
* @covers WP_Query::parse_query
*/
public function test_is_sitemap_false_for_empty_sitemap_var() {
$query = new WP_Query( array( 'sitemap' => '' ) );

$this->assertFalse( $query->is_sitemap(), 'An empty "sitemap" query var should not set is_sitemap.' );
}

/**
* The sitemap stylesheet route uses the "sitemap-stylesheet" query var, which must
* not flag the query as a sitemap.
*
* @covers WP_Query::parse_query
*/
public function test_is_sitemap_false_for_stylesheet_route() {
$query = new WP_Query( array( 'sitemap-stylesheet' => 'sitemap' ) );

$this->assertFalse( $query->is_sitemap(), 'The sitemap stylesheet route should not flag the query as a sitemap.' );
}

/**
* is_robots takes precedence over is_sitemap in the parse_query branch.
*
* @covers WP_Query::parse_query
*/
public function test_robots_takes_precedence_over_sitemap() {
$query = new WP_Query(
array(
'robots' => true,
'sitemap' => 'index',
)
);

$this->assertTrue( $query->is_robots(), 'is_robots() should be true when the robots query var is set.' );
$this->assertFalse( $query->is_sitemap(), 'is_sitemap() should be false when is_robots() takes precedence.' );
}

/**
* A regular query is never flagged as a sitemap.
*
* @covers WP_Query::is_sitemap
*/
public function test_is_sitemap_false_for_regular_query() {
$post_id = self::factory()->post->create();

$query = new WP_Query( array( 'p' => $post_id ) );

$this->assertFalse( $query->is_sitemap(), 'A regular post query should not be flagged as a sitemap.' );
}

/**
* A sitemap query must not also be treated as the home/front page.
*
* This is the practical motivation for the conditional tag: distinguishing a
* sitemap request from the home page (see #51542).
*
* @covers WP_Query::parse_query
*/
public function test_sitemap_query_is_not_home() {
$query = new WP_Query( array( 'sitemap' => 'index' ) );

$this->assertTrue( $query->is_sitemap(), 'The sitemap query should be flagged as a sitemap.' );
$this->assertFalse( $query->is_home(), 'A sitemap query should not be treated as the home page.' );
$this->assertFalse( $query->is_front_page(), 'A sitemap query should not be treated as the front page.' );
}

/**
* The global is_sitemap() conditional tag reflects the main query.
*
* @covers ::is_sitemap
*/
public function test_global_is_sitemap_reflects_main_query() {
// Prevent WP_Sitemaps from rendering and calling exit during go_to().
remove_action( 'template_redirect', array( wp_sitemaps_get_server(), 'render_sitemaps' ) );

$this->go_to( home_url( '/?sitemap=index' ) );

$this->assertTrue( is_sitemap(), 'is_sitemap() should be true on a sitemap request.' );

// is_sitemap should be the only conditional that is true for a sitemap request.
$this->assertQueryTrue( 'is_sitemap' );
}

/**
* The global is_sitemap() conditional tag is false for a non-sitemap request.
*
* @covers ::is_sitemap
*/
public function test_global_is_sitemap_false_on_home() {
$this->go_to( home_url( '/' ) );

$this->assertFalse( is_sitemap(), 'is_sitemap() should be false on the home page.' );
$this->assertTrue( is_home(), 'is_home() should be true on the home page.' );
}

/**
* The global is_sitemap() returns false and triggers a notice when the query
* has not yet run.
*
* @covers ::is_sitemap
*
* @expectedIncorrectUsage is_sitemap
*/
public function test_global_is_sitemap_before_query_is_run() {
$wp_query_temp = $GLOBALS['wp_query'];
unset( $GLOBALS['wp_query'] );

$result = is_sitemap();

$GLOBALS['wp_query'] = $wp_query_temp;

$this->assertFalse( $result, 'is_sitemap() should return false before the query is run.' );
}
}
Loading