class-wp-sitemaps-renderer.php 6.7 KB

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