PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.9.2
Jetpack – WP Security, Backup, Speed, & Growth v13.9.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / sitemaps / sitemap-builder.php
jetpack / modules / sitemaps Last commit date
sitemap-buffer-fallback.php 2 years ago sitemap-buffer-image-fallback.php 2 years ago sitemap-buffer-image.php 4 years ago sitemap-buffer-master-fallback.php 2 years ago sitemap-buffer-master.php 4 years ago sitemap-buffer-news-fallback.php 2 years ago sitemap-buffer-news.php 4 years ago sitemap-buffer-page-fallback.php 2 years ago sitemap-buffer-page.php 4 years ago sitemap-buffer-video-fallback.php 2 years ago sitemap-buffer-video.php 4 years ago sitemap-buffer.php 3 years ago sitemap-builder.php 2 years ago sitemap-constants.php 4 years ago sitemap-finder.php 2 years ago sitemap-librarian.php 2 years ago sitemap-logger.php 4 years ago sitemap-state.php 2 years ago sitemap-stylist.php 2 years ago sitemaps.php 2 years ago
sitemap-builder.php
1480 lines
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
10 /* Include sitemap subclasses, if not already, and include proper buffer based on phpxml's availability. */
11 require_once __DIR__ . '/sitemap-constants.php';
12 require_once __DIR__ . '/sitemap-buffer.php';
13
14 if ( ! class_exists( 'DOMDocument' ) ) {
15 require_once __DIR__ . '/sitemap-buffer-fallback.php';
16 require_once __DIR__ . '/sitemap-buffer-image-fallback.php';
17 require_once __DIR__ . '/sitemap-buffer-master-fallback.php';
18 require_once __DIR__ . '/sitemap-buffer-news-fallback.php';
19 require_once __DIR__ . '/sitemap-buffer-page-fallback.php';
20 require_once __DIR__ . '/sitemap-buffer-video-fallback.php';
21 } else {
22 require_once __DIR__ . '/sitemap-buffer-image.php';
23 require_once __DIR__ . '/sitemap-buffer-master.php';
24 require_once __DIR__ . '/sitemap-buffer-news.php';
25 require_once __DIR__ . '/sitemap-buffer-page.php';
26 require_once __DIR__ . '/sitemap-buffer-video.php';
27 }
28
29 require_once __DIR__ . '/sitemap-librarian.php';
30 require_once __DIR__ . '/sitemap-finder.php';
31 require_once __DIR__ . '/sitemap-state.php';
32
33 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
34 require_once __DIR__ . '/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 * Jetpack_Sitemap_Buffer_Empty constructor.
43 */
44 public function __construct() {
45 parent::__construct( JP_SITEMAP_MAX_ITEMS, JP_SITEMAP_MAX_BYTES, '1970-01-01 00:00:00' );
46
47 $this->doc->appendChild(
48 $this->doc->createComment( "generator='jetpack-" . JETPACK__VERSION . "'" )
49 );
50
51 $this->doc->appendChild(
52 $this->doc->createComment( 'Jetpack_Sitemap_Buffer_Empty' )
53 );
54
55 $this->doc->appendChild(
56 $this->doc->createProcessingInstruction(
57 'xml-stylesheet',
58 'type="text/xsl" href="' . $this->finder->construct_sitemap_url( 'sitemap-index.xsl' ) . '"'
59 )
60 );
61 }
62
63 /**
64 * Returns a DOM element for an empty sitemap.
65 */
66 protected function get_root_element() {
67 if ( ! isset( $this->root ) ) {
68 $this->root = $this->doc->createElement( 'sitemapindex' );
69 $this->root->setAttribute( 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
70 $this->doc->appendChild( $this->root );
71 $this->byte_capacity -= strlen( $this->doc->saveXML( $this->root ) );
72 }
73
74 return $this->root;
75 }
76 }
77
78 /**
79 * The Jetpack_Sitemap_Builder object handles the construction of
80 * all sitemap files (except the XSL files, which are handled by
81 * Jetpack_Sitemap_Stylist.) Other than the constructor, there are
82 * only two public functions: build_all_sitemaps and news_sitemap_xml.
83 *
84 * @since 4.8.0
85 */
86 class Jetpack_Sitemap_Builder { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace
87
88 /**
89 * Librarian object for storing and retrieving sitemap data.
90 *
91 * @access private
92 * @since 4.8.0
93 * @var $librarian Jetpack_Sitemap_Librarian
94 */
95 private $librarian;
96
97 /**
98 * Logger object for reporting debug messages.
99 *
100 * @access private
101 * @since 4.8.0
102 * @var $logger Jetpack_Sitemap_Logger
103 */
104 private $logger = false;
105
106 /**
107 * Finder object for dealing with sitemap URIs.
108 *
109 * @access private
110 * @since 4.8.0
111 * @var $finder Jetpack_Sitemap_Finder
112 */
113 private $finder;
114
115 /**
116 * Construct a new Jetpack_Sitemap_Builder object.
117 *
118 * @access public
119 * @since 4.8.0
120 */
121 public function __construct() {
122 $this->librarian = new Jetpack_Sitemap_Librarian();
123 $this->finder = new Jetpack_Sitemap_Finder();
124
125 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
126 $this->logger = new Jetpack_Sitemap_Logger();
127 }
128
129 update_option(
130 'jetpack_sitemap_post_types',
131 /**
132 * The array of post types to be included in the sitemap.
133 *
134 * Add your custom post type name to the array to have posts of
135 * that type included in the sitemap. The default array includes
136 * 'page' and 'post'.
137 *
138 * The result of this filter is cached in an option, 'jetpack_sitemap_post_types',
139 * so this filter only has to be applied once per generation.
140 *
141 * @since 4.8.0
142 */
143 apply_filters(
144 'jetpack_sitemap_post_types',
145 array( 'post', 'page' )
146 )
147 );
148 }
149
150 /**
151 * Update the sitemap.
152 *
153 * All we do here is call build_next_sitemap_file a bunch of times.
154 *
155 * @since 4.8.0
156 */
157 public function update_sitemap() {
158 if ( $this->logger ) {
159 $this->logger->report( '-- Updating...' );
160 if ( ! class_exists( 'DOMDocument' ) ) {
161 $this->logger->report(
162 __(
163 '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/ .',
164 'jetpack'
165 ),
166 true
167 );
168 }
169 }
170
171 for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) {
172 if ( true === $this->build_next_sitemap_file() ) {
173 break; // All finished!
174 }
175 }
176
177 if ( $this->logger ) {
178 $this->logger->report( '-- ...done for now.' );
179 $this->logger->time();
180 }
181 }
182
183 /**
184 * Generate the next sitemap file.
185 *
186 * Reads the most recent state of the sitemap generation phase,
187 * constructs the next file, and updates the state.
188 *
189 * @since 4.8.0
190 *
191 * @return bool True when finished.
192 */
193 private function build_next_sitemap_file() {
194 $finished = false; // Initialize finished flag.
195
196 // Get the most recent state, and lock the state.
197 $state = Jetpack_Sitemap_State::check_out();
198
199 // Do nothing if the state was locked.
200 if ( false === $state ) {
201 return false;
202 }
203
204 // Otherwise, branch on the sitemap-type key of $state.
205 switch ( $state['sitemap-type'] ) {
206 case JP_PAGE_SITEMAP_TYPE:
207 $this->build_next_sitemap_of_type(
208 JP_PAGE_SITEMAP_TYPE,
209 array( $this, 'build_one_page_sitemap' ),
210 $state
211 );
212 break;
213
214 case JP_PAGE_SITEMAP_INDEX_TYPE:
215 $this->build_next_sitemap_index_of_type(
216 JP_PAGE_SITEMAP_INDEX_TYPE,
217 JP_IMAGE_SITEMAP_TYPE,
218 $state
219 );
220 break;
221
222 case JP_IMAGE_SITEMAP_TYPE:
223 $this->build_next_sitemap_of_type(
224 JP_IMAGE_SITEMAP_TYPE,
225 array( $this, 'build_one_image_sitemap' ),
226 $state
227 );
228 break;
229
230 case JP_IMAGE_SITEMAP_INDEX_TYPE:
231 $this->build_next_sitemap_index_of_type(
232 JP_IMAGE_SITEMAP_INDEX_TYPE,
233 JP_VIDEO_SITEMAP_TYPE,
234 $state
235 );
236 break;
237
238 case JP_VIDEO_SITEMAP_TYPE:
239 $this->build_next_sitemap_of_type(
240 JP_VIDEO_SITEMAP_TYPE,
241 array( $this, 'build_one_video_sitemap' ),
242 $state
243 );
244 break;
245
246 case JP_VIDEO_SITEMAP_INDEX_TYPE:
247 $this->build_next_sitemap_index_of_type(
248 JP_VIDEO_SITEMAP_INDEX_TYPE,
249 JP_MASTER_SITEMAP_TYPE,
250 $state
251 );
252 break;
253
254 case JP_MASTER_SITEMAP_TYPE:
255 $this->build_master_sitemap( $state['max'] );
256
257 // Reset the state and quit.
258 Jetpack_Sitemap_State::reset(
259 JP_PAGE_SITEMAP_TYPE
260 );
261
262 if ( $this->logger ) {
263 $this->logger->report( '-- Finished.' );
264 $this->logger->time();
265 }
266 $finished = true;
267
268 break;
269
270 default:
271 Jetpack_Sitemap_State::reset(
272 JP_PAGE_SITEMAP_TYPE
273 );
274 $finished = true;
275
276 break;
277 } // End switch.
278
279 // Unlock the state.
280 Jetpack_Sitemap_State::unlock();
281
282 return $finished;
283 }
284
285 /**
286 * Build the next sitemap of a given type and update the sitemap state.
287 *
288 * @since 4.8.0
289 *
290 * @param string $sitemap_type The type of the sitemap being generated.
291 * @param callback $build_one A callback which builds a single sitemap file.
292 * @param array $state A sitemap state.
293 */
294 private function build_next_sitemap_of_type( $sitemap_type, $build_one, $state ) {
295 $index_type = jp_sitemap_index_type_of( $sitemap_type );
296
297 // Try to build a sitemap.
298 $result = call_user_func_array(
299 $build_one,
300 array(
301 $state['number'] + 1,
302 $state['last-added'],
303 )
304 );
305
306 if ( false === $result ) {
307 // If no sitemap was generated, advance to the next type.
308 Jetpack_Sitemap_State::check_in(
309 array(
310 'sitemap-type' => $index_type,
311 'last-added' => 0,
312 'number' => 0,
313 'last-modified' => '1970-01-01 00:00:00',
314 )
315 );
316
317 if ( $this->logger ) {
318 $this->logger->report( "-- Cleaning Up $sitemap_type" );
319 }
320
321 // Clean up old files.
322 $this->librarian->delete_numbered_sitemap_rows_after(
323 $state['number'],
324 $sitemap_type
325 );
326
327 return;
328 }
329
330 // Otherwise, update the state.
331 Jetpack_Sitemap_State::check_in(
332 array(
333 'sitemap-type' => $state['sitemap-type'],
334 'last-added' => $result['last_id'],
335 'number' => $state['number'] + 1,
336 'last-modified' => $result['last_modified'],
337 )
338 );
339
340 if ( true === $result['any_left'] ) {
341 // If there's more work to be done with this type, return.
342 return;
343 }
344
345 // Otherwise, advance state to the next sitemap type.
346 Jetpack_Sitemap_State::check_in(
347 array(
348 'sitemap-type' => $index_type,
349 'last-added' => 0,
350 'number' => 0,
351 'last-modified' => '1970-01-01 00:00:00',
352 )
353 );
354
355 if ( $this->logger ) {
356 $this->logger->report( "-- Cleaning Up $sitemap_type" );
357 }
358
359 // Clean up old files.
360 $this->librarian->delete_numbered_sitemap_rows_after(
361 $state['number'] + 1,
362 $sitemap_type
363 );
364 }
365
366 /**
367 * Build the next sitemap index of a given type and update the state.
368 *
369 * @since 4.8.0
370 *
371 * @param string $index_type The type of index being generated.
372 * @param string $next_type The next type to generate after this one.
373 * @param array $state A sitemap state.
374 */
375 private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) {
376 $sitemap_type = jp_sitemap_child_type_of( $index_type );
377
378 // If only 0 or 1 sitemaps were built, advance to the next type and return.
379 if ( 1 >= $state['max'][ $sitemap_type ]['number'] ) {
380 Jetpack_Sitemap_State::check_in(
381 array(
382 'sitemap-type' => $next_type,
383 'last-added' => 0,
384 'number' => 0,
385 'last-modified' => '1970-01-01 00:00:00',
386 )
387 );
388
389 if ( $this->logger ) {
390 $this->logger->report( "-- Cleaning Up $index_type" );
391 }
392
393 // There are no indices of this type.
394 $this->librarian->delete_numbered_sitemap_rows_after(
395 0,
396 $index_type
397 );
398
399 return;
400 }
401
402 // Otherwise, try to build a sitemap index.
403 $result = $this->build_one_sitemap_index(
404 $state['number'] + 1,
405 $state['last-added'],
406 $state['last-modified'],
407 $index_type
408 );
409
410 // If no index was built, advance to the next type and return.
411 if ( false === $result ) {
412 Jetpack_Sitemap_State::check_in(
413 array(
414 'sitemap-type' => $next_type,
415 'last-added' => 0,
416 'number' => 0,
417 'last-modified' => '1970-01-01 00:00:00',
418 )
419 );
420
421 if ( $this->logger ) {
422 $this->logger->report( "-- Cleaning Up $index_type" );
423 }
424
425 // Clean up old files.
426 $this->librarian->delete_numbered_sitemap_rows_after(
427 $state['number'],
428 $index_type
429 );
430
431 return;
432 }
433
434 // Otherwise, check in the state.
435 Jetpack_Sitemap_State::check_in(
436 array(
437 'sitemap-type' => $index_type,
438 'last-added' => $result['last_id'],
439 'number' => $state['number'] + 1,
440 'last-modified' => $result['last_modified'],
441 )
442 );
443
444 // If there are still sitemaps left to index, return.
445 if ( true === $result['any_left'] ) {
446 return;
447 }
448
449 // Otherwise, advance to the next type.
450 Jetpack_Sitemap_State::check_in(
451 array(
452 'sitemap-type' => $next_type,
453 'last-added' => 0,
454 'number' => 0,
455 'last-modified' => '1970-01-01 00:00:00',
456 )
457 );
458
459 if ( $this->logger ) {
460 $this->logger->report( "-- Cleaning Up $index_type" );
461 }
462
463 // We're done generating indices of this type.
464 $this->librarian->delete_numbered_sitemap_rows_after(
465 $state['number'] + 1,
466 $index_type
467 );
468 }
469
470 /**
471 * Builds the master sitemap index.
472 *
473 * @param array $max Array of sitemap types with max index and datetime.
474 *
475 * @since 4.8.0
476 */
477 private function build_master_sitemap( $max ) {
478 $page = array();
479 $image = array();
480 $video = array();
481 if ( $this->logger ) {
482 $this->logger->report( '-- Building Master Sitemap.' );
483 }
484
485 $buffer = new Jetpack_Sitemap_Buffer_Master(
486 JP_SITEMAP_MAX_ITEMS,
487 JP_SITEMAP_MAX_BYTES
488 );
489
490 if ( 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
491 if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
492 $page['filename'] = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, 1 );
493 $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
494 } else {
495 $page['filename'] = jp_sitemap_filename(
496 JP_PAGE_SITEMAP_INDEX_TYPE,
497 $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number']
498 );
499 $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
500 }
501
502 $buffer->append(
503 array(
504 'sitemap' => array(
505 'loc' => $this->finder->construct_sitemap_url( $page['filename'] ),
506 'lastmod' => $page['last_modified'],
507 ),
508 )
509 );
510 }
511
512 if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
513 if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
514 $image['filename'] = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 );
515 $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
516 } else {
517 $image['filename'] = jp_sitemap_filename(
518 JP_IMAGE_SITEMAP_INDEX_TYPE,
519 $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
520 );
521 $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
522 }
523
524 $buffer->append(
525 array(
526 'sitemap' => array(
527 'loc' => $this->finder->construct_sitemap_url( $image['filename'] ),
528 'lastmod' => $image['last_modified'],
529 ),
530 )
531 );
532 }
533
534 if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
535 if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
536 $video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
537 $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] );
538 } else {
539 $video['filename'] = jp_sitemap_filename(
540 JP_VIDEO_SITEMAP_INDEX_TYPE,
541 $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
542 );
543 $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] );
544 }
545
546 $buffer->append(
547 array(
548 'sitemap' => array(
549 'loc' => $this->finder->construct_sitemap_url( $video['filename'] ),
550 'lastmod' => $video['last_modified'],
551 ),
552 )
553 );
554 }
555
556 $this->librarian->store_sitemap_data(
557 0,
558 JP_MASTER_SITEMAP_TYPE,
559 $buffer->contents(),
560 ''
561 );
562 }
563
564 /**
565 * Build and store a single page sitemap. Returns false if no sitemap is built.
566 *
567 * Side effect: Create/update a sitemap row.
568 *
569 * @access private
570 * @since 4.8.0
571 *
572 * @param int $number The number of the current sitemap.
573 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
574 *
575 * @return bool|array @args {
576 * @type int $last_id The ID of the last item to be successfully added to the buffer.
577 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
578 * @type string $last_modified The most recent timestamp to appear on the sitemap.
579 * }
580 */
581 public function build_one_page_sitemap( $number, $from_id ) {
582 $last_post_id = $from_id;
583 $any_posts_left = true;
584
585 if ( $this->logger ) {
586 $debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
587 $this->logger->report( "-- Building $debug_name" );
588 }
589
590 $buffer = new Jetpack_Sitemap_Buffer_Page(
591 JP_SITEMAP_MAX_ITEMS,
592 JP_SITEMAP_MAX_BYTES
593 );
594
595 // 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.
596 if ( 1 === $number && 'page' !== get_option( 'show_on_front' ) ) {
597 $item_array = array(
598 'url' => array(
599 'loc' => home_url( '/' ),
600 ),
601 );
602
603 /**
604 * Filter associative array with data to build <url> node
605 * and its descendants for site home.
606 *
607 * @module sitemaps
608 *
609 * @since 3.9.0
610 *
611 * @param array $blog_home Data to build parent and children nodes for site home.
612 */
613 $item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
614
615 $buffer->append( $item_array );
616 }
617
618 // Add as many items to the buffer as possible.
619 while ( $last_post_id >= 0 && false === $buffer->is_full() ) {
620 $posts = $this->librarian->query_posts_after_id(
621 $last_post_id,
622 JP_SITEMAP_BATCH_SIZE
623 );
624
625 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
626 $any_posts_left = false;
627 break;
628 }
629
630 foreach ( $posts as $post ) {
631 $current_item = $this->post_to_sitemap_item( $post );
632
633 if ( true === $buffer->append( $current_item['xml'] ) ) {
634 $last_post_id = $post->ID;
635 $buffer->view_time( $current_item['last_modified'] );
636 } else {
637 break;
638 }
639 }
640 }
641
642 // Handle other page sitemap URLs.
643 if ( false === $any_posts_left || $last_post_id < 0 ) {
644 // Negative IDs are used to track URL indexes.
645 $last_post_id = min( 0, $last_post_id );
646 $any_posts_left = true; // Reinitialize.
647
648 /**
649 * Filter other page sitemap URLs.
650 *
651 * @module sitemaps
652 *
653 * @since 6.1.0
654 *
655 * @param array $urls An array of other URLs.
656 */
657 $other_urls = apply_filters( 'jetpack_page_sitemap_other_urls', array() );
658
659 if ( $other_urls ) { // Start with index [1].
660 $other_urls = array_values( $other_urls );
661 array_unshift( $other_urls, $other_urls[0] );
662 unset( $other_urls[0] );
663 }
664
665 // Add as many items to the buffer as possible.
666 while ( false === $buffer->is_full() ) {
667 $last_post_id_index = abs( $last_post_id );
668 $start_from_post_id_index = $last_post_id_index ? $last_post_id_index + 1 : 0;
669 $urls = array_slice(
670 $other_urls,
671 $start_from_post_id_index,
672 JP_SITEMAP_BATCH_SIZE,
673 true
674 );
675
676 if ( ! $urls ) {
677 $any_posts_left = false;
678 break;
679 }
680
681 foreach ( $urls as $index => $url ) {
682 if ( ! is_array( $url ) ) {
683 $url = array( 'loc' => $url );
684 }
685 $item = array( 'xml' => compact( 'url' ) );
686
687 if ( true === $buffer->append( $item['xml'] ) ) {
688 $last_post_id = -$index;
689 if ( isset( $url['lastmod'] ) ) {
690 $buffer->view_time( jp_sitemap_datetime( $url['lastmod'] ) );
691 }
692 } else {
693 break;
694 }
695 }
696 }
697 }
698
699 // If no items were added, return false.
700 if ( true === $buffer->is_empty() ) {
701 return false;
702 }
703
704 /**
705 * Filter sitemap before rendering it as XML.
706 *
707 * @module sitemaps
708 *
709 * @since 3.9.0
710 * @since 5.3.0 returns an element of DOMDocument type instead of SimpleXMLElement
711 *
712 * @param DOMDocument $doc Data tree for sitemap.
713 * @param string $last_modified Date of last modification.
714 */
715 $tree = apply_filters( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
716 'jetpack_print_sitemap',
717 $buffer->get_document(),
718 $buffer->last_modified()
719 );
720
721 // Store the buffer as the content of a sitemap row.
722 $this->librarian->store_sitemap_data(
723 $number,
724 JP_PAGE_SITEMAP_TYPE,
725 $buffer->contents(),
726 $buffer->last_modified()
727 );
728
729 /*
730 * Now report back with the ID of the last post ID to be
731 * successfully added and whether there are any posts left.
732 */
733 return array(
734 'last_id' => $last_post_id,
735 'any_left' => $any_posts_left,
736 'last_modified' => $buffer->last_modified(),
737 );
738 }
739
740 /**
741 * Build and store a single image sitemap. Returns false if no sitemap is built.
742 *
743 * Side effect: Create/update an image sitemap row.
744 *
745 * @access private
746 * @since 4.8.0
747 *
748 * @param int $number The number of the current sitemap.
749 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
750 *
751 * @return bool|array @args {
752 * @type int $last_id The ID of the last item to be successfully added to the buffer.
753 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
754 * @type string $last_modified The most recent timestamp to appear on the sitemap.
755 * }
756 */
757 public function build_one_image_sitemap( $number, $from_id ) {
758 $last_post_id = $from_id;
759 $any_posts_left = true;
760
761 if ( $this->logger ) {
762 $debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
763 $this->logger->report( "-- Building $debug_name" );
764 }
765
766 $buffer = new Jetpack_Sitemap_Buffer_Image(
767 JP_SITEMAP_MAX_ITEMS,
768 JP_SITEMAP_MAX_BYTES
769 );
770
771 // Add as many items to the buffer as possible.
772 while ( false === $buffer->is_full() ) {
773 $posts = $this->librarian->query_images_after_id(
774 $last_post_id,
775 JP_SITEMAP_BATCH_SIZE
776 );
777
778 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
779 $any_posts_left = false;
780 break;
781 }
782
783 foreach ( $posts as $post ) {
784 $current_item = $this->image_post_to_sitemap_item( $post );
785
786 if ( true === $buffer->append( $current_item['xml'] ) ) {
787 $last_post_id = $post->ID;
788 $buffer->view_time( $current_item['last_modified'] );
789 } else {
790 break;
791 }
792 }
793 }
794
795 // If no items were added, return false.
796 if ( true === $buffer->is_empty() ) {
797 return false;
798 }
799
800 // Store the buffer as the content of a jp_sitemap post.
801 $this->librarian->store_sitemap_data(
802 $number,
803 JP_IMAGE_SITEMAP_TYPE,
804 $buffer->contents(),
805 $buffer->last_modified()
806 );
807
808 /*
809 * Now report back with the ID of the last post to be
810 * successfully added and whether there are any posts left.
811 */
812 return array(
813 'last_id' => $last_post_id,
814 'any_left' => $any_posts_left,
815 'last_modified' => $buffer->last_modified(),
816 );
817 }
818
819 /**
820 * Build and store a single video sitemap. Returns false if no sitemap is built.
821 *
822 * Side effect: Create/update an video sitemap row.
823 *
824 * @access private
825 * @since 4.8.0
826 *
827 * @param int $number The number of the current sitemap.
828 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
829 *
830 * @return bool|array @args {
831 * @type int $last_id The ID of the last item to be successfully added to the buffer.
832 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
833 * @type string $last_modified The most recent timestamp to appear on the sitemap.
834 * }
835 */
836 public function build_one_video_sitemap( $number, $from_id ) {
837 $last_post_id = $from_id;
838 $any_posts_left = true;
839
840 if ( $this->logger ) {
841 $debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
842 $this->logger->report( "-- Building $debug_name" );
843 }
844
845 $buffer = new Jetpack_Sitemap_Buffer_Video(
846 JP_SITEMAP_MAX_ITEMS,
847 JP_SITEMAP_MAX_BYTES
848 );
849
850 // Add as many items to the buffer as possible.
851 while ( false === $buffer->is_full() ) {
852 $posts = $this->librarian->query_videos_after_id(
853 $last_post_id,
854 JP_SITEMAP_BATCH_SIZE
855 );
856
857 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
858 $any_posts_left = false;
859 break;
860 }
861
862 foreach ( $posts as $post ) {
863 $current_item = $this->video_post_to_sitemap_item( $post );
864
865 if ( true === $buffer->append( $current_item['xml'] ) ) {
866 $last_post_id = $post->ID;
867 $buffer->view_time( $current_item['last_modified'] );
868 } else {
869 break;
870 }
871 }
872 }
873
874 // If no items were added, return false.
875 if ( true === $buffer->is_empty() ) {
876 return false;
877 }
878
879 if ( false === $buffer->is_empty() ) {
880 $this->librarian->store_sitemap_data(
881 $number,
882 JP_VIDEO_SITEMAP_TYPE,
883 $buffer->contents(),
884 $buffer->last_modified()
885 );
886 }
887
888 /*
889 * Now report back with the ID of the last post to be
890 * successfully added and whether there are any posts left.
891 */
892 return array(
893 'last_id' => $last_post_id,
894 'any_left' => $any_posts_left,
895 'last_modified' => $buffer->last_modified(),
896 );
897 }
898
899 /**
900 * Build and store a single page sitemap index. Return false if no index is built.
901 *
902 * Side effect: Create/update a sitemap index row.
903 *
904 * @access private
905 * @since 4.8.0
906 *
907 * @param int $number The number of the current sitemap index.
908 * @param int $from_id The greatest lower bound of the IDs of the sitemaps to be included.
909 * @param string $datetime Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
910 * @param string $index_type Sitemap index type.
911 *
912 * @return bool|array @args {
913 * @type int $last_id The ID of the last item to be successfully added to the buffer.
914 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
915 * @type string $last_modified The most recent timestamp to appear on the sitemap.
916 * }
917 */
918 private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
919 $last_sitemap_id = $from_id;
920 $any_sitemaps_left = true;
921
922 // Check the datetime format.
923 $datetime = jp_sitemap_datetime( $datetime );
924
925 $sitemap_type = jp_sitemap_child_type_of( $index_type );
926
927 if ( $this->logger ) {
928 $index_debug_name = jp_sitemap_filename( $index_type, $number );
929 $this->logger->report( "-- Building $index_debug_name" );
930 }
931
932 $buffer = new Jetpack_Sitemap_Buffer_Master(
933 JP_SITEMAP_MAX_ITEMS,
934 JP_SITEMAP_MAX_BYTES,
935 $datetime
936 );
937
938 // Add pointer to the previous sitemap index (unless we're at the first one).
939 if ( 1 !== $number ) {
940 $i = $number - 1;
941 $prev_index_url = $this->finder->construct_sitemap_url(
942 jp_sitemap_filename( $index_type, $i )
943 );
944
945 $item_array = array(
946 'sitemap' => array(
947 'loc' => $prev_index_url,
948 'lastmod' => $datetime,
949 ),
950 );
951
952 $buffer->append( $item_array );
953 }
954
955 // Add as many items to the buffer as possible.
956 while ( false === $buffer->is_full() ) {
957 // Retrieve a batch of posts (in order).
958 $posts = $this->librarian->query_sitemaps_after_id(
959 $sitemap_type,
960 $last_sitemap_id,
961 JP_SITEMAP_BATCH_SIZE
962 );
963
964 // If there were no posts to get, make a note.
965 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
966 $any_sitemaps_left = false;
967 break;
968 }
969
970 // Otherwise, loop through each post in the batch.
971 foreach ( $posts as $post ) {
972 // Generate the sitemap XML for the post.
973 $current_item = $this->sitemap_row_to_index_item( (array) $post );
974
975 // Try adding this item to the buffer.
976 if ( true === $buffer->append( $current_item['xml'] ) ) {
977 $last_sitemap_id = $post['ID'];
978 $buffer->view_time( $current_item['last_modified'] );
979 } else {
980 // Otherwise stop looping through posts.
981 break;
982 }
983 }
984 }
985
986 // If no items were added, return false.
987 if ( true === $buffer->is_empty() ) {
988 return false;
989 }
990
991 $this->librarian->store_sitemap_data(
992 $number,
993 $index_type,
994 $buffer->contents(),
995 $buffer->last_modified()
996 );
997
998 /*
999 * Now report back with the ID of the last sitemap post ID to
1000 * be successfully added, whether there are any sitemap posts
1001 * left, and the most recent modification time seen.
1002 */
1003 return array(
1004 'last_id' => $last_sitemap_id,
1005 'any_left' => $any_sitemaps_left,
1006 'last_modified' => $buffer->last_modified(),
1007 );
1008 }
1009
1010 /**
1011 * Construct the sitemap index url entry for a sitemap row.
1012 *
1013 * @link https://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
1014 *
1015 * @access private
1016 * @since 4.8.0
1017 *
1018 * @param array $row The sitemap data to be processed.
1019 *
1020 * @return string An XML fragment representing the post URL.
1021 */
1022 private function sitemap_row_to_index_item( $row ) {
1023 $url = $this->finder->construct_sitemap_url( $row['post_title'] );
1024
1025 $item_array = array(
1026 'sitemap' => array(
1027 'loc' => $url,
1028 'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
1029 ),
1030 );
1031
1032 return array(
1033 'xml' => $item_array,
1034 'last_modified' => $row['post_date'],
1035 );
1036 }
1037
1038 /**
1039 * This is served instead of a 404 when the master sitemap is requested
1040 * but not yet generated.
1041 *
1042 * @access public
1043 * @since 6.7.0
1044 *
1045 * @return string The empty sitemap xml.
1046 */
1047 public function empty_sitemap_xml() {
1048 $empty_sitemap = new Jetpack_Sitemap_Buffer_Empty();
1049 return $empty_sitemap->contents();
1050 }
1051
1052 /**
1053 * Build and return the news sitemap xml. Note that the result of this
1054 * function is cached in the transient 'jetpack_news_sitemap_xml'.
1055 *
1056 * @access public
1057 * @since 4.8.0
1058 *
1059 * @return string The news sitemap xml.
1060 */
1061 public function news_sitemap_xml() {
1062 $the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
1063
1064 if ( false === $the_stored_news_sitemap ) {
1065
1066 if ( $this->logger ) {
1067 $this->logger->report( 'Beginning news sitemap generation.' );
1068 }
1069
1070 /**
1071 * Filter limit of entries to include in news sitemap.
1072 *
1073 * @module sitemaps
1074 *
1075 * @since 3.9.0
1076 *
1077 * @param int $count Number of entries to include in news sitemap.
1078 */
1079 $item_limit = apply_filters(
1080 'jetpack_sitemap_news_sitemap_count',
1081 JP_NEWS_SITEMAP_MAX_ITEMS
1082 );
1083
1084 $buffer = new Jetpack_Sitemap_Buffer_News(
1085 min( $item_limit, JP_NEWS_SITEMAP_MAX_ITEMS ),
1086 JP_SITEMAP_MAX_BYTES
1087 );
1088
1089 $posts = $this->librarian->query_most_recent_posts( JP_NEWS_SITEMAP_MAX_ITEMS );
1090
1091 foreach ( $posts as $post ) {
1092 $current_item = $this->post_to_news_sitemap_item( $post );
1093
1094 if ( false === $buffer->append( $current_item['xml'] ) ) {
1095 break;
1096 }
1097 }
1098
1099 if ( $this->logger ) {
1100 $this->logger->time( 'End news sitemap generation.' );
1101 }
1102
1103 $the_stored_news_sitemap = $buffer->contents();
1104
1105 set_transient(
1106 'jetpack_news_sitemap_xml',
1107 $the_stored_news_sitemap,
1108 JP_NEWS_SITEMAP_INTERVAL
1109 );
1110 } // End if.
1111
1112 return $the_stored_news_sitemap;
1113 }
1114
1115 /**
1116 * Construct the sitemap url entry for a WP_Post.
1117 *
1118 * @link https://www.sitemaps.org/protocol.html#urldef
1119 * @access private
1120 * @since 4.8.0
1121 *
1122 * @param WP_Post $post The post to be processed.
1123 *
1124 * @return array
1125 * @type array $xml An XML fragment representing the post URL.
1126 * @type string $last_modified Date post was last modified.
1127 */
1128 private function post_to_sitemap_item( $post ) {
1129
1130 /**
1131 * Filter condition to allow skipping specific posts in sitemap.
1132 *
1133 * @module sitemaps
1134 *
1135 * @since 3.9.0
1136 *
1137 * @param bool $skip Current boolean. False by default, so no post is skipped.
1138 * @param object $post Current post in the form of a $wpdb result object. Not WP_Post.
1139 */
1140 if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
1141 return array(
1142 'xml' => null,
1143 'last_modified' => null,
1144 );
1145 }
1146
1147 $url = esc_url( get_permalink( $post ) );
1148
1149 /*
1150 * Spec requires the URL to be <=2048 bytes.
1151 * In practice this constraint is unlikely to be violated.
1152 */
1153 if ( 2048 < strlen( $url ) ) {
1154 $url = home_url() . '/?p=' . $post->ID;
1155 }
1156
1157 $last_modified = $post->post_modified_gmt;
1158
1159 // Check for more recent comments.
1160 // Note that 'Y-m-d h:i:s' strings sort lexicographically.
1161 if ( 0 < $post->comment_count ) {
1162 $last_modified = max(
1163 $last_modified,
1164 $this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
1165 );
1166 }
1167
1168 $item_array = array(
1169 'url' => array(
1170 'loc' => $url,
1171 'lastmod' => jp_sitemap_datetime( $last_modified ),
1172 ),
1173 );
1174
1175 /**
1176 * Filter sitemap URL item before rendering it as XML.
1177 *
1178 * @module sitemaps
1179 *
1180 * @since 3.9.0
1181 *
1182 * @param array $tree Associative array representing sitemap URL element.
1183 * @param int $post_id ID of the post being processed.
1184 */
1185 $item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
1186
1187 return array(
1188 'xml' => $item_array,
1189 'last_modified' => $last_modified,
1190 );
1191 }
1192
1193 /**
1194 * Construct the image sitemap url entry for a WP_Post of image type.
1195 *
1196 * @link https://www.sitemaps.org/protocol.html#urldef
1197 *
1198 * @access private
1199 * @since 4.8.0
1200 *
1201 * @param WP_Post $post The image post to be processed.
1202 *
1203 * @return array
1204 * @type array $xml An XML fragment representing the post URL.
1205 * @type string $last_modified Date post was last modified.
1206 */
1207 private function image_post_to_sitemap_item( $post ) {
1208
1209 /**
1210 * Filter condition to allow skipping specific image posts in the sitemap.
1211 *
1212 * @module sitemaps
1213 *
1214 * @since 4.8.0
1215 *
1216 * @param bool $skip Current boolean. False by default, so no post is skipped.
1217 * @param WP_POST $post Current post object.
1218 */
1219 if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
1220 return array(
1221 'xml' => null,
1222 'last_modified' => null,
1223 );
1224 }
1225
1226 $url = wp_get_attachment_url( $post->ID );
1227
1228 // Do not include the image if the attached parent is not published.
1229 // Unattached will be published. Otherwise, will inherit parent status.
1230 if ( 'publish' !== get_post_status( $post ) ) {
1231 return array(
1232 'xml' => null,
1233 'last_modified' => null,
1234 );
1235 }
1236
1237 $parent_url = get_permalink( get_post( $post->post_parent ) );
1238 if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1239 $parent_url = get_permalink( $post );
1240 }
1241
1242 $item_array = array(
1243 'url' => array(
1244 'loc' => $parent_url,
1245 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1246 'image:image' => array(
1247 'image:loc' => $url,
1248 ),
1249 ),
1250 );
1251
1252 /**
1253 * Filter associative array with data to build <url> node
1254 * and its descendants for current post in image sitemap.
1255 *
1256 * @module sitemaps
1257 *
1258 * @since 4.8.0
1259 *
1260 * @param array $item_array Data to build parent and children nodes for current post.
1261 * @param int $post_id Current image post ID.
1262 */
1263 $item_array = apply_filters(
1264 'jetpack_sitemap_image_sitemap_item',
1265 $item_array,
1266 $post->ID
1267 );
1268
1269 return array(
1270 'xml' => $item_array,
1271 'last_modified' => $post->post_modified_gmt,
1272 );
1273 }
1274
1275 /**
1276 * Construct the video sitemap url entry for a WP_Post of video type.
1277 *
1278 * @link https://www.sitemaps.org/protocol.html#urldef
1279 * @link https://developers.google.com/webmasters/videosearch/sitemaps
1280 *
1281 * @access private
1282 * @since 4.8.0
1283 *
1284 * @param WP_Post $post The video post to be processed.
1285 *
1286 * @return array
1287 * @type array $xml An XML fragment representing the post URL.
1288 * @type string $last_modified Date post was last modified.
1289 */
1290 private function video_post_to_sitemap_item( $post ) {
1291
1292 /**
1293 * Filter condition to allow skipping specific video posts in the sitemap.
1294 *
1295 * @module sitemaps
1296 *
1297 * @since 4.8.0
1298 *
1299 * @param bool $skip Current boolean. False by default, so no post is skipped.
1300 * @param WP_POST $post Current post object.
1301 */
1302 if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
1303 return array(
1304 'xml' => null,
1305 'last_modified' => null,
1306 );
1307 }
1308
1309 // Do not include the video if the attached parent is not published.
1310 // Unattached will be published. Otherwise, will inherit parent status.
1311 if ( 'publish' !== get_post_status( $post ) ) {
1312 return array(
1313 'xml' => null,
1314 'last_modified' => null,
1315 );
1316 }
1317
1318 $parent_url = esc_url( get_permalink( get_post( $post->post_parent ) ) );
1319 if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1320 $parent_url = esc_url( get_permalink( $post ) );
1321 }
1322
1323 // Prepare the content like get_the_content_feed().
1324 $content = $post->post_content;
1325 /** This filter is already documented in core/wp-includes/post-template.php */
1326 $content = apply_filters( 'the_content', $content );
1327
1328 /** This filter is already documented in core/wp-includes/feed.php */
1329 $content = apply_filters( 'the_content_feed', $content, 'rss2' );
1330
1331 // Include thumbnails for VideoPress videos, use blank image for others.
1332 if ( 'complete' === get_post_meta( $post->ID, 'videopress_status', true ) && has_post_thumbnail( $post ) ) {
1333 $video_thumbnail_url = get_the_post_thumbnail_url( $post );
1334 } else {
1335 /**
1336 * Filter the thumbnail image used in the video sitemap for non-VideoPress videos.
1337 *
1338 * @since 7.2.0
1339 *
1340 * @param string $str Image URL.
1341 */
1342 $video_thumbnail_url = apply_filters( 'jetpack_video_sitemap_default_thumbnail', 'https://s0.wp.com/i/blank.jpg' );
1343 }
1344
1345 $item_array = array(
1346 'url' => array(
1347 'loc' => $parent_url,
1348 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1349 'video:video' => array(
1350 /** This filter is already documented in core/wp-includes/feed.php */
1351 'video:title' => apply_filters( 'the_title_rss', $post->post_title ),
1352 'video:thumbnail_loc' => esc_url( $video_thumbnail_url ),
1353 'video:description' => $content,
1354 'video:content_loc' => esc_url( wp_get_attachment_url( $post->ID ) ),
1355 ),
1356 ),
1357 );
1358
1359 // TODO: Integrate with VideoPress here.
1360 // cf. video:player_loc tag in video sitemap spec.
1361
1362 /**
1363 * Filter associative array with data to build <url> node
1364 * and its descendants for current post in video sitemap.
1365 *
1366 * @module sitemaps
1367 *
1368 * @since 4.8.0
1369 *
1370 * @param array $item_array Data to build parent and children nodes for current post.
1371 * @param int $post_id Current video post ID.
1372 */
1373 $item_array = apply_filters(
1374 'jetpack_sitemap_video_sitemap_item',
1375 $item_array,
1376 $post->ID
1377 );
1378
1379 return array(
1380 'xml' => $item_array,
1381 'last_modified' => $post->post_modified_gmt,
1382 );
1383 }
1384
1385 /**
1386 * Construct the news sitemap url entry for a WP_Post.
1387 *
1388 * @link https://www.sitemaps.org/protocol.html#urldef
1389 *
1390 * @access private
1391 * @since 4.8.0
1392 *
1393 * @param WP_Post $post The post to be processed.
1394 *
1395 * @return string An XML fragment representing the post URL.
1396 */
1397 private function post_to_news_sitemap_item( $post ) {
1398
1399 // Exclude posts with meta 'jetpack_seo_noindex' set true from the Jetpack news sitemap.
1400 add_filter( 'jetpack_sitemap_news_skip_post', array( 'Jetpack_SEO_Posts', 'exclude_noindex_posts_from_jetpack_sitemap' ), 10, 2 );
1401
1402 /**
1403 * Filter condition to allow skipping specific posts in news sitemap.
1404 *
1405 * @module sitemaps
1406 *
1407 * @since 3.9.0
1408 *
1409 * @param bool $skip Current boolean. False by default, so no post is skipped.
1410 * @param WP_POST $post Current post object.
1411 */
1412 if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1413 return array(
1414 'xml' => null,
1415 );
1416 }
1417
1418 $url = get_permalink( $post );
1419
1420 /*
1421 * Spec requires the URL to be <=2048 bytes.
1422 * In practice this constraint is unlikely to be violated.
1423 */
1424 if ( 2048 < strlen( $url ) ) {
1425 $url = home_url() . '/?p=' . $post->ID;
1426 }
1427
1428 /*
1429 * Trim the locale to an ISO 639 language code as required by Google.
1430 * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1431 * @link https://www.loc.gov/standards/iso639-2/php/code_list.php
1432 */
1433 $language = strtolower( get_locale() );
1434
1435 if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1436 $language = str_replace( '_', '-', $language );
1437 } else {
1438 $language = preg_replace( '/(_.*)$/i', '', $language );
1439 }
1440
1441 $item_array = array(
1442 'url' => array(
1443 'loc' => $url,
1444 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1445 'news:news' => array(
1446 'news:publication' => array(
1447 'news:name' => html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
1448 'news:language' => $language,
1449 ),
1450 /** This filter is already documented in core/wp-includes/feed.php */
1451 'news:title' => apply_filters( 'the_title_rss', $post->post_title ),
1452 'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1453 'news:genres' => 'Blog',
1454 ),
1455 ),
1456 );
1457
1458 /**
1459 * Filter associative array with data to build <url> node
1460 * and its descendants for current post in news sitemap.
1461 *
1462 * @module sitemaps
1463 *
1464 * @since 3.9.0
1465 *
1466 * @param array $item_array Data to build parent and children nodes for current post.
1467 * @param int $post_id Current post ID.
1468 */
1469 $item_array = apply_filters(
1470 'jetpack_sitemap_news_sitemap_item',
1471 $item_array,
1472 $post->ID
1473 );
1474
1475 return array(
1476 'xml' => $item_array,
1477 );
1478 }
1479 }
1480