PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
← All changes | inc/sitemaps/class-sitemaps-renderer.php +59 -46 18.1 → trunk View file →
@@ -60,9 +60,9 @@
60 60
61 61 /**
62 62 * Builds the sitemap index.
63 63 *
64 - * @param array $links Set of sitemaps index links.
64 + * @param array<string> $links Set of sitemaps index links.
65 65 *
66 66 * @return string
67 67 */
68 68 public function get_index( $links ) {
@@ -86,11 +86,11 @@
86 86
87 87 /**
88 88 * Builds the sitemap.
89 89 *
90 - * @param array $links Set of sitemap links.
91 - * @param string $type Sitemap type.
92 - * @param int $current_page Current sitemap page number.
90 + * @param array<string> $links Set of sitemap links.
91 + * @param string $type Sitemap type.
92 + * @param int $current_page Current sitemap page number.
93 93 *
94 94 * @return string
95 95 */
96 96 public function get_sitemap( $links, $type, $current_page ) {
@@ -95,16 +95,23 @@
95 95 */
96 96 public function get_sitemap( $links, $type, $current_page ) {
97 97
98 98 $urlset = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" '
99 - . 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd '
100 - . 'http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" '
101 - . 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
99 + . 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd '
100 + . 'http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" '
101 + . 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
102 102
103 103 /**
104 + * Filters the `urlset` for all sitemaps.
105 + *
106 + * @param string $urlset The output for the sitemap's `urlset`.
107 + */
108 + $urlset = apply_filters( 'wpseo_sitemap_urlset', $urlset );
109 +
110 + /**
104 111 * Filters the `urlset` for a sitemap by type.
105 112 *
106 - * @api string $urlset The output for the sitemap's `urlset`.
113 + * @param string $urlset The output for the sitemap's `urlset`.
107 114 */
108 115 $xml = apply_filters( "wpseo_sitemap_{$type}_urlset", $urlset );
109 116
110 117 foreach ( $links as $url ) {
@@ -129,9 +136,9 @@
129 136
130 137 /**
131 138 * Produce final XML output with debug information.
132 139 *
133 - * @param string $sitemap Sitemap XML.
140 + * @param string $sitemap Sitemap XML.
134 141 *
135 142 * @return string
136 143 */
137 144 public function get_output( $sitemap ) {
@@ -165,8 +172,10 @@
165 172 /**
166 173 * Set a custom stylesheet for this sitemap. Set to empty to just remove the default stylesheet.
167 174 *
168 175 * @param string $stylesheet Full XML-stylesheet declaration.
176 + *
177 + * @return void
169 178 */
170 179 public function set_stylesheet( $stylesheet ) {
171 180 $this->stylesheet = $stylesheet;
172 181 }
@@ -173,9 +182,9 @@
173 182
174 183 /**
175 184 * Build the `<sitemap>` tag for a given URL.
176 185 *
177 - * @param array $url Array of parts that make up this entry.
186 + * @param array<string> $url Array of parts that make up this entry.
178 187 *
179 188 * @return string
180 189 */
181 190 protected function sitemap_index_url( $url ) {
@@ -200,9 +209,9 @@
200 209 * Build the `<url>` tag for a given URL.
201 210 *
202 211 * Public access for backwards compatibility reasons.
203 212 *
204 - * @param array $url Array of parts that make up this entry.
213 + * @param array<string> $url Array of parts that make up this entry.
205 214 *
206 215 * @return string
207 216 */
208 217 public function sitemap_url( $url ) {
@@ -208,18 +217,15 @@
208 217 public function sitemap_url( $url ) {
209 218
210 219 $date = null;
211 220
212 -
213 221 if ( ! empty( $url['mod'] ) ) {
214 222 // Create a DateTime object date in the correct timezone.
215 223 $date = YoastSEO()->helpers->date->format( $url['mod'] );
216 224 }
217 225
218 - $url['loc'] = htmlspecialchars( $url['loc'], ENT_COMPAT, $this->output_charset, false );
219 -
220 226 $output = "\t<url>\n";
221 - $output .= "\t\t<loc>" . $this->encode_url_rfc3986( $url['loc'] ) . "</loc>\n";
227 + $output .= "\t\t<loc>" . $this->encode_and_escape( $url['loc'] ) . "</loc>\n";
222 228 $output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date, ENT_COMPAT, $this->output_charset, false ) . "</lastmod>\n";
223 229
224 230 if ( empty( $url['images'] ) ) {
225 231 $url['images'] = [];
@@ -231,37 +237,12 @@
231 237 continue;
232 238 }
233 239
234 240 $output .= "\t\t<image:image>\n";
235 - $output .= "\t\t\t<image:loc>" . esc_html( $this->encode_url_rfc3986( $img['src'] ) ) . "</image:loc>\n";
236 -
237 - if ( ! empty( $img['title'] ) ) {
238 -
239 - $title = $img['title'];
240 -
241 - if ( $this->needs_conversion ) {
242 - $title = mb_convert_encoding( $title, $this->output_charset, $this->charset );
243 - }
244 -
245 - $title = _wp_specialchars( html_entity_decode( $title, ENT_QUOTES, $this->output_charset ) );
246 - $output .= "\t\t\t<image:title><![CDATA[{$title}]]></image:title>\n";
247 - }
248 -
249 - if ( ! empty( $img['alt'] ) ) {
250 -
251 - $alt = $img['alt'];
252 -
253 - if ( $this->needs_conversion ) {
254 - $alt = mb_convert_encoding( $alt, $this->output_charset, $this->charset );
255 - }
256 -
257 - $alt = _wp_specialchars( html_entity_decode( $alt, ENT_QUOTES, $this->output_charset ) );
258 - $output .= "\t\t\t<image:caption><![CDATA[{$alt}]]></image:caption>\n";
259 - }
260 -
241 + $output .= "\t\t\t<image:loc>" . $this->encode_and_escape( $img['src'] ) . "</image:loc>\n";
261 242 $output .= "\t\t</image:image>\n";
262 243 }
263 - unset( $img, $title, $alt );
244 + unset( $img );
264 245
265 246 $output .= "\t</url>\n";
266 247
267 248 /**
@@ -266,16 +247,48 @@
266 247
267 248 /**
268 249 * Filters the output for the sitemap URL tag.
269 250 *
270 - * @api string $output The output for the sitemap url tag.
271 - *
272 - * @param array $url The sitemap URL array on which the output is based.
251 + * @param string $output The output for the sitemap url tag.
252 + * @param array $url The sitemap URL array on which the output is based.
273 253 */
274 254 return apply_filters( 'wpseo_sitemap_url', $output, $url );
275 255 }
276 256
277 257 /**
258 + * Ensure the URL is encoded per RFC3986 and correctly escaped for use in an XML sitemap.
259 + *
260 + * This method works around a two quirks in esc_url():
261 + * 1. `esc_url()` leaves schema-relative URLs alone, while according to the sitemap specs,
262 + * the URL must always begin with a protocol.
263 + * 2. `esc_url()` escapes ampersands as `&#038;` instead of the more common `&amp;`.
264 + * According to the specs, `&amp;` should be used, and even though this shouldn't
265 + * really make a difference in practice, to quote Jono: "I'd be nervous about &#038;
266 + * given how many weird and wonderful things eat sitemaps", so better safe than sorry.
267 + *
268 + * @link https://www.sitemaps.org/protocol.html#xmlTagDefinitions
269 + * @link https://www.sitemaps.org/protocol.html#escaping
270 + * @link https://developer.wordpress.org/reference/functions/esc_url/
271 + *
272 + * @param string $url URL to encode and escape.
273 + *
274 + * @return string
275 + */
276 + protected function encode_and_escape( $url ) {
277 + $url = $this->encode_url_rfc3986( $url );
278 + $url = esc_url( $url );
279 + $url = str_replace( '&#038;', '&amp;', $url );
280 + $url = str_replace( '&#039;', '&apos;', $url );
281 +
282 + if ( strpos( $url, '//' ) === 0 ) {
283 + // Schema-relative URL for which esc_url() does not add a scheme.
284 + $url = 'http:' . $url;
285 + }
286 +
287 + return $url;
288 + }
289 +
290 + /**
278 291 * Apply some best effort conversion to comply with RFC3986.
279 292 *
280 293 * @param string $url URL to encode.
281 294 *
@@ -325,9 +338,9 @@
325 338 * @return string The XSL URL that needs to be used.
326 339 */
327 340 protected function get_xsl_url() {
328 341 if ( home_url() !== site_url() ) {
329 - return home_url( 'main-sitemap.xsl' );
342 + return apply_filters( 'wpseo_sitemap_public_url', home_url( 'main-sitemap.xsl' ) );
330 343 }
331 344
332 345 /*
333 346 * Fallback to circumvent a cross-domain security problem when the XLS file is