1<?php
2/**
3 * Sitemaps: WP_Sitemaps class
4 *
5 * This is the main class integrating all other classes.
6 *
7 * @package WordPress
8 * @subpackage Sitemaps
9 * @since 5.5.0
10 */
11
12/**
13 * Class WP_Sitemaps.
14 *
15 * @since 5.5.0
16 */
17#[AllowDynamicProperties]
18class WP_Sitemaps {
19 /**
20 * The main index of supported sitemaps.
21 *
22 * @since 5.5.0
23 *
24 * @var WP_Sitemaps_Index
25 */
26 public $index;
27
28 /**
29 * The main registry of supported sitemaps.
30 *
31 * @since 5.5.0
32 *
33 * @var WP_Sitemaps_Registry
34 */
35 public $registry;
36
37 /**
38 * An instance of the renderer class.
39 *
40 * @since 5.5.0
41 *
42 * @var WP_Sitemaps_Renderer
43 */
44 public $renderer;
45
46 /**
47 * WP_Sitemaps constructor.
48 *
49 * @since 5.5.0
50 */
51 public function __construct() {
52 $this->registry = new WP_Sitemaps_Registry();
53 $this->renderer = new WP_Sitemaps_Renderer();
54 $this->index = new WP_Sitemaps_Index( $this->registry );
55 }
56
57 /**
58 * Initiates all sitemap functionality.
59 *
60 * If sitemaps are disabled, only the rewrite rules will be registered
61 * by this method, in order to properly send 404s.
62 *
63 * @since 5.5.0
64 */
65 public function init() {
66 // These will all fire on the init hook.
67 $this->register_rewrites();
68
69 add_action( 'template_redirect', array( $this, 'render_sitemaps' ) );
70
71 if ( ! $this->sitemaps_enabled() ) {
72 return;
73 }
74
75 $this->register_sitemaps();
76
77 // Add additional action callbacks.
78 add_filter( 'robots_txt', array( $this, 'add_robots' ), 0, 2 );
79 }
80
81 /**
82 * Determines whether sitemaps are enabled or not.
83 *
84 * @since 5.5.0
85 *
86 * @return bool Whether sitemaps are enabled.
87 */
88 public function sitemaps_enabled() {
89 $is_enabled = (bool) get_option( 'blog_public' );
90
91 /**
92 * Filters whether XML Sitemaps are enabled or not.
93 *
94 * When XML Sitemaps are disabled via this filter, rewrite rules are still
95 * in place to ensure a 404 is returned.
96 *
97 * @see WP_Sitemaps::register_rewrites()
98 *
99 * @since 5.5.0
100 *
101 * @param bool $is_enabled Whether XML Sitemaps are enabled or not.
102 * Defaults to true for public sites.
103 */
104 return (bool) apply_filters( 'wp_sitemaps_enabled', $is_enabled );
105 }
106
107 /**
108 * Registers and sets up the functionality for all supported sitemaps.
109 *
110 * @since 5.5.0
111 */
112 public function register_sitemaps() {
113 $providers = array(
114 'posts' => new WP_Sitemaps_Posts(),
115 'taxonomies' => new WP_Sitemaps_Taxonomies(),
116 'users' => new WP_Sitemaps_Users(),
117 );
118
119 /* @var WP_Sitemaps_Provider $provider */
120 foreach ( $providers as $name => $provider ) {
121 $this->registry->add_provider( $name, $provider );
122 }
123 }
124
125 /**
126 * Registers sitemap rewrite tags and routing rules.
127 *
128 * @since 5.5.0
129 */
130 public function register_rewrites() {
131 // Add rewrite tags.
132 add_rewrite_tag( '%sitemap%', '([^?]+)' );
133 add_rewrite_tag( '%sitemap-subtype%', '([^?]+)' );
134
135 // Register index route.
136 add_rewrite_rule( '^wp-sitemap\.xml$', 'index.php?sitemap=index', 'top' );
137
138 // Register rewrites for the XSL stylesheet.
139 add_rewrite_tag( '%sitemap-stylesheet%', '([^?]+)' );
140 add_rewrite_rule( '^wp-sitemap\.xsl$', 'index.php?sitemap-stylesheet=sitemap', 'top' );
141 add_rewrite_rule( '^wp-sitemap-index\.xsl$', 'index.php?sitemap-stylesheet=index', 'top' );
142
143 // Register routes for providers.
144 add_rewrite_rule(
145 '^wp-sitemap-([a-z]+?)-([a-z\d_-]+?)-(\d+?)\.xml$',
146 'index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]',
147 'top'
148 );
149 add_rewrite_rule(
150 '^wp-sitemap-([a-z]+?)-(\d+?)\.xml$',
151 'index.php?sitemap=$matches[1]&paged=$matches[2]',
152 'top'
153 );
154 }
155
156 /**
157 * Renders sitemap templates based on rewrite rules.
158 *
159 * @since 5.5.0
160 *
161 * @global WP_Query $wp_query WordPress Query object.
162 */
163 public function render_sitemaps() {
164 global $wp_query;
165
166 $sitemap = sanitize_text_field( get_query_var( 'sitemap' ) );
167 $object_subtype = sanitize_text_field( get_query_var( 'sitemap-subtype' ) );
168 $stylesheet_type = sanitize_text_field( get_query_var( 'sitemap-stylesheet' ) );
169 $paged = absint( get_query_var( 'paged' ) );
170
171 // Bail early if this isn't a sitemap or stylesheet route.
172 if ( ! ( $sitemap || $stylesheet_type ) ) {
173 return;
174 }
175
176 if ( ! $this->sitemaps_enabled() ) {
177 $wp_query->set_404();
178 status_header( 404 );
179 return;
180 }
181
182 // Render stylesheet if this is stylesheet route.
183 if ( $stylesheet_type ) {
184 $stylesheet = new WP_Sitemaps_Stylesheet();
185
186 $stylesheet->render_stylesheet( $stylesheet_type );
187 exit;
188 }
189
190 // Render the index.
191 if ( 'index' === $sitemap ) {
192 $sitemap_list = $this->index->get_sitemap_list();
193
194 $this->renderer->render_index( $sitemap_list );
195 exit;
196 }
197
198 $provider = $this->registry->get_provider( $sitemap );
199
200 if ( ! $provider ) {
201 return;
202 }
203
204 if ( empty( $paged ) ) {
205 $paged = 1;
206 }
207
208 $url_list = $provider->get_url_list( $paged, $object_subtype );
209
210 // Force a 404 and bail early if no URLs are present.
211 if ( empty( $url_list ) ) {
212 $wp_query->set_404();
213 status_header( 404 );
214 return;
215 }
216
217 $this->renderer->render_sitemap( $url_list );
218 exit;
219 }
220
221 /**
222 * Redirects a URL to the wp-sitemap.xml
223 *
224 * @since 5.5.0
225 * @deprecated 6.7.0 Deprecated in favor of {@see WP_Rewrite::rewrite_rules()}
226 *
227 * @param bool $bypass Pass-through of the pre_handle_404 filter value.
228 * @param WP_Query $query The WP_Query object.
229 * @return bool Bypass value.
230 */
231 public function redirect_sitemapxml( $bypass, $query ) {
232 _deprecated_function( __FUNCTION__, '6.7.0' );
233
234 // If a plugin has already utilized the pre_handle_404 function, return without action to avoid conflicts.
235 if ( $bypass ) {
236 return $bypass;
237 }
238
239 // 'pagename' is for most permalink types, name is for when the %postname% is used as a top-level field.
240 if ( 'sitemap-xml' === $query->get( 'pagename' )
241 || 'sitemap-xml' === $query->get( 'name' )
242 ) {
243 wp_safe_redirect( $this->index->get_index_url() );
244 exit();
245 }
246
247 return $bypass;
248 }
249
250 /**
251 * Adds the sitemap index to robots.txt.
252 *
253 * @since 5.5.0
254 *
255 * @param string $output robots.txt output.
256 * @param bool $is_public Whether the site is public.
257 * @return string The robots.txt output.
258 */
259 public function add_robots( $output, $is_public ) {
260 if ( $is_public ) {
261 $output .= "\nSitemap: " . esc_url( $this->index->get_index_url() ) . "\n";
262 }
263
264 return $output;
265 }
266}
267