class-wp-sitemaps-stylesheet.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. /**
  3. * Sitemaps: WP_Sitemaps_Stylesheet class
  4. *
  5. * This class provides the XSL stylesheets to style all sitemaps.
  6. *
  7. * @package WordPress
  8. * @subpackage Sitemaps
  9. * @since 5.5.0
  10. */
  11. /**
  12. * Stylesheet provider class.
  13. *
  14. * @since 5.5.0
  15. */
  16. #[AllowDynamicProperties]
  17. class WP_Sitemaps_Stylesheet {
  18. /**
  19. * Renders the XSL stylesheet depending on whether it's the sitemap index or not.
  20. *
  21. * @param string $type Stylesheet type. Either 'sitemap' or 'index'.
  22. */
  23. public function render_stylesheet( $type ) {
  24. header( 'Content-type: application/xml; charset=UTF-8' );
  25. if ( 'sitemap' === $type ) {
  26. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All content escaped below.
  27. echo $this->get_sitemap_stylesheet();
  28. }
  29. if ( 'index' === $type ) {
  30. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All content escaped below.
  31. echo $this->get_sitemap_index_stylesheet();
  32. }
  33. exit;
  34. }
  35. /**
  36. * Returns the escaped XSL for all sitemaps, except index.
  37. *
  38. * @since 5.5.0
  39. */
  40. public function get_sitemap_stylesheet() {
  41. $css = $this->get_stylesheet_css();
  42. $title = esc_xml( __( 'XML Sitemap' ) );
  43. $description = esc_xml( __( 'This XML Sitemap is generated by WordPress to make your content more visible for search engines.' ) );
  44. $learn_more = sprintf(
  45. '<a href="%s">%s</a>',
  46. esc_url( __( 'https://www.sitemaps.org/' ) ),
  47. esc_xml( __( 'Learn more about XML sitemaps.' ) )
  48. );
  49. $text = sprintf(
  50. /* translators: %s: Number of URLs. */
  51. esc_xml( __( 'Number of URLs in this XML Sitemap: %s.' ) ),
  52. '<xsl:value-of select="count( sitemap:urlset/sitemap:url )" />'
  53. );
  54. $lang = get_language_attributes( 'html' );
  55. $url = esc_xml( __( 'URL' ) );
  56. $lastmod = esc_xml( __( 'Last Modified' ) );
  57. $changefreq = esc_xml( __( 'Change Frequency' ) );
  58. $priority = esc_xml( __( 'Priority' ) );
  59. $xsl_content = <<<XSL
  60. <?xml version="1.0" encoding="UTF-8"?>
  61. <xsl:stylesheet
  62. version="1.0"
  63. xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  64. xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
  65. exclude-result-prefixes="sitemap"
  66. >
  67. <xsl:output method="html" encoding="UTF-8" indent="yes" />
  68. <!--
  69. Set variables for whether lastmod, changefreq or priority occur for any url in the sitemap.
  70. We do this up front because it can be expensive in a large sitemap.
  71. -->
  72. <xsl:variable name="has-lastmod" select="count( /sitemap:urlset/sitemap:url/sitemap:lastmod )" />
  73. <xsl:variable name="has-changefreq" select="count( /sitemap:urlset/sitemap:url/sitemap:changefreq )" />
  74. <xsl:variable name="has-priority" select="count( /sitemap:urlset/sitemap:url/sitemap:priority )" />
  75. <xsl:template match="/">
  76. <html {$lang}>
  77. <head>
  78. <title>{$title}</title>
  79. <style>
  80. {$css}
  81. </style>
  82. </head>
  83. <body>
  84. <div id="sitemap">
  85. <div id="sitemap__header">
  86. <h1>{$title}</h1>
  87. <p>{$description}</p>
  88. <p>{$learn_more}</p>
  89. </div>
  90. <div id="sitemap__content">
  91. <p class="text">{$text}</p>
  92. <table id="sitemap__table">
  93. <thead>
  94. <tr>
  95. <th class="loc">{$url}</th>
  96. <xsl:if test="\$has-lastmod">
  97. <th class="lastmod">{$lastmod}</th>
  98. </xsl:if>
  99. <xsl:if test="\$has-changefreq">
  100. <th class="changefreq">{$changefreq}</th>
  101. </xsl:if>
  102. <xsl:if test="\$has-priority">
  103. <th class="priority">{$priority}</th>
  104. </xsl:if>
  105. </tr>
  106. </thead>
  107. <tbody>
  108. <xsl:for-each select="sitemap:urlset/sitemap:url">
  109. <tr>
  110. <td class="loc"><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc" /></a></td>
  111. <xsl:if test="\$has-lastmod">
  112. <td class="lastmod"><xsl:value-of select="sitemap:lastmod" /></td>
  113. </xsl:if>
  114. <xsl:if test="\$has-changefreq">
  115. <td class="changefreq"><xsl:value-of select="sitemap:changefreq" /></td>
  116. </xsl:if>
  117. <xsl:if test="\$has-priority">
  118. <td class="priority"><xsl:value-of select="sitemap:priority" /></td>
  119. </xsl:if>
  120. </tr>
  121. </xsl:for-each>
  122. </tbody>
  123. </table>
  124. </div>
  125. </div>
  126. </body>
  127. </html>
  128. </xsl:template>
  129. </xsl:stylesheet>
  130. XSL;
  131. /**
  132. * Filters the content of the sitemap stylesheet.
  133. *
  134. * @since 5.5.0
  135. *
  136. * @param string $xsl_content Full content for the XML stylesheet.
  137. */
  138. return apply_filters( 'wp_sitemaps_stylesheet_content', $xsl_content );
  139. }
  140. /**
  141. * Returns the escaped XSL for the index sitemaps.
  142. *
  143. * @since 5.5.0
  144. */
  145. public function get_sitemap_index_stylesheet() {
  146. $css = $this->get_stylesheet_css();
  147. $title = esc_xml( __( 'XML Sitemap' ) );
  148. $description = esc_xml( __( 'This XML Sitemap is generated by WordPress to make your content more visible for search engines.' ) );
  149. $learn_more = sprintf(
  150. '<a href="%s">%s</a>',
  151. esc_url( __( 'https://www.sitemaps.org/' ) ),
  152. esc_xml( __( 'Learn more about XML sitemaps.' ) )
  153. );
  154. $text = sprintf(
  155. /* translators: %s: Number of URLs. */
  156. esc_xml( __( 'Number of URLs in this XML Sitemap: %s.' ) ),
  157. '<xsl:value-of select="count( sitemap:sitemapindex/sitemap:sitemap )" />'
  158. );
  159. $lang = get_language_attributes( 'html' );
  160. $url = esc_xml( __( 'URL' ) );
  161. $lastmod = esc_xml( __( 'Last Modified' ) );
  162. $xsl_content = <<<XSL
  163. <?xml version="1.0" encoding="UTF-8"?>
  164. <xsl:stylesheet
  165. version="1.0"
  166. xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  167. xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
  168. exclude-result-prefixes="sitemap"
  169. >
  170. <xsl:output method="html" encoding="UTF-8" indent="yes" />
  171. <!--
  172. Set variables for whether lastmod occurs for any sitemap in the index.
  173. We do this up front because it can be expensive in a large sitemap.
  174. -->
  175. <xsl:variable name="has-lastmod" select="count( /sitemap:sitemapindex/sitemap:sitemap/sitemap:lastmod )" />
  176. <xsl:template match="/">
  177. <html {$lang}>
  178. <head>
  179. <title>{$title}</title>
  180. <style>
  181. {$css}
  182. </style>
  183. </head>
  184. <body>
  185. <div id="sitemap">
  186. <div id="sitemap__header">
  187. <h1>{$title}</h1>
  188. <p>{$description}</p>
  189. <p>{$learn_more}</p>
  190. </div>
  191. <div id="sitemap__content">
  192. <p class="text">{$text}</p>
  193. <table id="sitemap__table">
  194. <thead>
  195. <tr>
  196. <th class="loc">{$url}</th>
  197. <xsl:if test="\$has-lastmod">
  198. <th class="lastmod">{$lastmod}</th>
  199. </xsl:if>
  200. </tr>
  201. </thead>
  202. <tbody>
  203. <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
  204. <tr>
  205. <td class="loc"><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc" /></a></td>
  206. <xsl:if test="\$has-lastmod">
  207. <td class="lastmod"><xsl:value-of select="sitemap:lastmod" /></td>
  208. </xsl:if>
  209. </tr>
  210. </xsl:for-each>
  211. </tbody>
  212. </table>
  213. </div>
  214. </div>
  215. </body>
  216. </html>
  217. </xsl:template>
  218. </xsl:stylesheet>
  219. XSL;
  220. /**
  221. * Filters the content of the sitemap index stylesheet.
  222. *
  223. * @since 5.5.0
  224. *
  225. * @param string $xsl_content Full content for the XML stylesheet.
  226. */
  227. return apply_filters( 'wp_sitemaps_stylesheet_index_content', $xsl_content );
  228. }
  229. /**
  230. * Gets the CSS to be included in sitemap XSL stylesheets.
  231. *
  232. * @since 5.5.0
  233. *
  234. * @return string The CSS.
  235. */
  236. public function get_stylesheet_css() {
  237. $text_align = is_rtl() ? 'right' : 'left';
  238. $css = <<<EOF
  239. body {
  240. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  241. color: #444;
  242. }
  243. #sitemap {
  244. max-width: 980px;
  245. margin: 0 auto;
  246. }
  247. #sitemap__table {
  248. width: 100%;
  249. border: solid 1px #ccc;
  250. border-collapse: collapse;
  251. }
  252. #sitemap__table tr td.loc {
  253. /*
  254. * URLs should always be LTR.
  255. * See https://core.trac.wordpress.org/ticket/16834
  256. * and https://core.trac.wordpress.org/ticket/49949
  257. */
  258. direction: ltr;
  259. }
  260. #sitemap__table tr th {
  261. text-align: {$text_align};
  262. }
  263. #sitemap__table tr td,
  264. #sitemap__table tr th {
  265. padding: 10px;
  266. }
  267. #sitemap__table tr:nth-child(odd) td {
  268. background-color: #eee;
  269. }
  270. a:hover {
  271. text-decoration: none;
  272. }
  273. EOF;
  274. /**
  275. * Filters the CSS only for the sitemap stylesheet.
  276. *
  277. * @since 5.5.0
  278. *
  279. * @param string $css CSS to be applied to default XSL file.
  280. */
  281. return apply_filters( 'wp_sitemaps_stylesheet_css', $css );
  282. }
  283. }