Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 110 additions & 6 deletions src/wp-includes/sitemaps/class-wp-sitemaps.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function init() {
// These will all fire on the init hook.
$this->register_rewrites();

add_action( 'template_redirect', array( $this, 'render_sitemaps' ) );
add_action( 'parse_request', array( $this, 'render_sitemaps' ) );

if ( ! $this->sitemaps_enabled() ) {
return;
Expand Down Expand Up @@ -159,14 +159,33 @@ public function register_rewrites() {
* @since 5.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param WP|null $wp Optional. Current WordPress environment instance.
*/
public function render_sitemaps() {
public function render_sitemaps( $wp = null ) {
global $wp_query;

$sitemap = sanitize_text_field( get_query_var( 'sitemap' ) );
$object_subtype = sanitize_text_field( get_query_var( 'sitemap-subtype' ) );
$stylesheet_type = sanitize_text_field( get_query_var( 'sitemap-stylesheet' ) );
$paged = absint( get_query_var( 'paged' ) );
$query_vars = $wp instanceof WP ? $wp->query_vars : $wp_query->query_vars;

$sitemap = '';
if ( ! empty( $query_vars['sitemap'] ) && is_scalar( $query_vars['sitemap'] ) ) {
$sitemap = sanitize_text_field( $query_vars['sitemap'] );
}

$object_subtype = '';
if ( ! empty( $query_vars['sitemap-subtype'] ) && is_scalar( $query_vars['sitemap-subtype'] ) ) {
$object_subtype = sanitize_text_field( $query_vars['sitemap-subtype'] );
}

$stylesheet_type = '';
if ( ! empty( $query_vars['sitemap-stylesheet'] ) && is_scalar( $query_vars['sitemap-stylesheet'] ) ) {
$stylesheet_type = sanitize_text_field( $query_vars['sitemap-stylesheet'] );
}

$paged = 0;
if ( ! empty( $query_vars['paged'] ) && is_scalar( $query_vars['paged'] ) ) {
$paged = absint( $query_vars['paged'] );
}

// Bail early if this isn't a sitemap or stylesheet route.
if ( ! ( $sitemap || $stylesheet_type ) ) {
Expand All @@ -176,9 +195,18 @@ public function render_sitemaps() {
if ( ! $this->sitemaps_enabled() ) {
$wp_query->set_404();
status_header( 404 );

if ( $wp instanceof WP ) {
exit;
}

return;
}

if ( $wp instanceof WP ) {
$this->redirect_sitemap_to_canonical_url( $sitemap, $object_subtype, $stylesheet_type, $paged );
}

// Render stylesheet if this is stylesheet route.
if ( $stylesheet_type ) {
$stylesheet = new WP_Sitemaps_Stylesheet();
Expand All @@ -198,6 +226,13 @@ public function render_sitemaps() {
$provider = $this->registry->get_provider( $sitemap );

if ( ! $provider ) {
$wp_query->set_404();
status_header( 404 );

if ( $wp instanceof WP ) {
exit;
}

return;
}

Expand All @@ -211,13 +246,82 @@ public function render_sitemaps() {
if ( empty( $url_list ) ) {
$wp_query->set_404();
status_header( 404 );

if ( $wp instanceof WP ) {
exit;
}

return;
}

$this->renderer->render_sitemap( $url_list );
exit;
}

/**
* Redirects sitemap requests to their canonical URL.
*
* @since x.x.x
*
* @param string $sitemap Sitemap name.
* @param string $object_subtype Sitemap object subtype.
* @param string $stylesheet_type Sitemap stylesheet type.
* @param int $paged Sitemap page number.
*/
private function redirect_sitemap_to_canonical_url( $sitemap, $object_subtype, $stylesheet_type, $paged ) {
global $wp_rewrite;

if (
! $wp_rewrite->using_permalinks()
|| empty( $_SERVER['HTTP_HOST'] )
|| ! is_scalar( $_SERVER['HTTP_HOST'] )
|| empty( $_SERVER['REQUEST_URI'] )
|| ! is_scalar( $_SERVER['REQUEST_URI'] )
) {
return;
}

$redirect_url = '';

if ( $sitemap ) {
$redirect_url = get_sitemap_url( $sitemap, $object_subtype, $paged );
} elseif ( 'sitemap' === $stylesheet_type ) {
$redirect_url = home_url( '/wp-sitemap.xsl' );
} elseif ( 'index' === $stylesheet_type ) {
$redirect_url = home_url( '/wp-sitemap-index.xsl' );
}

if ( ! $redirect_url ) {
return;
}

$request_uri = (string) wp_unslash( $_SERVER['REQUEST_URI'] );
$query_string = wp_parse_url( $request_uri, PHP_URL_QUERY );

if ( is_string( $query_string ) && '' !== $query_string ) {
wp_parse_str( $query_string, $query_args );
$redirect_url = add_query_arg(
array_diff_key(
$query_args,
array_flip( array( 'sitemap', 'sitemap-subtype', 'sitemap-stylesheet', 'paged' ) )
),
$redirect_url
);
}

$requested_url = is_ssl() ? 'https://' : 'http://';
$requested_url .= (string) wp_unslash( $_SERVER['HTTP_HOST'] );
$requested_url .= $request_uri;

if ( $requested_url === $redirect_url ) {
return;
}

if ( wp_redirect( $redirect_url, 301 ) ) {
exit;
}
}

/**
* Redirects a URL to the wp-sitemap.xml
*
Expand Down
44 changes: 42 additions & 2 deletions tests/phpunit/tests/canonical/sitemaps.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,48 @@ class Tests_Canonical_Sitemaps extends WP_Canonical_UnitTestCase {

public function set_up() {
parent::set_up();
$wp_sitemaps = new WP_Sitemaps();
$wp_sitemaps->init();
wp_sitemaps_get_server();
$this->remove_sitemap_parse_request_callbacks();
}

public function tear_down() {
add_action( 'parse_request', array( wp_sitemaps_get_server(), 'render_sitemaps' ) );

parent::tear_down();
}

/**
* Assert that a given URL is the same as the canonical URL generated by WP.
*
* @param string $test_url Raw URL that will be run through redirect_canonical().
* @param string $expected Expected string.
* @param int $ticket Optional. Trac ticket number.
* @param array $expected_doing_it_wrong Array of class/function names expected to throw _doing_it_wrong() notices.
*/
public function assertCanonical( $test_url, $expected, $ticket = 0, $expected_doing_it_wrong = array() ) {
$this->remove_sitemap_parse_request_callbacks();
parent::assertCanonical( $test_url, $expected, $ticket, $expected_doing_it_wrong );
}

/**
* Removes sitemap rendering callbacks so canonical tests can inspect redirects.
*/
private function remove_sitemap_parse_request_callbacks() {
global $wp_filter;

if ( empty( $wp_filter['parse_request']->callbacks ) ) {
return;
}

foreach ( $wp_filter['parse_request']->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
$function = $callback['function'];

if ( is_array( $function ) && $function[0] instanceof WP_Sitemaps && 'render_sitemaps' === $function[1] ) {
remove_action( 'parse_request', $function, $priority );
}
}
}
}

public function test_remove_trailing_slashes_for_sitemap_index_requests() {
Expand Down
Loading
Loading