PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.7.1
Jetpack – WP Security, Backup, Speed, & Growth v14.7.1
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-factory.php 1 year ago sitemap-buffer-fallback.php 2 years ago sitemap-buffer-image-fallback.php 2 years ago sitemap-buffer-image-xmlwriter.php 1 year ago sitemap-buffer-image.php 4 years ago sitemap-buffer-master-fallback.php 2 years ago sitemap-buffer-master-xmlwriter.php 1 year ago sitemap-buffer-master.php 4 years ago sitemap-buffer-news-fallback.php 2 years ago sitemap-buffer-news-xmlwriter.php 1 year ago sitemap-buffer-news.php 4 years ago sitemap-buffer-page-fallback.php 2 years ago sitemap-buffer-page-xmlwriter.php 1 year ago sitemap-buffer-page.php 4 years ago sitemap-buffer-video-fallback.php 2 years ago sitemap-buffer-video-xmlwriter.php 1 year ago sitemap-buffer-video.php 4 years ago sitemap-buffer-xmlwriter.php 1 year ago sitemap-buffer.php 1 year ago sitemap-builder.php 1 year ago sitemap-constants.php 1 year ago sitemap-finder.php 2 years ago sitemap-librarian.php 1 year ago sitemap-logger.php 1 year ago sitemap-state.php 2 years ago sitemap-stylist.php 2 years ago sitemaps.php 1 year ago
sitemap-builder.php
1508 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 = Jetpack_Sitemap_Buffer_Factory::create(
486 'master',
487 JP_SITEMAP_MAX_ITEMS,
488 JP_SITEMAP_MAX_BYTES
489 );
490
491 if ( ! $buffer ) {
492 return;
493 }
494
495 if ( 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
496 if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) {
497 $page['filename'] = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, 1 );
498 $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] );
499 } else {
500 $page['filename'] = jp_sitemap_filename(
501 JP_PAGE_SITEMAP_INDEX_TYPE,
502 $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number']
503 );
504 $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
505 }
506
507 $buffer->append(
508 array(
509 'sitemap' => array(
510 'loc' => $this->finder->construct_sitemap_url( $page['filename'] ),
511 'lastmod' => $page['last_modified'],
512 ),
513 )
514 );
515 }
516
517 if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
518 if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) {
519 $image['filename'] = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 );
520 $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] );
521 } else {
522 $image['filename'] = jp_sitemap_filename(
523 JP_IMAGE_SITEMAP_INDEX_TYPE,
524 $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number']
525 );
526 $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] );
527 }
528
529 $buffer->append(
530 array(
531 'sitemap' => array(
532 'loc' => $this->finder->construct_sitemap_url( $image['filename'] ),
533 'lastmod' => $image['last_modified'],
534 ),
535 )
536 );
537 }
538
539 if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
540 if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
541 $video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
542 $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] );
543 } else {
544 $video['filename'] = jp_sitemap_filename(
545 JP_VIDEO_SITEMAP_INDEX_TYPE,
546 $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
547 );
548 $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] );
549 }
550
551 $buffer->append(
552 array(
553 'sitemap' => array(
554 'loc' => $this->finder->construct_sitemap_url( $video['filename'] ),
555 'lastmod' => $video['last_modified'],
556 ),
557 )
558 );
559 }
560
561 $this->librarian->store_sitemap_data(
562 0,
563 JP_MASTER_SITEMAP_TYPE,
564 $buffer->contents(),
565 ''
566 );
567 }
568
569 /**
570 * Build and store a single page sitemap. Returns false if no sitemap is built.
571 *
572 * Side effect: Create/update a sitemap row.
573 *
574 * @access private
575 * @since 4.8.0
576 *
577 * @param int $number The number of the current sitemap.
578 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
579 *
580 * @return bool|array @args {
581 * @type int $last_id The ID of the last item to be successfully added to the buffer.
582 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
583 * @type string $last_modified The most recent timestamp to appear on the sitemap.
584 * }
585 */
586 public function build_one_page_sitemap( $number, $from_id ) {
587 $last_post_id = $from_id;
588 $any_posts_left = true;
589
590 if ( $this->logger ) {
591 $debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number );
592 $this->logger->report( "-- Building $debug_name" );
593 }
594
595 $buffer = Jetpack_Sitemap_Buffer_Factory::create(
596 'page',
597 JP_SITEMAP_MAX_ITEMS,
598 JP_SITEMAP_MAX_BYTES
599 );
600
601 if ( ! $buffer ) {
602 return false;
603 }
604
605 // 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.
606 if ( 1 === $number && 'page' !== get_option( 'show_on_front' ) ) {
607 $item_array = array(
608 'url' => array(
609 'loc' => home_url( '/' ),
610 ),
611 );
612
613 /**
614 * Filter associative array with data to build <url> node
615 * and its descendants for site home.
616 *
617 * @module sitemaps
618 *
619 * @since 3.9.0
620 *
621 * @param array $blog_home Data to build parent and children nodes for site home.
622 */
623 $item_array = apply_filters( 'jetpack_sitemap_url_home', $item_array );
624
625 $buffer->append( $item_array );
626 }
627
628 // Add as many items to the buffer as possible.
629 while ( $last_post_id >= 0 && false === $buffer->is_full() ) {
630 $posts = $this->librarian->query_posts_after_id(
631 $last_post_id,
632 JP_SITEMAP_BATCH_SIZE
633 );
634
635 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
636 $any_posts_left = false;
637 break;
638 }
639
640 foreach ( $posts as $post ) {
641 $current_item = $this->post_to_sitemap_item( $post );
642
643 if ( true === $buffer->append( $current_item['xml'] ) ) {
644 $last_post_id = $post->ID;
645 $buffer->view_time( $current_item['last_modified'] );
646 } else {
647 break;
648 }
649 }
650 }
651
652 // Handle other page sitemap URLs.
653 if ( false === $any_posts_left || $last_post_id < 0 ) {
654 // Negative IDs are used to track URL indexes.
655 $last_post_id = min( 0, $last_post_id );
656 $any_posts_left = true; // Reinitialize.
657
658 /**
659 * Filter other page sitemap URLs.
660 *
661 * @module sitemaps
662 *
663 * @since 6.1.0
664 *
665 * @param array $urls An array of other URLs.
666 */
667 $other_urls = apply_filters( 'jetpack_page_sitemap_other_urls', array() );
668
669 if ( $other_urls ) { // Start with index [1].
670 $other_urls = array_values( $other_urls );
671 array_unshift( $other_urls, $other_urls[0] );
672 unset( $other_urls[0] );
673 }
674
675 // Add as many items to the buffer as possible.
676 while ( false === $buffer->is_full() ) {
677 $last_post_id_index = abs( $last_post_id );
678 $start_from_post_id_index = $last_post_id_index ? $last_post_id_index + 1 : 0;
679 $urls = array_slice(
680 $other_urls,
681 $start_from_post_id_index,
682 JP_SITEMAP_BATCH_SIZE,
683 true
684 );
685
686 if ( ! $urls ) {
687 $any_posts_left = false;
688 break;
689 }
690
691 foreach ( $urls as $index => $url ) {
692 if ( ! is_array( $url ) ) {
693 $url = array( 'loc' => $url );
694 }
695 $item = array( 'xml' => compact( 'url' ) );
696
697 if ( true === $buffer->append( $item['xml'] ) ) {
698 $last_post_id = -$index;
699 if ( isset( $url['lastmod'] ) ) {
700 $buffer->view_time( jp_sitemap_datetime( $url['lastmod'] ) );
701 }
702 } else {
703 break;
704 }
705 }
706 }
707 }
708
709 // If no items were added, return false.
710 if ( true === $buffer->is_empty() ) {
711 return false;
712 }
713
714 /**
715 * Filter sitemap before rendering it as XML.
716 *
717 * @module sitemaps
718 *
719 * @since 3.9.0
720 * @since 5.3.0 returns an element of DOMDocument type instead of SimpleXMLElement
721 *
722 * @param DOMDocument $doc Data tree for sitemap.
723 * @param string $last_modified Date of last modification.
724 */
725 $tree = apply_filters( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
726 'jetpack_print_sitemap',
727 $buffer->get_document(),
728 $buffer->last_modified()
729 );
730
731 // Store the buffer as the content of a sitemap row.
732 $this->librarian->store_sitemap_data(
733 $number,
734 JP_PAGE_SITEMAP_TYPE,
735 $buffer->contents(),
736 $buffer->last_modified()
737 );
738
739 /*
740 * Now report back with the ID of the last post ID to be
741 * successfully added and whether there are any posts left.
742 */
743 return array(
744 'last_id' => $last_post_id,
745 'any_left' => $any_posts_left,
746 'last_modified' => $buffer->last_modified(),
747 );
748 }
749
750 /**
751 * Build and store a single image sitemap. Returns false if no sitemap is built.
752 *
753 * Side effect: Create/update an image sitemap row.
754 *
755 * @access private
756 * @since 4.8.0
757 *
758 * @param int $number The number of the current sitemap.
759 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
760 *
761 * @return bool|array @args {
762 * @type int $last_id The ID of the last item to be successfully added to the buffer.
763 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
764 * @type string $last_modified The most recent timestamp to appear on the sitemap.
765 * }
766 */
767 public function build_one_image_sitemap( $number, $from_id ) {
768 $last_post_id = $from_id;
769 $any_posts_left = true;
770
771 if ( $this->logger ) {
772 $debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number );
773 $this->logger->report( "-- Building $debug_name" );
774 }
775
776 $buffer = Jetpack_Sitemap_Buffer_Factory::create(
777 'image',
778 JP_SITEMAP_MAX_ITEMS,
779 JP_SITEMAP_MAX_BYTES
780 );
781
782 if ( ! $buffer ) {
783 return false;
784 }
785
786 // Add as many items to the buffer as possible.
787 while ( false === $buffer->is_full() ) {
788 $posts = $this->librarian->query_images_after_id(
789 $last_post_id,
790 JP_SITEMAP_BATCH_SIZE
791 );
792
793 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
794 $any_posts_left = false;
795 break;
796 }
797
798 foreach ( $posts as $post ) {
799 $current_item = $this->image_post_to_sitemap_item( $post );
800
801 if ( true === $buffer->append( $current_item['xml'] ) ) {
802 $last_post_id = $post->ID;
803 $buffer->view_time( $current_item['last_modified'] );
804 } else {
805 break;
806 }
807 }
808 }
809
810 // If no items were added, return false.
811 if ( true === $buffer->is_empty() ) {
812 return false;
813 }
814
815 // Store the buffer as the content of a jp_sitemap post.
816 $this->librarian->store_sitemap_data(
817 $number,
818 JP_IMAGE_SITEMAP_TYPE,
819 $buffer->contents(),
820 $buffer->last_modified()
821 );
822
823 /*
824 * Now report back with the ID of the last post to be
825 * successfully added and whether there are any posts left.
826 */
827 return array(
828 'last_id' => $last_post_id,
829 'any_left' => $any_posts_left,
830 'last_modified' => $buffer->last_modified(),
831 );
832 }
833
834 /**
835 * Build and store a single video sitemap. Returns false if no sitemap is built.
836 *
837 * Side effect: Create/update an video sitemap row.
838 *
839 * @access private
840 * @since 4.8.0
841 *
842 * @param int $number The number of the current sitemap.
843 * @param int $from_id The greatest lower bound of the IDs of the posts to be included.
844 *
845 * @return bool|array @args {
846 * @type int $last_id The ID of the last item to be successfully added to the buffer.
847 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
848 * @type string $last_modified The most recent timestamp to appear on the sitemap.
849 * }
850 */
851 public function build_one_video_sitemap( $number, $from_id ) {
852 $last_post_id = $from_id;
853 $any_posts_left = true;
854
855 if ( $this->logger ) {
856 $debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number );
857 $this->logger->report( "-- Building $debug_name" );
858 }
859
860 $buffer = Jetpack_Sitemap_Buffer_Factory::create(
861 'video',
862 JP_SITEMAP_MAX_ITEMS,
863 JP_SITEMAP_MAX_BYTES
864 );
865
866 if ( ! $buffer ) {
867 return false;
868 }
869
870 // Add as many items to the buffer as possible.
871 while ( false === $buffer->is_full() ) {
872 $posts = $this->librarian->query_videos_after_id(
873 $last_post_id,
874 JP_SITEMAP_BATCH_SIZE
875 );
876
877 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
878 $any_posts_left = false;
879 break;
880 }
881
882 foreach ( $posts as $post ) {
883 $current_item = $this->video_post_to_sitemap_item( $post );
884
885 if ( true === $buffer->append( $current_item['xml'] ) ) {
886 $last_post_id = $post->ID;
887 $buffer->view_time( $current_item['last_modified'] );
888 } else {
889 break;
890 }
891 }
892 }
893
894 // If no items were added, return false.
895 if ( true === $buffer->is_empty() ) {
896 return false;
897 }
898
899 if ( false === $buffer->is_empty() ) {
900 $this->librarian->store_sitemap_data(
901 $number,
902 JP_VIDEO_SITEMAP_TYPE,
903 $buffer->contents(),
904 $buffer->last_modified()
905 );
906 }
907
908 /*
909 * Now report back with the ID of the last post to be
910 * successfully added and whether there are any posts left.
911 */
912 return array(
913 'last_id' => $last_post_id,
914 'any_left' => $any_posts_left,
915 'last_modified' => $buffer->last_modified(),
916 );
917 }
918
919 /**
920 * Build and store a single page sitemap index. Return false if no index is built.
921 *
922 * Side effect: Create/update a sitemap index row.
923 *
924 * @access private
925 * @since 4.8.0
926 *
927 * @param int $number The number of the current sitemap index.
928 * @param int $from_id The greatest lower bound of the IDs of the sitemaps to be included.
929 * @param string $datetime Datetime of previous sitemap in 'YYYY-MM-DD hh:mm:ss' format.
930 * @param string $index_type Sitemap index type.
931 *
932 * @return bool|array @args {
933 * @type int $last_id The ID of the last item to be successfully added to the buffer.
934 * @type bool $any_left 'true' if there are items which haven't been saved to a sitemap, 'false' otherwise.
935 * @type string $last_modified The most recent timestamp to appear on the sitemap.
936 * }
937 */
938 private function build_one_sitemap_index( $number, $from_id, $datetime, $index_type ) {
939 $last_sitemap_id = $from_id;
940 $any_sitemaps_left = true;
941
942 // Check the datetime format.
943 $datetime = jp_sitemap_datetime( $datetime );
944
945 $sitemap_type = jp_sitemap_child_type_of( $index_type );
946
947 if ( $this->logger ) {
948 $index_debug_name = jp_sitemap_filename( $index_type, $number );
949 $this->logger->report( "-- Building $index_debug_name" );
950 }
951
952 $buffer = new Jetpack_Sitemap_Buffer_Master(
953 JP_SITEMAP_MAX_ITEMS,
954 JP_SITEMAP_MAX_BYTES,
955 $datetime
956 );
957
958 // Add pointer to the previous sitemap index (unless we're at the first one).
959 if ( 1 !== $number ) {
960 $i = $number - 1;
961 $prev_index_url = $this->finder->construct_sitemap_url(
962 jp_sitemap_filename( $index_type, $i )
963 );
964
965 $item_array = array(
966 'sitemap' => array(
967 'loc' => $prev_index_url,
968 'lastmod' => $datetime,
969 ),
970 );
971
972 $buffer->append( $item_array );
973 }
974
975 // Add as many items to the buffer as possible.
976 while ( false === $buffer->is_full() ) {
977 // Retrieve a batch of posts (in order).
978 $posts = $this->librarian->query_sitemaps_after_id(
979 $sitemap_type,
980 $last_sitemap_id,
981 JP_SITEMAP_BATCH_SIZE
982 );
983
984 // If there were no posts to get, make a note.
985 if ( null == $posts ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
986 $any_sitemaps_left = false;
987 break;
988 }
989
990 // Otherwise, loop through each post in the batch.
991 foreach ( $posts as $post ) {
992 // Generate the sitemap XML for the post.
993 $current_item = $this->sitemap_row_to_index_item( (array) $post );
994
995 // Try adding this item to the buffer.
996 if ( true === $buffer->append( $current_item['xml'] ) ) {
997 $last_sitemap_id = $post['ID'];
998 $buffer->view_time( $current_item['last_modified'] );
999 } else {
1000 // Otherwise stop looping through posts.
1001 break;
1002 }
1003 }
1004 }
1005
1006 // If no items were added, return false.
1007 if ( true === $buffer->is_empty() ) {
1008 return false;
1009 }
1010
1011 $this->librarian->store_sitemap_data(
1012 $number,
1013 $index_type,
1014 $buffer->contents(),
1015 $buffer->last_modified()
1016 );
1017
1018 /*
1019 * Now report back with the ID of the last sitemap post ID to
1020 * be successfully added, whether there are any sitemap posts
1021 * left, and the most recent modification time seen.
1022 */
1023 return array(
1024 'last_id' => $last_sitemap_id,
1025 'any_left' => $any_sitemaps_left,
1026 'last_modified' => $buffer->last_modified(),
1027 );
1028 }
1029
1030 /**
1031 * Construct the sitemap index url entry for a sitemap row.
1032 *
1033 * @link https://www.sitemaps.org/protocol.html#sitemapIndex_sitemap
1034 *
1035 * @access private
1036 * @since 4.8.0
1037 *
1038 * @param array $row The sitemap data to be processed.
1039 *
1040 * @return string An XML fragment representing the post URL.
1041 */
1042 private function sitemap_row_to_index_item( $row ) {
1043 $url = $this->finder->construct_sitemap_url( $row['post_title'] );
1044
1045 $item_array = array(
1046 'sitemap' => array(
1047 'loc' => $url,
1048 'lastmod' => jp_sitemap_datetime( $row['post_date'] ),
1049 ),
1050 );
1051
1052 return array(
1053 'xml' => $item_array,
1054 'last_modified' => $row['post_date'],
1055 );
1056 }
1057
1058 /**
1059 * This is served instead of a 404 when the master sitemap is requested
1060 * but not yet generated.
1061 *
1062 * @access public
1063 * @since 6.7.0
1064 *
1065 * @return string The empty sitemap xml.
1066 */
1067 public function empty_sitemap_xml() {
1068 $empty_sitemap = new Jetpack_Sitemap_Buffer_Empty();
1069 return $empty_sitemap->contents();
1070 }
1071
1072 /**
1073 * Build and return the news sitemap xml. Note that the result of this
1074 * function is cached in the transient 'jetpack_news_sitemap_xml'.
1075 *
1076 * @access public
1077 * @since 4.8.0
1078 *
1079 * @return string The news sitemap xml.
1080 */
1081 public function news_sitemap_xml() {
1082 $buffer = Jetpack_Sitemap_Buffer_Factory::create(
1083 'news',
1084 JP_SITEMAP_MAX_ITEMS,
1085 JP_SITEMAP_MAX_BYTES
1086 );
1087
1088 if ( ! $buffer ) {
1089 return '';
1090 }
1091
1092 $the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' );
1093
1094 if ( false === $the_stored_news_sitemap ) {
1095
1096 if ( $this->logger ) {
1097 $this->logger->report( 'Beginning news sitemap generation.' );
1098 }
1099
1100 /**
1101 * Filter limit of entries to include in news sitemap.
1102 *
1103 * @module sitemaps
1104 *
1105 * @since 3.9.0
1106 *
1107 * @param int $count Number of entries to include in news sitemap.
1108 */
1109 $item_limit = apply_filters(
1110 'jetpack_sitemap_news_sitemap_count',
1111 JP_NEWS_SITEMAP_MAX_ITEMS
1112 );
1113
1114 $posts = $this->librarian->query_most_recent_posts( $item_limit );
1115 if ( empty( $posts ) ) {
1116 $buffer->append( array( 'url' => array( 'loc' => home_url( '/' ) ) ) );
1117 } else {
1118 foreach ( $posts as $post ) {
1119 $current_item = $this->post_to_news_sitemap_item( $post );
1120
1121 if ( $current_item['xml'] !== null && false === $buffer->append( $current_item['xml'] ) ) {
1122 break;
1123 }
1124 }
1125 }
1126
1127 if ( $this->logger ) {
1128 $this->logger->time( 'End news sitemap generation.' );
1129 }
1130
1131 $the_stored_news_sitemap = $buffer->contents();
1132
1133 set_transient(
1134 'jetpack_news_sitemap_xml',
1135 $the_stored_news_sitemap,
1136 JP_NEWS_SITEMAP_INTERVAL
1137 );
1138 } // End if.
1139
1140 return $the_stored_news_sitemap;
1141 }
1142
1143 /**
1144 * Construct the sitemap url entry for a WP_Post.
1145 *
1146 * @link https://www.sitemaps.org/protocol.html#urldef
1147 * @access private
1148 * @since 4.8.0
1149 *
1150 * @param WP_Post $post The post to be processed.
1151 *
1152 * @return array
1153 * @type array $xml An XML fragment representing the post URL.
1154 * @type string $last_modified Date post was last modified.
1155 */
1156 private function post_to_sitemap_item( $post ) {
1157
1158 /**
1159 * Filter condition to allow skipping specific posts in sitemap.
1160 *
1161 * @module sitemaps
1162 *
1163 * @since 3.9.0
1164 *
1165 * @param bool $skip Current boolean. False by default, so no post is skipped.
1166 * @param object $post Current post in the form of a $wpdb result object. Not WP_Post.
1167 */
1168 if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) {
1169 return array(
1170 'xml' => null,
1171 'last_modified' => null,
1172 );
1173 }
1174
1175 $url = esc_url( get_permalink( $post ) );
1176
1177 /*
1178 * Spec requires the URL to be <=2048 bytes.
1179 * In practice this constraint is unlikely to be violated.
1180 */
1181 if ( 2048 < strlen( $url ) ) {
1182 $url = home_url() . '/?p=' . $post->ID;
1183 }
1184
1185 $last_modified = $post->post_modified_gmt;
1186
1187 // Check for more recent comments.
1188 // Note that 'Y-m-d h:i:s' strings sort lexicographically.
1189 if ( 0 < $post->comment_count ) {
1190 $last_modified = max(
1191 $last_modified,
1192 $this->librarian->query_latest_approved_comment_time_on_post( $post->ID )
1193 );
1194 }
1195
1196 $item_array = array(
1197 'url' => array(
1198 'loc' => $url,
1199 'lastmod' => jp_sitemap_datetime( $last_modified ),
1200 ),
1201 );
1202
1203 /**
1204 * Filter sitemap URL item before rendering it as XML.
1205 *
1206 * @module sitemaps
1207 *
1208 * @since 3.9.0
1209 *
1210 * @param array $tree Associative array representing sitemap URL element.
1211 * @param int $post_id ID of the post being processed.
1212 */
1213 $item_array = apply_filters( 'jetpack_sitemap_url', $item_array, $post->ID );
1214
1215 return array(
1216 'xml' => $item_array,
1217 'last_modified' => $last_modified,
1218 );
1219 }
1220
1221 /**
1222 * Construct the image sitemap url entry for a WP_Post of image type.
1223 *
1224 * @link https://www.sitemaps.org/protocol.html#urldef
1225 *
1226 * @access private
1227 * @since 4.8.0
1228 *
1229 * @param WP_Post $post The image post to be processed.
1230 *
1231 * @return array
1232 * @type array $xml An XML fragment representing the post URL.
1233 * @type string $last_modified Date post was last modified.
1234 */
1235 private function image_post_to_sitemap_item( $post ) {
1236
1237 /**
1238 * Filter condition to allow skipping specific image posts in the sitemap.
1239 *
1240 * @module sitemaps
1241 *
1242 * @since 4.8.0
1243 *
1244 * @param bool $skip Current boolean. False by default, so no post is skipped.
1245 * @param WP_POST $post Current post object.
1246 */
1247 if ( apply_filters( 'jetpack_sitemap_image_skip_post', false, $post ) ) {
1248 return array(
1249 'xml' => null,
1250 'last_modified' => null,
1251 );
1252 }
1253
1254 $url = wp_get_attachment_url( $post->ID );
1255
1256 // Do not include the image if the attached parent is not published.
1257 // Unattached will be published. Otherwise, will inherit parent status.
1258 if ( 'publish' !== get_post_status( $post ) ) {
1259 return array(
1260 'xml' => null,
1261 'last_modified' => null,
1262 );
1263 }
1264
1265 $parent_url = get_permalink( get_post( $post->post_parent ) );
1266 if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1267 $parent_url = get_permalink( $post );
1268 }
1269
1270 $item_array = array(
1271 'url' => array(
1272 'loc' => $parent_url,
1273 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1274 'image:image' => array(
1275 'image:loc' => $url,
1276 ),
1277 ),
1278 );
1279
1280 /**
1281 * Filter associative array with data to build <url> node
1282 * and its descendants for current post in image sitemap.
1283 *
1284 * @module sitemaps
1285 *
1286 * @since 4.8.0
1287 *
1288 * @param array $item_array Data to build parent and children nodes for current post.
1289 * @param int $post_id Current image post ID.
1290 */
1291 $item_array = apply_filters(
1292 'jetpack_sitemap_image_sitemap_item',
1293 $item_array,
1294 $post->ID
1295 );
1296
1297 return array(
1298 'xml' => $item_array,
1299 'last_modified' => $post->post_modified_gmt,
1300 );
1301 }
1302
1303 /**
1304 * Construct the video sitemap url entry for a WP_Post of video type.
1305 *
1306 * @link https://www.sitemaps.org/protocol.html#urldef
1307 * @link https://developers.google.com/webmasters/videosearch/sitemaps
1308 *
1309 * @access private
1310 * @since 4.8.0
1311 *
1312 * @param WP_Post $post The video post to be processed.
1313 *
1314 * @return array
1315 * @type array $xml An XML fragment representing the post URL.
1316 * @type string $last_modified Date post was last modified.
1317 */
1318 private function video_post_to_sitemap_item( $post ) {
1319
1320 /**
1321 * Filter condition to allow skipping specific video posts in the sitemap.
1322 *
1323 * @module sitemaps
1324 *
1325 * @since 4.8.0
1326 *
1327 * @param bool $skip Current boolean. False by default, so no post is skipped.
1328 * @param WP_POST $post Current post object.
1329 */
1330 if ( apply_filters( 'jetpack_sitemap_video_skip_post', false, $post ) ) {
1331 return array(
1332 'xml' => null,
1333 'last_modified' => null,
1334 );
1335 }
1336
1337 // Do not include the video if the attached parent is not published.
1338 // Unattached will be published. Otherwise, will inherit parent status.
1339 if ( 'publish' !== get_post_status( $post ) ) {
1340 return array(
1341 'xml' => null,
1342 'last_modified' => null,
1343 );
1344 }
1345
1346 $parent_url = esc_url( get_permalink( get_post( $post->post_parent ) ) );
1347 if ( '' == $parent_url ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- WPCS: loose comparison ok.
1348 $parent_url = esc_url( get_permalink( $post ) );
1349 }
1350
1351 // Prepare the content like get_the_content_feed().
1352 $content = $post->post_content;
1353 /** This filter is already documented in core/wp-includes/post-template.php */
1354 $content = apply_filters( 'the_content', $content );
1355
1356 /** This filter is already documented in core/wp-includes/feed.php */
1357 $content = apply_filters( 'the_content_feed', $content, 'rss2' );
1358
1359 // Include thumbnails for VideoPress videos, use blank image for others.
1360 if ( 'complete' === get_post_meta( $post->ID, 'videopress_status', true ) && has_post_thumbnail( $post ) ) {
1361 $video_thumbnail_url = get_the_post_thumbnail_url( $post );
1362 } else {
1363 /**
1364 * Filter the thumbnail image used in the video sitemap for non-VideoPress videos.
1365 *
1366 * @since 7.2.0
1367 *
1368 * @param string $str Image URL.
1369 */
1370 $video_thumbnail_url = apply_filters( 'jetpack_video_sitemap_default_thumbnail', 'https://s0.wp.com/i/blank.jpg' );
1371 }
1372
1373 $item_array = array(
1374 'url' => array(
1375 'loc' => $parent_url,
1376 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1377 'video:video' => array(
1378 /** This filter is already documented in core/wp-includes/feed.php */
1379 'video:title' => apply_filters( 'the_title_rss', $post->post_title ),
1380 'video:thumbnail_loc' => esc_url( $video_thumbnail_url ),
1381 'video:description' => $content,
1382 'video:content_loc' => esc_url( wp_get_attachment_url( $post->ID ) ),
1383 ),
1384 ),
1385 );
1386
1387 // TODO: Integrate with VideoPress here.
1388 // cf. video:player_loc tag in video sitemap spec.
1389
1390 /**
1391 * Filter associative array with data to build <url> node
1392 * and its descendants for current post in video sitemap.
1393 *
1394 * @module sitemaps
1395 *
1396 * @since 4.8.0
1397 *
1398 * @param array $item_array Data to build parent and children nodes for current post.
1399 * @param int $post_id Current video post ID.
1400 */
1401 $item_array = apply_filters(
1402 'jetpack_sitemap_video_sitemap_item',
1403 $item_array,
1404 $post->ID
1405 );
1406
1407 return array(
1408 'xml' => $item_array,
1409 'last_modified' => $post->post_modified_gmt,
1410 );
1411 }
1412
1413 /**
1414 * Construct the news sitemap url entry for a WP_Post.
1415 *
1416 * @link https://www.sitemaps.org/protocol.html#urldef
1417 *
1418 * @access private
1419 * @since 4.8.0
1420 *
1421 * @param WP_Post $post The post to be processed.
1422 *
1423 * @return string An XML fragment representing the post URL.
1424 */
1425 private function post_to_news_sitemap_item( $post ) {
1426
1427 // Exclude posts with meta 'jetpack_seo_noindex' set true from the Jetpack news sitemap.
1428 add_filter( 'jetpack_sitemap_news_skip_post', array( 'Jetpack_SEO_Posts', 'exclude_noindex_posts_from_jetpack_sitemap' ), 10, 2 );
1429
1430 /**
1431 * Filter condition to allow skipping specific posts in news sitemap.
1432 *
1433 * @module sitemaps
1434 *
1435 * @since 3.9.0
1436 *
1437 * @param bool $skip Current boolean. False by default, so no post is skipped.
1438 * @param WP_POST $post Current post object.
1439 */
1440 if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) {
1441 return array(
1442 'xml' => null,
1443 );
1444 }
1445
1446 $url = get_permalink( $post );
1447
1448 /*
1449 * Spec requires the URL to be <=2048 bytes.
1450 * In practice this constraint is unlikely to be violated.
1451 */
1452 if ( 2048 < strlen( $url ) ) {
1453 $url = home_url() . '/?p=' . $post->ID;
1454 }
1455
1456 /*
1457 * Trim the locale to an ISO 639 language code as required by Google.
1458 * Special cases are zh-cn (Simplified Chinese) and zh-tw (Traditional Chinese).
1459 * @link https://www.loc.gov/standards/iso639-2/php/code_list.php
1460 */
1461 $language = strtolower( get_locale() );
1462
1463 if ( in_array( $language, array( 'zh_tw', 'zh_cn' ), true ) ) {
1464 $language = str_replace( '_', '-', $language );
1465 } else {
1466 $language = preg_replace( '/(_.*)$/i', '', $language );
1467 }
1468
1469 $item_array = array(
1470 'url' => array(
1471 'loc' => $url,
1472 'lastmod' => jp_sitemap_datetime( $post->post_modified_gmt ),
1473 'news:news' => array(
1474 'news:publication' => array(
1475 'news:name' => html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
1476 'news:language' => $language,
1477 ),
1478 /** This filter is already documented in core/wp-includes/feed.php */
1479 'news:title' => apply_filters( 'the_title_rss', $post->post_title ),
1480 'news:publication_date' => jp_sitemap_datetime( $post->post_date_gmt ),
1481 'news:genres' => 'Blog',
1482 ),
1483 ),
1484 );
1485
1486 /**
1487 * Filter associative array with data to build <url> node
1488 * and its descendants for current post in news sitemap.
1489 *
1490 * @module sitemaps
1491 *
1492 * @since 3.9.0
1493 *
1494 * @param array $item_array Data to build parent and children nodes for current post.
1495 * @param int $post_id Current post ID.
1496 */
1497 $item_array = apply_filters(
1498 'jetpack_sitemap_news_sitemap_item',
1499 $item_array,
1500 $post->ID
1501 );
1502
1503 return array(
1504 'xml' => $item_array,
1505 );
1506 }
1507 }
1508