Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
55.63% covered (warning)
55.63%
390 / 701
15.79% covered (danger)
15.79%
3 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 2
Jetpack_Sitemap_Buffer_Empty
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 2
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 get_root_element
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
Jetpack_Sitemap_Builder
59.00% covered (warning)
59.00%
390 / 661
17.65% covered (danger)
17.65%
3 / 17
1165.59
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 update_sitemap
65.00% covered (warning)
65.00%
13 / 20
0.00% covered (danger)
0.00%
0 / 1
7.54
 build_next_sitemap_file
89.47% covered (warning)
89.47%
51 / 57
0.00% covered (danger)
0.00%
0 / 1
11.14
 build_next_sitemap_of_type
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
1 / 1
5
 build_next_sitemap_index_of_type
75.00% covered (warning)
75.00%
48 / 64
0.00% covered (danger)
0.00%
0 / 1
10.27
 build_master_sitemap
52.17% covered (warning)
52.17%
36 / 69
0.00% covered (danger)
0.00%
0 / 1
27.75
 build_one_page_sitemap
89.02% covered (warning)
89.02%
73 / 82
0.00% covered (danger)
0.00%
0 / 1
22.64
 build_one_image_sitemap
53.85% covered (warning)
53.85%
21 / 39
0.00% covered (danger)
0.00%
0 / 1
14.29
 build_one_video_sitemap
52.50% covered (warning)
52.50%
21 / 40
0.00% covered (danger)
0.00%
0 / 1
17.68
 build_one_sitemap_index
74.55% covered (warning)
74.55%
41 / 55
0.00% covered (danger)
0.00%
0 / 1
10.34
 sitemap_row_to_index_item
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 empty_sitemap_xml
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 news_sitemap_xml
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
90
 post_to_sitemap_item
