PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
16.2 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 All 502 releases
jetpack / modules / sitemaps / sitemaps.php

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

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