PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 10.3
Jetpack – WP Security, Backup, Speed, & Growth v10.3
16.2-beta 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 All 501 releases
jetpack / modules / sitemaps / sitemaps.php

sitemaps.php in Jetpack – WP Security, Backup, Speed, & Growth 10.3, at modules/sitemaps/sitemaps.php

601 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Generate sitemap files in base XML as well as some namespace extensions.
4 *
5 * This module generates two different base sitemaps.
6 *
7 * 1. sitemap.xml
8 * The basic sitemap is updated regularly by wp-cron. It is stored in the
9 * database and retrieved when requested. This sitemap aims to include canonical
10 * URLs for all published content and abide by the sitemap spec. This is the root
11 * of a tree of sitemap and sitemap index xml files, depending on the number of URLs.
12 *
13 * By default the sitemap contains published posts of type 'post' and 'page', as
14 * well as the home url. To include other post types use the 'jetpack_sitemap_post_types'
15 * filter.
16 *
17 * @link https://www.sitemaps.org/protocol.html Base sitemaps protocol.
18 * @link https://support.google.com/webmasters/answer/178636 Image sitemap extension.
19 * @link https://developers.google.com/webmasters/videosearch/sitemaps Video sitemap extension.
20 *
21 * 2. news-sitemap.xml
22 * The news sitemap is generated on the fly when requested. It does not aim for
23 * completeness, instead including at most 1000 of the most recent published posts
24 * from the previous 2 days, per the news-sitemap spec.
25 *
26 * @link https://support.google.com/webmasters/answer/74288 News sitemap extension.
27 *
28 * @package automattic/jetpack
29 * @since 3.9.0
30 * @since 4.8.0 Remove 1000 post limit.
31 * @author Automattic
32 */
33
34 /* Include all of the sitemap subclasses. */
35 require_once __DIR__ . '/sitemap-constants.php';
36 require_once __DIR__ . '/sitemap-buffer.php';
37 require_once __DIR__ . '/sitemap-stylist.php';
38 require_once __DIR__ . '/sitemap-librarian.php';
39 require_once __DIR__ . '/sitemap-finder.php';
40 require_once __DIR__ . '/sitemap-builder.php';
41
42 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
43 require_once __DIR__ . '/sitemap-logger.php';
44 }
45
46 /**
47 * Governs the generation, storage, and serving of sitemaps.
48 *
49 * @since 4.8.0
50 */
51 class Jetpack_Sitemap_Manager {
52
53 /**
54 * Librarian object for storing and retrieving sitemap data.
55 *
56 * @see Jetpack_Sitemap_Librarian
57 * @since 4.8.0
58 * @var Jetpack_Sitemap_Librarian $librarian Librarian object for storing and retrieving sitemap data.
59 */
60 private $librarian;
61
62 /**
63 * Logger object for reporting debug messages.
64 *
65 * @see Jetpack_Sitemap_Logger
66 * @since 4.8.0
67 * @var Jetpack_Sitemap_Logger $logger Logger object for reporting debug messages.
68 */
69 private $logger;
70
71 /**
72 * Finder object for handling sitemap URIs.
73 *
74 * @see Jetpack_Sitemap_Finder
75 * @since 4.8.0
76 * @var Jetpack_Sitemap_Finder $finder Finder object for handling with sitemap URIs.
77 */
78 private $finder;
79
80 /**
81 * Construct a new Jetpack_Sitemap_Manager.
82 *
83 * @access public
84 * @since 4.8.0
85 */
86 public function __construct() {
87 $this->librarian = new Jetpack_Sitemap_Librarian();
88 $this->finder = new Jetpack_Sitemap_Finder();
89
90 if ( defined( 'WP_DEBUG' ) && ( true === WP_DEBUG ) ) {
91 $this->logger = new Jetpack_Sitemap_Logger();
92 }
93
94 // Add callback for sitemap URL handler.
95 add_action(
96 'init',
97 array( $this, 'callback_action_catch_sitemap_urls' ),
98 defined( 'IS_WPCOM' ) && IS_WPCOM ? 100 : 10
99 );
100
101 // Add generator to wp_cron task list.
102 $this->schedule_sitemap_generation();
103
104 // Add sitemap to robots.txt.
105 add_action(
106 'do_robotstxt',
107 array( $this, 'callback_action_do_robotstxt' ),
108 20
109 );
110
111 // The news sitemap is cached; here we add a callback to
112 // flush the cached news sitemap when a post is published.
113 add_action(
114 'publish_post',
115 array( $this, 'callback_action_flush_news_sitemap_cache' ),
116 10
117 );
118
119 // In case we need to purge all sitemaps, we do this.
120 add_action(
121 'jetpack_sitemaps_purge_data',
122 array( $this, 'callback_action_purge_data' )
123 );
124
125 /*
126 * Module parameters are stored as options in the database.
127 * This allows us to avoid having to process all of init
128 * before serving the sitemap data. The following actions
129 * process and store these filters.
130 */
131
132 // Process filters and store location string for sitemap.
133 add_action(
134 'init',
135 array( $this, 'callback_action_filter_sitemap_location' ),
136 999
137 );
138 }
139
140 /**
141 * Echo a raw string of given content-type.
142 *
143 * @access private
144 * @since 4.8.0
145 *
146 * @param string $the_content_type The content type to be served.
147 * @param string $the_content The string to be echoed.
148 */
149 private function serve_raw_and_die( $the_content_type, $the_content ) {
150 header( 'Content-Type: ' . $the_content_type . '; charset=UTF-8' );
151
152 global $wp_query;
153 $wp_query->is_feed = true;
154 set_query_var( 'feed', 'sitemap' );
155
156 if ( '' === $the_content ) {
157 $error = __( 'No sitemap found. Please try again later.', 'jetpack' );
158 if ( current_user_can( 'manage_options' ) ) {
159 $next = human_time_diff( wp_next_scheduled( 'jp_sitemap_cron_hook' ) );
160 /* translators: %s is a human_time_diff until next sitemap generation. */
161 $error = sprintf( __( 'No sitemap found. The system will try to build it again in %s.', 'jetpack' ), $next );
162 }
163
164 wp_die(
165 esc_html( $error ),
166 esc_html__( 'Sitemaps', 'jetpack' ),
167 array(
168 'response' => 404,
169 )
170 );
171 }
172
173 echo $the_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All content created by Jetpack.
174
175 die();
176 }
177
178 /**
179 * Callback to intercept sitemap url requests and serve sitemap files.
180 *
181 * @access public
182 * @since 4.8.0
183 */
184 public function callback_action_catch_sitemap_urls() {
185 // Regular expressions for sitemap URL routing.
186 $regex = array(
187 'master' => '/^sitemap\.xml$/',
188 'sitemap' => '/^sitemap-[1-9][0-9]*\.xml$/',
189 'index' => '/^sitemap-index-[1-9][0-9]*\.xml$/',
190 'sitemap-style' => '/^sitemap\.xsl$/',
191 'index-style' => '/^sitemap-index\.xsl$/',
192 'image' => '/^image-sitemap-[1-9][0-9]*\.xml$/',
193 'image-index' => '/^image-sitemap-index-[1-9][0-9]*\.xml$/',
194 'image-style' => '/^image-sitemap\.xsl$/',
195 'video' => '/^video-sitemap-[1-9][0-9]*\.xml$/',
196 'video-index' => '/^video-sitemap-index-[1-9][0-9]*\.xml$/',
197 'video-style' => '/^video-sitemap\.xsl$/',
198 'news' => '/^news-sitemap\.xml$/',
199 'news-style' => '/^news-sitemap\.xsl$/',
200 );
201
202 // The raw path(+query) of the requested URI.
203 if ( isset( $_SERVER['REQUEST_URI'] ) ) { // WPCS: Input var okay.
204 $raw_uri = sanitize_text_field(
205 wp_unslash( $_SERVER['REQUEST_URI'] ) // WPCS: Input var okay.
206 );
207 } else {
208 $raw_uri = '';
209 }
210
211 $request = $this->finder->recognize_sitemap_uri( $raw_uri );
212
213 if ( isset( $request['sitemap_name'] ) ) {
214
215 /**
216 * Filter the content type used to serve the sitemap XML files.
217 *
218 * @module sitemaps
219 *
220 * @since 3.9.0
221 *
222 * @param string $xml_content_type By default, it's 'text/xml'.
223 */
224 $xml_content_type = apply_filters( 'jetpack_sitemap_content_type', 'text/xml' );
225
226 // Catch master sitemap xml.
227 if ( preg_match( $regex['master'], $request['sitemap_name'] ) ) {
228 $sitemap_content = $this->librarian->get_sitemap_text(
229 jp_sitemap_filename( JP_MASTER_SITEMAP_TYPE, 0 ),
230 JP_MASTER_SITEMAP_TYPE
231 );
232
233 // if there is no master sitemap yet, let's just return an empty sitemap with a short TTL instead of a 404.
234 if ( empty( $sitemap_content ) ) {
235 $builder = new Jetpack_Sitemap_Builder();
236 $sitemap_content = $builder->empty_sitemap_xml();
237 }
238
239 $this->serve_raw_and_die(
240 $xml_content_type,
241 $sitemap_content
242 );
243 }
244
245 // Catch sitemap xml.
246 if ( preg_match( $regex['sitemap'], $request['sitemap_name'] ) ) {
247 $this->serve_raw_and_die(
248 $xml_content_type,
249 $this->librarian->get_sitemap_text(
250 $request['sitemap_name'],
251 JP_PAGE_SITEMAP_TYPE
252 )
253 );
254 }
255
256 // Catch sitemap index xml.
257 if ( preg_match( $regex['index'], $request['sitemap_name'] ) ) {
258 $this->serve_raw_and_die(
259 $xml_content_type,
260 $this->librarian->get_sitemap_text(
261 $request['sitemap_name'],
262 JP_PAGE_SITEMAP_INDEX_TYPE
263 )
264 );
265 }
266
267 // Catch sitemap xsl.
268 if ( preg_match( $regex['sitemap-style'], $request['sitemap_name'] ) ) {
269 $this->serve_raw_and_die(
270 'application/xml',
271 Jetpack_Sitemap_Stylist::sitemap_xsl()
272 );
273 }
274
275 // Catch sitemap index xsl.
276 if ( preg_match( $regex['index-style'], $request['sitemap_name'] ) ) {
277 $this->serve_raw_and_die(
278 'application/xml',
279 Jetpack_Sitemap_Stylist::sitemap_index_xsl()
280 );
281 }
282
283 // Catch image sitemap xml.
284 if ( preg_match( $regex['image'], $request['sitemap_name'] ) ) {
285 $this->serve_raw_and_die(
286 $xml_content_type,
287 $this->librarian->get_sitemap_text(
288 $request['sitemap_name'],
289 JP_IMAGE_SITEMAP_TYPE
290 )
291 );
292 }
293
294 // Catch image sitemap index xml.
295 if ( preg_match( $regex['image-index'], $request['sitemap_name'] ) ) {
296 $this->serve_raw_and_die(
297 $xml_content_type,
298 $this->librarian->get_sitemap_text(
299 $request['sitemap_name'],
300 JP_IMAGE_SITEMAP_INDEX_TYPE
301 )
302 );
303 }
304
305 // Catch image sitemap xsl.
306 if ( preg_match( $regex['image-style'], $request['sitemap_name'] ) ) {
307 $this->serve_raw_and_die(
308 'application/xml',
309 Jetpack_Sitemap_Stylist::image_sitemap_xsl()
310 );
311 }
312
313 // Catch video sitemap xml.
314 if ( preg_match( $regex['video'], $request['sitemap_name'] ) ) {
315 $this->serve_raw_and_die(
316 $xml_content_type,
317 $this->librarian->get_sitemap_text(
318 $request['sitemap_name'],
319 JP_VIDEO_SITEMAP_TYPE
320 )
321 );
322 }
323
324 // Catch video sitemap index xml.
325 if ( preg_match( $regex['video-index'], $request['sitemap_name'] ) ) {
326 $this->serve_raw_and_die(
327 $xml_content_type,
328 $this->librarian->get_sitemap_text(
329 $request['sitemap_name'],
330 JP_VIDEO_SITEMAP_INDEX_TYPE
331 )
332 );
333 }
334
335 // Catch video sitemap xsl.
336 if ( preg_match( $regex['video-style'], $request['sitemap_name'] ) ) {
337 $this->serve_raw_and_die(
338 'application/xml',
339 Jetpack_Sitemap_Stylist::video_sitemap_xsl()
340 );
341 }
342
343 // Catch news sitemap xml.
344 if ( preg_match( $regex['news'], $request['sitemap_name'] ) ) {
345 $sitemap_builder = new Jetpack_Sitemap_Builder();
346 $this->serve_raw_and_die(
347 $xml_content_type,
348 $sitemap_builder->news_sitemap_xml()
349 );
350 }
351
352 // Catch news sitemap xsl.
353 if ( preg_match( $regex['news-style'], $request['sitemap_name'] ) ) {
354 $this->serve_raw_and_die(
355 'application/xml',
356 Jetpack_Sitemap_Stylist::news_sitemap_xsl()
357 );
358 }
359 }
360 }
361
362 /**
363 * Callback for adding sitemap-interval to the list of schedules.
364 *
365 * @access public
366 * @since 4.8.0
367 *
368 * @param array $schedules The array of WP_Cron schedules.
369 *
370 * @return array The updated array of WP_Cron schedules.
371 */
372 public function callback_add_sitemap_schedule( $schedules ) {
373 $schedules['sitemap-interval'] = array(
374 'interval' => JP_SITEMAP_INTERVAL,
375 'display' => __( 'Sitemap Interval', 'jetpack' ),
376 );
377 return $schedules;
378 }
379
380 /**
381 * Callback handler for sitemap cron hook
382 *
383 * @access public
384 */
385 public function callback_sitemap_cron_hook() {
386 $sitemap_builder = new Jetpack_Sitemap_Builder();
387 $sitemap_builder->update_sitemap();
388 }
389
390 /**
391 * Add actions to schedule sitemap generation.
392 * Should only be called once, in the constructor.
393 *
394 * @access private
395 * @since 4.8.0
396 */
397 private function schedule_sitemap_generation() {
398 // Add cron schedule.
399 add_filter( 'cron_schedules', array( $this, 'callback_add_sitemap_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
400
401 add_action(
402 'jp_sitemap_cron_hook',
403 array( $this, 'callback_sitemap_cron_hook' )
404 );
405
406 if ( ! wp_next_scheduled( 'jp_sitemap_cron_hook' ) ) {
407 /**
408 * Filter the delay in seconds until sitemap generation cron job is started.
409 *
410 * This filter allows a site operator or hosting provider to potentialy spread out sitemap generation for a
411 * lot of sites over time. By default, it will be randomly done over 15 minutes.
412 *
413 * @module sitemaps
414 * @since 6.6.1
415 *
416 * @param int $delay Time to delay in seconds.
417 */
418 $delay = apply_filters( 'jetpack_sitemap_generation_delay', MINUTE_IN_SECONDS * wp_rand( 1, 15 ) ); // Randomly space it out to start within next fifteen minutes.
419 wp_schedule_event(
420 time() + $delay,
421 'sitemap-interval',
422 'jp_sitemap_cron_hook'
423 );
424 }
425 }
426
427 /**
428 * Callback to add sitemap to robots.txt.
429 *
430 * @access public
431 * @since 4.8.0
432 */
433 public function callback_action_do_robotstxt() {
434
435 /**
436 * Filter whether to make the default sitemap discoverable to robots or not. Default true.
437 *
438 * @module sitemaps
439 * @since 3.9.0
440 * @deprecated 7.4.0
441 *
442 * @param bool $discover_sitemap Make default sitemap discoverable to robots.
443 */
444 $discover_sitemap = apply_filters_deprecated( 'jetpack_sitemap_generate', array( true ), 'jetpack-7.4.0', 'jetpack_sitemap_include_in_robotstxt' );
445
446 /**
447 * Filter whether to make the default sitemap discoverable to robots or not. Default true.
448 *
449 * @module sitemaps
450 * @since 7.4.0
451 *
452 * @param bool $discover_sitemap Make default sitemap discoverable to robots.
453 */
454 $discover_sitemap = apply_filters( 'jetpack_sitemap_include_in_robotstxt', $discover_sitemap );
455
456 if ( true === $discover_sitemap ) {
457 $sitemap_url = $this->finder->construct_sitemap_url( 'sitemap.xml' );
458 echo 'Sitemap: ' . esc_url( $sitemap_url ) . "\n";
459 }
460
461 /**
462 * Filter whether to make the news sitemap discoverable to robots or not. Default true.
463 *
464 * @module sitemaps
465 * @since 3.9.0
466 * @deprecated 7.4.0
467 *
468 * @param bool $discover_news_sitemap Make default news sitemap discoverable to robots.
469 */
470 $discover_news_sitemap = apply_filters_deprecated( 'jetpack_news_sitemap_generate', array( true ), 'jetpack-7.4.0', 'jetpack_news_sitemap_include_in_robotstxt' );
471
472 /**
473 * Filter whether to make the news sitemap discoverable to robots or not. Default true.
474 *
475 * @module sitemaps
476 * @since 7.4.0
477 *
478 * @param bool $discover_news_sitemap Make default news sitemap discoverable to robots.
479 */
480 $discover_news_sitemap = apply_filters( 'jetpack_news_sitemap_include_in_robotstxt', $discover_news_sitemap );
481
482 if ( true === $discover_news_sitemap ) {
483 $news_sitemap_url = $this->finder->construct_sitemap_url( 'news-sitemap.xml' );
484 echo 'Sitemap: ' . esc_url( $news_sitemap_url ) . "\n";
485 }
486 }
487
488 /**
489 * Callback to delete the news sitemap cache.
490 *
491 * @access public
492 * @since 4.8.0
493 */
494 public function callback_action_flush_news_sitemap_cache() {
495 delete_transient( 'jetpack_news_sitemap_xml' );
496 }
497
498 /**
499 * Callback for resetting stored sitemap data.
500 *
501 * @access public
502 * @since 5.3.0
503 * @since 6.7.0 Schedules a regeneration.
504 */
505 public function callback_action_purge_data() {
506 $this->callback_action_flush_news_sitemap_cache();
507 $this->librarian->delete_all_stored_sitemap_data();
508 /** This filter is documented in modules/sitemaps/sitemaps.php */
509 $delay = apply_filters( 'jetpack_sitemap_generation_delay', MINUTE_IN_SECONDS * wp_rand( 1, 15 ) ); // Randomly space it out to start within next fifteen minutes.
510 wp_schedule_single_event( time() + $delay, 'jp_sitemap_cron_hook' );
511 }
512
513 /**
514 * Callback to set the sitemap location.
515 *
516 * @access public
517 * @since 4.8.0
518 */
519 public function callback_action_filter_sitemap_location() {
520 update_option(
521 'jetpack_sitemap_location',
522 /**
523 * Additional path for sitemap URIs. Default value is empty.
524 *
525 * This string is any additional path fragment you want included between
526 * the home URL and the sitemap filenames. Exactly how this fragment is
527 * interpreted depends on your permalink settings. For example:
528 *
529 * Pretty permalinks:
530 * home_url() . jetpack_sitemap_location . '/sitemap.xml'
531 *
532 * Plain ("ugly") permalinks:
533 * home_url() . jetpack_sitemap_location . '/?jetpack-sitemap=sitemap.xml'
534 *
535 * PATHINFO permalinks:
536 * home_url() . '/index.php' . jetpack_sitemap_location . '/sitemap.xml'
537 *
538 * where 'sitemap.xml' is the name of a specific sitemap file.
539 * The value of this filter must be a valid path fragment per RFC 3986;
540 * in particular it must either be empty or begin with a '/'.
541 * Also take care that any restrictions on sitemap location imposed by
542 * the sitemap protocol are satisfied.
543 *
544 * The result of this filter is stored in an option, 'jetpack_sitemap_location';
545 * that option is what gets read when the sitemap location is needed.
546 * This way we don't have to wait for init to finish before building sitemaps.
547 *
548 * @link https://tools.ietf.org/html/rfc3986#section-3.3 RFC 3986
549 * @link https://www.sitemaps.org/ The sitemap protocol
550 *
551 * @since 4.8.0
552 */
553 apply_filters(
554 'jetpack_sitemap_location',
555 ''
556 )
557 );
558 }
559
560 } // End Jetpack_Sitemap_Manager class.
561
562 new Jetpack_Sitemap_Manager();
563
564 /**
565 * Absolute URL of the current blog's sitemap.
566 *
567 * @module sitemaps
568 *
569 * @since 3.9.0
570 * @since 4.8.1 Code uses method found in Jetpack_Sitemap_Finder::construct_sitemap_url in 4.8.0.
571 * It has been moved here to avoid fatal errors with other plugins that were expecting to find this function.
572 *
573 * @param string $filename Sitemap file name. Defaults to 'sitemap.xml', the initial sitemaps page.
574 *
575 * @return string Sitemap URL.
576 */
577 function jetpack_sitemap_uri( $filename = 'sitemap.xml' ) {
578 global $wp_rewrite;
579
580 $location = Jetpack_Options::get_option_and_ensure_autoload( 'jetpack_sitemap_location', '' );
581
582 if ( $wp_rewrite->using_index_permalinks() ) {
583 $sitemap_url = home_url( '/index.php' . $location . '/' . $filename );
584 } elseif ( $wp_rewrite->using_permalinks() ) {
585 $sitemap_url = home_url( $location . '/' . $filename );
586 } else {
587 $sitemap_url = home_url( $location . '/?jetpack-sitemap=' . $filename );
588 }
589
590 /**
591 * Filter sitemap URL relative to home URL.
592 *
593 * @module sitemaps
594 *
595 * @since 3.9.0
596 *
597 * @param string $sitemap_url Sitemap URL.
598 */
599 return apply_filters( 'jetpack_sitemap_location', $sitemap_url );
600 }
601