Completed
Push — enhance/9539 ( 3ba0c7...bf6216 )
by
unknown
12:01
created

Jetpack_Sitemap_Builder   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 1378
Duplicated Lines 17.05 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 105
lcom 1
cbo 10
dl 235
loc 1378
rs 0.8
c 0
b 0
f 0

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Sitemap_Builder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Sitemap_Builder, and based on these observations, apply Extract Interface, too.

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

Syntax error, unexpected '*'
Loading history...
1328
			*
1329
			* @since 7.2.0
1330
			*
1331
			* @param string $str Image URL.
1332
			*/
1333
			$video_thumbnail_url = apply_filters( 'jetpack_video_sitemap_default_thumbnail', 'https://s0.wp.com/i/blank.jpg' );
1334
		}
1335
1336
		$item_array = array(
1337
			'url' => array(
1338
				'loc'         => $parent_url,
1339
				'lastmod'     => jp_sitemap_datetime( $post->post_modified_gmt ),
1340
				'video:video' => array(
1341
					/** This filter is already documented in core/wp-includes/feed.php */
1342
					'video:title'         => apply_filters( 'the_title_rss', $post->post_title ),
1343
					'video:thumbnail_loc' => $video_thumbnail_url,
1344
					'video:description'   => $content,
1345
					'video:content_loc'   => esc_url( wp_get_attachment_url( $post->ID ) ),
1346
				),
1347
			),
1348
		);
1349
1350
		// TODO: Integrate with VideoPress here.
1351
		// cf. video:player_loc tag in video sitemap spec.
1352
1353
		/**
1354
		 * Filter associative array with data to build <url> node
1355
		 * and its descendants for current post in video sitemap.
1356
		 *
1357
		 * @module sitemaps
1358
		 *
1359
		 * @since 4.8.0
1360
		 *
1361
		 * @param array $item_array Data to build parent and children nodes for current post.
1362
		 * @param int   $post_id Current video post ID.
1363
		 */
1364
		$item_array = apply_filters(
1365
			'jetpack_sitemap_video_sitemap_item',
1366
			$item_array,
1367
			$post->ID
1368
		);
1369
1370
		return array(
1371
			'xml'           => $item_array,
1372
			'last_modified' => $post->post_modified_gmt,
1373
		);
1374
	}
1375
1376
	/**
1377
	 * Construct the news sitemap url entry for a WP_Post.
1378
	 *
1379
	 * @link http://www.sitemaps.org/protocol.html#urldef
1380
	 *
1381
	 * @access private
1382
	 * @since 4.8.0
1383
	 *
1384
	 * @param WP_Post $post The post to be processed.
1385
	 *
1386
	 * @return string An XML fragment representing the post URL.
1387
	 */
1388
	private function post_to_news_sitemap_item( $post ) {
1389
1390
		/**
1391
		 * Filter condition to allow skipping specific posts in news sitemap.
1392
		 *
1393
		 * @module sitemaps
1394
		 *
1395
		 * @since 3.9.0
1396
		 *
1397
		 * @param bool    $skip Current boolean. False by default, so no post is skipped.
1398
		 * @param WP_POST $post Current post object.
1399
		 */
1400
		if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1401
			return array(
1402
				'xml' => null,
1403
			);
1404
		}
1405
1406
		$url = get_permalink( $post );
1407
1408
		/*
1409
		 * Spec requires the URL to be <=2048 bytes.
1410
		 * In practice this constraint is unlikely to be violated.
1411
		 */
1412
		if ( 2048 < strlen( $url ) ) {
1413
			$url = home_url() . '/?p=' . $post->ID;
1414
		}
1415
1416
		/*
1417
		 * Trim the locale to an ISO 639 language code as required by Google.
1418
		 * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1419
		 * @link http://www.loc.gov/standards/iso639-2/php/code_list.php
1420
		 */
1421
		$language = strtolower( get_locale() );
1422
1423
		if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1424
			$language = str_replace( '_', '-', $language );
1425
		} else {
1426
			$language = preg_replace( '/(_.*)$/i', '', $language );
1427
		}
1428
1429
		$item_array = array(
1430
			'url' => array(
1431
				'loc'       => $url,
1432
				'lastmod'   => jp_sitemap_datetime( $post->post_modified_gmt ),
1433
				'news:news' => array(
1434
					'news:publication'      => array(
1435
						'news:name'     => html_entity_decode( get_bloginfo( 'name' ) ),
1436
						'news:language' => $language,
1437
					),
1438
					/** This filter is already documented in core/wp-includes/feed.php */
1439
					'news:title'            => apply_filters( 'the_title_rss', $post->post_title ),
1440
					'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1441
					'news:genres'           => 'Blog',
1442
				),
1443
			),
1444
		);
1445
1446
		/**
1447
		 * Filter associative array with data to build <url> node
1448
		 * and its descendants for current post in news sitemap.
1449
		 *
1450
		 * @module sitemaps
1451
		 *
1452
		 * @since 3.9.0
1453
		 *
1454
		 * @param array $item_array Data to build parent and children nodes for current post.
1455
		 * @param int   $post_id Current post ID.
1456
		 */
1457
		$item_array = apply_filters(
1458
			'jetpack_sitemap_news_sitemap_item',
1459
			$item_array,
1460
			$post->ID
1461
		);
1462
1463
		return array(
1464
			'xml' => $item_array,
1465
		);
1466
	}
1467
}
1468