1<?php
2/**
3 * Sitemaps: WP_Sitemaps_Renderer class
4 *
5 * Responsible for rendering Sitemaps data to XML in accordance with sitemap protocol.
6 *
7 * @package WordPress
8 * @subpackage Sitemaps
9 * @since 5.5.0
10 */
11
12/**
13 * Class WP_Sitemaps_Renderer
14 *
15 * @since 5.5.0
16 */
17#[AllowDynamicProperties]
18class WP_Sitemaps_Renderer {
19 /**
20 * XSL stylesheet for styling a sitemap for web browsers.
21 *
22 * @since 5.5.0
23 *
24 * @var string
25 */
26 protected $stylesheet = '';
27
28 /**
29 * XSL stylesheet for styling a sitemap for web browsers.
30 *
31 * @since 5.5.0
32 *
33 * @var string
34 */
35 protected $stylesheet_index = '';
36
37 /**
38 * WP_Sitemaps_Renderer constructor.
39 *
40 * @since 5.5.0
41 */
42 public function __construct() {
43 $stylesheet_url = $this->get_sitemap_stylesheet_url();
44
45 if ( $stylesheet_url ) {
46 $this->stylesheet = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_url ) . '" ?>';
47 }
48
49 $stylesheet_index_url = $this->get_sitemap_index_stylesheet_url();
50
51 if ( $stylesheet_index_url ) {
52 $this->stylesheet_index = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_index_url ) . '" ?>';
53 }
54 }
55
56 /**
57 * Gets the URL for the sitemap stylesheet.
58 *
59 * @since 5.5.0
60 *
61 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
62 *
63 * @return string The sitemap stylesheet URL.
64 */
65 public function get_sitemap_stylesheet_url() {
66 global $wp_rewrite;
67
68 $sitemap_url = home_url( '/wp-sitemap.xsl' );
69
70 if ( ! $wp_rewrite->using_permalinks() ) {
71 $sitemap_url = home_url( '/?sitemap-stylesheet=sitemap' );
72 }
73
74 /**
75 * Filters the URL for the sitemap stylesheet.
76 *
77 * If a falsey value is returned, no stylesheet will be used and
78 * the "raw" XML of the sitemap will be displayed.
79 *
80 * @since 5.5.0
81 *
82 * @param string $sitemap_url Full URL for the sitemaps XSL file.
83 */
84 return apply_filters( 'wp_sitemaps_stylesheet_url', $sitemap_url );
85 }
86
87 /**
88 * Gets the URL for the sitemap index stylesheet.
89 *
90 * @since 5.5.0
91 *
92 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
93 *
94 * @return string The sitemap index stylesheet URL.
95 */
96 public function get_sitemap_index_stylesheet_url() {
97 global $wp_rewrite;
98
99 $sitemap_url = home_url( '/wp-sitemap-index.xsl' );
100
101 if ( ! $wp_rewrite->using_permalinks() ) {
102 $sitemap_url = home_url( '/?sitemap-stylesheet=index' );
103 }
104
105 /**
106 * Filters the URL for the sitemap index stylesheet.
107 *
108 * If a falsey value is returned, no stylesheet will be used and
109 * the "raw" XML of the sitemap index will be displayed.
110 *
111 * @since 5.5.0
112 *
113 * @param string $sitemap_url Full URL for the sitemaps index XSL file.
114 */
115 return apply_filters( 'wp_sitemaps_stylesheet_index_url', $sitemap_url );
116 }
117
118 /**
119 * Renders a sitemap index.
120 *
121 * @since 5.5.0
122 *
123 * @param array $sitemaps Array of sitemap URLs.
124 */
125 public function render_index( $sitemaps ) {
126 header( 'Content-Type: application/xml; charset=UTF-8' );
127
128 $this->check_for_simple_xml_availability();
129
130 $index_xml = $this->get_sitemap_index_xml( $sitemaps );
131
132 if ( ! empty( $index_xml ) ) {
133 // All output is escaped within get_sitemap_index_xml().
134 echo $index_xml;
135 }
136 }
137
138 /**
139 * Gets XML for a sitemap index.
140 *
141 * @since 5.5.0
142 *
143 * @param array $sitemaps Array of sitemap URLs.
144 * @return string|false A well-formed XML string for a sitemap index. False on error.
145 */
146 public function get_sitemap_index_xml( $sitemaps ) {
147 $sitemap_index = new SimpleXMLElement(
148 sprintf(
149 '%1$s%2$s%3$s',
150 '<?xml version="1.0" encoding="UTF-8" ?>',
151 $this->stylesheet_index,
152 '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />'
153 )
154 );
155
156 foreach ( $sitemaps as $entry ) {
157 $sitemap = $sitemap_index->addChild( 'sitemap' );
158
159 // Add each element as a child node to the <sitemap> entry.
160 foreach ( $entry as $name => $value ) {
161 if ( 'loc' === $name ) {
162 $sitemap->addChild( $name, esc_url( $value ) );
163 } elseif ( 'lastmod' === $name ) {
164 $sitemap->addChild( $name, esc_xml( $value ) );
165 } else {
166 _doing_it_wrong(
167 __METHOD__,
168 sprintf(
169 /* translators: %s: List of element names. */
170 __( 'Fields other than %s are not currently supported for the sitemap index.' ),
171 implode( ',', array( 'loc', 'lastmod' ) )
172 ),
173 '5.5.0'
174 );
175 }
176 }
177 }
178
179 return $sitemap_index->asXML();
180 }
181
182 /**
183 * Renders a sitemap.
184 *
185 * @since 5.5.0
186 *
187 * @param array $url_list Array of URLs for a sitemap.
188 */
189 public function render_sitemap( $url_list ) {
190 header( 'Content-Type: application/xml; charset=UTF-8' );
191
192 $this->check_for_simple_xml_availability();
193
194 $sitemap_xml = $this->get_sitemap_xml( $url_list );
195
196 if ( ! empty( $sitemap_xml ) ) {
197 // All output is escaped within get_sitemap_xml().
198 echo $sitemap_xml;
199 }
200 }
201
202 /**
203 * Gets XML for a sitemap.
204 *
205 * @since 5.5.0
206 *
207 * @param array $url_list Array of URLs for a sitemap.
208 * @return string|false A well-formed XML string for a sitemap index. False on error.
209 */
210 public function get_sitemap_xml( $url_list ) {
211 $urlset = new SimpleXMLElement(
212 sprintf(
213 '%1$s%2$s%3$s',
214 '<?xml version="1.0" encoding="UTF-8" ?>',
215 $this->stylesheet,
216 '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" />'
217 )
218 );
219
220 foreach ( $url_list as $url_item ) {
221 $url = $urlset->addChild( 'url' );
222
223 // Add each element as a child node to the <url> entry.
224 foreach ( $url_item as $name => $value ) {
225 if ( 'loc' === $name ) {
226 $url->addChild( $name, esc_url( $value ) );
227 } elseif ( in_array( $name, array( 'lastmod', 'changefreq', 'priority' ), true ) ) {
228 $url->addChild( $name, esc_xml( $value ) );
229 } else {
230 _doing_it_wrong(
231 __METHOD__,
232 sprintf(
233 /* translators: %s: List of element names. */
234 __( 'Fields other than %s are not currently supported for sitemaps.' ),
235 implode( ',', array( 'loc', 'lastmod', 'changefreq', 'priority' ) )
236 ),
237 '5.5.0'
238 );
239 }
240 }
241 }
242
243 return $urlset->asXML();
244 }
245
246 /**
247 * Checks for the availability of the SimpleXML extension and errors if missing.
248 *
249 * @since 5.5.0
250 */
251 private function check_for_simple_xml_availability() {
252 if ( ! class_exists( 'SimpleXMLElement' ) ) {
253 add_filter(
254 'wp_die_handler',
255 static function () {
256 return '_xml_wp_die_handler';
257 }
258 );
259
260 wp_die(
261 sprintf(
262 /* translators: %s: SimpleXML */
263 esc_xml( __( 'Could not generate XML sitemap due to missing %s extension' ) ),
264 'SimpleXML'
265 ),
266 esc_xml( __( 'WordPress › Error' ) ),
267 array(
268 'response' => 501, // "Not implemented".
269 )
270 );
271 }
272 }
273}
274