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