64.00% covered (warning)
64.00%
16 / 25
0.00% covered (danger)
0.00%
0 / 1
4.75
 image_post_to_sitemap_item
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
20
 video_post_to_sitemap_item
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
42
 post_to_news_sitemap_item
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
20
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * Build the sitemap tree.
4 *
5 * @package automattic/jetpack
6 * @since 4.8.0
7 * @author Automattic
8 */
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14/* Include sitemap subclasses, if not already, and include proper buffer based on phpxml's availability. */
15require_once __DIR__ . '/sitemap-constants.php';
16require_once __DIR__ . '/sitemap-buffer.php';
17
18if ( ! class_exists( 'DOMDocument' ) ) {
19    require_once __DIR__ . '/sitemap-buffer-fallback.php';
20    require_once __DIR__ . '/sitemap-buffer-image-fallback.php';
21    require_once __DIR__ . '/sitemap-buffer-master-fallback.php';
22    require_once __DIR__ . '/sitemap-buffer-news-fallback.php';
23    require_once __DIR__ . '/sitemap-buffer-page-fallback.php';
24    require_once __DIR__ . '/sitemap-buffer-video-fallback.php';
25} else {
26    require_once __DIR__ . '/sitemap-buffer-image.php';
27    require_once __DIR__ . '/sitemap-buffer-master.php';
28    require_once __DIR__ . '/sitemap-buffer-news.php';
29    require_once __DIR__ . '/sitemap-buffer-page.php';
30    require_once __DIR__ . '/sitemap-buffer-video.php';
31}
32
33require_once __DIR__ . '/sitemap-librarian.php';
34require_once __DIR__ . '/sitemap-finder.php';
35require_once __DIR__ . '/sitemap-state.php';
36
37if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
38    require_once __DIR__ . '/sitemap-logger.php';
39}
40
41/**
42 * Simple class for rendering an empty sitemap with a short TTL
43 */
44class Jetpack_Sitemap_Buffer_Empty extends Jetpack_Sitemap_Buffer {
45    /**
46     * Jetpack_Sitemap_Buffer_Empty constructor.
47     */
48    public function __construct() {
49        parent::__construct( JP_SITEMAP_MAX_ITEMS, JP_SITEMAP_MAX_BYTES, '1970-01-01 00:00:00' );
50
51        $this->doc->appendChild(
52            $this->doc->createComment( "generator='jetpack-" . JETPACK__VERSION . "'" )
53        );
54
55        $this->doc->appendChild(
56            $this->doc->createComment( 'Jetpack_Sitemap_Buffer_Empty' )
57        );
58
59        $this->doc->appendChild(
60            $this->doc->createProcessingInstruction(
61                'xml-stylesheet',
62                'type="text/xsl" href="' . $this->finder->construct_sitemap_url( 'sitemap-index.xsl' ) . '"'
63            )
64        );
65    }
66
67    /**
68     * Returns a DOM element for an empty sitemap.
69     */
70    protected function get_root_element() {
71        if ( ! isset( $this->root ) ) {
72            $this->root = $this->doc->createElement( 'sitemapindex' );
73            $this->root->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
74            $this->doc->appendChild( $this->root );
75            $this->byte_capacity -= strlen( $this->doc->saveXML( $this->root ) );
76        }
77
78        return $this->root;
79    }
80}
81
82/**
83 * The Jetpack_Sitemap_Builder object handles the construction of
84 * all sitemap files (except the XSL files, which are handled by
85 * Jetpack_Sitemap_Stylist.) Other than the constructor, there are
86 * only two public functions: build_all_sitemaps and news_sitemap_xml.
87 *
88 * @since 4.8.0
89 */
90class Jetpack_Sitemap_Builder { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace
91
92    /**
93     * Librarian object for storing and retrieving sitemap data.
94     *
95     * @access private
96     * @since 4.8.0
97     * @var $librarian Jetpack_Sitemap_Librarian
98     */
99    private $librarian;
100
101    /**
102     * Logger object for reporting debug messages.
103     *
104     * @access private
105     * @since 4.8.0
106     * @var $logger Jetpack_Sitemap_Logger
107     */
108    private $logger = false;
109
110    /**
111     * Finder object for dealing with sitemap URIs.
112     *
113     * @access private
114     * @since 4.8.0
115     * @var $finder Jetpack_Sitemap_Finder
116     */
117    private $finder;
118
119    /**
120     * Construct a new Jetpack_Sitemap_Builder object.
121     *
122     * @access public
123     * @since 4.8.0
124     */
125    public function __construct() {
126        $this->librarian = new Jetpack_Sitemap_Librarian();
127        $this->finder    = new Jetpack_Sitemap_Finder();
128
129        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
130            $this->logger = new Jetpack_Sitemap_Logger();
131        }
132
133        update_option(
134            'jetpack_sitemap_post_types',
135            /**
136             * The array of post types to be included in the sitemap.
137             *
138             * Add your custom post type name to the array to have posts of
139             * that type included in the sitemap. The default array includes
140             * 'page' and 'post'.
141             *
142             * The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
143             * so this filter only has to be applied once per generation.
144             *
145             * @since 4.8.0
146             */
147            apply_filters(
148                'jetpack_sitemap_post_types',
149                array( 'post', 'page' )
150            )
151        );
152    }
153
154    /**
155     * Update the sitemap.
156     *
157     * All we do here is call build_next_sitemap_file a bunch of times.
158     *
159     * @since 4.8.0
160     */
161    public function update_sitemap() {
162        if ( $this->logger ) {
163            $this->logger->report( '-- Updating...' );
164            if ( ! class_exists( 'DOMDocument' ) ) {
165                $this->logger->report(
166                    __(
167                        'Jetpack cannot load necessary XML manipulation libraries. Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ .',
168                        'jetpack'
169                    ),
170                    true
171                );
172            }
173        }
174
175        /**
176         * Filters whether to suspend cache addition for the entire sitemap generation.
177         *
178         * @since 15.0
179         *
180         * @param bool|null $suspend_addition Whether to suspend cache addition. Defaults to null.
181         * @return bool|null Whether to suspend cache addition.
182         */
183        $suspend_addition = apply_filters( 'jetpack_sitemap_suspend_cache_addition', null );
184
185        // Cache the previous state in case something else changed it.
186        $prev_suspend_addition = wp_suspend_cache_addition();
187
188        wp_suspend_cache_addition( $suspend_addition );
189
190        for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
191            if ( true === $this->build_next_sitemap_file() ) {
192                break; // All finished!
193            }
194        }
195
196        // Restore previous state.
197        wp_suspend_cache_addition( $prev_suspend_addition );
198
199        if ( $this->logger ) {
200            $this->logger->report( '-- ...done for now.' );
201            $this->logger->time();
202        }
203    }
204
205    /**
206     * Generate the next sitemap file.
207     *
208     * Reads the most recent state of the sitemap generation phase,
209     * constructs the next file, and updates the state.
210     *
211     * @since 4.8.0
212     *
213     * @return bool True when finished.
214     */
215    private function build_next_sitemap_file() {
216        $finished = false; // Initialize finished flag.
217
218        // Get the most recent state, and lock the state.
219        $state = Jetpack_Sitemap_State::check_out();
220
221        // Do nothing if the state was locked.
222        if ( false === $state ) {
223            return false;
224        }
225
226        // Otherwise, branch on the sitemap-type key of $state.
227        switch ( $state['sitemap-type'] ) {
228            case JP_PAGE_SITEMAP_TYPE:
229                $this->build_next_sitemap_of_type(
230                    JP_PAGE_SITEMAP_TYPE,
231                    array( $this, 'build_one_page_sitemap' ),
232                    $state
233                );
234                break;
235
236            case JP_PAGE_SITEMAP_INDEX_TYPE:
237                $this->build_next_sitemap_index_of_type(
238                    JP_PAGE_SITEMAP_INDEX_TYPE,
239                    JP_IMAGE_SITEMAP_TYPE,
240                    $state
241                );
242                break;
243
244            case JP_IMAGE_SITEMAP_TYPE:
245                $this->build_next_sitemap_of_type(
246                    JP_IMAGE_SITEMAP_TYPE,
247                    array( $this, 'build_one_image_sitemap' ),
248                    $state
249                );
250                break;
251
252            case JP_IMAGE_SITEMAP_INDEX_TYPE:
253                $this->build_next_sitemap_index_of_type(
254                    JP_IMAGE_SITEMAP_INDEX_TYPE,
255                    JP_VIDEO_SITEMAP_TYPE,
256                    $state
257                );
258                break;
259
260            case JP_VIDEO_SITEMAP_TYPE:
261                $this->build_next_sitemap_of_type(
262                    JP_VIDEO_SITEMAP_TYPE,
263                    array( $this, 'build_one_video_sitemap' ),
264                    $state
265                );
266                break;
267
268            case JP_VIDEO_SITEMAP_INDEX_TYPE:
269                $this->build_next_sitemap_index_of_type(
270                    JP_VIDEO_SITEMAP_INDEX_TYPE,
271                    JP_MASTER_SITEMAP_TYPE,
272                    $state
273                );
274                break;
275
276            case JP_MASTER_SITEMAP_TYPE:
277                $this->build_master_sitemap( $state['max'] );
278
279                // Reset the state and quit.
280                Jetpack_Sitemap_State::reset(
281                    JP_PAGE_SITEMAP_TYPE
282                );
283
284                if ( $this->logger ) {
285                    $this->logger->report( '-- Finished.' );
286                    $this->logger->time();
287                }
288                $finished = true;
289
290                break;
291
292            default:
293                Jetpack_Sitemap_State::reset(
294                    JP_PAGE_SITEMAP_TYPE
295                );
296                $finished = true;
297
298                break;
299        } // End switch.
300
301        // Unlock the state.
302        Jetpack_Sitemap_State::unlock();
303
304        return $finished;
305    }
306
307    /**
308     * Build the next sitemap of a given type and update the sitemap state.
309     *
310     * @since 4.8.0
311     *
312     * @param string   $sitemap_type The type of the sitemap being generated.
313     * @param callback $build_one    A callback which builds a single sitemap file.
314     * @param array    $state        A sitemap state.
315     */
316    private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
317        $index_type = jp_sitemap_index_type_of( $sitemap_type );
318
319        // Try to build a sitemap.
320        $result = call_user_func_array(
321            $build_one,
322            array(
323                $state['number'] + 1,
324                $state['last-added'],
325            )
326        );
327
328        if ( false === $result ) {
329            // If no sitemap was generated, advance to the next type.
330            Jetpack_Sitemap_State::check_in(
331                array(
332                    'sitemap-type'  => $index_type,
333                    'last-added'    => 0,
334                    'number'        => 0,
335                    'last-modified' => '1970-01-01 00:00:00',
336                )
337            );
338
339            if ( $this->logger ) {
340                $this->logger->report( "-- Cleaning Up $sitemap_type" );
341            }
342
343            // Clean up old files.
344            $this->librarian->delete_numbered_sitemap_rows_after(
345                $state['number'],
346                $sitemap_type
347            );
348
349            return;
350        }
351
352        // Otherwise, update the state.
353        Jetpack_Sitemap_State::check_in(
354            array(
355                'sitemap-type'  => $state['sitemap-type'],
356                'last-added'    => $result['last_id'],
357                'number'        => $state['number'] + 1,
358                'last-modified' => $result['last_modified'],
359            )
360        );
361
362        if ( true === $result['any_left'] ) {
363            // If there's more work to be done with this type, return.
364            return;
365        }
366
367        // Otherwise, advance state to the next sitemap type.
368        Jetpack_Sitemap_State::check_in(
369            array(
370                'sitemap-type'  => $index_type,
371                'last-added'    => 0,
372                'number'        => 0,
373                'last-modified' => '1970-01-01 00:00:00',
374            )
375        );
376
377        if ( $this->logger ) {
378            $this->logger->report( "-- Cleaning Up $sitemap_type" );
379        }
380
381        // Clean up old files.
382        $this->librarian->delete_numbered_sitemap_rows_after(
383            $state['number'] + 1,
384            $sitemap_type
385        );
386    }
387
388    /**
389     * Build the next sitemap index of a given type and update the state.
390     *
391     * @since 4.8.0
392     *
393     * @param string $index_type The type of index being generated.
394     * @param string $next_type  The next type to generate after this one.
395     * @param array  $state      A sitemap state.
396     */
397    private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
398        $sitemap_type = jp_sitemap_child_type_of( $index_type );
399
400        $sitemap_type_exists = isset( $state['max'][ $sitemap_type ] ) && is_array( $state['max'][ $sitemap_type ] );
401
402        // If only 0 or 1 sitemaps were built, advance to the next type and return.
403        if ( $sitemap_type_exists && 1 >= $state['max'][ $sitemap_type ]['number'] ) {
404            Jetpack_Sitemap_State::check_in(
405                array(
406                    'sitemap-type'  => $next_type,
407                    'last-added'    => 0,
408                    'number'        => 0,
409                    'last-modified' => '1970-01-01 00:00:00',
410                )
411            );
412
413            if ( $this->logger ) {
414                $this->logger->report( "-- Cleaning Up $index_type" );
415            }
416
417            // There are no indices of this type.
418            $this->librarian->delete_numbered_sitemap_rows_after(
419                0,
420                $index_type
421            );
422
423            return;
424        }
425
426        // Otherwise, try to build a sitemap index.
427        $result = $this->build_one_sitemap_index(
428            $state['number'] + 1,
429            $state['last-added'],
430            $state['last-modified'],
431            $index_type
432        );
433
434        // If no index was built, advance to the next type and return.
435        if ( false === $result ) {
436            Jetpack_Sitemap_State::check_in(
437                array(
438                    'sitemap-type'  => $next_type,
439                    'last-added'    => 0,
440                    'number'        => 0,
441                    'last-modified' => '1970-01-01 00:00:00',
442                )
443            );
444
445            if ( $this->logger ) {
446                $this->logger->report( "-- Cleaning Up $index_type" );
447            }
448
449            // Clean up old files.
450            $this->librarian->delete_numbered_sitemap_rows_after(
451                $state['number'],
452                $index_type
453            );
454
455            return;
456        }
457
458        // Otherwise, check in the state.
459        Jetpack_Sitemap_State::check_in(
460            array(
461                'sitemap-type'  => $index_type,
462                'last-added'    => $result['last_id'],
463                'number'        => $state['number'] + 1,
464                'last-modified' => $result['last_modified'],
465            )
466        );
467
468        // If there are still sitemaps left to index, return.
469        if ( true === $result['any_left'] ) {
470            return;
471        }
472
473        // Otherwise, advance to the next type.
474        Jetpack_Sitemap_State::check_in(
475            array(
476                'sitemap-type'  => $next_type,
477                'last-added'    => 0,
478                'number'        => 0,
479                'last-modified' => '1970-01-01 00:00:00',
480            )
481        );
482
483        if ( $this->logger ) {
484            $this->logger->report( "-- Cleaning Up $index_type" );
485        }
486
487        // We're done generating indices of this type.
488        $this->librarian->delete_numbered_sitemap_rows_after(
489            $state['number'] + 1,
490            $index_type
491        );
492    }
493
494    /**
495     * Builds the master sitemap index.
496     *
497     * @param array $max Array of sitemap types with max index and datetime.
498     *
499     * @since 4.8.0
500     */
501    private function build_master_sitemap( $max ) {
502        $page  = array();
503        $image = array();
504        $video = array();
505        if ( $this->logger ) {
506            $this->logger->report( '-- Building Master Sitemap.' );
507        }
508
509        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
510            'master',
511            JP_SITEMAP_MAX_ITEMS,
512            JP_SITEMAP_MAX_BYTES
513        );
514
515        if ( ! $buffer ) {
516            return;
517        }
518
519        if ( isset( $max[ JP_PAGE_SITEMAP_TYPE ] ) && 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
520            if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
521                $page['filename']      = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, 1 );
522                $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
523            } else {
524                $page['filename']      = jp_sitemap_filename(
525                    JP_PAGE_SITEMAP_INDEX_TYPE,
526                    $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number']
527                );
528                $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
529            }
530
531            $buffer->append(
532                array(
533                    'sitemap' => array(
534                        'loc'     => $this->finder->construct_sitemap_url( $page['filename'] ),
535                        'lastmod' => $page['last_modified'],
536                    ),
537                )
538            );
539        }
540
541        if ( isset( $max[ JP_IMAGE_SITEMAP_TYPE ] ) && 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
542            if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
543                $image['filename']      = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 );
544                $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
545            } else {
546                $image['filename']      = jp_sitemap_filename(
547                    JP_IMAGE_SITEMAP_INDEX_TYPE,
548                    $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
549                );
550                $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
551            }
552
553            $buffer->append(
554                array(
555                    'sitemap' => array(
556                        'loc'     => $this->finder->construct_sitemap_url( $image['filename'] ),
557                        'lastmod' => $image['last_modified'],
558                    ),
559                )
560            );
561        }
562
563        if ( isset( $max[ JP_VIDEO_SITEMAP_TYPE ] ) && 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
564            if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
565                $video['filename']      = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
566                $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] );
567            } else {
568                $video['filename']      = jp_sitemap_filename(
569                    JP_VIDEO_SITEMAP_INDEX_TYPE,
570                    $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
571                );
572                $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] );
573            }
574
575            $buffer->append(
576                array(
577                    'sitemap' => array(
578                        'loc'     => $this->finder->construct_sitemap_url( $video['filename'] ),
579                        'lastmod' => $video['last_modified'],
580                    ),
581                )
582            );
583        }
584
585        $this->librarian->store_sitemap_data(
586            0,
587            JP_MASTER_SITEMAP_TYPE,
588            $buffer->contents(),
589            ''
590        );
591    }
592
593    /**
594     * Build and store a single page sitemap. Returns false if no sitemap is built.
595     *
596     * Side effect: Create/update a sitemap row.
597     *
598     * @access private
599     * @since 4.8.0
600     *
601     * @param int $number The number of the current sitemap.
602     * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
603     *
604     * @return bool|array @args {
605     *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
606     *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
607     *   @type string $last_modified The most recent timestamp to appear on the sitemap.
608     * }
609     */
610    public function build_one_page_sitemap( $number, $from_id ) {
611        $last_post_id   = $from_id;
612        $any_posts_left = true;
613
614        if ( $this->logger ) {
615            $debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
616            $this->logger->report( "-- Building $debug_name" );
617        }
618
619        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
620            'page',
621            JP_SITEMAP_MAX_ITEMS,
622            JP_SITEMAP_MAX_BYTES
623        );
624
625        if ( ! $buffer ) {
626            return false;
627        }
628
629        // Add entry for the main page (only if we're at the first one) and it isn't already going to be included as a page.
630        if ( 1 === $number && 'page' !== get_option( 'show_on_front' ) ) {
631            $item_array = array(
632                'url' => array(
633                    'loc' => home_url( '/' ),
634                ),
635            );
636
637            /**
638             * Filter associative array with data to build <url> node
639             * and its descendants for site home.
640             *
641             * @module sitemaps
642             *
643             * @since 3.9.0
644             *
645             * @param array $blog_home Data to build parent and children nodes for site home.
646             */
647            $item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
648
649            $buffer->append( $item_array );
650        }
651
652        // Add as many items to the buffer as possible.
653        while ( $last_post_id >= 0 && false === $buffer->is_full() ) {
654            $posts = $this->librarian->query_posts_after_id(
655                $last_post_id,
656                JP_SITEMAP_BATCH_SIZE
657            );
658
659            if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
660                $any_posts_left = false;
661                break;
662            }
663
664            foreach ( $posts as $post ) {
665                $current_item = $this->post_to_sitemap_item( $post );
666
667                if ( true === $buffer->append( $current_item['xml'] ) ) {
668                    $last_post_id = $post->ID;
669                    $buffer->view_time( $current_item['last_modified'] );
670                } else {
671                    break;
672                }
673            }
674        }
675
676        // Handle other page sitemap URLs.
677        if ( false === $any_posts_left || $last_post_id < 0 ) {
678            // Negative IDs are used to track URL indexes.
679            $last_post_id   = min( 0, $last_post_id );
680            $any_posts_left = true; // Reinitialize.
681
682            /**
683             * Filter other page sitemap URLs.
684             *
685             * @module sitemaps
686             *
687             * @since 6.1.0
688             *
689             * @param array $urls An array of other URLs.
690             */
691            $other_urls = apply_filters( 'jetpack_page_sitemap_other_urls', array() );
692
693            if ( $other_urls ) { // Start with index [1].
694                $other_urls = array_values( $other_urls );
695                array_unshift( $other_urls, $other_urls[0] );
696                unset( $other_urls[0] );
697            }
698
699            // Add as many items to the buffer as possible.
700            while ( false === $buffer->is_full() ) {
701                $last_post_id_index       = abs( $last_post_id );
702                $start_from_post_id_index = $last_post_id_index ? $last_post_id_index + 1 : 0;
703                $urls                     = array_slice(
704                    $other_urls,
705                    $start_from_post_id_index,
706                    JP_SITEMAP_BATCH_SIZE,
707                    true
708                );
709
710                if ( ! $urls ) {
711                    $any_posts_left = false;
712                    break;
713                }
714
715                foreach ( $urls as $index => $url ) {
716                    if ( ! is_array( $url ) ) {
717                        $url = array( 'loc' => $url );
718                    }
719                    $item = array( 'xml' => compact( 'url' ) );
720
721                    if ( true === $buffer->append( $item['xml'] ) ) {
722                        $last_post_id = -$index;
723                        if ( isset( $url['lastmod'] ) ) {
724                            $buffer->view_time( jp_sitemap_datetime( $url['lastmod'] ) );
725                        }
726                    } else {
727                        break;
728                    }
729                }
730            }
731        }
732
733        // If no items were added, return false.
734        if ( true === $buffer->is_empty() ) {
735            return false;
736        }
737
738        /**
739         * Filter sitemap before rendering it as XML.
740         *
741         * @module sitemaps
742         *
743         * @since 3.9.0
744         * @since 5.3.0 returns an element of DOMDocument type instead of SimpleXMLElement
745         *
746         * @param DOMDocument      $doc Data tree for sitemap.
747         * @param string           $last_modified Date of last modification.
748         */
749        if ( has_filter( 'jetpack_print_sitemap' ) ) {
750            apply_filters(
751                'jetpack_print_sitemap',
752                $buffer->get_document(),
753                $buffer->last_modified()
754            );
755        }
756
757        // Store the buffer as the content of a sitemap row.
758        $this->librarian->store_sitemap_data(
759            $number,
760            JP_PAGE_SITEMAP_TYPE,
761            $buffer->contents(),
762            $buffer->last_modified()
763        );
764
765        /*
766         * Now report back with the ID of the last post ID to be
767         * successfully added and whether there are any posts left.
768         */
769        return array(
770            'last_id'       => $last_post_id,
771            'any_left'      => $any_posts_left,
772            'last_modified' => $buffer->last_modified(),
773        );
774    }
775
776    /**
777     * Build and store a single image sitemap. Returns false if no sitemap is built.
778     *
779     * Side effect: Create/update an image sitemap row.
780     *
781     * @access private
782     * @since 4.8.0
783     *
784     * @param int $number The number of the current sitemap.
785     * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
786     *
787     * @return bool|array @args {
788     *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
789     *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
790     *   @type string $last_modified The most recent timestamp to appear on the sitemap.
791     * }
792     */
793    public function build_one_image_sitemap( $number, $from_id ) {
794        $last_post_id   = $from_id;
795        $any_posts_left = true;
796
797        if ( $this->logger ) {
798            $debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
799            $this->logger->report( "-- Building $debug_name" );
800        }
801
802        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
803            'image',
804            JP_SITEMAP_MAX_ITEMS,
805            JP_SITEMAP_MAX_BYTES
806        );
807
808        if ( ! $buffer ) {
809            return false;
810        }
811
812        // Add as many items to the buffer as possible.
813        while ( false === $buffer->is_full() ) {
814            $posts = $this->librarian->query_images_after_id(
815                $last_post_id,
816                JP_SITEMAP_BATCH_SIZE
817            );
818
819            if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
820                $any_posts_left = false;
821                break;
822            }
823
824            foreach ( $posts as $post ) {
825                $current_item = $this->image_post_to_sitemap_item( $post );
826
827                if ( true === $buffer->append( $current_item['xml'] ) ) {
828                    $last_post_id = $post->ID;
829                    $buffer->view_time( $current_item['last_modified'] );
830                } else {
831                    break;
832                }
833            }
834        }
835
836        // If no items were added, return false.
837        if ( true === $buffer->is_empty() ) {
838            return false;
839        }
840
841        // Store the buffer as the content of a jp_sitemap post.
842        $this->librarian->store_sitemap_data(
843            $number,
844            JP_IMAGE_SITEMAP_TYPE,
845            $buffer->contents(),
846            $buffer->last_modified()
847        );
848
849        /*
850         * Now report back with the ID of the last post to be
851         * successfully added and whether there are any posts left.
852         */
853        return array(
854            'last_id'       => $last_post_id,
855            'any_left'      => $any_posts_left,
856            'last_modified' => $buffer->last_modified(),
857        );
858    }
859
860    /**
861     * Build and store a single video sitemap. Returns false if no sitemap is built.
862     *
863     * Side effect: Create/update an video sitemap row.
864     *
865     * @access private
866     * @since 4.8.0
867     *
868     * @param int $number The number of the current sitemap.
869     * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
870     *
871     * @return bool|array @args {
872     *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
873     *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
874     *   @type string $last_modified The most recent timestamp to appear on the sitemap.
875     * }
876     */
877    public function build_one_video_sitemap( $number, $from_id ) {
878        $last_post_id   = $from_id;
879        $any_posts_left = true;
880
881        if ( $this->logger ) {
882            $debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
883            $this->logger->report( "-- Building $debug_name" );
884        }
885
886        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
887            'video',
888            JP_SITEMAP_MAX_ITEMS,
889            JP_SITEMAP_MAX_BYTES
890        );
891
892        if ( ! $buffer ) {
893            return false;
894        }
895
896        // Add as many items to the buffer as possible.
897        while ( false === $buffer->is_full() ) {
898            $posts = $this->librarian->query_videos_after_id(
899                $last_post_id,
900                JP_SITEMAP_BATCH_SIZE
901            );
902
903            if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
904                $any_posts_left = false;
905                break;
906            }
907
908            foreach ( $posts as $post ) {
909                $current_item = $this->video_post_to_sitemap_item( $post );
910
911                if ( true === $buffer->append( $current_item['xml'] ) ) {
912                    $last_post_id = $post->ID;
913                    $buffer->view_time( $current_item['last_modified'] );
914                } else {
915                    break;
916                }
917            }
918        }
919
920        // If no items were added, return false.
921        if ( true === $buffer->is_empty() ) {
922            return false;
923        }
924
925        if ( false === $buffer->is_empty() ) {
926            $this->librarian->store_sitemap_data(
927                $number,
928                JP_VIDEO_SITEMAP_TYPE,
929                $buffer->contents(),
930                $buffer->last_modified()
931            );
932        }
933
934        /*
935         * Now report back with the ID of the last post to be
936         * successfully added and whether there are any posts left.
937         */
938        return array(
939            'last_id'       => $last_post_id,
940            'any_left'      => $any_posts_left,
941            'last_modified' => $buffer->last_modified(),
942        );
943    }
944
945    /**
946     * Build and store a single page sitemap index. Return false if no index is built.
947     *
948     * Side effect: Create/update a sitemap index row.
949     *
950     * @access private
951     * @since 4.8.0
952     *
953     * @param int    $number     The number of the current sitemap index.
954     * @param int    $from_id    The greatest lower bound of the IDs of the sitemaps to be included.
955     * @param string $datetime   Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
956     * @param string $index_type Sitemap index type.
957     *
958     * @return bool|array @args {
959     *   @type int    $last_id       The ID of the last item to be successfully added to the buffer.
960     *   @type bool   $any_left      'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
961     *   @type string $last_modified The most recent timestamp to appear on the sitemap.
962     * }
963     */
964    private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
965        $last_sitemap_id   = $from_id;
966        $any_sitemaps_left = true;
967
968        // Check the datetime format.
969        $datetime = jp_sitemap_datetime( $datetime );
970
971        $sitemap_type = jp_sitemap_child_type_of( $index_type );
972
973        if ( $this->logger ) {
974            $index_debug_name = jp_sitemap_filename( $index_type, $number );
975            $this->logger->report( "-- Building $index_debug_name" );
976        }
977
978        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
979            'master',
980            JP_SITEMAP_MAX_ITEMS,
981            JP_SITEMAP_MAX_BYTES,
982            $datetime
983        );
984        if ( ! $buffer ) {
985            return false;
986        }
987
988        // Add pointer to the previous sitemap index (unless we're at the first one).
989        if ( 1 !== $number ) {
990            $i              = $number - 1;
991            $prev_index_url = $this->finder->construct_sitemap_url(
992                jp_sitemap_filename( $index_type, $i )
993            );
994
995            $item_array = array(
996                'sitemap' => array(
997                    'loc'     => $prev_index_url,
998                    'lastmod' => $datetime,
999                ),
1000            );
1001
1002            $buffer->append( $item_array );
1003        }
1004
1005        // Add as many items to the buffer as possible.
1006        while ( false === $buffer->is_full() ) {
1007            // Retrieve a batch of posts (in order).
1008            $posts = $this->librarian->query_sitemaps_after_id(
1009                $sitemap_type,
1010                $last_sitemap_id,
1011                JP_SITEMAP_BATCH_SIZE
1012            );
1013
1014            // If there were no posts to get, make a note.
1015            if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1016                $any_sitemaps_left = false;
1017                break;
1018            }
1019
1020            // Otherwise, loop through each post in the batch.
1021            foreach ( $posts as $post ) {
1022                // Generate the sitemap XML for the post.
1023                $current_item = $this->sitemap_row_to_index_item( (array) $post );
1024
1025                // Try adding this item to the buffer.
1026                if ( true === $buffer->append( $current_item['xml'] ) ) {
1027                    $last_sitemap_id = $post['ID'];
1028                    $buffer->view_time( $current_item['last_modified'] );
1029                } else {
1030                    // Otherwise stop looping through posts.
1031                    break;
1032                }
1033            }
1034        }
1035
1036        // If no items were added, return false.
1037        if ( true === $buffer->is_empty() ) {
1038            return false;
1039        }
1040
1041        $this->librarian->store_sitemap_data(
1042            $number,
1043            $index_type,
1044            $buffer->contents(),
1045            $buffer->last_modified()
1046        );
1047
1048        /*
1049         * Now report back with the ID of the last sitemap post ID to
1050         * be successfully added, whether there are any sitemap posts
1051         * left, and the most recent modification time seen.
1052         */
1053        return array(
1054            'last_id'       => $last_sitemap_id,
1055            'any_left'      => $any_sitemaps_left,
1056            'last_modified' => $buffer->last_modified(),
1057        );
1058    }
1059
1060    /**
1061     * Construct the sitemap index url entry for a sitemap row.
1062     *
1063     * @link https://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
1064     *
1065     * @access private
1066     * @since 4.8.0
1067     *
1068     * @param array $row The sitemap data to be processed.
1069     *
1070     * @return string An XML fragment representing the post URL.
1071     */
1072    private function sitemap_row_to_index_item( $row ) {
1073        $url = $this->finder->construct_sitemap_url( $row['post_title'] );
1074
1075        $item_array = array(
1076            'sitemap' => array(
1077                'loc'     => $url,
1078                'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
1079            ),
1080        );
1081
1082        return array(
1083            'xml'           => $item_array,
1084            'last_modified' => $row['post_date'],
1085        );
1086    }
1087
1088    /**
1089     * This is served instead of a 404 when the master sitemap is requested
1090     * but not yet generated.
1091     *
1092     * @access public
1093     * @since 6.7.0
1094     *
1095     * @return string The empty sitemap xml.
1096     */
1097    public function empty_sitemap_xml() {
1098        $empty_sitemap = new Jetpack_Sitemap_Buffer_Empty();
1099        return $empty_sitemap->contents();
1100    }
1101
1102    /**
1103     * Build and return the news sitemap xml. Note that the result of this
1104     * function is cached in the transient 'jetpack_news_sitemap_xml'.
1105     *
1106     * @access public
1107     * @since 4.8.0
1108     *
1109     * @return string The news sitemap xml.
1110     */
1111    public function news_sitemap_xml() {
1112        $buffer = Jetpack_Sitemap_Buffer_Factory::create(
1113            'news',
1114            JP_SITEMAP_MAX_ITEMS,
1115            JP_SITEMAP_MAX_BYTES
1116        );
1117
1118        if ( ! $buffer ) {
1119            return '';
1120        }
1121
1122        $the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
1123
1124        if ( false === $the_stored_news_sitemap ) {
1125
1126            if ( $this->logger ) {
1127                $this->logger->report( 'Beginning news sitemap generation.' );
1128            }
1129
1130            /**
1131             * Filter limit of entries to include in news sitemap.
1132             *
1133             * @module sitemaps
1134             *
1135             * @since 3.9.0
1136             *
1137             * @param int $count Number of entries to include in news sitemap.
1138             */
1139            $item_limit = apply_filters(
1140                'jetpack_sitemap_news_sitemap_count',
1141                JP_NEWS_SITEMAP_MAX_ITEMS
1142            );
1143
1144            $posts = $this->librarian->query_most_recent_posts( $item_limit );
1145            if ( empty( $posts ) ) {
1146                $buffer->append( array( 'url' => array( 'loc' => home_url( '/' ) ) ) );
1147            } else {
1148                foreach ( $posts as $post ) {
1149                    $current_item = $this->post_to_news_sitemap_item( $post );
1150
1151                    if ( $current_item['xml'] !== null && false === $buffer->append( $current_item['xml'] ) ) {
1152                        break;
1153                    }
1154                }
1155            }
1156
1157            if ( $this->logger ) {
1158                $this->logger->time( 'End news sitemap generation.' );
1159            }
1160
1161            $the_stored_news_sitemap = $buffer->contents();
1162
1163            set_transient(
1164                'jetpack_news_sitemap_xml',
1165                $the_stored_news_sitemap,
1166                JP_NEWS_SITEMAP_INTERVAL
1167            );
1168        } // End if.
1169
1170        return $the_stored_news_sitemap;
1171    }
1172
1173    /**
1174     * Construct the sitemap url entry for a WP_Post.
1175     *
1176     * @link https://www.sitemaps.org/protocol.html#urldef
1177     * @access private
1178     * @since 4.8.0
1179     *
1180     * @param object $post The post to be processed. Similar to WP_Post, but without post_content and post_content_filtered.
1181     *
1182     * @return array
1183     *              @type array  $xml An XML fragment representing the post URL.
1184     *              @type string $last_modified Date post was last modified.
1185     */
1186    private function post_to_sitemap_item( $post ) {
1187
1188        /**
1189         * Filter condition to allow skipping specific posts in sitemap.
1190         *
1191         * @module sitemaps
1192         *
1193         * @since 3.9.0
1194         *
1195         * @param bool   $skip Current boolean. False by default, so no post is skipped.
1196         * @param object $post Current post in the form of a $wpdb result object. Not WP_Post.
1197         *                     Doesn't have all the properties of a WP_Post.
1198         */
1199        if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
1200            return array(
1201                'xml'           => null,
1202                'last_modified' => null,
1203            );
1204        }
1205
1206        $url = esc_url( get_permalink( $post ) );
1207
1208        /*
1209         * Spec requires the URL to be <=2048 bytes.
1210         * In practice this constraint is unlikely to be violated.
1211         */
1212        if ( 2048 < strlen( $url ) ) {
1213            $url = home_url() . '/?p=' . $post->ID;
1214        }
1215
1216        $last_modified = $post->post_modified_gmt;
1217
1218        // Check for more recent comments.
1219        // Note that 'Y-m-d h:i:s' strings sort lexicographically.
1220        if ( 0 < $post->comment_count ) {
1221            $last_modified = max(
1222                $last_modified,
1223                $this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
1224            );
1225        }
1226
1227        $item_array = array(
1228            'url' => array(
1229                'loc'     => $url,
1230                'lastmod' => jp_sitemap_datetime( $last_modified ),
1231            ),
1232        );
1233
1234        /**
1235         * Filter sitemap URL item before rendering it as XML.
1236         *
1237         * @module sitemaps
1238         *
1239         * @since 3.9.0
1240         *
1241         * @param array $tree Associative array representing sitemap URL element.
1242         * @param int   $post_id ID of the post being processed.
1243         */
1244        $item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
1245
1246        return array(
1247            'xml'           => $item_array,
1248            'last_modified' => $last_modified,
1249        );
1250    }
1251
1252    /**
1253     * Construct the image sitemap url entry for a WP_Post of image type.
1254     *
1255     * @link https://www.sitemaps.org/protocol.html#urldef
1256     *
1257     * @access private
1258     * @since 4.8.0
1259     *
1260     * @param WP_Post $post The image post to be processed.
1261     *
1262     * @return array
1263     *              @type array  $xml An XML fragment representing the post URL.
1264     *              @type string $last_modified Date post was last modified.
1265     */
1266    private function image_post_to_sitemap_item( $post ) {
1267
1268        /**
1269         * Filter condition to allow skipping specific image posts in the sitemap.
1270         *
1271         * @module sitemaps
1272         *
1273         * @since 4.8.0
1274         *
1275         * @param bool    $skip Current boolean. False by default, so no post is skipped.
1276         * @param WP_POST $post Current post object.
1277         */
1278        if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
1279            return array(
1280                'xml'           => null,
1281                'last_modified' => null,
1282            );
1283        }
1284
1285        $url = wp_get_attachment_url( $post->ID );
1286
1287        // Do not include the image if the attached parent is not published.
1288        // Unattached will be published. Otherwise, will inherit parent status.
1289        if ( 'publish' !== get_post_status( $post ) ) {
1290            return array(
1291                'xml'           => null,
1292                'last_modified' => null,
1293            );
1294        }
1295
1296        $parent_url = get_permalink( get_post( $post->post_parent ) );
1297        if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1298            $parent_url = get_permalink( $post );
1299        }
1300
1301        $item_array = array(
1302            'url' => array(
1303                'loc'         => $parent_url,
1304                'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1305                'image:image' => array(
1306                    'image:loc' => $url,
1307                ),
1308            ),
1309        );
1310
1311        /**
1312         * Filter associative array with data to build <url> node
1313         * and its descendants for current post in image sitemap.
1314         *
1315         * @module sitemaps
1316         *
1317         * @since 4.8.0
1318         *
1319         * @param array $item_array Data to build parent and children nodes for current post.
1320         * @param int   $post_id Current image post ID.
1321         */
1322        $item_array = apply_filters(
1323            'jetpack_sitemap_image_sitemap_item',
1324            $item_array,
1325            $post->ID
1326        );
1327
1328        return array(
1329            'xml'           => $item_array,
1330            'last_modified' => $post->post_modified_gmt,
1331        );
1332    }
1333
1334    /**
1335     * Construct the video sitemap url entry for a WP_Post of video type.
1336     *
1337     * @link https://www.sitemaps.org/protocol.html#urldef
1338     * @link https://developers.google.com/webmasters/videosearch/sitemaps
1339     *
1340     * @access private
1341     * @since 4.8.0
1342     *
1343     * @param WP_Post $post The video post to be processed.
1344     *
1345     * @return array
1346     *              @type array  $xml An XML fragment representing the post URL.
1347     *              @type string $last_modified Date post was last modified.
1348     */
1349    private function video_post_to_sitemap_item( $post ) {
1350
1351        /**
1352         * Filter condition to allow skipping specific video posts in the sitemap.
1353         *
1354         * @module sitemaps
1355         *
1356         * @since 4.8.0
1357         *
1358         * @param bool    $skip Current boolean. False by default, so no post is skipped.
1359         * @param WP_POST $post Current post object.
1360         */
1361        if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
1362            return array(
1363                'xml'           => null,
1364                'last_modified' => null,
1365            );
1366        }
1367
1368        // Do not include the video if the attached parent is not published.
1369        // Unattached will be published. Otherwise, will inherit parent status.
1370        if ( 'publish' !== get_post_status( $post ) ) {
1371            return array(
1372                'xml'           => null,
1373                'last_modified' => null,
1374            );
1375        }
1376
1377        $parent_url = esc_url( get_permalink( get_post( $post->post_parent ) ) );
1378        if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1379            $parent_url = esc_url( get_permalink( $post ) );
1380        }
1381
1382        // Prepare the content like get_the_content_feed().
1383        $content = $post->post_content;
1384        /** This filter is already documented in core/wp-includes/post-template.php */
1385        $content = apply_filters( 'the_content', $content );
1386
1387        /** This filter is already documented in core/wp-includes/feed.php */
1388        $content = apply_filters( 'the_content_feed', $content, 'rss2' );
1389
1390        // Include thumbnails for VideoPress videos, use blank image for others.
1391        if ( 'complete' === get_post_meta( $post->ID, 'videopress_status', true ) && has_post_thumbnail( $post ) ) {
1392            $video_thumbnail_url = get_the_post_thumbnail_url( $post );
1393        } else {
1394            /**
1395             * Filter the thumbnail image used in the video sitemap for non-VideoPress videos.
1396             *
1397             * @since 7.2.0
1398             *
1399             * @param string $str Image URL.
1400             */
1401            $video_thumbnail_url = apply_filters( 'jetpack_video_sitemap_default_thumbnail', 'https://s0.wp.com/i/blank.jpg' );
1402        }
1403
1404        $item_array = array(
1405            'url' => array(
1406                'loc'         => $parent_url,
1407                'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1408                'video:video' => array(
1409                    /** This filter is already documented in core/wp-includes/feed.php */
1410                    'video:title'         => apply_filters( 'the_title_rss', $post->post_title ),
1411                    'video:thumbnail_loc' => esc_url( $video_thumbnail_url ),
1412                    'video:description'   => $content,
1413                    'video:content_loc'   => esc_url( wp_get_attachment_url( $post->ID ) ),
1414                ),
1415            ),
1416        );
1417
1418        // TODO: Integrate with VideoPress here.
1419        // cf. video:player_loc tag in video sitemap spec.
1420
1421        /**
1422         * Filter associative array with data to build <url> node
1423         * and its descendants for current post in video sitemap.
1424         *
1425         * @module sitemaps
1426         *
1427         * @since 4.8.0
1428         *
1429         * @param array $item_array Data to build parent and children nodes for current post.
1430         * @param int   $post_id Current video post ID.
1431         */
1432        $item_array = apply_filters(
1433            'jetpack_sitemap_video_sitemap_item',
1434            $item_array,
1435            $post->ID
1436        );
1437
1438        return array(
1439            'xml'           => $item_array,
1440            'last_modified' => $post->post_modified_gmt,
1441        );
1442    }
1443
1444    /**
1445     * Construct the news sitemap url entry for a WP_Post.
1446     *
1447     * @link https://www.sitemaps.org/protocol.html#urldef
1448     *
1449     * @access private
1450     * @since 4.8.0
1451     *
1452     * @param object $post The post to be processed. Similar to WP_Post, but without post_content and post_content_filtered.
1453     *
1454     * @return string An XML fragment representing the post URL.
1455     */
1456    private function post_to_news_sitemap_item( $post ) {
1457
1458        // Exclude posts with meta 'jetpack_seo_noindex' set true from the Jetpack news sitemap.
1459        add_filter( 'jetpack_sitemap_news_skip_post', array( 'Jetpack_SEO_Posts', 'exclude_noindex_posts_from_jetpack_sitemap' ), 10, 2 );
1460
1461        /**
1462         * Filter condition to allow skipping specific posts in news sitemap.
1463         *
1464         * @module sitemaps
1465         *
1466         * @since 3.9.0
1467         *
1468         * @param bool   $skip Current boolean. False by default, so no post is skipped.
1469         * @param object $post Current post in the form of a $wpdb result object. Not WP_Post.
1470         *                     Doesn't have all the properties of a WP_Post.
1471         */
1472        if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1473            return array(
1474                'xml' => null,
1475            );
1476        }
1477
1478        $url = get_permalink( $post );
1479
1480        /*
1481         * Spec requires the URL to be <=2048 bytes.
1482         * In practice this constraint is unlikely to be violated.
1483         */
1484        if ( 2048 < strlen( $url ) ) {
1485            $url = home_url() . '/?p=' . $post->ID;
1486        }
1487
1488        /*
1489         * Trim the locale to an ISO 639 language code as required by Google.
1490         * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1491         * @link https://www.loc.gov/standards/iso639-2/php/code_list.php
1492         */
1493        $language = strtolower( get_locale() );
1494
1495        if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1496            $language = str_replace( '_', '-', $language );
1497        } else {
1498            $language = preg_replace( '/(_.*)$/i', '', $language );
1499        }
1500
1501        $item_array = array(
1502            'url' => array(
1503                'loc'       => $url,
1504                'lastmod'   => jp_sitemap_datetime( $post->post_modified_gmt ),
1505                'news:news' => array(
1506                    'news:publication'      => array(
1507                        'news:name'     => html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
1508                        'news:language' => $language,
1509                    ),
1510                    /** This filter is already documented in core/wp-includes/feed.php */
1511                    'news:title'            => apply_filters( 'the_title_rss', $post->post_title ),
1512                    'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1513                    'news:genres'           => 'Blog',
1514                ),
1515            ),
1516        );
1517
1518        /**
1519         * Filter associative array with data to build <url> node
1520         * and its descendants for current post in news sitemap.
1521         *
1522         * @module sitemaps
1523         *
1524         * @since 3.9.0
1525         *
1526         * @param array $item_array Data to build parent and children nodes for current post.
1527         * @param int   $post_id Current post ID.
1528         */
1529        $item_array = apply_filters(
1530            'jetpack_sitemap_news_sitemap_item',
1531            $item_array,
1532            $post->ID
1533        );
1534
1535        return array(
1536            'xml' => $item_array,
1537        );
1538    }
1539}