<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[48072] trunk: Sitemaps: Add XML sitemaps functionality to WordPress.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/48072">48072</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/48072","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>swissspidy</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-06-17 15:22:49 +0000 (Wed, 17 Jun 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Sitemaps: Add XML sitemaps functionality to WordPress.

While web crawlers are able to discover pages from links within the site and from other sites, XML sitemaps supplement this approach by allowing crawlers to quickly and comprehensively identify all URLs included in the sitemap and learn other signals about those URLs using the associated metadata.

See https://make.wordpress.org/core/2020/06/10/merge-announcement-extensible-core-sitemaps/ for more details.

This feature exposes the sitemap index via `/wp-sitemap.xml` and exposes a variety of new filters and hooks for developers to modify the behavior. Users can disable sitemaps completely by turning off search engine visibility in WordPress admin.

This change also introduces a new `esc_xml()` function to escape strings for output in XML, as well as XML support to `wp_kses_normalize_entities()`.

Props Adrian McShane, afragen, adamsilverstein, casiepa, flixos90, garrett-eclipse, joemcgill, kburgoine, kraftbj, milana_cap, pacifika, pbiron, pfefferle, Ruxandra Gradina, swissspidy, szepeviktor, tangrufus, tweetythierry.
Fixes <a href="https://core.trac.wordpress.org/ticket/50117">#50117</a>.
See <a href="https://core.trac.wordpress.org/ticket/3670">#3670</a>. See <a href="https://core.trac.wordpress.org/ticket/19998">#19998</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkphpcsxmldist">trunk/phpcs.xml.dist</a></li>
<li><a href="#trunksrcwpincludescanonicalphp">trunk/src/wp-includes/canonical.php</a></li>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpincludesformattingphp">trunk/src/wp-includes/formatting.php</a></li>
<li><a href="#trunksrcwpincludesksesphp">trunk/src/wp-includes/kses.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
<li><a href="#trunktestsphpunitincludesbootstrapphp">trunk/tests/phpunit/includes/bootstrap.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>trunk/src/wp-includes/sitemaps/</li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsindexphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps-index.php</a></li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsproviderphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps-provider.php</a></li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsregistryphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps-registry.php</a></li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsrendererphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps-renderer.php</a></li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsstylesheetphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps-stylesheet.php</a></li>
<li><a href="#trunksrcwpincludessitemapsclasswpsitemapsphp">trunk/src/wp-includes/sitemaps/class-wp-sitemaps.php</a></li>
<li>trunk/src/wp-includes/sitemaps/providers/</li>
<li><a href="#trunksrcwpincludessitemapsprovidersclasswpsitemapspostsphp">trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php</a></li>
<li><a href="#trunksrcwpincludessitemapsprovidersclasswpsitemapstaxonomiesphp">trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-taxonomies.php</a></li>
<li><a href="#trunksrcwpincludessitemapsprovidersclasswpsitemapsusersphp">trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-users.php</a></li>
<li><a href="#trunksrcwpincludessitemapsphp">trunk/src/wp-includes/sitemaps.php</a></li>
<li><a href="#trunktestsphpunitincludesclasswpsitemapsemptytestproviderphp">trunk/tests/phpunit/includes/class-wp-sitemaps-empty-test-provider.php</a></li>
<li><a href="#trunktestsphpunitincludesclasswpsitemapstestproviderphp">trunk/tests/phpunit/includes/class-wp-sitemaps-test-provider.php</a></li>
<li><a href="#trunktestsphpunitincludesnormalizexmlxsl">trunk/tests/phpunit/includes/normalize-xml.xsl</a></li>
<li><a href="#trunktestsphpunitincludestestcasexmlphp">trunk/tests/phpunit/includes/testcase-xml.php</a></li>
<li><a href="#trunktestsphpunittestscanonicalsitemapsphp">trunk/tests/phpunit/tests/canonical/sitemaps.php</a></li>
<li><a href="#trunktestsphpunittestsformattingEscXmlphp">trunk/tests/phpunit/tests/formatting/EscXml.php</a></li>
<li>trunk/tests/phpunit/tests/sitemaps/</li>
<li><a href="#trunktestsphpunittestssitemapsfunctionsphp">trunk/tests/phpunit/tests/sitemaps/functions.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsindexphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-index.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapspostsphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-posts.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsregistryphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-registry.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsrendererphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-renderer.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsstylesheetphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-stylesheet.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapstaxonomiesphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-taxonomies.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsusersphp">trunk/tests/phpunit/tests/sitemaps/sitemaps-users.php</a></li>
<li><a href="#trunktestsphpunittestssitemapssitemapsphp">trunk/tests/phpunit/tests/sitemaps/sitemaps.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkphpcsxmldist"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/phpcs.xml.dist</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/phpcs.xml.dist      2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/phpcs.xml.dist        2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -247,6 +247,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                <element value="WP_Tests_Image_Resize_UnitTestCase"/>
</span><span class="cx" style="display: block; padding: 0 10px">                                <element value="WP_Import_UnitTestCase"/>
</span><span class="cx" style="display: block; padding: 0 10px">                                <element value="Tests_Query_Conditionals"/>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                <element value="WP_Test_XML_TestCase"/>
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                <!-- Mock classes. -->
</span><span class="cx" style="display: block; padding: 0 10px">                                <element value="Spy_REST_Server"/>
</span></span></pre></div>
<a id="trunksrcwpincludescanonicalphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/canonical.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/canonical.php       2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/src/wp-includes/canonical.php 2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -509,6 +509,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $redirect['path'] = trailingslashit( $redirect['path'] ) . $addl_path;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Remove trailing slash for sitemaps requests.
+                       if ( ! empty( get_query_var( 'sitemap' ) ) ) {
+                               $redirect['path'] = untrailingslashit( $redirect['path'] );
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $redirect_url = $redirect['scheme'] . '://' . $redirect['host'] . $redirect['path'];
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -651,6 +656,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $redirect['path'] = trailingslashit( $redirect['path'] );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Remove trailing slash for sitemaps requests.
+       if ( ! empty( get_query_var( 'sitemap' ) ) || ! empty( get_query_var( 'sitemap-stylesheet' ) ) ) {
+               $redirect['path'] = untrailingslashit( $redirect['path'] );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // Strip multiple slashes out of the URL.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( strpos( $redirect['path'], '//' ) > -1 ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $redirect['path'] = preg_replace( '|/+|', '/', $redirect['path'] );
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/src/wp-includes/default-filters.php   2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -456,6 +456,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'rest_api_init', 'create_initial_rest_routes', 99 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'parse_request', 'rest_api_loaded' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Sitemaps actions.
+add_action( 'init', 'wp_sitemaps_get_server' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Filters formerly mixed into wp-includes.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span></span></pre></div>
<a id="trunksrcwpincludesformattingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/formatting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/formatting.php      2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/src/wp-includes/formatting.php        2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -935,6 +935,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * &quot;, or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 1.2.2
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0 `$quote_style` also accepts '`ENT_XML1`.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @staticvar string $_charset
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -942,7 +943,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string       $string        The text which is to be encoded.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param int|string   $quote_style   Optional. Converts double quotes if set to ENT_COMPAT,
</span><span class="cx" style="display: block; padding: 0 10px">  *                                    both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- *                                    Also compatible with old values; converting single quotes if set to 'single',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *                                    Converts single and double quotes, as well as converting HTML
+ *                                    named entities (that are not also XML named entities) to their
+ *                                    code points if set to ENT_XML1. Also compatible with old values;
+ *                                    converting single quotes if set to 'single',
</ins><span class="cx" style="display: block; padding: 0 10px">  *                                    double if set to 'double' or both if otherwise set.
</span><span class="cx" style="display: block; padding: 0 10px">  *                                    Default is ENT_NOQUOTES.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param false|string $charset       Optional. The character encoding of the string. Default is false.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -964,7 +968,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">        // Account for the previous behaviour of the function when the $quote_style is not an accepted value.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( empty( $quote_style ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $quote_style = ENT_NOQUOTES;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        } elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ } elseif ( ENT_XML1 === $quote_style ) {
+               $quote_style = ENT_QUOTES | ENT_XML1;
+       } elseif ( ! in_array( $quote_style, array( ENT_NOQUOTES, ENT_COMPAT, ENT_QUOTES, 'single', 'double' ), true ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $quote_style = ENT_QUOTES;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -994,7 +1000,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! $double_encode ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // Guarantee every &entity; is valid, convert &garbage; into &amp;garbage;
</span><span class="cx" style="display: block; padding: 0 10px">                // This is required for PHP < 5.4.0 because ENT_HTML401 flag is unavailable.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $string = wp_kses_normalize_entities( $string );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $string = wp_kses_normalize_entities( $string, ( $quote_style & ENT_XML1 ) ? 'xml' : 'html' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $string = htmlspecialchars( $string, $quote_style, $charset, $double_encode );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4537,6 +4543,63 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Escaping for XML blocks.
+ *
+ * @since 5.5.0
+ *
+ * @param string $text Text to escape.
+ * @return string Escaped text.
+ */
+function esc_xml( $text ) {
+       $safe_text = wp_check_invalid_utf8( $text );
+
+       $cdata_regex = '\<\!\[CDATA\[.*?\]\]\>';
+       $regex       = <<<EOF
+/
+       (?=.*?{$cdata_regex})                 # lookahead that will match anything followed by a CDATA Section
+       (?<non_cdata_followed_by_cdata>(.*?)) # the "anything" matched by the lookahead
+       (?<cdata>({$cdata_regex}))            # the CDATA Section matched by the lookahead
+
+|                                            # alternative
+
+       (?<non_cdata>(.*))                    # non-CDATA Section
+/sx
+EOF;
+
+       $safe_text = (string) preg_replace_callback(
+               $regex,
+               static function( $matches ) {
+                       if ( ! $matches[0] ) {
+                               return '';
+                       }
+
+                       if ( ! empty( $matches['non_cdata'] ) ) {
+                               // escape HTML entities in the non-CDATA Section.
+                               return _wp_specialchars( $matches['non_cdata'], ENT_XML1 );
+                       }
+
+                       // Return the CDATA Section unchanged, escape HTML entities in the rest.
+                       return _wp_specialchars( $matches['non_cdata_followed_by_cdata'], ENT_XML1 ) . $matches['cdata'];
+               },
+               $safe_text
+       );
+
+       /**
+        * Filters a string cleaned and escaped for output in XML.
+        *
+        * Text passed to esc_xml() is stripped of invalid or special characters
+        * before output. HTML named character references are converted to their
+        * equivalent code points.
+        *
+        * @since 5.5.0
+        *
+        * @param string $safe_text The text after it has been escaped.
+        * @param string $text      The text prior to being escaped.
+        */
+       return apply_filters( 'esc_xml', $safe_text, $text );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Escape an HTML tag name.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.5.0
</span></span></pre></div>
<a id="trunksrcwpincludesksesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/kses.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/kses.php    2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/src/wp-includes/kses.php      2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -47,7 +47,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> // Ensure that these variables are added to the global namespace
</span><span class="cx" style="display: block; padding: 0 10px"> // (e.g. if using namespaces / autoload in the current PHP environment).
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-global $allowedposttags, $allowedtags, $allowedentitynames;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+global $allowedposttags, $allowedtags, $allowedentitynames, $allowedxmlentitynames;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! CUSTOM_TAGS ) {
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -704,6 +704,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">                'there4',
</span><span class="cx" style="display: block; padding: 0 10px">        );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * @var string[] $allowedxmlentitynames Array of KSES allowed XML entitity names.
+        * @since 5.5.0
+        */
+       $allowedxmlnamedentities = array(
+               'amp',
+               'lt',
+               'gt',
+               'apos',
+               'quot',
+       );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
</span><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px">        $allowedtags     = wp_kses_array_lc( $allowedtags );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1745,17 +1757,27 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * This function normalizes HTML entities. It will convert `AT&T` to the correct
</span><span class="cx" style="display: block; padding: 0 10px">  * `AT&amp;T`, `&#00058;` to `&#58;`, `&#XYZZY;` to `&amp;#XYZZY;` and so on.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * When `$context` is set to 'xml', HTML entities are converted to their code points.  For
+ * example, `AT&T&hellip;&#XYZZY;` is converted to `AT&amp;T…&amp;#XYZZY;`.
+ *
</ins><span class="cx" style="display: block; padding: 0 10px">  * @since 1.0.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0 Added `$context` parameter.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param string $string Content to normalize entities.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param string $string  Content to normalize entities.
+ * @param string $context Context for normalization. Can be either 'html' or 'xml'.
+ *                        Default 'html'.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return string Content with normalized entities.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function wp_kses_normalize_entities( $string ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function wp_kses_normalize_entities( $string, $context = 'html' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         // Disarm all entities by converting & to &amp;
</span><span class="cx" style="display: block; padding: 0 10px">        $string = str_replace( '&', '&amp;', $string );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Change back the allowed entities in our entity whitelist.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'xml' === $context ) {
+               $string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $string );
+       } else {
+               $string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px">         $string = preg_replace_callback( '/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string );
</span><span class="cx" style="display: block; padding: 0 10px">        $string = preg_replace_callback( '/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1789,6 +1811,39 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Callback for `wp_kses_normalize_entities()` regular expression.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * This function only accepts valid named entity references, which are finite,
+ * case-sensitive, and highly scrutinized by XML validators.  HTML named entity
+ * references are converted to their code points.
+ *
+ * @since 5.5.0
+ *
+ * @global array $allowedentitynames
+ * @global array $allowedxmlnamedentities
+ *
+ * @param array $matches preg_replace_callback() matches array.
+ * @return string Correctly encoded entity.
+ */
+function wp_kses_xml_named_entities( $matches ) {
+       global $allowedentitynames, $allowedxmlnamedentities;
+
+       if ( empty( $matches[1] ) ) {
+               return '';
+       }
+
+       $i = $matches[1];
+
+       if ( in_array( $i, $allowedxmlnamedentities, true ) ) {
+               return "&$i;";
+       } elseif ( in_array( $i, $allowedentitynames, true ) ) {
+               return html_entity_decode( "&$i;", ENT_HTML5 );
+       }
+
+       return "&amp;$i;";
+}
+
+/**
+ * Callback for `wp_kses_normalize_entities()` regular expression.
+ *
</ins><span class="cx" style="display: block; padding: 0 10px">  * This function helps `wp_kses_normalize_entities()` to only accept 16-bit
</span><span class="cx" style="display: block; padding: 0 10px">  * values and nothing more for `&#number;` entities.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span></span></pre></div>
<a id="trunksrcwpincludessitemapsclasswpsitemapsindexphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-index.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps-index.php                                (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps-index.php  2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,82 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Index class.
+ *
+ * Generates the sitemap index.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Class WP_Sitemaps_Index.
+ * Builds the sitemap index page that lists the links to all of the sitemaps.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Index {
+       /**
+        * The main registry of supported sitemaps.
+        *
+        * @since 5.5.0
+        * @var WP_Sitemaps_Registry
+        */
+       protected $registry;
+
+       /**
+        * WP_Sitemaps_Index constructor.
+        *
+        * @since 5.5.0
+        *
+        * @param WP_Sitemaps_Registry $registry Sitemap provider registry.
+        */
+       public function __construct( WP_Sitemaps_Registry $registry ) {
+               $this->registry = $registry;
+       }
+
+       /**
+        * Gets a sitemap list for the index.
+        *
+        * @since 5.5.0
+        *
+        * @return array List of all sitemaps.
+        */
+       public function get_sitemap_list() {
+               $sitemaps = array();
+
+               $providers = $this->registry->get_sitemaps();
+               /* @var WP_Sitemaps_Provider $provider */
+               foreach ( $providers as $provider ) {
+                       $sitemap_entries = $provider->get_sitemap_entries();
+
+                       // Prevent issues with array_push and empty arrays on PHP < 7.3.
+                       if ( ! $sitemap_entries ) {
+                               continue;
+                       }
+
+                       // Using array_push is more efficient than array_merge in a loop.
+                       array_push( $sitemaps, ...$sitemap_entries );
+               }
+
+               return $sitemaps;
+       }
+
+       /**
+        * Builds the URL for the sitemap index.
+        *
+        * @since 5.5.0
+        *
+        * @return string The sitemap index url.
+        */
+       public function get_index_url() {
+               /* @var WP_Rewrite $wp_rewrite */
+               global $wp_rewrite;
+
+               if ( ! $wp_rewrite->using_permalinks() ) {
+                       return add_query_arg( 'sitemap', 'index', home_url( '/' ) );
+               }
+
+               return home_url( '/wp-sitemap.xml' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-index.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsclasswpsitemapsproviderphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-provider.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps-provider.php                             (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps-provider.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,190 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Provider class
+ *
+ * This class is a base class for other sitemap providers to extend and contains shared functionality.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Class WP_Sitemaps_Provider.
+ *
+ * @since 5.5.0
+ */
+abstract class WP_Sitemaps_Provider {
+       /**
+        * Provider name.
+        *
+        * This will also be used as the public-facing name in URLs.
+        *
+        * @since 5.5.0
+        *
+        * @var string
+        */
+       protected $name = '';
+
+       /**
+        * Object type name (e.g. 'post', 'term', 'user').
+        *
+        * @since 5.5.0
+        *
+        * @var string
+        */
+       protected $object_type = '';
+
+       /**
+        * Gets a URL list for a sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param int    $page_num       Page of results.
+        * @param string $object_subtype Optional. Object subtype name. Default empty.
+        * @return array $url_list Array of URLs for a sitemap.
+        */
+       abstract public function get_url_list( $page_num, $object_subtype = '' );
+
+       /**
+        * Gets the max number of pages available for the object type.
+        *
+        * @since 5.5.0
+        *
+        * @param string $object_subtype Optional. Object subtype. Default empty.
+        * @return int Total number of pages.
+        */
+       abstract public function get_max_num_pages( $object_subtype = '' );
+
+       /**
+        * Gets data about each sitemap type.
+        *
+        * @since 5.5.0
+        *
+        * @return array List of sitemap types including object subtype name and number of pages.
+        */
+       public function get_sitemap_type_data() {
+               $sitemap_data = array();
+
+               $object_subtypes = $this->get_object_subtypes();
+
+               // If there are no object subtypes, include a single sitemap for the
+               // entire object type.
+               if ( empty( $object_subtypes ) ) {
+                       $sitemap_data[] = array(
+                               'name'  => '',
+                               'pages' => $this->get_max_num_pages(),
+                       );
+                       return $sitemap_data;
+               }
+
+               // Otherwise, include individual sitemaps for every object subtype.
+               foreach ( $object_subtypes as $object_subtype_name => $data ) {
+                       $object_subtype_name = (string) $object_subtype_name;
+
+                       $sitemap_data[] = array(
+                               'name'  => $object_subtype_name,
+                               'pages' => $this->get_max_num_pages( $object_subtype_name ),
+                       );
+               }
+
+               return $sitemap_data;
+       }
+
+       /**
+        * Lists sitemap pages exposed by this provider.
+        *
+        * The returned data is used to populate the sitemap entries of the index.
+        *
+        * @since 5.5.0
+        *
+        * @return array List of sitemaps.
+        */
+       public function get_sitemap_entries() {
+               $sitemaps = array();
+
+               $sitemap_types = $this->get_sitemap_type_data();
+
+               foreach ( $sitemap_types as $type ) {
+                       for ( $page = 1; $page <= $type['pages']; $page ++ ) {
+                               $loc        = $this->get_sitemap_url( $type['name'], $page );
+                               $sitemap_entry = array(
+                                       'loc' => $this->get_sitemap_url( $type['name'], $page ),
+                               );
+
+                               /**
+                                * Filters the sitemap entry for the sitemap index.
+                                *
+                                * @since 5.5.0
+                                *
+                                * @param array  $sitemap_entry  Sitemap entry for the post.
+                                * @param string $object_type    Object empty name.
+                                * @param string $object_subtype Object subtype name.
+                                *                               Empty string if the object type does not support subtypes.
+                                * @param string $page           Page of results.
+                                */
+                               $sitemap_entry = apply_filters( 'wp_sitemaps_index_entry', $sitemap_entry, $this->object_type, $type['name'], $page );
+
+                               $sitemaps[] = $sitemap_entry;
+                       }
+               }
+
+               return $sitemaps;
+       }
+
+       /**
+        * Gets the URL of a sitemap entry.
+        *
+        * @since 5.5.0
+        *
+        * @param string $name The name of the sitemap.
+        * @param int    $page The page of the sitemap.
+        * @return string The composed URL for a sitemap entry.
+        */
+       public function get_sitemap_url( $name, $page ) {
+               /* @var WP_Rewrite $wp_rewrite */
+               global $wp_rewrite;
+
+               if ( ! $wp_rewrite->using_permalinks() ) {
+                       return add_query_arg(
+                               // Accounts for cases where name is not included, ex: sitemaps-users-1.xml.
+                               array_filter(
+                                       array(
+                                               'sitemap'         => $this->name,
+                                               'sitemap-subtype' => $name,
+                                               'paged'           => $page,
+                                       )
+                               ),
+                               home_url( '/' )
+                       );
+               }
+
+               $basename = sprintf(
+                       '/wp-sitemap-%1$s.xml',
+                       implode(
+                               '-',
+                               // Accounts for cases where name is not included, ex: sitemaps-users-1.xml.
+                               array_filter(
+                                       array(
+                                               $this->name,
+                                               $name,
+                                               (string) $page,
+                                       )
+                               )
+                       )
+               );
+
+               return home_url( $basename );
+       }
+
+       /**
+        * Returns the list of supported object subtypes exposed by the provider.
+        *
+        * @since 5.5.0
+        *
+        * @return array List of object subtypes objects keyed by their name.
+        */
+       public function get_object_subtypes() {
+               return array();
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-provider.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsclasswpsitemapsregistryphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-registry.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps-registry.php                             (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps-registry.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,87 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Registry class
+ *
+ * Handles registering sitemaps.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Class WP_Sitemaps_Registry.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Registry {
+       /**
+        * Registered sitemaps.
+        *
+        * @since 5.5.0
+        *
+        * @var array Array of registered sitemaps.
+        */
+       private $sitemaps = array();
+
+       /**
+        * Maximum number of sitemaps to include in an index.
+        *
+        * @sincee 5.5.0
+        *
+        * @var int Maximum number of sitemaps.
+        */
+       private $max_sitemaps = 50000;
+
+       /**
+        * Adds a sitemap with route to the registry.
+        *
+        * @since 5.5.0
+        *
+        * @param string               $name     Name of the sitemap.
+        * @param WP_Sitemaps_Provider $provider Instance of a WP_Sitemaps_Provider.
+        * @return bool True if the sitemap was added, false if it is already registered.
+        */
+       public function add_sitemap( $name, WP_Sitemaps_Provider $provider ) {
+               if ( isset( $this->sitemaps[ $name ] ) ) {
+                       return false;
+               }
+
+               $this->sitemaps[ $name ] = $provider;
+
+               return true;
+       }
+
+       /**
+        * Returns a single registered sitemaps provider.
+        *
+        * @since 5.5.0
+        *
+        * @param string $name Sitemap provider name.
+        * @return WP_Sitemaps_Provider|null Sitemaps provider if it exists, null otherwise.
+        */
+       public function get_sitemap( $name ) {
+               if ( ! isset( $this->sitemaps[ $name ] ) ) {
+                       return null;
+               }
+
+               return $this->sitemaps[ $name ];
+       }
+
+       /**
+        * Lists all registered sitemaps.
+        *
+        * @since 5.5.0
+        *
+        * @return array List of sitemaps.
+        */
+       public function get_sitemaps() {
+               $total_sitemaps = count( $this->sitemaps );
+
+               if ( $total_sitemaps > $this->max_sitemaps ) {
+                       return array_slice( $this->sitemaps, 0, $this->max_sitemaps, true );
+               }
+
+               return $this->sitemaps;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-registry.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsclasswpsitemapsrendererphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-renderer.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps-renderer.php                             (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps-renderer.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,269 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Renderer class
+ *
+ * Responsible for rendering Sitemaps data to XML in accordance with sitemap protocol.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Class WP_Sitemaps_Renderer
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Renderer {
+       /**
+        * XSL stylesheet for styling a sitemap for web browsers.
+        *
+        * @since 5.5.0
+        *
+        * @var string
+        */
+       protected $stylesheet = '';
+
+       /**
+        * XSL stylesheet for styling a sitemap for web browsers.
+        *
+        * @since 5.5.0
+        *
+        * @var string
+        */
+       protected $stylesheet_index = '';
+
+       /**
+        * WP_Sitemaps_Renderer constructor.
+        *
+        * @since 5.5.0
+        */
+       public function __construct() {
+               $stylesheet_url = $this->get_sitemap_stylesheet_url();
+               if ( $stylesheet_url ) {
+                       $this->stylesheet = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_url ) . '" ?>';
+               }
+               $stylesheet_index_url   = $this->get_sitemap_index_stylesheet_url();
+               if ( $stylesheet_index_url ) {
+                       $this->stylesheet_index = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_index_url ) . '" ?>';
+               }
+       }
+
+       /**
+        * Gets the URL for the sitemap stylesheet.
+        *
+        * @since 5.5.0
+        *
+        * @return string The sitemap stylesheet url.
+        */
+       public function get_sitemap_stylesheet_url() {
+               /* @var WP_Rewrite $wp_rewrite */
+               global $wp_rewrite;
+
+               $sitemap_url = home_url( '/wp-sitemap.xsl' );
+
+               if ( ! $wp_rewrite->using_permalinks() ) {
+                       $sitemap_url = add_query_arg( 'sitemap-stylesheet', 'sitemap', home_url( '/' ) );
+               }
+
+               /**
+                * Filters the URL for the sitemap stylesheet.
+                *
+                * If a falsy value is returned, no stylesheet will be used and
+                * the "raw" XML of the sitemap will be displayed.
+                *
+                * @since 5.5.0
+                *
+                * @param string $sitemap_url Full URL for the sitemaps xsl file.
+                */
+               return apply_filters( 'wp_sitemaps_stylesheet_url', $sitemap_url );
+       }
+
+       /**
+        * Gets the URL for the sitemap index stylesheet.
+        *
+        * @since 5.5.0
+        *
+        * @return string The sitemap index stylesheet url.
+        */
+       public function get_sitemap_index_stylesheet_url() {
+               /* @var WP_Rewrite $wp_rewrite */
+               global $wp_rewrite;
+
+               $sitemap_url = home_url( '/wp-sitemap-index.xsl' );
+
+               if ( ! $wp_rewrite->using_permalinks() ) {
+                       $sitemap_url = add_query_arg( 'sitemap-stylesheet', 'index', home_url( '/' ) );
+               }
+
+               /**
+                * Filters the URL for the sitemap index stylesheet.
+                *
+                * If a falsy value is returned, no stylesheet will be used and
+                * the "raw" XML of the sitemap index will be displayed.
+                *
+                * @since 5.5.0
+                *
+                * @param string $sitemap_url Full URL for the sitemaps index xsl file.
+                */
+               return apply_filters( 'wp_sitemaps_stylesheet_index_url', $sitemap_url );
+       }
+
+       /**
+        * Renders a sitemap index.
+        *
+        * @since 5.5.0
+        *
+        * @param array $sitemaps Array of sitemap URLs.
+        */
+       public function render_index( $sitemaps ) {
+               header( 'Content-type: application/xml; charset=UTF-8' );
+
+               $this->check_for_simple_xml_availability();
+
+               $index_xml = $this->get_sitemap_index_xml( $sitemaps );
+
+               if ( ! empty( $index_xml ) ) {
+                       // All output is escaped within get_sitemap_index_xml().
+                       // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                       echo $index_xml;
+               }
+       }
+
+       /**
+        * Gets XML for a sitemap index.
+        *
+        * @since 5.5.0
+        *
+        * @param array $sitemaps Array of sitemap URLs.
+        * @return string|false A well-formed XML string for a sitemap index. False on error.
+        */
+       public function get_sitemap_index_xml( $sitemaps ) {
+               $sitemap_index = new SimpleXMLElement(
+                       sprintf(
+                               '%1$s%2$s%3$s',
+                               '<?xml version="1.0" encoding="UTF-8" ?>',
+                               $this->stylesheet_index,
+                               '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />'
+                       )
+               );
+
+               foreach ( $sitemaps as $entry ) {
+                       $sitemap = $sitemap_index->addChild( 'sitemap' );
+
+                       // Add each element as a child node to the <sitemap> entry.
+                       foreach ( $entry as $name => $value ) {
+                               if ( 'loc' === $name ) {
+                                       $sitemap->addChild( $name, esc_url( $value ) );
+                               } elseif ( 'lastmod' === $name ) {
+                                       $sitemap->addChild( $name, esc_xml( $value ) );
+                               } else {
+                                       _doing_it_wrong(
+                                               __METHOD__,
+                                               /* translators: %s: list of element names */
+                                               sprintf(
+                                                       __( 'Fields other than %s are not currently supported for the sitemap index.' ),
+                                                       implode( ',', array( 'loc', 'lastmod' ) )
+                                               ),
+                                               '5.5.0'
+                                       );
+                               }
+                       }
+               }
+
+               return $sitemap_index->asXML();
+       }
+
+       /**
+        * Renders a sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param array $url_list Array of URLs for a sitemap.
+        */
+       public function render_sitemap( $url_list ) {
+               header( 'Content-type: application/xml; charset=UTF-8' );
+
+               $this->check_for_simple_xml_availability();
+
+               $sitemap_xml = $this->get_sitemap_xml( $url_list );
+
+               if ( ! empty( $sitemap_xml ) ) {
+                       // All output is escaped within get_sitemap_xml().
+                       // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                       echo $sitemap_xml;
+               }
+       }
+
+       /**
+        * Gets XML for a sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param array $url_list Array of URLs for a sitemap.
+        * @return string|false A well-formed XML string for a sitemap index. False on error.
+        */
+       public function get_sitemap_xml( $url_list ) {
+               $urlset = new SimpleXMLElement(
+                       sprintf(
+                               '%1$s%2$s%3$s',
+                               '<?xml version="1.0" encoding="UTF-8" ?>',
+                               $this->stylesheet,
+                               '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />'
+                       )
+               );
+
+               foreach ( $url_list as $url_item ) {
+                       $url = $urlset->addChild( 'url' );
+
+                       // Add each element as a child node to the <url> entry.
+                       foreach ( $url_item as $name => $value ) {
+                               if ( 'loc' === $name ) {
+                                       $url->addChild( $name, esc_url( $value ) );
+                               } elseif ( in_array( $name, array( 'lastmod', 'changefreq', 'priority' ), true ) ) {
+                                       $url->addChild( $name, esc_xml( $value ) );
+                               } else {
+                                       _doing_it_wrong(
+                                               __METHOD__,
+                                               /* translators: %s: list of element names */
+                                               sprintf(
+                                                       __( 'Fields other than %s are not currently supported for sitemaps.' ),
+                                                       implode( ',', array( 'loc', 'lastmod', 'changefreq', 'priority' ) )
+                                               ),
+                                               '5.5.0'
+                                       );
+                               }
+                       }
+               }
+
+               return $urlset->asXML();
+       }
+
+       /**
+        * Checks for the availability of the SimpleXML extension and errors if missing.
+        *
+        * @since 5.5.0
+        */
+       private function check_for_simple_xml_availability() {
+               if ( ! class_exists( 'SimpleXMLElement' ) ) {
+                       add_filter(
+                               'wp_die_handler',
+                               static function () {
+                                       return '_xml_wp_die_handler';
+                               }
+                       );
+
+                       wp_die(
+                               sprintf(
+                                       /* translators: %s: SimpleXML */
+                                       esc_xml( __( 'Could not generate XML sitemap due to missing %s extension' ) ),
+                                       'SimpleXML'
+                               ),
+                               esc_xml( __( 'WordPress &rsaquo; Error' ) ),
+                               array(
+                                       'response' => 501, // "Not implemented".
+                               )
+                       );
+               }
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-renderer.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsclasswpsitemapsstylesheetphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-stylesheet.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps-stylesheet.php                           (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps-stylesheet.php     2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,288 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Stylesheet class
+ *
+ * This class provides the XSL stylesheets to style all sitemaps.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Stylesheet provider class.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Stylesheet {
+       /**
+        * Renders the xsl stylesheet depending on whether its the sitemap index or not.
+        *
+        * @param string $type Stylesheet type. Either 'sitemap' or 'index'.
+        */
+       public function render_stylesheet( $type ) {
+               header( 'Content-type: application/xml; charset=UTF-8' );
+
+               if ( 'sitemap' === $type ) {
+                       // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All content escaped below.
+                       echo $this->get_sitemap_stylesheet();
+               }
+
+               if ( 'index' === $type ) {
+                       // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All content escaped below.
+                       echo $this->get_sitemap_index_stylesheet();
+               }
+
+               exit;
+       }
+
+       /**
+        * Returns the escaped xsl for all sitemaps, except index.
+        *
+        * @since 5.5.0
+        */
+       public function get_sitemap_stylesheet() {
+               $css         = $this->get_stylesheet_css();
+               $title       = esc_xml( __( 'XML Sitemap' ) );
+               $description = esc_xml( __( 'This XML Sitemap is generated by WordPress to make your content more visible for search engines.' ) );
+               $learn_more  = sprintf(
+                       '<a href="%s">%s</a>',
+                       esc_url( __( 'https://www.sitemaps.org/' ) ),
+                       esc_xml( __( 'Learn more about XML sitemaps.' ) )
+               );
+
+               $text = sprintf(
+                       /* translators: %s: number of URLs. */
+                       esc_xml( __( 'Number of URLs in this XML Sitemap: %s.' ) ),
+                       '<xsl:value-of select="count( sitemap:urlset/sitemap:url )" />'
+               );
+
+               $lang       = get_language_attributes( 'html' );
+               $url        = esc_xml( __( 'URL' ) );
+               $lastmod    = esc_xml( __( 'Last Modified' ) );
+               $changefreq = esc_xml( __( 'Change Frequency' ) );
+               $priority   = esc_xml( __( 'Priority' ) );
+
+               $xsl_content = <<<XSL
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+               version="1.0"
+               xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
+               exclude-result-prefixes="sitemap"
+               >
+
+       <xsl:output method="html" encoding="UTF-8" indent="yes"/>
+
+       <!--
+         Set variables for whether lastmod, changefreq or priority occur for any url in the sitemap.
+         We do this up front because it can be expensive in a large sitemap.
+         -->
+       <xsl:variable name="has-lastmod"    select="count( /sitemap:urlset/sitemap:url/sitemap:lastmod )"    />
+       <xsl:variable name="has-changefreq" select="count( /sitemap:urlset/sitemap:url/sitemap:changefreq )" />
+       <xsl:variable name="has-priority"   select="count( /sitemap:urlset/sitemap:url/sitemap:priority )"   />
+
+       <xsl:template match="/">
+               <html {$lang}>
+                       <head>
+                               <title>{$title}</title>
+                               <style>{$css}</style>
+                       </head>
+                       <body>
+                               <div id="sitemap__header">
+                                       <h1>{$title}</h1>
+                                       <p>{$description}</p>
+                                       <p>{$learn_more}</p>
+                               </div>
+                               <div id="sitemap__content">
+                                       <p class="text">{$text}</p>
+                                       <table id="sitemap__table">
+                                               <thead>
+                                                       <tr>
+                                                               <th class="loc">{$url}</th>
+                                                               <xsl:if test="\$has-lastmod">
+                                                                       <th class="lastmod">{$lastmod}</th>
+                                                               </xsl:if>
+                                                               <xsl:if test="\$has-changefreq">
+                                                                       <th class="changefreq">{$changefreq}</th>
+                                                               </xsl:if>
+                                                               <xsl:if test="\$has-priority">
+                                                                       <th class="priority">{$priority}</th>
+                                                               </xsl:if>
+                                                       </tr>
+                                               </thead>
+                                               <tbody>
+                                                       <xsl:for-each select="sitemap:urlset/sitemap:url">
+                                                               <tr>
+                                                                       <td class="loc"><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc" /></a></td>
+                                                                       <xsl:if test="\$has-lastmod">
+                                                                               <td class="lastmod"><xsl:value-of select="sitemap:lastmod" /></td>
+                                                                       </xsl:if>
+                                                                       <xsl:if test="\$has-changefreq">
+                                                                               <td class="changefreq"><xsl:value-of select="sitemap:changefreq" /></td>
+                                                                       </xsl:if>
+                                                                       <xsl:if test="\$has-priority">
+                                                                               <td class="priority"><xsl:value-of select="sitemap:priority" /></td>
+                                                                       </xsl:if>
+                                                               </tr>
+                                                       </xsl:for-each>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </body>
+               </html>
+       </xsl:template>
+</xsl:stylesheet>
+
+XSL;
+
+               /**
+                * Filters the content of the sitemap stylesheet.
+                *
+                * @since 5.5.0
+                *
+                * @param string $xsl Full content for the xml stylesheet.
+                */
+               return apply_filters( 'wp_sitemaps_stylesheet_content', $xsl_content );
+       }
+
+       /**
+        * Returns the escaped xsl for the index sitemaps.
+        *
+        * @since 5.5.0
+        */
+       public function get_sitemap_index_stylesheet() {
+               $css           = $this->get_stylesheet_css();
+               $title         = esc_xml( __( 'XML Sitemap' ) );
+               $description   = esc_xml( __( 'This XML Sitemap is generated by WordPress to make your content more visible for search engines.' ) );
+               $learn_more    = sprintf(
+                       '<a href="%s">%s</a>',
+                       esc_url( __( 'https://www.sitemaps.org/' ) ),
+                       esc_xml( __( 'Learn more about XML sitemaps.' ) )
+               );
+
+               $text = sprintf(
+                       /* translators: %s: number of URLs. */
+                       esc_xml( __( 'Number of URLs in this XML Sitemap: %s.' ) ),
+                       '<xsl:value-of select="count( sitemap:sitemapindex/sitemap:sitemap )" />'
+               );
+
+               $lang    = get_language_attributes( 'html' );
+               $url     = esc_xml( __( 'URL' ) );
+               $lastmod = esc_xml( __( 'Last Modified' ) );
+
+               $xsl_content = <<<XSL
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+               version="1.0"
+               xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
+               exclude-result-prefixes="sitemap"
+               >
+
+       <xsl:output method="html" encoding="UTF-8" indent="yes" />
+
+       <!--
+         Set variables for whether lastmod occurs for any sitemap in the index.
+         We do this up front because it can be expensive in a large sitemap.
+         -->
+       <xsl:variable name="has-lastmod" select="count( /sitemap:sitemapindex/sitemap:sitemap/sitemap:lastmod )" />
+
+       <xsl:template match="/">
+               <html {$lang}>
+                       <head>
+                               <title>{$title}</title>
+                               <style>{$css}</style>
+                       </head>
+                       <body>
+                               <div id="sitemap__header">
+                                       <h1>{$title}</h1>
+                                       <p>{$description}</p>
+                                       <p>{$learn_more}</p>
+                               </div>
+                               <div id="sitemap__content">
+                                       <p class="text">{$text}</p>
+                                       <table id="sitemap__table">
+                                               <thead>
+                                                       <tr>
+                                                               <th class="loc">{$url}</th>
+                                                               <xsl:if test="\$has-lastmod">
+                                                                       <th class="lastmod">{$lastmod}</th>
+                                                               </xsl:if>
+                                                       </tr>
+                                               </thead>
+                                               <tbody>
+                                                       <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
+                                                               <tr>
+                                                                       <td class="loc"><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc" /></a></td>
+                                                                       <xsl:if test="\$has-lastmod">
+                                                                               <td class="lastmod"><xsl:value-of select="sitemap:lastmod" /></td>
+                                                                       </xsl:if>
+                                                               </tr>
+                                                       </xsl:for-each>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </body>
+               </html>
+       </xsl:template>
+</xsl:stylesheet>
+
+XSL;
+
+               /**
+                * Filters the content of the sitemap index stylesheet.
+                *
+                * @since 5.5.0
+                *
+                * @param string $xsl Full content for the xml stylesheet.
+                */
+               return apply_filters( 'wp_sitemaps_stylesheet_index_content', $xsl_content );
+       }
+
+       /**
+        * Gets the CSS to be included in sitemap XSL stylesheets.
+        *
+        * @since 5.5.0
+        *
+        * @return string The CSS.
+        */
+       public function get_stylesheet_css() {
+               $css = '
+                       body {
+                               font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+                               color: #444;
+                       }
+
+                       #sitemap__table {
+                               border: solid 1px #ccc;
+                               border-collapse: collapse;
+                       }
+
+                       #sitemap__table tr th {
+                               text-align: left;
+                       }
+
+                       #sitemap__table tr td,
+                       #sitemap__table tr th {
+                               padding: 10px;
+                       }
+
+                       #sitemap__table tr:nth-child(odd) td {
+                               background-color: #eee;
+                       }
+
+                       a:hover {
+                               text-decoration: none;
+                       }';
+
+               /**
+                * Filters the css only for the sitemap stylesheet.
+                *
+                * @since 5.5.0
+                *
+                * @param string $css CSS to be applied to default xsl file.
+                */
+               return apply_filters( 'wp_sitemaps_stylesheet_css', $css );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps-stylesheet.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsclasswpsitemapsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/class-wp-sitemaps.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/class-wp-sitemaps.php                              (rev 0)
+++ trunk/src/wp-includes/sitemaps/class-wp-sitemaps.php        2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,235 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps class
+ *
+ * This is the main class integrating all other classes.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Class WP_Sitemaps.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps {
+       /**
+        * The main index of supported sitemaps.
+        *
+        * @since 5.5.0
+        *
+        * @var WP_Sitemaps_Index
+        */
+       public $index;
+
+       /**
+        * The main registry of supported sitemaps.
+        *
+        * @since 5.5.0
+        *
+        * @var WP_Sitemaps_Registry
+        */
+       public $registry;
+
+       /**
+        * An instance of the renderer class.
+        *
+        * @since 5.5.0
+        *
+        * @var WP_Sitemaps_Renderer
+        */
+       public $renderer;
+
+       /**
+        * WP_Sitemaps constructor.
+        *
+        * @since 5.5.0
+        */
+       public function __construct() {
+               $this->registry = new WP_Sitemaps_Registry();
+               $this->renderer = new WP_Sitemaps_Renderer();
+               $this->index    = new WP_Sitemaps_Index( $this->registry );
+       }
+
+       /**
+        * Initiates all sitemap functionality.
+        *
+        * @since 5.5.0
+        */
+       public function init() {
+               // These will all fire on the init hook.
+               $this->register_rewrites();
+               $this->register_sitemaps();
+
+               // Add additional action callbacks.
+               add_action( 'template_redirect', array( $this, 'render_sitemaps' ) );
+               add_filter( 'pre_handle_404', array( $this, 'redirect_sitemapxml' ), 10, 2 );
+               add_filter( 'robots_txt', array( $this, 'add_robots' ), 0, 2 );
+       }
+
+       /**
+        * Registers and sets up the functionality for all supported sitemaps.
+        *
+        * @since 5.5.0
+        */
+       public function register_sitemaps() {
+               /**
+                * Filters the list of registered sitemap providers.
+                *
+                * @since 5.5.0
+                *
+                * @param array $providers {
+                *     Array of WP_Sitemaps_Provider objects keyed by their name.
+                *
+                *     @type object $posts      The WP_Sitemaps_Posts object.
+                *     @type object $taxonomies The WP_Sitemaps_Taxonomies object.
+                *     @type object $users      The WP_Sitemaps_Users object.
+                * }
+                */
+               $providers = apply_filters(
+                       'wp_sitemaps_register_providers',
+                       array(
+                               'posts'      => new WP_Sitemaps_Posts(),
+                               'taxonomies' => new WP_Sitemaps_Taxonomies(),
+                               'users'      => new WP_Sitemaps_Users(),
+                       )
+               );
+
+               // Register each supported provider.
+               /* @var WP_Sitemaps_Provider $provider */
+               foreach ( $providers as $name => $provider ) {
+                       $this->registry->add_sitemap( $name, $provider );
+               }
+       }
+
+       /**
+        * Registers sitemap rewrite tags and routing rules.
+        *
+        * @since 5.5.0
+        */
+       public function register_rewrites() {
+               // Add rewrite tags.
+               add_rewrite_tag( '%sitemap%', '([^?]+)' );
+               add_rewrite_tag( '%sitemap-subtype%', '([^?]+)' );
+
+               // Register index route.
+               add_rewrite_rule( '^wp-sitemap\.xml$', 'index.php?sitemap=index', 'top' );
+
+               // Register rewrites for the XSL stylesheet.
+               add_rewrite_tag( '%sitemap-stylesheet%', '([^?]+)' );
+               add_rewrite_rule( '^wp-sitemap\.xsl$', 'index.php?sitemap-stylesheet=sitemap', 'top' );
+               add_rewrite_rule( '^wp-sitemap-index\.xsl$', 'index.php?sitemap-stylesheet=index', 'top' );
+
+               // Register routes for providers.
+               add_rewrite_rule(
+                       '^wp-sitemap-([a-z]+?)-([a-z\d_-]+?)-(\d+?)\.xml$',
+                       'index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]',
+                       'top'
+               );
+               add_rewrite_rule(
+                       '^wp-sitemap-([a-z]+?)-(\d+?)\.xml$',
+                       'index.php?sitemap=$matches[1]&paged=$matches[2]',
+                       'top'
+               );
+       }
+
+       /**
+        * Renders sitemap templates based on rewrite rules.
+        *
+        * @since 5.5.0
+        */
+       public function render_sitemaps() {
+               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' ) );
+
+               // Bail early if this isn't a sitemap or stylesheet route.
+               if ( ! ( $sitemap || $stylesheet_type ) ) {
+                       return;
+               }
+
+               // Render stylesheet if this is stylesheet route.
+               if ( $stylesheet_type ) {
+                       $stylesheet = new WP_Sitemaps_Stylesheet();
+
+                       $stylesheet->render_stylesheet( $stylesheet_type );
+                       exit;
+               }
+
+               // Render the index.
+               if ( 'index' === $sitemap ) {
+                       $sitemap_list = $this->index->get_sitemap_list();
+
+                       $this->renderer->render_index( $sitemap_list );
+                       exit;
+               }
+
+               $provider = $this->registry->get_sitemap( $sitemap );
+
+               if ( ! $provider ) {
+                       return;
+               }
+
+               if ( empty( $paged ) ) {
+                       $paged = 1;
+               }
+
+               $url_list = $provider->get_url_list( $paged, $object_subtype );
+
+               // Force a 404 and bail early if no URLs are present.
+               if ( empty( $url_list ) ) {
+                       $wp_query->set_404();
+                       return;
+               }
+
+               $this->renderer->render_sitemap( $url_list );
+               exit;
+       }
+
+       /**
+        * Redirects a URL to the wp-sitemap.xml
+        *
+        * @since 5.5.0
+        *
+        * @param bool     $bypass Pass-through of the pre_handle_404 filter value.
+        * @param WP_Query $query The WP_Query object.
+        * @return bool Bypass value.
+        */
+       public function redirect_sitemapxml( $bypass, $query ) {
+               // If a plugin has already utilized the pre_handle_404 function, return without action to avoid conflicts.
+               if ( $bypass ) {
+                       return $bypass;
+               }
+
+               // 'pagename' is for most permalink types, name is for when the %postname% is used as a top-level field.
+               if ( 'sitemap-xml' === $query->get( 'pagename' ) ||
+                        'sitemap-xml' === $query->get( 'name' ) ) {
+                       wp_safe_redirect( $this->index->get_index_url() );
+                       exit();
+               }
+
+               return $bypass;
+       }
+
+       /**
+        * Adds the sitemap index to robots.txt.
+        *
+        * @since 5.5.0
+        *
+        * @param string $output robots.txt output.
+        * @param bool   $public Whether the site is public or not.
+        * @return string The robots.txt output.
+        */
+       public function add_robots( $output, $public ) {
+               if ( $public ) {
+                       $output .= "\nSitemap: " . esc_url( $this->index->get_index_url() ) . "\n";
+               }
+
+               return $output;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/class-wp-sitemaps.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsprovidersclasswpsitemapspostsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php                              (rev 0)
+++ trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php        2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,221 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Posts class
+ *
+ * Builds the sitemaps for the 'post' object type.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Posts XML sitemap provider.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Posts extends WP_Sitemaps_Provider {
+       /**
+        * WP_Sitemaps_Posts constructor.
+        *
+        * @since 5.5.0
+        */
+       public function __construct() {
+               $this->name        = 'posts';
+               $this->object_type = 'post';
+       }
+
+       /**
+        * Returns the public post types, which excludes nav_items and similar types.
+        * Attachments are also excluded. This includes custom post types with public = true.
+        *
+        * @since 5.5.0
+        *
+        * @return array Map of registered post type objects (WP_Post_Type) keyed by their name.
+        */
+       public function get_object_subtypes() {
+               $post_types = get_post_types( array( 'public' => true ), 'objects' );
+               unset( $post_types['attachment'] );
+
+               /**
+                * Filters the list of post object sub types available within the sitemap.
+                *
+                * @since 5.5.0
+                *
+                * @param array $post_types Map of registered post type objects (WP_Post_Type) keyed by their name.
+                */
+               return apply_filters( 'wp_sitemaps_post_types', $post_types );
+       }
+
+       /**
+        * Gets a URL list for a post type sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param int    $page_num  Page of results.
+        * @param string $post_type Optional. Post type name. Default empty.
+        * @return array $url_list Array of URLs for a sitemap.
+        */
+       public function get_url_list( $page_num, $post_type = '' ) {
+               // Bail early if the queried post type is not supported.
+               $supported_types = $this->get_object_subtypes();
+
+               if ( ! isset( $supported_types[ $post_type ] ) ) {
+                       return array();
+               }
+
+               /**
+                * Filters the posts URL list before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param array  $url_list  The URL list. Default null.
+                * @param string $post_type Post type name.
+                * @param int    $page_num  Page of results.
+                */
+               $url_list = apply_filters(
+                       'wp_sitemaps_posts_pre_url_list',
+                       null,
+                       $post_type,
+                       $page_num
+               );
+
+               if ( null !== $url_list ) {
+                       return $url_list;
+               }
+
+               $args          = $this->get_posts_query_args( $post_type );
+               $args['paged'] = $page_num;
+
+               $query = new WP_Query( $args );
+
+               /**
+                * Returns an array of posts.
+                *
+                * @var array<int, \WP_Post> $posts
+                */
+               $posts = $query->get_posts();
+
+               $url_list = array();
+
+               /*
+                * Add a URL for the homepage in the pages sitemap.
+                * Shows only on the first page if the reading settings are set to display latest posts.
+                */
+               if ( 'page' === $post_type && 1 === $page_num && 'posts' === get_option( 'show_on_front' ) ) {
+                       // Extract the data needed for home URL to add to the array.
+                       $sitemap_entry = array(
+                               'loc' => home_url(),
+                       );
+
+                       /**
+                        * Filters the sitemap entry for the home page when the 'show_on_front' option equals 'posts'.
+                        *
+                        * @since 5.5.0
+                        *
+                        * @param array $sitemap_entry Sitemap entry for the home page.
+                        */
+                       $sitemap_entry = apply_filters( 'wp_sitemaps_posts_show_on_front_entry', $sitemap_entry );
+                       $url_list[]    = $sitemap_entry;
+               }
+
+               foreach ( $posts as $post ) {
+                       $sitemap_entry = array(
+                               'loc' => get_permalink( $post ),
+                       );
+
+                       /**
+                        * Filters the sitemap entry for an individual post.
+                        *
+                        * @since 5.5.0
+                        *
+                        * @param array   $sitemap_entry Sitemap entry for the post.
+                        * @param WP_Post $post          Post object.
+                        * @param string  $post_type     Name of the post_type.
+                        */
+                       $sitemap_entry = apply_filters( 'wp_sitemaps_posts_entry', $sitemap_entry, $post, $post_type );
+                       $url_list[]    = $sitemap_entry;
+               }
+
+               return $url_list;
+       }
+
+       /**
+        * Gets the max number of pages available for the object type.
+        *
+        * @since 5.5.0
+        *
+        * @param string $post_type Optional. Post type name. Default empty.
+        * @return int Total number of pages.
+        */
+       public function get_max_num_pages( $post_type = '' ) {
+               if ( empty( $post_type ) ) {
+                       return 0;
+               }
+
+               /**
+                * Filters the max number of pages before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param int $max_num_pages The maximum number of pages. Default null.
+                * @param string $post_type Post type name.
+                */
+               $max_num_pages = apply_filters( 'wp_sitemaps_posts_pre_max_num_pages', null, $post_type );
+
+               if ( null !== $max_num_pages ) {
+                       return $max_num_pages;
+               }
+
+               $args                  = $this->get_posts_query_args( $post_type );
+               $args['fields']        = 'ids';
+               $args['no_found_rows'] = false;
+
+               $query = new WP_Query( $args );
+
+               return isset( $query->max_num_pages ) ? $query->max_num_pages : 1;
+       }
+
+       /**
+        * Returns the query args for retrieving posts to list in the sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param string $post_type Post type name.
+        * @return array $args Array of WP_Query arguments.
+        */
+       protected function get_posts_query_args( $post_type ) {
+               /**
+                * Filters the query arguments for post type sitemap queries.
+                *
+                * @see WP_Query for a full list of arguments.
+                *
+                * @since 5.5.0
+                *
+                * @param array  $args      Array of WP_Query arguments.
+                * @param string $post_type Post type name.
+                */
+               $args = apply_filters(
+                       'wp_sitemaps_posts_query_args',
+                       array(
+                               'orderby'                => 'ID',
+                               'order'                  => 'ASC',
+                               'post_type'              => $post_type,
+                               'posts_per_page'         => wp_sitemaps_get_max_urls( $this->object_type ),
+                               'post_status'            => array( 'publish' ),
+                               'no_found_rows'          => true,
+                               'update_post_term_cache' => false,
+                               'update_post_meta_cache' => false,
+                       ),
+                       $post_type
+               );
+
+               return $args;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsprovidersclasswpsitemapstaxonomiesphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-taxonomies.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-taxonomies.php                         (rev 0)
+++ trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-taxonomies.php   2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,193 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Taxonomies class
+ *
+ * Builds the sitemaps for the 'taxonomy' object type.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Taxonomies XML sitemap provider.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Taxonomies extends WP_Sitemaps_Provider {
+       /**
+        * WP_Sitemaps_Taxonomies constructor.
+        *
+        * @since 5.5.0
+        */
+       public function __construct() {
+               $this->name        = 'taxonomies';
+               $this->object_type = 'term';
+       }
+
+       /**
+        * Returns all public, registered taxonomies.
+        *
+        * @since 5.5.0
+        *
+        * @return array Map of registered taxonomy objects keyed by their name.
+        */
+       public function get_object_subtypes() {
+               $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
+
+               /**
+                * Filter the list of taxonomy object subtypes available within the sitemap.
+                *
+                * @since 5.5.0
+                *
+                * @param array $taxonomies Map of registered taxonomy objects keyed by their name.
+                */
+               return apply_filters( 'wp_sitemaps_taxonomies', $taxonomies );
+       }
+
+       /**
+        * Gets a URL list for a taxonomy sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param int    $page_num Page of results.
+        * @param string $taxonomy Optional. Taxonomy name. Default empty.
+        * @return array $url_list Array of URLs for a sitemap.
+        */
+       public function get_url_list( $page_num, $taxonomy = '' ) {
+               $supported_types = $this->get_object_subtypes();
+
+               // Bail early if the queried taxonomy is not supported.
+               if ( ! isset( $supported_types[ $taxonomy ] ) ) {
+                       return array();
+               }
+
+               /**
+                * Filters the taxonomies URL list before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param array  $url_list The URL list. Default null.
+                * @param string $taxonomy Taxonomy name.
+                * @param int    $page_num Page of results.
+                */
+               $url_list = apply_filters(
+                       'wp_sitemaps_taxonomies_pre_url_list',
+                       null,
+                       $taxonomy,
+                       $page_num
+               );
+
+               if ( null !== $url_list ) {
+                       return $url_list;
+               }
+
+               $url_list = array();
+
+               // Offset by how many terms should be included in previous pages.
+               $offset = ( $page_num - 1 ) * wp_sitemaps_get_max_urls( $this->object_type );
+
+               $args           = $this->get_taxonomies_query_args( $taxonomy );
+               $args['offset'] = $offset;
+
+               $taxonomy_terms = new WP_Term_Query( $args );
+
+               if ( ! empty( $taxonomy_terms->terms ) ) {
+                       foreach ( $taxonomy_terms->terms as $term ) {
+                               $sitemap_entry = array(
+                                       'loc' => get_term_link( $term ),
+                               );
+
+                               /**
+                                * Filters the sitemap entry for an individual term.
+                                *
+                                * @since 5.5.0
+                                *
+                                * @param array   $sitemap_entry Sitemap entry for the term.
+                                * @param WP_Term $term          Term object.
+                                * @param string  $taxonomy      Taxonomy name.
+                                */
+                               $sitemap_entry = apply_filters( 'wp_sitemaps_taxonomies_entry', $sitemap_entry, $term, $taxonomy );
+                               $url_list[]    = $sitemap_entry;
+                       }
+               }
+
+               return $url_list;
+       }
+
+       /**
+        * Gets the max number of pages available for the object type.
+        *
+        * @since 5.5.0
+        *
+        * @param string $taxonomy Taxonomy name.
+        * @return int Total number of pages.
+        */
+       public function get_max_num_pages( $taxonomy = '' ) {
+               if ( empty( $taxonomy ) ) {
+                       return 0;
+               }
+
+               /**
+                * Filters the max number of pages before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param int $max_num_pages The maximum number of pages. Default null.
+                * @param string $taxonomy Taxonomy name.
+                */
+               $max_num_pages = apply_filters( 'wp_sitemaps_taxonomies_pre_max_num_pages', null, $taxonomy );
+
+               if ( null !== $max_num_pages ) {
+                       return $max_num_pages;
+               }
+
+               $term_count = wp_count_terms( $taxonomy, $this->get_taxonomies_query_args( $taxonomy ) );
+
+               return (int) ceil( $term_count / wp_sitemaps_get_max_urls( $this->object_type ) );
+       }
+
+       /**
+        * Returns the query args for retrieving taxonomy terms to list in the sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param string $taxonomy Taxonomy name.
+        * @return array $args Array of WP_Term_Query arguments.
+        */
+       protected function get_taxonomies_query_args( $taxonomy ) {
+               /**
+                * Filters the taxonomy terms query arguments.
+                *
+                * Allows modification of the taxonomy query arguments before querying.
+                *
+                * @see WP_Term_Query for a full list of arguments
+                *
+                * @since 5.5.0
+                *
+                * @param array  $args     Array of WP_Term_Query arguments.
+                * @param string $taxonomy Taxonomy name.
+                */
+               $args = apply_filters(
+                       'wp_sitemaps_taxonomies_query_args',
+                       array(
+                               'fields'                 => 'ids',
+                               'taxonomy'               => $taxonomy,
+                               'orderby'                => 'term_order',
+                               'number'                 => wp_sitemaps_get_max_urls( $this->object_type ),
+                               'hide_empty'             => true,
+                               'hierarchical'           => false,
+                               'update_term_meta_cache' => false,
+                       ),
+                       $taxonomy
+               );
+
+               return $args;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-taxonomies.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsprovidersclasswpsitemapsusersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-users.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-users.php                              (rev 0)
+++ trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-users.php        2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,163 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: WP_Sitemaps_Users class
+ *
+ * Builds the sitemaps for the 'user' object type.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Users XML sitemap provider.
+ *
+ * @since 5.5.0
+ */
+class WP_Sitemaps_Users extends WP_Sitemaps_Provider {
+       /**
+        * WP_Sitemaps_Users constructor.
+        *
+        * @since 5.5.0
+        */
+       public function __construct() {
+               $this->name        = 'users';
+               $this->object_type = 'user';
+       }
+
+       /**
+        * Gets a URL list for a user sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param int    $page_num       Page of results.
+        * @param string $object_subtype Optional. Not applicable for Users but
+        *                               required for compatibility with the parent
+        *                               provider class. Default empty.
+        * @return array $url_list Array of URLs for a sitemap.
+        */
+       public function get_url_list( $page_num, $object_subtype = '' ) {
+               /**
+                * Filters the users URL list before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param array  $url_list The URL list. Default null.
+                * @param int    $page_num Page of results.
+                */
+               $url_list = apply_filters(
+                       'wp_sitemaps_users_pre_url_list',
+                       null,
+                       $page_num
+               );
+
+               if ( null !== $url_list ) {
+                       return $url_list;
+               }
+
+               $args          = $this->get_users_query_args();
+               $args['paged'] = $page_num;
+
+               $query    = new WP_User_Query( $args );
+               $users    = $query->get_results();
+               $url_list = array();
+
+               foreach ( $users as $user ) {
+                       $sitemap_entry = array(
+                               'loc' => get_author_posts_url( $user->ID ),
+                       );
+
+                       /**
+                        * Filters the sitemap entry for an individual user.
+                        *
+                        * @since 5.5.0
+                        *
+                        * @param array   $sitemap_entry Sitemap entry for the user.
+                        * @param WP_User $user          User object.
+                        */
+                       $sitemap_entry = apply_filters( 'wp_sitemaps_users_entry', $sitemap_entry, $user );
+                       $url_list[]    = $sitemap_entry;
+               }
+
+               return $url_list;
+       }
+
+       /**
+        * Gets the max number of pages available for the object type.
+        *
+        * @since 5.5.0
+        *
+        * @see WP_Sitemaps_Provider::max_num_pages
+        *
+        * @param string $object_subtype Optional. Not applicable for Users but
+        *                               required for compatibility with the parent
+        *                               provider class. Default empty.
+        * @return int Total page count.
+        */
+       public function get_max_num_pages( $object_subtype = '' ) {
+               /**
+                * Filters the max number of pages before it is generated.
+                *
+                * Passing a non-null value will effectively short-circuit the generation,
+                * returning that value instead.
+                *
+                * @since 5.5.0
+                *
+                * @param int $max_num_pages The maximum number of pages. Default null.
+                */
+               $max_num_pages = apply_filters( 'wp_sitemaps_users_pre_max_num_pages', null );
+
+               if ( null !== $max_num_pages ) {
+                       return $max_num_pages;
+               }
+
+               $args  = $this->get_users_query_args();
+               $query = new WP_User_Query( $args );
+
+               $total_users = $query->get_total();
+
+               return (int) ceil( $total_users / wp_sitemaps_get_max_urls( $this->object_type ) );
+       }
+
+       /**
+        * Returns the query args for retrieving users to list in the sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @return array $args Array of WP_User_Query arguments.
+        */
+       protected function get_users_query_args() {
+               $public_post_types = get_post_types(
+                       array(
+                               'public' => true,
+                       )
+               );
+
+               // We're not supporting sitemaps for author pages for attachments.
+               unset( $public_post_types['attachment'] );
+
+               /**
+                * Filters the query arguments for authors with public posts.
+                *
+                * Allows modification of the authors query arguments before querying.
+                *
+                * @see WP_User_Query for a full list of arguments
+                *
+                * @since 5.5.0
+                *
+                * @param array $args Array of WP_User_Query arguments.
+                */
+               $args = apply_filters(
+                       'wp_sitemaps_users_query_args',
+                       array(
+                               'has_published_posts' => array_keys( $public_post_types ),
+                               'number'              => wp_sitemaps_get_max_urls( $this->object_type ),
+                       )
+               );
+
+               return $args;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-users.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludessitemapsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/sitemaps.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/sitemaps.php                                (rev 0)
+++ trunk/src/wp-includes/sitemaps.php  2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,119 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: Public functions
+ *
+ * This file contains a variety of public functions developers can use to interact with
+ * the XML Sitemaps API.
+ *
+ * @package WordPress
+ * @subpackage Sitemaps
+ * @since 5.5.0
+ */
+
+/**
+ * Retrieves the current Sitemaps server instance.
+ *
+ * @since 5.5.0
+ *
+ * @return WP_Sitemaps|null Sitemaps instance, or null if sitemaps are disabled.
+ */
+function wp_sitemaps_get_server() {
+       /**
+        * Global Core Sitemaps instance.
+        *
+        * @since 5.5.0
+        *
+        * @var WP_Sitemaps $wp_sitemaps
+        */
+       global $wp_sitemaps;
+
+       $is_enabled = (bool) get_option( 'blog_public' );
+
+       /**
+        * Filters whether XML Sitemaps are enabled or not.
+        *
+        * @since 5.5.0
+        *
+        * @param bool $is_enabled Whether XML Sitemaps are enabled or not. Defaults to true for public sites.
+        */
+       $is_enabled = (bool) apply_filters( 'wp_sitemaps_is_enabled', $is_enabled );
+
+       if ( ! $is_enabled ) {
+               return null;
+       }
+
+       // If there isn't a global instance, set and bootstrap the sitemaps system.
+       if ( empty( $wp_sitemaps ) ) {
+               $wp_sitemaps = new WP_Sitemaps();
+               $wp_sitemaps->init();
+
+               /**
+                * Fires when initializing the Sitemaps object.
+                *
+                * Additional sitemaps should be registered on this hook.
+                *
+                * @since 5.5.0
+                *
+                * @param WP_Sitemaps $sitemaps Server object.
+                */
+               do_action( 'wp_sitemaps_init', $wp_sitemaps );
+       }
+
+       return $wp_sitemaps;
+}
+
+/**
+ * Gets a list of sitemap providers.
+ *
+ * @since 5.5.0
+ *
+ * @return array $sitemaps A list of registered sitemap providers.
+ */
+function wp_get_sitemaps() {
+       $sitemaps = wp_sitemaps_get_server();
+
+       if ( ! $sitemaps ) {
+               return array();
+       }
+
+       return $sitemaps->registry->get_sitemaps();
+}
+
+/**
+ * Registers a new sitemap provider.
+ *
+ * @since 5.5.0
+ *
+ * @param string               $name     Unique name for the sitemap provider.
+ * @param WP_Sitemaps_Provider $provider The `Sitemaps_Provider` instance implementing the sitemap.
+ * @return bool Returns true if the sitemap was added. False on failure.
+ */
+function wp_register_sitemap( $name, WP_Sitemaps_Provider $provider ) {
+       $sitemaps = wp_sitemaps_get_server();
+
+       if ( ! $sitemaps ) {
+               return false;
+       }
+
+       return $sitemaps->registry->add_sitemap( $name, $provider );
+}
+
+/**
+ * Gets the maximum number of URLs for a sitemap.
+ *
+ * @since 5.5.0
+ *
+ * @param string $object_type Object type for sitemap to be filtered (e.g. 'post', 'term', 'user').
+ * @return int The maximum number of URLs.
+ */
+function wp_sitemaps_get_max_urls( $object_type ) {
+       /**
+        * Filters the maximum number of URLs displayed on a sitemap.
+        *
+        * @since 5.5.0
+        *
+        * @param int    $max_urls    The maximum number of URLs included in a sitemap. Default 2000.
+        * @param string $object_type Object type for sitemap to be filtered (e.g. 'post', 'term', 'user').
+        */
+       return apply_filters( 'wp_sitemaps_max_urls', 2000, $object_type );
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/sitemaps.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/src/wp-settings.php   2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -263,6 +263,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-user-meta-fields.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-search-handler.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-post-search-handler.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/sitemaps.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-index.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-provider.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-registry.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-renderer.php';
+require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-stylesheet.php';
+require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-posts.php';
+require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-taxonomies.php';
+require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-users.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-type.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-styles-registry.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-type-registry.php';
</span></span></pre></div>
<a id="trunktestsphpunitincludesbootstrapphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/includes/bootstrap.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/bootstrap.php        2020-06-17 14:46:23 UTC (rev 48071)
+++ trunk/tests/phpunit/includes/bootstrap.php  2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -153,6 +153,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/testcase-xmlrpc.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/testcase-ajax.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/testcase-canonical.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require __DIR__ . '/testcase-xml.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/exceptions.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/utils.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/spy-rest-server.php';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -159,6 +160,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/class-wp-rest-test-search-handler.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/class-wp-rest-test-configurable-controller.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/class-wp-fake-block-type.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require __DIR__ . '/class-wp-sitemaps-test-provider.php';
+require __DIR__ . '/class-wp-sitemaps-empty-test-provider.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * A class to handle additional command line arguments passed to the script.
</span></span></pre></div>
<a id="trunktestsphpunitincludesclasswpsitemapsemptytestproviderphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/includes/class-wp-sitemaps-empty-test-provider.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/class-wp-sitemaps-empty-test-provider.php                            (rev 0)
+++ trunk/tests/phpunit/includes/class-wp-sitemaps-empty-test-provider.php      2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,38 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Class WP_Sitemaps_Empty_Test_Provider.
+ *
+ * Provides test data for additional registered providers.
+ */
+class WP_Sitemaps_Empty_Test_Provider extends WP_Sitemaps_Provider {
+       /**
+        * WP_Sitemaps_Empty_Test_Provider constructor.
+        *
+        * @param string $object_type Optional. Object type name to use. Default 'test'.
+        */
+       public function __construct( $object_type = 'test' ) {
+               $this->object_type = $object_type;
+       }
+
+       /**
+        * Gets a URL list for a sitemap.
+        *
+        * @param int    $page_num       Page of results.
+        * @param string $object_subtype Optional. Object subtype name. Default empty.
+        * @return array List of URLs for a sitemap.
+        */
+       public function get_url_list( $page_num, $object_subtype = '' ) {
+               return array();
+       }
+
+       /**
+        * Query for determining the number of pages.
+        *
+        * @param string $object_subtype Optional. Object subtype. Default empty.
+        * @return int Total number of pages.
+        */
+       public function get_max_num_pages( $object_subtype = '' ) {
+               return 0;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/includes/class-wp-sitemaps-empty-test-provider.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitincludesclasswpsitemapstestproviderphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/includes/class-wp-sitemaps-test-provider.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/class-wp-sitemaps-test-provider.php                          (rev 0)
+++ trunk/tests/phpunit/includes/class-wp-sitemaps-test-provider.php    2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,52 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Class WP_Sitemaps_Test_Provider.
+ *
+ * Provides test data for additional registered providers.
+ */
+class WP_Sitemaps_Test_Provider extends WP_Sitemaps_Provider {
+       /**
+        * WP_Sitemaps_Posts constructor.
+        *
+        * @param string $object_type Optional. Object type name to use. Default 'test'.
+        */
+       public function __construct( $object_type = 'test' ) {
+               $this->object_type = $object_type;
+       }
+
+       /**
+        * Return the public post types, which excludes nav_items and similar types.
+        * Attachments are also excluded. This includes custom post types with public = true
+        *
+        * @return array Map of object subtype objects (WP_Post_Type) keyed by their name.
+        */
+       public function get_object_subtypes() {
+               return array(
+                       'type-1' => (object) array( 'name' => 'type-1' ),
+                       'type-2' => (object) array( 'name' => 'type-2' ),
+                       'type-3' => (object) array( 'name' => 'type-3' ),
+               );
+       }
+
+       /**
+        * Gets a URL list for a sitemap.
+        *
+        * @param int    $page_num       Page of results.
+        * @param string $object_subtype Optional. Object subtype name. Default empty.
+        * @return array List of URLs for a sitemap.
+        */
+       public function get_url_list( $page_num, $object_subtype = '' ) {
+               return array();
+       }
+
+       /**
+        * Query for determining the number of pages.
+        *
+        * @param string $object_subtype Optional. Object subtype. Default empty.
+        * @return int Total number of pages.
+        */
+       public function get_max_num_pages( $object_subtype = '' ) {
+               return 4;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/includes/class-wp-sitemaps-test-provider.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitincludesnormalizexmlxsl"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/includes/normalize-xml.xsl</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/normalize-xml.xsl                            (rev 0)
+++ trunk/tests/phpunit/includes/normalize-xml.xsl      2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,76 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?xml version='1.0' encoding='UTF-8' ?>
+<!--
+       Normalize an XML document to make it easier to compare whether 2 documents will
+       be seen as "equal" to an XML processor.
+
+       The normalization is similiar, in spirit, to {@link https://www.w3.org/TR/xml-c14n11/ Canonical XML},
+       but without some aspects of C14N that make the kinds of assertions we need difficult.
+
+       For example, the following XML documents will be interpreted the same by an XML processor,
+       even though a string comparison of them would show differences:
+
+       <root xmlns='urn:example'>
+               <ns0:child xmlns:ns0='urn:another-example'>this is a test</ns0:child>
+       </root>
+
+       <ns0:root xmlns:ns0='urn:example'>
+               <child xmlns='urn:another-example'>this is a test</child>
+       </ns0:root>
+  -->
+<xsl:transform
+               xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+               version='1.0'
+       >
+
+       <!--
+               Output UTF-8 XML, no indendation and all CDATA sections replaced with their character content. 
+         -->
+       <xsl:output
+               method='xml'
+               indent='no'
+               cdata-section-elements=''
+               encoding='UTF-8' />
+
+       <!--
+               Strip insignificant white space.
+         -->
+       <xsl:strip-space elements='*' />
+
+       <!--
+               Noramlize elements by not relying on the prefix used in the input document
+               and ordering attributes first by namespace-uri and then by local-name.
+         -->
+       <xsl:template match='*' priority='10'>
+               <xsl:element name='{local-name()}' namespace='{namespace-uri()}'>
+                       <xsl:apply-templates select='@*'>
+                               <xsl:sort select='namespace-uri()' />
+                               <xsl:sort select='local-name()' />
+                       </xsl:apply-templates>
+
+                       <xsl:apply-templates select='node()' />
+               </xsl:element>
+       </xsl:template>
+
+       <!--
+               Noramlize attributes by not relying on the prefix used in the input document.
+         -->
+       <xsl:template match='@*'>
+               <xsl:attribute name='{local-name()}' namespace='{namespace-uri()}'>
+                       <xsl:value-of select='.' />
+               </xsl:attribute>
+       </xsl:template>
+
+       <!--
+               Strip comments. 
+         -->
+       <xsl:template match='comment()' priority='10' />
+
+       <!--
+               Pass all other nodes through unchanged.  
+         -->
+       <xsl:template match='node()'>
+               <xsl:copy>
+                       <xsl:apply-templates select='node()' />
+               </xsl:copy>
+       </xsl:template>
+</xsl:transform>
</ins></span></pre></div>
<a id="trunktestsphpunitincludestestcasexmlphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/includes/testcase-xml.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/testcase-xml.php                             (rev 0)
+++ trunk/tests/phpunit/includes/testcase-xml.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,92 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+abstract class WP_Test_XML_TestCase extends WP_UnitTestCase {
+       /**
+        * Load XML from a string.
+        *
+        * @param string $xml
+        * @param int    $options Bitwise OR of the {@link https://www.php.net/manual/en/libxml.constants.php libxml option constants}.
+        *                        Default is 0.
+        * @return DOMDocument The DOMDocument object loaded from the XML.
+        */
+       public function loadXML( $xml, $options = 0 ) {
+               // Suppress PHP warnings generated by DOMDocument::loadXML(), which would cause
+               // PHPUnit to incorrectly report an error instead of a just a failure.
+               $internal = libxml_use_internal_errors( true );
+               libxml_clear_errors();
+
+               $xml_dom = new DOMDocument();
+               $xml_dom->loadXML( $xml, $options );
+               $libxml_last_error = libxml_get_last_error();
+
+               $this->assertFalse(
+                       isset( $libxml_last_error->message ),
+                       isset( $libxml_last_error->message ) ? sprintf( 'Non-well-formed XML: %s.', $libxml_last_error->message ) : ''
+               );
+
+               // Restore default error handler.
+               libxml_use_internal_errors( $internal );
+               libxml_clear_errors();
+
+               return $xml_dom;
+       }
+
+       /**
+        * Normalize an XML document to make comparing two documents easier.
+        *
+        * @param string $xml
+        * @param int    $options Bitwise OR of the {@link https://www.php.net/manual/en/libxml.constants.php libxml option constants}.
+        *                        Default is 0.
+        * @return string The normalized form of `$xml`.
+        */
+       public function normalizeXML( $xml, $options = 0 ) {
+               if ( ! class_exists( 'XSLTProcessor' ) ) {
+                       $this->markTestSkipped( 'This test requires the XSL extension.' );
+               }
+
+               static $xslt_proc;
+
+               if ( ! $xslt_proc ) {
+                       $xslt_proc = new XSLTProcessor();
+                       $xslt_proc->importStyleSheet( simplexml_load_file( __DIR__ . '/normalize-xml.xsl' ) );
+               }
+
+               return $xslt_proc->transformToXML( $this->loadXML( $xml, $options ) );
+       }
+
+       /**
+        * Reports an error identified by `$message` if the namespace normalized form of the XML document in `$actualXml`
+        * is equal to the namespace normalized form of the XML document in `$expectedXml`.
+        *
+        * This is similar to {@link https://phpunit.de/manual/6.5/en/appendixes.assertions.html#appendixes.assertions.assertXmlStringEqualsXmlString assertXmlStringEqualsXmlString()}
+        * except that differences in namespace prefixes are normalized away, such that given
+        * `$actualXml = "<root xmlns='urn:wordpress.org'><child/></root>";` and
+        * `$expectedXml = "<ns0:root xmlns:ns0='urn:wordpress.org'><ns0:child></ns0:root>";`
+        * then `$this->assertXMLEquals( $expectedXml, $actualXml )` will succeed.
+        *
+        * @param string $expectedXml
+        * @param string $actualXml
+        * @param string $message   Optional. Message to display when the assertion fails.
+        */
+       public function assertXMLEquals( $expectedXml, $actualXml, $message = '' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+               $this->assertEquals( $this->normalizeXML( $expectedXml ), $this->normalizeXML( $actualXml ), $message ); //phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+       }
+
+       /**
+        * Reports an error identified by `$message` if the namespace normalized form of the XML document in `$actualXml`
+        * is not equal to the namespace normalized form of the XML document in `$expectedXml`.
+        *
+        * This is similar to {@link https://phpunit.de/manual/6.5/en/appendixes.assertions.html#appendixes.assertions.assertXmlStringEqualsXmlString assertXmlStringNotEqualsXmlString()}
+        * except that differences in namespace prefixes are normalized away, such that given
+        * `$actualXml = "<root xmlns='urn:wordpress.org'><child></root>";` and
+        * `$expectedXml = "<ns0:root xmlns:ns0='urn:wordpress.org'><ns0:child/></ns0:root>";`
+        * then `$this->assertXMLNotEquals( $expectedXml, $actualXml )` will fail.
+        *
+        * @param string $expectedXml
+        * @param string $actualXml
+        * @param string $message   Optional. Message to display when the assertion fails.
+        */
+       public function assertXMLNotEquals( $expectedXml, $actualXml, $message = '' ) { //phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+               $this->assertNotEquals( $this->normalizeXML( $expectedXml ), $this->normalizeXML( $actualXml ), $message ); //phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/includes/testcase-xml.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestscanonicalsitemapsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/canonical/sitemaps.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/canonical/sitemaps.php                          (rev 0)
+++ trunk/tests/phpunit/tests/canonical/sitemaps.php    2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,41 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group canonical
+ * @group rewrite
+ * @group query
+ * @group sitemaps
+ */
+class Tests_Canonical_Sitemaps extends WP_Canonical_UnitTestCase {
+       public function setUp() {
+               parent::setUp();
+               $wp_sitemaps = new WP_Sitemaps();
+               $wp_sitemaps->init();
+       }
+
+       public function test_remove_trailing_slashes_for_sitemap_index_requests() {
+               $this->set_permalink_structure( '/%postname%/' );
+               $this->assertCanonical( '/wp-sitemap.xml', '/wp-sitemap.xml' );
+               $this->assertCanonical( '/wp-sitemap.xml/', '/wp-sitemap.xml' );
+       }
+
+       public function test_remove_trailing_slashes_for_sitemap_index_stylesheet_requests() {
+               $this->set_permalink_structure( '/%postname%/' );
+               $this->assertCanonical( '/wp-sitemap-index.xsl', '/wp-sitemap-index.xsl' );
+               $this->assertCanonical( '/wp-sitemap-index.xsl/', '/wp-sitemap-index.xsl' );
+       }
+
+       public function test_remove_trailing_slashes_for_sitemap_requests() {
+               $this->set_permalink_structure( '/%postname%/' );
+               $this->assertCanonical( '/wp-sitemap-posts-post-1.xml', '/wp-sitemap-posts-post-1.xml' );
+               $this->assertCanonical( '/wp-sitemap-posts-post-1.xml/', '/wp-sitemap-posts-post-1.xml' );
+               $this->assertCanonical( '/wp-sitemap-users-1.xml', '/wp-sitemap-users-1.xml' );
+               $this->assertCanonical( '/wp-sitemap-users-1.xml/', '/wp-sitemap-users-1.xml' );
+       }
+
+       public function test_remove_trailing_slashes_for_sitemap_stylesheet_requests() {
+               $this->set_permalink_structure( '/%postname%/' );
+               $this->assertCanonical( '/wp-sitemap.xsl', '/wp-sitemap.xsl' );
+               $this->assertCanonical( '/wp-sitemap.xsl/', '/wp-sitemap.xsl' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/canonical/sitemaps.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsformattingEscXmlphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/formatting/EscXml.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/formatting/EscXml.php                           (rev 0)
+++ trunk/tests/phpunit/tests/formatting/EscXml.php     2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,135 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group formatting
+ */
+class Tests_Formatting_EscXml extends WP_UnitTestCase {
+       /**
+        * Test basic escaping
+        *
+        * @dataProvider _test_esc_xml_basics_dataprovider
+        *
+        * @param string $source   The source string to be escaped.
+        * @param string $expected The expected escaped value of `$source`.
+        */
+       public function test_esc_xml_basics( $source, $expected ) {
+               $actual = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * Data provider for `test_esc_xml_basics()`.
+        *
+        * @return array {
+        *    @type string $source   The source string to be escaped.
+        *    @type string $expected The expected escaped value of `$source`.
+        * }
+        */
+       public function _test_esc_xml_basics_dataprovider() {
+               return array(
+                       // Simple string.
+                       array(
+                               'The quick brown fox.',
+                               'The quick brown fox.',
+                       ),
+                       // URL with &.
+                       array(
+                               'http://localhost/trunk/wp-login.php?action=logout&_wpnonce=cd57d75985',
+                               'http://localhost/trunk/wp-login.php?action=logout&amp;_wpnonce=cd57d75985',
+                       ),
+                       // SQL query w/ single quotes.
+                       array(
+                               "SELECT meta_key, meta_value FROM wp_trunk_sitemeta WHERE meta_key IN ('site_name', 'siteurl', 'active_sitewide_plugins', '_site_transient_timeout_theme_roots', '_site_transient_theme_roots', 'site_admins', 'can_compress_scripts', 'global_terms_enabled') AND site_id = 1",
+                               'SELECT meta_key, meta_value FROM wp_trunk_sitemeta WHERE meta_key IN (&apos;site_name&apos;, &apos;siteurl&apos;, &apos;active_sitewide_plugins&apos;, &apos;_site_transient_timeout_theme_roots&apos;, &apos;_site_transient_theme_roots&apos;, &apos;site_admins&apos;, &apos;can_compress_scripts&apos;, &apos;global_terms_enabled&apos;) AND site_id = 1',
+                       ),
+               );
+       }
+
+       public function test_escapes_ampersands() {
+               $source   = 'penn & teller & at&t';
+               $expected = 'penn &amp; teller &amp; at&amp;t';
+               $actual   = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function test_escapes_greater_and_less_than() {
+               $source   = 'this > that < that <randomhtml />';
+               $expected = 'this &gt; that &lt; that &lt;randomhtml /&gt;';
+               $actual   = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function test_escapes_html_named_entities() {
+               $source   = 'this &amp; is a &hellip; followed by &rsaquo; and more and a &nonexistent; entity';
+               $expected = 'this &amp; is a â€¦ followed by â€º and more and a &amp;nonexistent; entity';
+               $actual   = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function test_ignores_existing_entities() {
+               $source = '&#038; &#x00A3; &#x22; &amp;';
+               // note that _wp_specialchars() strips leading 0's from numeric character references.
+               $expected = '&#038; &#xA3; &#x22; &amp;';
+               $actual   = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * Test that CDATA Sections are not escaped.
+        *
+        * @dataProvider _test_ignores_cdata_sections_dataprovider
+        *
+        * @param string $source   The source string to be escaped.
+        * @param string $expected The expected escaped value of `$source`.
+        */
+       public function test_ignores_cdata_sections( $source, $expected ) {
+               $actual = esc_xml( $source );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * Data provider for `test_ignores_cdata_sections()`.
+        *
+        * @return array {
+        *    @type string $source   The source string to be escaped.
+        *    @type string $expected The expected escaped value of `$source`.
+        * }
+        */
+       public function _test_ignores_cdata_sections_dataprovider() {
+               return array(
+                       // basic CDATA Section containing chars that would otherwise be escaped if not in a CDATA Section
+                       // not to mention the CDATA Section markup itself :-)
+                       // $source contains embedded newlines to test that the regex that ignores CDATA Sections
+                       // correctly handles that case.
+                       array(
+                               "This is\na<![CDATA[test of\nthe <emergency>]]>\nbroadcast system",
+                               "This is\na<![CDATA[test of\nthe <emergency>]]>\nbroadcast system",
+                       ),
+                       // string with chars that should be escaped as well as a CDATA Section that should be not be.
+                       array(
+                               'This is &hellip; a <![CDATA[test of the <emergency>]]> broadcast <system />',
+                               'This is â€¦ a <![CDATA[test of the <emergency>]]> broadcast &lt;system /&gt;',
+                       ),
+                       // Same as above, but with the CDATA Section at the start of the string.
+                       array(
+                               '<![CDATA[test of the <emergency>]]> This is &hellip; a broadcast <system />',
+                               '<![CDATA[test of the <emergency>]]> This is â€¦ a broadcast &lt;system /&gt;',
+                       ),
+                       // Same as above, but with the CDATA Section at the end of the string.
+                       array(
+                               'This is &hellip; a broadcast <system /><![CDATA[test of the <emergency>]]>',
+                               'This is â€¦ a broadcast &lt;system /&gt;<![CDATA[test of the <emergency>]]>',
+                       ),
+                       // Multiple CDATA Sections.
+                       array(
+                               'This is &hellip; a <![CDATA[test of the <emergency>]]> &broadcast; <![CDATA[<system />]]>',
+                               'This is â€¦ a <![CDATA[test of the <emergency>]]> &amp;broadcast; <![CDATA[<system />]]>',
+                       ),
+                       // Ensure that ']]>' that does not mark the end of a CDATA Section is escaped.
+                       array(
+                               '<![CDATA[<&]]>]]>',
+                               '<![CDATA[<&]]>]]&gt;',
+                       ),
+               );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/formatting/EscXml.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapsfunctionsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/functions.php                          (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/functions.php    2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,61 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_Sitemaps_Functions extends WP_UnitTestCase {
+       /**
+        * Test getting the correct number of URLs for a sitemap.
+        */
+       public function test_wp_sitemaps_get_max_urls() {
+               // Apply a filter to test filterable values.
+               add_filter( 'wp_sitemaps_max_urls', array( $this, '_filter_max_url_value' ), 10, 2 );
+
+               $expected_posts      = wp_sitemaps_get_max_urls( 'post' );
+               $expected_taxonomies = wp_sitemaps_get_max_urls( 'term' );
+               $expected_users      = wp_sitemaps_get_max_urls( 'user' );
+
+               $this->assertEquals( $expected_posts, 300, 'Can not confirm max URL number for posts.' );
+               $this->assertEquals( $expected_taxonomies, 50, 'Can not confirm max URL number for taxonomies.' );
+               $this->assertEquals( $expected_users, 1, 'Can not confirm max URL number for users.' );
+       }
+
+       /**
+        * Callback function for testing the `sitemaps_max_urls` filter.
+        *
+        * @param int    $max_urls The maximum number of URLs included in a sitemap. Default 2000.
+        * @param string $type     Optional. The type of sitemap to be filtered. Default empty.
+        * @return int The maximum number of URLs.
+        */
+       public function _filter_max_url_value( $max_urls, $type ) {
+               switch ( $type ) {
+                       case 'post':
+                               return 300;
+                       case 'term':
+                               return 50;
+                       case 'user':
+                               return 1;
+                       default:
+                               return $max_urls;
+               }
+       }
+
+       /**
+        * Test wp_get_sitemaps default functionality
+        */
+       public function test_wp_get_sitemaps() {
+               $sitemaps = wp_get_sitemaps();
+
+               $expected = array(
+                       'posts'      => 'WP_Sitemaps_Posts',
+                       'taxonomies' => 'WP_Sitemaps_Taxonomies',
+                       'users'      => 'WP_Sitemaps_Users',
+               );
+
+               $this->assertEquals( array_keys( $expected ), array_keys( $sitemaps ), 'Unable to confirm default sitemap types are registered.' );
+
+               foreach ( $expected as $name => $provider ) {
+                       $this->assertTrue( is_a( $sitemaps[ $name ], $provider ), "Default $name sitemap is not a $provider object." );
+               }
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/functions.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsindexphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-index.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-index.php                             (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-index.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,51 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Index extends WP_UnitTestCase {
+       public function test_get_sitemap_list() {
+               $registry = new WP_Sitemaps_Registry();
+
+               /*
+                * The test provider has 3 subtypes.
+                * Each subtype has 4 pages with results.
+                * There are 2 providers registered.
+                * Hence, 3*4*2=24.
+                */
+               $registry->add_sitemap( 'foo', new WP_Sitemaps_Test_Provider( 'foo' ) );
+               $registry->add_sitemap( 'bar', new WP_Sitemaps_Test_Provider( 'bar' ) );
+
+               $sitemap_index = new WP_Sitemaps_Index( $registry );
+               $this->assertCount( 24, $sitemap_index->get_sitemap_list() );
+       }
+
+       public function test_get_sitemap_list_no_entries() {
+               $registry = new WP_Sitemaps_Registry();
+
+               $registry->add_sitemap( 'foo', new WP_Sitemaps_Empty_Test_Provider( 'foo' ) );
+
+               $sitemap_index = new WP_Sitemaps_Index( $registry );
+               $this->assertCount( 0, $sitemap_index->get_sitemap_list() );
+       }
+
+       public function test_get_index_url() {
+               $sitemap_index = new WP_Sitemaps_Index( new WP_Sitemaps_Registry() );
+               $index_url     = $sitemap_index->get_index_url();
+
+               $this->assertStringEndsWith( '/?sitemap=index', $index_url );
+       }
+
+       public function test_get_index_url_pretty_permalinks() {
+               // Set permalinks for testing.
+               $this->set_permalink_structure( '/%year%/%postname%/' );
+
+               $sitemap_index = new WP_Sitemaps_Index( new WP_Sitemaps_Registry() );
+               $index_url     = $sitemap_index->get_index_url();
+
+               // Clean up permalinks.
+               $this->set_permalink_structure();
+
+               $this->assertStringEndsWith( '/wp-sitemap.xml', $index_url );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-index.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapspostsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-posts.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-posts.php                             (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-posts.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,49 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Posts extends WP_UnitTestCase {
+       /**
+        * Test ability to filter object subtypes.
+        */
+       public function test_filter_sitemaps_post_types() {
+               $posts_provider = new WP_Sitemaps_Posts();
+
+               // Return an empty array to show that the list of subtypes is filterable.
+               add_filter( 'wp_sitemaps_post_types', '__return_empty_array' );
+               $subtypes = $posts_provider->get_object_subtypes();
+
+               $this->assertEquals( array(), $subtypes, 'Could not filter posts subtypes.' );
+       }
+
+       /**
+        * Test `wp_sitemaps_posts_show_on_front_entry` filter.
+        */
+       public function test_posts_show_on_front_entry() {
+               $posts_provider = new WP_Sitemaps_Posts();
+               update_option( 'show_on_front', 'page' );
+
+               add_filter( 'wp_sitemaps_posts_show_on_front_entry', array( $this, '_show_on_front_entry' ) );
+
+               $url_list = $posts_provider->get_url_list( 1, 'page' );
+
+               $this->assertEquals( array(), $url_list );
+
+               update_option( 'show_on_front', 'posts' );
+
+               $url_list      = $posts_provider->get_url_list( 1, 'page' );
+               $sitemap_entry = array_shift( $url_list );
+
+               $this->assertTrue( isset( $sitemap_entry['lastmod'] ) );
+       }
+
+       /**
+        * Callback for 'wp_sitemaps_posts_show_on_front_entry' filter.
+        */
+       public function _show_on_front_entry( $sitemap_entry ) {
+               $sitemap_entry['lastmod'] = wp_date( DATE_W3C, time() );
+
+               return $sitemap_entry;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-posts.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsregistryphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-registry.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-registry.php                          (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-registry.php    2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,33 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Registry extends WP_UnitTestCase {
+       public function test_add_sitemap() {
+               $provider = new WP_Sitemaps_Test_Provider();
+               $registry = new WP_Sitemaps_Registry();
+
+               $actual   = $registry->add_sitemap( 'foo', $provider );
+               $sitemaps = $registry->get_sitemaps();
+
+               $this->assertTrue( $actual );
+               $this->assertCount( 1, $sitemaps );
+               $this->assertSame( $sitemaps['foo'], $provider, 'Can not confirm sitemap registration is working.' );
+       }
+
+       public function test_add_sitemap_prevent_duplicates() {
+               $provider1 = new WP_Sitemaps_Test_Provider();
+               $provider2 = new WP_Sitemaps_Test_Provider();
+               $registry  = new WP_Sitemaps_Registry();
+
+               $actual1  = $registry->add_sitemap( 'foo', $provider1 );
+               $actual2  = $registry->add_sitemap( 'foo', $provider2 );
+               $sitemaps = $registry->get_sitemaps();
+
+               $this->assertTrue( $actual1 );
+               $this->assertFalse( $actual2 );
+               $this->assertCount( 1, $sitemaps );
+               $this->assertSame( $sitemaps['foo'], $provider1, 'Can not confirm sitemap registration is working.' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-registry.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsrendererphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-renderer.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-renderer.php                          (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-renderer.php    2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,283 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Renderer extends WP_Test_XML_TestCase {
+       public function test_get_sitemap_stylesheet_url() {
+               $sitemap_renderer = new WP_Sitemaps_Renderer();
+               $stylesheet_url   = $sitemap_renderer->get_sitemap_stylesheet_url();
+
+               $this->assertStringEndsWith( '/?sitemap-stylesheet=sitemap', $stylesheet_url );
+       }
+
+       public function test_get_sitemap_stylesheet_url_pretty_permalinks() {
+               // Set permalinks for testing.
+               $this->set_permalink_structure( '/%year%/%postname%/' );
+
+               $sitemap_renderer = new WP_Sitemaps_Renderer();
+               $stylesheet_url   = $sitemap_renderer->get_sitemap_stylesheet_url();
+
+               // Clean up permalinks.
+               $this->set_permalink_structure();
+
+               $this->assertStringEndsWith( '/wp-sitemap.xsl', $stylesheet_url );
+       }
+
+       public function test_get_sitemap_index_stylesheet_url() {
+               $sitemap_renderer = new WP_Sitemaps_Renderer();
+               $stylesheet_url   = $sitemap_renderer->get_sitemap_index_stylesheet_url();
+
+               $this->assertStringEndsWith( '/?sitemap-stylesheet=index', $stylesheet_url );
+       }
+
+       public function test_get_sitemap_index_stylesheet_url_pretty_permalinks() {
+               // Set permalinks for testing.
+               $this->set_permalink_structure( '/%year%/%postname%/' );
+
+               $sitemap_renderer = new WP_Sitemaps_Renderer();
+               $stylesheet_url   = $sitemap_renderer->get_sitemap_index_stylesheet_url();
+
+               // Clean up permalinks.
+               $this->set_permalink_structure();
+
+               $this->assertStringEndsWith( '/wp-sitemap-index.xsl', $stylesheet_url );
+       }
+
+       /**
+        * Test XML output for the sitemap index renderer.
+        */
+       public function test_get_sitemap_index_xml() {
+               $entries = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-category-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-post_tag-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-users-1.xml',
+                       ),
+               );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $actual   = $renderer->get_sitemap_index_xml( $entries );
+               $expected = '<?xml version="1.0" encoding="UTF-8"?>' .
+                                       '<?xml-stylesheet type="text/xsl" href="http://' . WP_TESTS_DOMAIN . '/?sitemap-stylesheet=index" ?>' .
+                                       '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' .
+                                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml</loc></sitemap>' .
+                                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml</loc></sitemap>' .
+                                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-category-1.xml</loc></sitemap>' .
+                                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-post_tag-1.xml</loc></sitemap>' .
+                                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-users-1.xml</loc></sitemap>' .
+                                       '</sitemapindex>';
+
+               $this->assertXMLEquals( $expected, $actual, 'Sitemap index markup incorrect.' );
+       }
+
+       /**
+        * Test XML output for the sitemap index renderer with lastmod attributes.
+        */
+       public function test_get_sitemap_index_xml_with_lastmod() {
+               $entries = array(
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml',
+                               'lastmod' => '2005-01-01',
+                       ),
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml',
+                               'lastmod' => '2005-01-01',
+                       ),
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-category-1.xml',
+                               'lastmod' => '2005-01-01',
+                       ),
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-post_tag-1.xml',
+                               'lastmod' => '2005-01-01',
+                       ),
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-users-1.xml',
+                               'lastmod' => '2005-01-01',
+                       ),
+               );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $actual   = $renderer->get_sitemap_index_xml( $entries );
+               $expected = '<?xml version="1.0" encoding="UTF-8"?>' .
+                       '<?xml-stylesheet type="text/xsl" href="http://' . WP_TESTS_DOMAIN . '/?sitemap-stylesheet=index" ?>' .
+                       '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' .
+                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml</loc><lastmod>2005-01-01</lastmod></sitemap>' .
+                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml</loc><lastmod>2005-01-01</lastmod></sitemap>' .
+                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-category-1.xml</loc><lastmod>2005-01-01</lastmod></sitemap>' .
+                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-post_tag-1.xml</loc><lastmod>2005-01-01</lastmod></sitemap>' .
+                       '<sitemap><loc>http://' . WP_TESTS_DOMAIN . '/wp-sitemap-users-1.xml</loc><lastmod>2005-01-01</lastmod></sitemap>' .
+                       '</sitemapindex>';
+
+               $this->assertXMLEquals( $expected, $actual, 'Sitemap index markup incorrect.' );
+       }
+
+       /**
+        * Test that all children of Q{http://www.sitemaps.org/schemas/sitemap/0.9}sitemap in the
+        * rendered index XML are defined in the Sitemaps spec (i.e., loc, lastmod).
+        *
+        * Note that when a means of adding elements in extension namespaces is settled on,
+        * this test will need to be updated accordingly.
+        *
+        * @expectedIncorrectUsage WP_Sitemaps_Renderer::get_sitemap_index_xml
+        */
+       public function test_get_sitemap_index_xml_extra_elements() {
+               $url_list = array(
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml',
+                               'unknown' => 'this is a test',
+                       ),
+                       array(
+                               'loc'     => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml',
+                               'unknown' => 'that was a test',
+                       ),
+               );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $xml_dom = $this->loadXML( $renderer->get_sitemap_index_xml( $url_list ) );
+               $xpath   = new DOMXPath( $xml_dom );
+               $xpath->registerNamespace( 'sitemap', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
+
+               $this->assertEquals(
+                       0,
+                       $xpath->evaluate( "count( /sitemap:sitemapindex/sitemap:sitemap/*[  namespace-uri() != 'http://www.sitemaps.org/schemas/sitemap/0.9' or not( local-name() = 'loc' or local-name() = 'lastmod' ) ] )" ),
+                       'Invalid child of "sitemap:sitemap" in rendered index XML.'
+               );
+       }
+
+       /**
+        * Test XML output for the sitemap index renderer when stylesheet is disabled.
+        */
+       public function test_get_sitemap_index_xml_without_stylesheet() {
+               $entries = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml',
+                       ),
+               );
+
+               add_filter( 'wp_sitemaps_stylesheet_index_url', '__return_false' );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $xml_dom = $this->loadXML( $renderer->get_sitemap_index_xml( $entries ) );
+               $xpath   = new DOMXPath( $xml_dom );
+
+               $this->assertSame(
+                       0,
+                       $xpath->query( '//processing-instruction( "xml-stylesheet" )' )->length,
+                       'Sitemap index incorrectly contains the xml-stylesheet processing instruction.'
+               );
+       }
+
+       /**
+        * Test XML output for the sitemap page renderer.
+        */
+       public function test_get_sitemap_xml() {
+               $url_list = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-1',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-2',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-3',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-4',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-5',
+                       ),
+               );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $actual   = $renderer->get_sitemap_xml( $url_list );
+               $expected = '<?xml version="1.0" encoding="UTF-8"?>' .
+                                       '<?xml-stylesheet type="text/xsl" href="http://' . WP_TESTS_DOMAIN . '/?sitemap-stylesheet=sitemap" ?>' .
+                                       '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' .
+                                       '<url><loc>http://' . WP_TESTS_DOMAIN . '/2019/10/post-1</loc></url>' .
+                                       '<url><loc>http://' . WP_TESTS_DOMAIN . '/2019/10/post-2</loc></url>' .
+                                       '<url><loc>http://' . WP_TESTS_DOMAIN . '/2019/10/post-3</loc></url>' .
+                                       '<url><loc>http://' . WP_TESTS_DOMAIN . '/2019/10/post-4</loc></url>' .
+                                       '<url><loc>http://' . WP_TESTS_DOMAIN . '/2019/10/post-5</loc></url>' .
+                                       '</urlset>';
+
+               $this->assertXMLEquals( $expected, $actual, 'Sitemap page markup incorrect.' );
+       }
+
+       /**
+        * Test XML output for the sitemap page renderer when stylesheet is disabled.
+        */
+       public function test_get_sitemap_xml_without_stylesheet() {
+               $url_list = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-1',
+                       ),
+               );
+
+               add_filter( 'wp_sitemaps_stylesheet_url', '__return_false' );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $xml_dom = $this->loadXML( $renderer->get_sitemap_xml( $url_list ) );
+               $xpath   = new DOMXPath( $xml_dom );
+
+               $this->assertSame(
+                       0,
+                       $xpath->query( '//processing-instruction( "xml-stylesheet" )' )->length,
+                       'Sitemap incorrectly contains the xml-stylesheet processing instruction.'
+               );
+       }
+
+       /**
+        * Test that all children of Q{http://www.sitemaps.org/schemas/sitemap/0.9}url in the
+        * rendered sitemap XML are defined in the Sitemaps spec (i.e., loc, lastmod, changefreq, priority).
+        *
+        * Note that when a means of adding elements in extension namespaces is settled on,
+        * this test will need to be updated accordingly.
+        *
+        * @expectedIncorrectUsage WP_Sitemaps_Renderer::get_sitemap_xml
+        */
+       public function test_get_sitemap_xml_extra_elements() {
+               $url_list = array(
+                       array(
+                               'loc'    => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-1',
+                               'string' => 'value',
+                               'number' => 200,
+                       ),
+                       array(
+                               'loc'    => 'http://' . WP_TESTS_DOMAIN . '/2019/10/post-2',
+                               'string' => 'another value',
+                               'number' => 300,
+                       ),
+               );
+
+               $renderer = new WP_Sitemaps_Renderer();
+
+               $xml_dom = $this->loadXML( $renderer->get_sitemap_xml( $url_list ) );
+               $xpath   = new DOMXPath( $xml_dom );
+               $xpath->registerNamespace( 'sitemap', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
+
+               $this->assertEquals(
+                       0,
+                       $xpath->evaluate( "count( /sitemap:urlset/sitemap:url/*[  namespace-uri() != 'http://www.sitemaps.org/schemas/sitemap/0.9' or not( local-name() = 'loc' or local-name() = 'lastmod' or local-name() = 'changefreq' or local-name() = 'priority' ) ] )" ),
+                       'Invalid child of "sitemap:url" in rendered XML.'
+               );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-renderer.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsstylesheetphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-stylesheet.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-stylesheet.php                                (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-stylesheet.php  2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,42 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Stylesheet extends WP_UnitTestCase {
+       /**
+        * Test that stylesheet content can be filtered.
+        */
+       public function test_filter_sitemaps_stylesheet_content() {
+               $stylesheet = new WP_Sitemaps_Stylesheet();
+
+               add_filter( 'wp_sitemaps_stylesheet_content', '__return_empty_string' );
+               $content = $stylesheet->get_sitemap_stylesheet();
+
+               $this->assertSame( '', $content, 'Could not filter stylesheet content' );
+       }
+
+       /**
+        * Test that sitemap index stylesheet content can be filtered.
+        */
+       public function test_filter_sitemaps_stylesheet_index_content() {
+               $stylesheet = new WP_Sitemaps_Stylesheet();
+
+               add_filter( 'wp_sitemaps_stylesheet_index_content', '__return_empty_string' );
+               $content = $stylesheet->get_sitemap_index_stylesheet();
+
+               $this->assertSame( '', $content, 'Could not filter sitemap index stylesheet content' );
+       }
+
+       /**
+        * Test that sitemap stylesheet CSS can be filtered.
+        */
+       public function test_filter_sitemaps_stylesheet_css() {
+               $stylesheet = new WP_Sitemaps_Stylesheet();
+
+               add_filter( 'wp_sitemaps_stylesheet_css', '__return_empty_string' );
+               $css = $stylesheet->get_stylesheet_css();
+
+               $this->assertSame( '', $css, 'Could not filter sitemap stylesheet CSS' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-stylesheet.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapstaxonomiesphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-taxonomies.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-taxonomies.php                                (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-taxonomies.php  2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,192 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Taxonomies extends WP_UnitTestCase {
+       /**
+        * List of post_tag IDs.
+        *
+        * @var array
+        */
+       public static $post_tags;
+
+       /**
+        * List of category IDs.
+        *
+        * @var array
+        */
+       public static $cats;
+
+       /**
+        * Editor ID for use in some tests.
+        *
+        * @var int
+        */
+       public static $editor_id;
+
+       /**
+        * Set up fixtures.
+        *
+        * @param WP_UnitTest_Factory $factory A WP_UnitTest_Factory object.
+        */
+       public static function wpSetUpBeforeClass( $factory ) {
+               self::$cats      = $factory->term->create_many( 10, array( 'taxonomy' => 'category' ) );
+               self::$post_tags = $factory->term->create_many( 10 );
+               self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) );
+       }
+
+       /**
+        * Test getting a URL list for default taxonomies via
+        * WP_Sitemaps_Taxonomies::get_url_list().
+        */
+       public function test_get_url_list_taxonomies() {
+               // Add the default category to the list of categories we're testing.
+               $categories = array_merge( array( 1 ), self::$cats );
+
+               // Create a test post to calculate update times.
+               $post = self::factory()->post->create_and_get(
+                       array(
+                               'tags_input'    => self::$post_tags,
+                               'post_category' => $categories,
+                       )
+               );
+
+               $tax_provider = new WP_Sitemaps_Taxonomies();
+
+               $cat_list = $tax_provider->get_url_list( 1, 'category' );
+
+               $expected_cats = array_map(
+                       static function ( $id ) use ( $post ) {
+                               return array(
+                                       'loc' => get_term_link( $id, 'category' ),
+                               );
+                       },
+                       $categories
+               );
+
+               $this->assertSame( $expected_cats, $cat_list, 'Category URL list does not match.' );
+
+               $tag_list = $tax_provider->get_url_list( 1, 'post_tag' );
+
+               $expected_tags = array_map(
+                       static function ( $id ) use ( $post ) {
+                               return array(
+                                       'loc' => get_term_link( $id, 'post_tag' ),
+                               );
+                       },
+                       self::$post_tags
+               );
+
+               $this->assertSame( $expected_tags, $tag_list, 'Post Tags URL list does not match.' );
+       }
+
+       /**
+        * Test getting a URL list for a custom taxonomy via
+        * WP_Sitemaps_Taxonomies::get_url_list().
+        */
+       public function test_get_url_list_custom_taxonomy() {
+               wp_set_current_user( self::$editor_id );
+
+               // Create a custom taxonomy for this test.
+               $taxonomy = 'test_taxonomy';
+               register_taxonomy( $taxonomy, 'post' );
+
+               // Create test terms in the custom taxonomy.
+               $terms = self::factory()->term->create_many( 10, array( 'taxonomy' => $taxonomy ) );
+
+               // Create a test post applied to all test terms.
+               $post = self::factory()->post->create_and_get( array( 'tax_input' => array( $taxonomy => $terms ) ) );
+
+               $expected = array_map(
+                       static function ( $id ) use ( $taxonomy, $post ) {
+                               return array(
+                                       'loc' => get_term_link( $id, $taxonomy ),
+                               );
+                       },
+                       $terms
+               );
+
+               $tax_provider = new WP_Sitemaps_Taxonomies();
+
+               $post_list = $tax_provider->get_url_list( 1, $taxonomy );
+
+               // Clean up.
+               unregister_taxonomy_for_object_type( $taxonomy, 'post' );
+
+               $this->assertEquals( $expected, $post_list, 'Custom taxonomy term links are not visible.' );
+       }
+
+       /**
+        * Test getting a URL list for a private custom taxonomy via
+        * WP_Sitemaps_Taxonomies::get_url_list().
+        */
+       public function test_get_url_list_custom_taxonomy_private() {
+               // Create a custom taxonomy for this test.
+               $taxonomy = 'private_taxonomy';
+               register_taxonomy( $taxonomy, 'post', array( 'public' => false ) );
+
+               // Create test terms in the custom taxonomy.
+               $terms = self::factory()->term->create_many( 10, array( 'taxonomy' => $taxonomy ) );
+
+               // Create a test post applied to all test terms.
+               self::factory()->post->create( array( 'tax_input' => array( $taxonomy => $terms ) ) );
+
+               $tax_provider = new WP_Sitemaps_Taxonomies();
+
+               $post_list = $tax_provider->get_url_list( 1, $taxonomy );
+
+               // Clean up.
+               unregister_taxonomy_for_object_type( $taxonomy, 'post' );
+
+               $this->assertEmpty( $post_list, 'Private taxonomy term links are visible.' );
+       }
+
+       /**
+        * Test sitemap index entries with public and private taxonomies.
+        */
+       public function test_get_sitemap_entries_custom_taxonomies() {
+               wp_set_current_user( self::$editor_id );
+
+               // Create a custom public and private taxonomies for this test.
+               register_taxonomy( 'public_taxonomy', 'post' );
+               register_taxonomy( 'private_taxonomy', 'post', array( 'public' => false ) );
+
+               // Create test terms in the custom taxonomy.
+               $public_term  = self::factory()->term->create( array( 'taxonomy' => 'public_taxonomy' ) );
+               $private_term = self::factory()->term->create( array( 'taxonomy' => 'private_taxonomy' ) );
+
+               // Create a test post applied to all test terms.
+               self::factory()->post->create_and_get(
+                       array(
+                               'tax_input' => array(
+                                       'public_taxonomy'  => array( $public_term ),
+                                       'private_taxonomy' => array( $private_term ),
+                               ),
+                       )
+               );
+
+               $tax_provider = new WP_Sitemaps_Taxonomies();
+               $entries      = wp_list_pluck( $tax_provider->get_sitemap_entries(), 'loc' );
+
+               // Clean up.
+               unregister_taxonomy_for_object_type( 'public_taxonomy', 'post' );
+               unregister_taxonomy_for_object_type( 'private_taxonomy', 'post' );
+
+               $this->assertContains( 'http://' . WP_TESTS_DOMAIN . '/?sitemap=taxonomies&sitemap-subtype=public_taxonomy&paged=1', $entries, 'Public Taxonomies are not in the index.' );
+               $this->assertNotContains( 'http://' . WP_TESTS_DOMAIN . '/?sitemap=taxonomies&sitemap-subtype=private_taxonomy&paged=1', $entries, 'Private Taxonomies are visible in the index.' );
+       }
+
+       /**
+        * Test ability to filter object subtypes.
+        */
+       public function test_filter_sitemaps_taxonomies() {
+               $taxonomies_provider = new WP_Sitemaps_Taxonomies();
+
+               // Return an empty array to show that the list of subtypes is filterable.
+               add_filter( 'wp_sitemaps_taxonomies', '__return_empty_array' );
+               $subtypes = $taxonomies_provider->get_object_subtypes();
+
+               $this->assertEquals( array(), $subtypes, 'Could not filter taxonomies subtypes.' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-taxonomies.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsusersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps-users.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps-users.php                             (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps-users.php       2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,57 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group sitemaps
+ */
+class Test_WP_Sitemaps_Users extends WP_UnitTestCase {
+       /**
+        * List of user IDs.
+        *
+        * @var array
+        */
+       public static $users;
+
+       /**
+        * Editor ID for use in some tests.
+        *
+        * @var int
+        */
+       public static $editor_id;
+
+       /**
+        * Set up fixtures.
+        *
+        * @param WP_UnitTest_Factory $factory A WP_UnitTest_Factory object.
+        */
+       public static function wpSetUpBeforeClass( $factory ) {
+               self::$users     = $factory->user->create_many( 10, array( 'role' => 'editor' ) );
+               self::$editor_id = self::$users[0];
+       }
+
+       /**
+        * Test getting a URL list for a users sitemap page via
+        * WP_Sitemaps_Users::get_url_list().
+        */
+       public function test_get_url_list_users() {
+               // Set up the user to an editor to assign posts to other users.
+               wp_set_current_user( self::$editor_id );
+
+               // Create a set of posts for each user and generate the expected URL list data.
+               $expected = array_map(
+                       static function ( $user_id ) {
+                               $post = self::factory()->post->create_and_get( array( 'post_author' => $user_id ) );
+
+                               return array(
+                                       'loc' => get_author_posts_url( $user_id ),
+                               );
+                       },
+                       self::$users
+               );
+
+               $user_provider = new WP_Sitemaps_Users();
+
+               $url_list = $user_provider->get_url_list( 1 );
+
+               $this->assertEqualSets( $expected, $url_list );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps-users.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestssitemapssitemapsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/sitemaps/sitemaps.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/sitemaps/sitemaps.php                           (rev 0)
+++ trunk/tests/phpunit/tests/sitemaps/sitemaps.php     2020-06-17 15:22:49 UTC (rev 48072)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,396 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Sitemaps: Sitemaps_Tests class
+ *
+ * Main test class.
+ *
+ * @package   Sitemaps
+ * @copyright 2019 The Core Sitemaps Contributors
+ * @license   GNU General Public License, version 2
+ * @link      https://github.com/GoogleChromeLabs/wp-sitemaps
+ */
+
+/**
+ * Core sitemaps test cases.
+ *
+ * @group sitemaps
+ */
+class Test_Sitemaps extends WP_UnitTestCase {
+
+       /**
+        * List of user IDs.
+        *
+        * @var array
+        */
+       public static $users;
+
+       /**
+        * List of post_tag IDs.
+        *
+        * @var array
+        */
+       public static $post_tags;
+
+       /**
+        * List of category IDs.
+        *
+        * @var array
+        */
+       public static $cats;
+
+       /**
+        * List of post type post IDs.
+        *
+        * @var array
+        */
+       public static $posts;
+
+       /**
+        * List of post type page IDs.
+        *
+        * @var array
+        */
+       public static $pages;
+
+       /**
+        * Editor ID for use in some tests.
+        *
+        * @var int
+        */
+       public static $editor_id;
+
+       /**
+        * Test sitemap provider.
+        *
+        * @var WP_Sitemaps_Test_Provider
+        */
+       public static $test_provider;
+
+       /**
+        * Set up fixtures.
+        *
+        * @param WP_UnitTest_Factory $factory A WP_UnitTest_Factory object.
+        */
+       public static function wpSetUpBeforeClass( $factory ) {
+               self::$users     = $factory->user->create_many( 10 );
+               self::$post_tags = $factory->term->create_many( 10 );
+               self::$cats      = $factory->term->create_many( 10, array( 'taxonomy' => 'category' ) );
+               self::$pages     = $factory->post->create_many( 10, array( 'post_type' => 'page' ) );
+
+               // Create a set of posts pre-assigned to tags and authors.
+               self::$posts = $factory->post->create_many(
+                       10,
+                       array(
+                               'tags_input'  => self::$post_tags,
+                               'post_author' => reset( self::$users ),
+                       )
+               );
+
+               // Create a user with an editor role to complete some tests.
+               self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) );
+
+               self::$test_provider = new WP_Sitemaps_Test_Provider();
+       }
+
+       /**
+        * Helper function to get all sitemap entries data.
+        *
+        * @return array A list of sitemap entires.
+        */
+       public function _get_sitemap_entries() {
+               $entries = array();
+
+               $providers = wp_get_sitemaps();
+
+               foreach ( $providers as $provider ) {
+                       // Using `array_push` is more efficient than `array_merge` in the loop.
+                       array_push( $entries, ...$provider->get_sitemap_entries() );
+               }
+
+               return $entries;
+       }
+
+       /**
+        * Test default sitemap entries.
+        */
+       public function test_get_sitemap_entries() {
+               $entries = $this->_get_sitemap_entries();
+
+               $expected = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/?sitemap=posts&sitemap-subtype=post&paged=1',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/?sitemap=posts&sitemap-subtype=page&paged=1',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/?sitemap=taxonomies&sitemap-subtype=category&paged=1',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/?sitemap=taxonomies&sitemap-subtype=post_tag&paged=1',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/?sitemap=users&paged=1',
+                       ),
+               );
+
+               $this->assertSame( $expected, $entries );
+       }
+
+       /**
+        * Test default sitemap entries with permalinks on.
+        */
+       public function test_get_sitemap_entries_post_with_permalinks() {
+               $this->set_permalink_structure( '/%year%/%postname%/' );
+
+               $entries = $this->_get_sitemap_entries();
+
+               $expected = array(
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-post-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-posts-page-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-category-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-taxonomies-post_tag-1.xml',
+                       ),
+                       array(
+                               'loc' => 'http://' . WP_TESTS_DOMAIN . '/wp-sitemap-users-1.xml',
+                       ),
+               );
+
+               // Clean up permalinks.
+               $this->set_permalink_structure();
+
+               $this->assertSame( $expected, $entries );
+       }
+
+       /**
+        * Test sitemap index entries with public and private custom post types.
+        */
+       public function test_get_sitemap_entries_custom_post_types() {
+               // Register and create a public post type post.
+               register_post_type( 'public_cpt', array( 'public' => true ) );
+               self::factory()->post->create( array( 'post_type' => 'public_cpt' ) );
+
+               // Register and create a private post type post.
+               register_post_type( 'private_cpt', array( 'public' => false ) );
+               self::factory()->post->create( array( 'post_type' => 'private_cpt' ) );
+
+               $entries = wp_list_pluck( $this->_get_sitemap_entries(), 'loc' );
+
+               // Clean up.
+               unregister_post_type( 'public_cpt' );
+               unregister_post_type( 'private_cpt' );
+
+               $this->assertContains( 'http://' . WP_TESTS_DOMAIN . '/?sitemap=posts&sitemap-subtype=public_cpt&paged=1', $entries, 'Public CPTs are not in the index.' );
+               $this->assertNotContains( 'http://' . WP_TESTS_DOMAIN . '/?sitemap=posts&sitemap-subtype=private_cpt&paged=1', $entries, 'Private CPTs are visible in the index.' );
+       }
+
+       /**
+        * Tests getting a URL list for post type post.
+        */
+       public function test_get_url_list_post() {
+               $providers = wp_get_sitemaps();
+
+               $post_list = $providers['posts']->get_url_list( 1, 'post' );
+
+               $expected = $this->_get_expected_url_list( 'post', self::$posts );
+
+               $this->assertEquals( $expected, $post_list );
+       }
+
+       /**
+        * Tests getting a URL list for post type page.
+        */
+       public function test_get_url_list_page() {
+               // Short circuit the show on front option.
+               add_filter( 'pre_option_show_on_front', '__return_true' );
+
+               $providers = wp_get_sitemaps();
+
+               $post_list = $providers['posts']->get_url_list( 1, 'page' );
+
+               $expected = $this->_get_expected_url_list( 'page', self::$pages );
+
+               $this->assertEquals( $expected, $post_list );
+       }
+
+       /**
+        * Tests getting a URL list for post type page with included home page.
+        */
+       public function test_get_url_list_page_with_home() {
+               $providers = wp_get_sitemaps();
+
+               $post_list = $providers['posts']->get_url_list( 1, 'page' );
+
+               $expected = $this->_get_expected_url_list( 'page', self::$pages );
+
+               // Add the homepage to the front of the URL list.
+               array_unshift(
+                       $expected,
+                       array(
+                               'loc' => home_url(),
+                       )
+               );
+
+               $this->assertEquals( $expected, $post_list );
+       }
+
+       /**
+        * Tests getting a URL list for post with private post.
+        */
+       public function test_get_url_list_private_post() {
+               wp_set_current_user( self::$editor_id );
+
+               $providers = wp_get_sitemaps();
+
+               $post_list_before = $providers['posts']->get_url_list( 1, 'post' );
+
+               $private_post_id = self::factory()->post->create( array( 'post_status' => 'private' ) );
+
+               $post_list_after = $providers['posts']->get_url_list( 1, 'post' );
+
+               $private_post = array(
+                       'loc' => get_permalink( $private_post_id ),
+               );
+
+               $this->assertNotContains( $private_post, $post_list_after );
+               $this->assertEqualSets( $post_list_before, $post_list_after );
+       }
+
+       /**
+        * Tests getting a URL list for a custom post type.
+        */
+       public function test_get_url_list_cpt() {
+               $post_type = 'custom_type';
+
+               // Registered post types are private unless explicitly set to public.
+               register_post_type( $post_type, array( 'public' => true ) );
+
+               $ids = self::factory()->post->create_many( 10, array( 'post_type' => $post_type ) );
+
+               $providers = wp_get_sitemaps();
+
+               $post_list = $providers['posts']->get_url_list( 1, $post_type );
+
+               $expected = $this->_get_expected_url_list( $post_type, $ids );
+
+               // Clean up.
+               unregister_post_type( $post_type );
+
+               $this->assertEquals( $expected, $post_list, 'Custom post type posts are not visible.' );
+       }
+
+       /**
+        * Tests getting a URL list for a private custom post type.
+        */
+       public function test_get_url_list_cpt_private() {
+               $post_type = 'private_type';
+
+               // Create a private post type for testing against data leaking.
+               register_post_type( $post_type, array( 'public' => false ) );
+
+               self::factory()->post->create_many( 10, array( 'post_type' => $post_type ) );
+
+               $providers = wp_get_sitemaps();
+
+               $post_list = $providers['posts']->get_url_list( 1, $post_type );
+
+               // Clean up.
+               unregister_post_type( $post_type );
+
+               $this->assertEmpty( $post_list, 'Private post types may be returned by the post provider.' );
+       }
+
+       /**
+        * Helper function for building an expected url list.
+        *
+        * @param string $type An object sub type, e.g., post type.
+        * @param array  $ids  Array of object IDs.
+        * @return array A formed URL list.
+        */
+       public function _get_expected_url_list( $type, $ids ) {
+               $posts = get_posts(
+                       array(
+                               'include'   => $ids,
+                               'orderby'   => 'ID',
+                               'order'     => 'ASC',
+                               'post_type' => $type,
+                       )
+               );
+
+               return array_map(
+                       static function ( $post ) {
+                               return array(
+                                       'loc' => get_permalink( $post ),
+                               );
+                       },
+                       $posts
+               );
+       }
+
+       /**
+        * Test functionality that adds a new sitemap provider to the registry.
+        */
+       public function test_register_sitemap_provider() {
+               wp_register_sitemap( 'test_sitemap', self::$test_provider );
+
+               $sitemaps = wp_get_sitemaps();
+
+               $this->assertEquals( $sitemaps['test_sitemap'], self::$test_provider, 'Can not confirm sitemap registration is working.' );
+       }
+
+       /**
+        * Test robots.txt output.
+        */
+       public function test_robots_text() {
+               // Get the text added to the default robots text output.
+               $robots_text    = apply_filters( 'robots_txt', '', true );
+               $sitemap_string = 'Sitemap: http://' . WP_TESTS_DOMAIN . '/?sitemap=index';
+
+               $this->assertContains( $sitemap_string, $robots_text, 'Sitemap URL not included in robots text.' );
+       }
+
+       /**
+        * Test robots.txt output for a private site.
+        */
+       public function test_robots_text_private_site() {
+               $robots_text    = apply_filters( 'robots_txt', '', false );
+               $sitemap_string = 'Sitemap: http://' . WP_TESTS_DOMAIN . '/?sitemap=index';
+
+               $this->assertNotContains( $sitemap_string, $robots_text );
+       }
+
+       /**
+        * Test robots.txt output with permalinks set.
+        */
+       public function test_robots_text_with_permalinks() {
+               // Set permalinks for testing.
+               $this->set_permalink_structure( '/%year%/%postname%/' );
+
+               // Get the text added to the default robots text output.
+               $robots_text    = apply_filters( 'robots_txt', '', true );
+               $sitemap_string = 'Sitemap: http://' . WP_TESTS_DOMAIN . '/wp-sitemap.xml';
+
+               // Clean up permalinks.
+               $this->set_permalink_structure();
+
+               $this->assertContains( $sitemap_string, $robots_text, 'Sitemap URL not included in robots text.' );
+       }
+
+       /**
+        * Test robots.txt output with line feed prefix.
+        */
+       public function test_robots_text_prefixed_with_line_feed() {
+               // Get the text added to the default robots text output.
+               $robots_text    = apply_filters( 'robots_txt', '', true );
+               $sitemap_string = "\nSitemap: ";
+
+               $this->assertContains( $sitemap_string, $robots_text, 'Sitemap URL not prefixed with "\n".' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/sitemaps/sitemaps.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>