<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
* Build the sitemap tree.
* @package automattic/jetpack
if ( ! defined( 'ABSPATH' ) ) {
/* Include sitemap subclasses, if not already, and include proper buffer based on phpxml's availability. */
require_once __DIR__ . '/sitemap-constants.php';
require_once __DIR__ . '/sitemap-buffer.php';
if ( ! class_exists( 'DOMDocument' ) ) {
require_once __DIR__ . '/sitemap-buffer-fallback.php';
require_once __DIR__ . '/sitemap-buffer-image-fallback.php';
require_once __DIR__ . '/sitemap-buffer-master-fallback.php';
require_once __DIR__ . '/sitemap-buffer-news-fallback.php';
require_once __DIR__ . '/sitemap-buffer-page-fallback.php';
require_once __DIR__ . '/sitemap-buffer-video-fallback.php';
require_once __DIR__ . '/sitemap-buffer-image.php';
require_once __DIR__ . '/sitemap-buffer-master.php';
require_once __DIR__ . '/sitemap-buffer-news.php';
require_once __DIR__ . '/sitemap-buffer-page.php';
require_once __DIR__ . '/sitemap-buffer-video.php';
require_once __DIR__ . '/sitemap-librarian.php';
require_once __DIR__ . '/sitemap-finder.php';
require_once __DIR__ . '/sitemap-state.php';
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
require_once __DIR__ . '/sitemap-logger.php';
* Simple class for rendering an empty sitemap with a short TTL
class Jetpack_Sitemap_Buffer_Empty extends Jetpack_Sitemap_Buffer {
* Jetpack_Sitemap_Buffer_Empty constructor.
public function __construct() {
parent::__construct( JP_SITEMAP_MAX_ITEMS, JP_SITEMAP_MAX_BYTES, '1970-01-01 00:00:00' );
$this->doc->createComment( "generator='jetpack-" . JETPACK__VERSION . "'" )
$this->doc->createComment( 'Jetpack_Sitemap_Buffer_Empty' )
$this->doc->createProcessingInstruction(
'type="text/xsl" href="' . $this->finder->construct_sitemap_url( 'sitemap-index.xsl' ) . '"'
* Returns a DOM element for an empty sitemap.
protected function get_root_element() {
if ( ! isset( $this->root ) ) {
$this->root = $this->doc->createElement( 'sitemapindex' );
$this->root->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
$this->doc->appendChild( $this->root );
$this->byte_capacity -= strlen( $this->doc->saveXML( $this->root ) );
* The Jetpack_Sitemap_Builder object handles the construction of
* all sitemap files (except the XSL files, which are handled by
* Jetpack_Sitemap_Stylist.) Other than the constructor, there are
* only two public functions: build_all_sitemaps and news_sitemap_xml.
class Jetpack_Sitemap_Builder { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace
* Librarian object for storing and retrieving sitemap data.
* @var $librarian Jetpack_Sitemap_Librarian
* Logger object for reporting debug messages.
* @var $logger Jetpack_Sitemap_Logger
* Finder object for dealing with sitemap URIs.
* @var $finder Jetpack_Sitemap_Finder
* Construct a new Jetpack_Sitemap_Builder object.
public function __construct() {
$this->librarian = new Jetpack_Sitemap_Librarian();
$this->finder = new Jetpack_Sitemap_Finder();
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$this->logger = new Jetpack_Sitemap_Logger();
'jetpack_sitemap_post_types',
* The array of post types to be included in the sitemap.
* Add your custom post type name to the array to have posts of
* that type included in the sitemap. The default array includes
* The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
* so this filter only has to be applied once per generation.
'jetpack_sitemap_post_types',
* All we do here is call build_next_sitemap_file a bunch of times.
public function update_sitemap() {
$this->logger->report( '-- Updating...' );
if ( ! class_exists( 'DOMDocument' ) ) {
'Jetpack can not load necessary XML manipulation libraries. Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ .',
* Filters whether to suspend cache addition for the entire sitemap generation.
* @param bool|null $suspend_addition Whether to suspend cache addition. Defaults to null.
* @return bool|null Whether to suspend cache addition.
$suspend_addition = apply_filters( 'jetpack_sitemap_suspend_cache_addition', null );
// Cache the previous state in case something else changed it.
$prev_suspend_addition = wp_suspend_cache_addition();
wp_suspend_cache_addition( $suspend_addition );
for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
if ( true === $this->build_next_sitemap_file() ) {
// Restore previous state.
wp_suspend_cache_addition( $prev_suspend_addition );
$this->logger->report( '-- ...done for now.' );
* Generate the next sitemap file.
* Reads the most recent state of the sitemap generation phase,
* constructs the next file, and updates the state.
* @return bool True when finished.
private function build_next_sitemap_file() {
$finished = false; // Initialize finished flag.
// Get the most recent state, and lock the state.
$state = Jetpack_Sitemap_State::check_out();
// Do nothing if the state was locked.
if ( false === $state ) {
// Otherwise, branch on the sitemap-type key of $state.
switch ( $state['sitemap-type'] ) {
case JP_PAGE_SITEMAP_TYPE:
$this->build_next_sitemap_of_type(
array( $this, 'build_one_page_sitemap' ),
case JP_PAGE_SITEMAP_INDEX_TYPE:
$this->build_next_sitemap_index_of_type(
JP_PAGE_SITEMAP_INDEX_TYPE,
case JP_IMAGE_SITEMAP_TYPE:
$this->build_next_sitemap_of_type(
array( $this, 'build_one_image_sitemap' ),
case JP_IMAGE_SITEMAP_INDEX_TYPE:
$this->build_next_sitemap_index_of_type(
JP_IMAGE_SITEMAP_INDEX_TYPE,
case JP_VIDEO_SITEMAP_TYPE:
$this->build_next_sitemap_of_type(
array( $this, 'build_one_video_sitemap' ),
case JP_VIDEO_SITEMAP_INDEX_TYPE:
$this->build_next_sitemap_index_of_type(
JP_VIDEO_SITEMAP_INDEX_TYPE,
case JP_MASTER_SITEMAP_TYPE:
$this->build_master_sitemap( $state['max'] );
// Reset the state and quit.
Jetpack_Sitemap_State::reset(
$this->logger->report( '-- Finished.' );
Jetpack_Sitemap_State::reset(
Jetpack_Sitemap_State::unlock();
* Build the next sitemap of a given type and update the sitemap state.
* @param string $sitemap_type The type of the sitemap being generated.
* @param callback $build_one A callback which builds a single sitemap file.
* @param array $state A sitemap state.
private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
$index_type = jp_sitemap_index_type_of( $sitemap_type );
// Try to build a sitemap.
$result = call_user_func_array(
if ( false === $result ) {
// If no sitemap was generated, advance to the next type.
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $index_type,
'last-modified' => '1970-01-01 00:00:00',
$this->logger->report( "-- Cleaning Up $sitemap_type" );
$this->librarian->delete_numbered_sitemap_rows_after(
// Otherwise, update the state.
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $state['sitemap-type'],
'last-added' => $result['last_id'],
'number' => $state['number'] + 1,
'last-modified' => $result['last_modified'],
if ( true === $result['any_left'] ) {
// If there's more work to be done with this type, return.
// Otherwise, advance state to the next sitemap type.
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $index_type,
'last-modified' => '1970-01-01 00:00:00',
$this->logger->report( "-- Cleaning Up $sitemap_type" );
$this->librarian->delete_numbered_sitemap_rows_after(
* Build the next sitemap index of a given type and update the state.
* @param string $index_type The type of index being generated.
* @param string $next_type The next type to generate after this one.
* @param array $state A sitemap state.
private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
$sitemap_type = jp_sitemap_child_type_of( $index_type );
$sitemap_type_exists = isset( $state['max'][ $sitemap_type ] ) && is_array( $state['max'][ $sitemap_type ] );
// If only 0 or 1 sitemaps were built, advance to the next type and return.
if ( $sitemap_type_exists && 1 >= $state['max'][ $sitemap_type ]['number'] ) {
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $next_type,
'last-modified' => '1970-01-01 00:00:00',
$this->logger->report( "-- Cleaning Up $index_type" );
// There are no indices of this type.
$this->librarian->delete_numbered_sitemap_rows_after(
// Otherwise, try to build a sitemap index.
$result = $this->build_one_sitemap_index(
// If no index was built, advance to the next type and return.
if ( false === $result ) {
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $next_type,
'last-modified' => '1970-01-01 00:00:00',
$this->logger->report( "-- Cleaning Up $index_type" );
$this->librarian->delete_numbered_sitemap_rows_after(
// Otherwise, check in the state.
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $index_type,
'last-added' => $result['last_id'],
'number' => $state['number'] + 1,
'last-modified' => $result['last_modified'],
// If there are still sitemaps left to index, return.
if ( true === $result['any_left'] ) {
// Otherwise, advance to the next type.
Jetpack_Sitemap_State::check_in(
'sitemap-type' => $next_type,
'last-modified' => '1970-01-01 00:00:00',
$this->logger->report( "-- Cleaning Up $index_type" );
// We're done generating indices of this type.
$this->librarian->delete_numbered_sitemap_rows_after(
* Builds the master sitemap index.
* @param array $max Array of sitemap types with max index and datetime.