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