Skip to content

Instantly share code, notes, and snippets.

@ArrayIterator
Created February 11, 2022 04:41
Show Gist options
  • Save ArrayIterator/ae45d3c5eb2a8ff25709e597d02999d5 to your computer and use it in GitHub Desktop.
Save ArrayIterator/ae45d3c5eb2a8ff25709e597d02999d5 to your computer and use it in GitHub Desktop.
custom wordpress sitemap styling & sitemap generator
<?php
namespace ArrayIterator\WP\Components\Sitemap;
use DOMDocument;
use WP_Sitemaps_Renderer;
if (!class_exists(WP_Sitemaps_Renderer::class)) {
return;
}
class Renderer extends WP_Sitemaps_Renderer
{
/**
* @var string
*/
protected $xmlns = '"http://www.sitemaps.org/schemas/sitemap/0.9';
/**
* @var array<string, string>
*/
protected $xmlns_list = [
'xsi' => "https://www.w3.org/2001/XMLSchema-instance",
'image' => "https://www.google.com/schemas/sitemap-image/1.1",
];
/**
* @var array<string, array<int, string>>
*/
protected $xsi = [
'schemaLocation' => [
"https://www.sitemaps.org/schemas/sitemap/0.9",
"https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd",
"https://www.google.com/schemas/sitemap-image/1.1",
"https://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd"
]
];
/**
* @param array $url_list
*
* @return false|string
*/
public function get_sitemap_xml( $url_list )
{
$attrs = '';
foreach ($this->xmlns_list as $key => $item) {
$item = esc_url($item);
$attrs .= " xmlns:{$key}='$item'";
}
foreach ($this->xmlns_list as $key => $item) {
$item = (array) $item;
$item = implode(' ', array_map('esc_url', $item));
$attrs .= " xsi:{$key}='$item'";
}
$xmlns = esc_url($this->xmlns);
$attrs .= " xmlns='{$xmlns}'";
$xml_array = [
'<?xml version="1.0" encoding="UTF-8"?>',
$this->stylesheet,
sprintf("<urlset %s>", trim($attrs))
];
foreach ( $url_list as $url_item ) {
$xml_array[] = "<url>";
foreach ( $url_item as $name => $value ) {
if ( 'loc' === $name ) {
$xml_array[] = sprintf('<loc>%s</loc>', esc_url($value));
continue;
}
if ( in_array( $name, array( 'lastmod', 'changefreq', 'priority' ), true ) ) {
$xml_array[] = sprintf('<%1$s>%2$s</%1$s>', esc_attr($name), esc_xml( $value ) );
continue;
}
if ($name === 'image') {
if (!is_array($value)) {
continue;
}
foreach ($value as $item) {
if (!is_array($item) || !isset($item['loc'])) {
continue;
}
$xml_array[] = "<image:image>";
$xml_array[] = sprintf('<image:loc>%s</image:loc>', esc_url($item['loc']));
if (!empty($item['title']) && is_string($item['title'])) {
$xml_array[] = sprintf('<image:title><![CDATA[%s]]></image:title>', esc_xml($item['title']));
}
if (!empty($item['caption']) && is_string($item['caption'])) {
$xml_array[] = sprintf('<image:caption><![CDATA[%s]]></image:caption>', esc_xml($item['caption']));
}
$xml_array[] = "</image:image>";
}
continue;
}
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %s: List of element names. */
__( 'Fields other than %s are not currently supported for sitemaps.' ),
implode( ',', array( 'loc', 'lastmod', 'changefreq', 'priority', 'image' ) )
),
'5.5.0'
);
}
$xml_array[] = "</url>";
}
$xml_array[] = "</urlset>";
$xml_array = implode("\n", $xml_array);
if (!apply_filters('format_sitemap_output', true) && class_exists(DOMDocument::class)) {
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml_array);
$xml_array = $dom->saveXML();
}
return $xml_array;
}
}
<?php
use ArrayIterator\WP\Components\Sitemap\Sitemaps;
if (!defined('ABSPATH')) {
return;
}
if (!function_exists('array_iterator_custom_sitemap_stylesheet_index_url')) {
/**
* Replace sitemap-index.xsl
*
* @param string $url
*
* @return string
*/
function array_iterator_custom_sitemap_stylesheet_index_url($url)
{
// url to sitemap-index.xsl
return get_theme_file_uri('assets/xsl/sitemap-index.xsl');
}
}
add_filter('wp_sitemaps_stylesheet_index_url', 'array_iterator_custom_sitemap_stylesheet_index_url');
if (!function_exists('array_iterator_custom_sitemap_stylesheet_url')) {
/**
* Replace sitemap.xsl
*
* @param string $url
*
* @return string
*/
function array_iterator_custom_sitemap_stylesheet_url($url)
{
// url to sitemap-index.xsl
return get_theme_file_uri('assets/xsl/sitemap.xsl');
}
}
add_filter('wp_sitemaps_stylesheet_url', 'array_iterator_custom_sitemap_stylesheet_url');
if (!function_exists('array_iterator_custom_sitemap_wp_sitemaps_index_entry')) {
function array_iterator_custom_sitemap_wp_sitemaps_index_entry($sitemap_entry, $object_type, $type, $page)
{
$args = [
'post_status' => 'publish',
'order_by' => 'date',
'order' => 'DESC',
'posts_per_page' => 1,
];
if ($object_type === 'term') {
$args['tax_query'] = [
'taxonomy' => $type,
];
} else {
$args['post_type'] = $type;
}
$posts = get_posts($args);
foreach ($posts as $post) {
$sitemap_entry = array_iterator_custom_sitemap_sitemaps_posts_entry(
$sitemap_entry,
$post,
$post->post_type
);
break;
}
return $sitemap_entry;
}
}
add_action('wp_sitemaps_index_entry', 'array_iterator_custom_sitemap_wp_sitemaps_index_entry', 10, 4);
if (!function_exists('array_iterator_custom_sitemap_sitemaps_posts_entry')) {
function array_iterator_custom_sitemap_sitemaps_posts_entry($sitemap_entry, $post, $post_type)
{
/**
* @var WP_Post $post
*/
$sitemap_entry['lastmod'] = get_gmt_from_date($post->post_date, 'c');
$medias = get_attached_media('image', $post->ID);
if (!empty($medias)) {
foreach ($medias as $_post) {
$sitemap_entry['image'][] = [
'loc' => wp_get_attachment_url($_post->ID),
'title' => $_post->post_title,
'caption' => $_post->post_excerpt,
];
}
}
return $sitemap_entry;
}
}
add_filter('wp_sitemaps_posts_entry', 'array_iterator_custom_sitemap_sitemaps_posts_entry', 10, 3);
if (!function_exists('array_iterator_custom_sitemap_sitemaps_posts_show_on_front_entry')) {
function array_iterator_custom_sitemap_sitemaps_posts_show_on_front_entry($sitemap_entry)
{
$posts = get_posts([
'posts_per_page' => 1,
'post_status' => 'publish',
'order' => 'DESC',
'order_by' => 'date',
]);
foreach ($posts as $post) {
$sitemap_entry = array_iterator_custom_sitemap_sitemaps_posts_entry($sitemap_entry, $post, $post->post_type);
break;
}
return $sitemap_entry;
}
}
add_action('wp_sitemaps_posts_show_on_front_entry', 'array_iterator_custom_sitemap_sitemaps_posts_show_on_front_entry');
if (!function_exists('array_iterator_custom_sitemap_sitemaps_taxonomy_entry')) {
function array_iterator_custom_sitemap_sitemaps_taxonomy_entry($sitemap_entry, $term, $taxonomy)
{
$posts = get_posts([
'posts_per_page' => 1,
'order' => 'DESC',
'order_by' => 'date',
'post_status' => 'publish',
'post_type' => 'post',
'tax_query' => [
'taxonomy' => $taxonomy,
'terms' => $term,
]
]);
foreach ($posts as $post) {
$sitemap_entry['lastmod'] = get_gmt_from_date($post->post_date, 'c');
break;
}
return $sitemap_entry;
}
}
add_filter('wp_sitemaps_taxonomies_entry', 'array_iterator_custom_sitemap_sitemaps_taxonomy_entry', 10, 3);
if (!function_exists('array_iterator_custom_sitemap_sitemaps_user_entry')) {
function array_iterator_custom_sitemap_sitemaps_user_entry($sitemap_entry, $user)
{
$posts = get_posts([
'posts_per_page' => 1,
'order' => 'DESC',
'order_by' => 'date',
'post_status' => 'publish',
'post_type' => 'post',
'author' => $user->ID
]);
foreach ($posts as $post) {
$sitemap_entry['lastmod'] = get_gmt_from_date($post->post_date, 'c');
break;
}
return $sitemap_entry;
}
}
add_filter('wp_sitemaps_users_entry', 'array_iterator_custom_sitemap_sitemaps_user_entry', 10, 2);
if (!function_exists('array_iterator_custom_wp_sitemaps_get_server')) {
function array_iterator_custom_wp_sitemaps_get_server()
{
global $wp_sitemaps;
if ( ! $wp_sitemaps) {
$wp_sitemaps = (new Sitemaps())->sitemap();
}
return $wp_sitemaps;
}
}
// replace wp_sitemap_get_server() hooks / global $wp_sitemaps;
add_action('init', 'array_iterator_custom_wp_sitemaps_get_server', 1);
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9" exclude-result-prefixes="sitemap">
<xsl:output method="html" encoding="UTF-8" indent="yes" />
<!--
Set variables for whether lastmod occurs for any sitemap in the index.
We do this up front because it can be expensive in a large sitemap.
-->
<xsl:variable name="has-lastmod" select="count( /sitemap:sitemapindex/sitemap:sitemap/sitemap:lastmod )" />
<xsl:template match="/">
<html lang="en-US">
<head>
<title>XML Sitemap</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: #444;
padding: 0;
margin: 0;
font-size: 16px;
box-sizing: border-box;
}
body * {
box-sizing: border-box;
}
a:hover {
text-decoration: underline;
}
#sitemap {
margin: 0;
text-align: left;
padding: 0 .75rem;
}
#sitemap-header h1 {
text-transform: uppercase;
letter-spacing: 1px;
font-weight: lighter;
margin-bottom: 1em;
}
#sitemap-header {
color: #fff;
background: #42a18f;
margin-left: -.75rem;
margin-right: -.75rem;
padding: .75rem 1.5rem;
border-bottom: 3px solid;
padding-bottom: 4rem;
line-height: 1.3em;
text-align: center;
}
#sitemap-header a {
color: #fff;
text-decoration:none;
}
.sitemap-header-link {
display: block;
margin: 1rem 0 .5rem;
}
#sitemap-content {
margin-left: -.75rem;
margin-right: -.75rem;
position: relative;
}
.number-sitemap-count {
width: 6rem;
height: 6rem;
font-size: 1.8rem;
text-align: center;
background: #42a18f;
color: #fff;
display: block;
margin: -3.2rem auto 1rem;
border-radius: 50%;
line-height: 5.4rem;
border: 3px solid;
}
#sitemap-con#42a18f .#42a18f {
direction: ltr;
}
ul {
list-style: none;
padding: 20px 0;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: stretch;
align-items: stretch;
margin: 0;
}
ul li a {
color: #444;
text-decoration:none;
}
ul li a:hover {
color: #111;
}
ul li {
font-size: .9em;
flex: 1;
padding: 1rem;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
align-content: stretch;
width: 100%;
}
ul li:nth-child(odd) {
background: #f1f1f1;
}
ul li span {
flex: 1 1 calc(100% - 200px);
width: calc(100% - 200px);
}
ul li span.lastmod {
flex: 1 1 200px;
text-align: center;
}
ul li span.desc {
font-weight: bold;
font-size: .8em;
text-align: left;
}
ul li span.lastmod,
ul li span.desc.lastmod {
text-align: right;
padding: 0 10px;
}
</style>
</head>
<body>
<div id="sitemap">
<div id="sitemap-header">
<h1 class="sitemap-header-title">XML SITEMAP / INDEX</h1>
<div class="sitemap-header-link">
<a target="_blank" rel="noopener noreferrer external" href="https://www.sitemaps.org/">Learn more about XML sitemaps.</a>
</div>
</div>
<div id="sitemap-content">
<div class="number-sitemap-count">
<xsl:value-of select="count( sitemap:sitemapindex/sitemap:sitemap )"/>
</div>
<ul id="list-style-of-sitemap">
<li>
<span class="loc desc">
URL
</span>
<xsl:if test="$has-lastmod">
<span class="lastmod desc">
LAST MODIFICATION
</span>
</xsl:if>
</li>
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
<li>
<span class="loc"><a href="{sitemap:loc}">
<xsl:value-of select="sitemap:loc" /></a>
</span>
<xsl:if test="$has-lastmod">
<span class="lastmod">
<xsl:value-of select="concat(substring(sitemap:lastmod,0,5), '/', substring(sitemap:lastmod,6,2), '/', substring(sitemap:lastmod,9,2), concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
</span>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
<?php
namespace ArrayIterator\WP\Components\Sitemap;
use WP_Sitemaps;
if (!class_exists(WP_Sitemaps::class)) {
return;
}
class Sitemap extends WP_Sitemaps
{
public function __construct()
{
parent::__construct();
$this->renderer = new Renderer();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<!--
Set variables for whether lastmod, changefreq or priority occur for any url in the sitemap.
We do this up front because it can be expensive in a large sitemap.
-->
<xsl:variable name="has-lastmod" select="count( /sitemap:urlset/sitemap:url/sitemap:lastmod )"/>
<xsl:variable name="has-changefreq" select="count( /sitemap:urlset/sitemap:url/sitemap:changefreq )"/>
<xsl:variable name="has-image" select="count( /sitemap:urlset/sitemap:url/image:image )"/>
<xsl:template match="/">
<html lang="en-US">
<head>
<title>XML Sitemap</title>
<style>
/*# sourceURL=/custom.css*/
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: #444;
padding: 0;
margin: 0;
font-size: 16px;
box-sizing: border-box;
}
body * {
box-sizing: border-box;
}
a:hover {
text-decoration: underline;
}
#sitemap {
margin: 0;
text-align: left;
padding: 0 .75rem;
}
#sitemap-header h1 {
text-transform: uppercase;
letter-spacing: 1px;
font-weight: lighter;
margin-bottom: 1em;
}
#sitemap-header {
color: #fff;
background: #42a18f;
margin-left: -.75rem;
margin-right: -.75rem;
padding: .75rem 1.5rem;
border-bottom: 3px solid;
padding-bottom: 4rem;
line-height: 1.3em;
text-align: center;
}
#sitemap-header a {
color: #fff;
text-decoration:none;
}
.sitemap-header-link {
display: block;
margin: 1rem 0 .5rem;
}
#sitemap-content {
margin-left: -.75rem;
margin-right: -.75rem;
position: relative;
}
.number-sitemap-count {
width: 6rem;
height: 6rem;
font-size: 1.8rem;
text-align: center;
background: #42a18f;
color: #fff;
display: block;
margin: -3.2rem auto 1rem;
border-radius: 50%;
line-height: 5.4rem;
border: 3px solid;
}
#sitemap-con#42a18f .#42a18f {
direction: ltr;
}
ul {
list-style: none;
padding: 20px 0;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: stretch;
align-items: stretch;
margin: 0;
}
ul li a {
color: #444;
text-decoration:none;
}
ul li a:hover {
color: #111;
}
ul li {
font-size: .9em;
flex: 1;
padding: 1rem;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
align-content: stretch;
width: 100%;
}
ul li:nth-child(odd) {
background: #f1f1f1;
}
ul li span {
flex: 1 1 calc(100% - 200px - 50px);
width: calc(100% - 200px - 50px);
}
ul li span.lastmod {
flex: 1 1 200px;
text-align: center;
}
ul li span.image {
flex: 1 1 50px;
width: 50px;
text-align: center;
}
ul li span.desc {
font-weight: bold;
font-size: .8em;
text-align: left;
}
ul li span.lastmod,
ul li span.desc.lastmod {
text-align: right;
padding: 0 10px;
}
ul li span.image:not(.desc) {
background: #42a18f;
color: #fff;
display: inline-block;
border-radius: 3px;
}
</style>
</head>
<body>
<div id="sitemap">
<div id="sitemap-header">
<h1 class="sitemap-header-title">XML SITEMAP</h1>
<div class="sitemap-header-link">
<a target="_blank" rel="noopener noreferrer external" href="https://www.sitemaps.org/">Learn more about XML sitemaps.</a>
</div>
</div>
<div id="sitemap-content">
<div class="number-sitemap-count"><xsl:value-of select="count( sitemap:urlset/sitemap:url )"/></div>
<xsl:if test="count( sitemap:urlset/sitemap:url ) &gt; 0">
<ul id="list-style-of-sitemap">
<li>
<span class="loc desc">
URL
</span>
<xsl:if test="$has-lastmod">
<span class="lastmod desc">
LAST MODIFICATION
</span>
</xsl:if>
<xsl:if test="$has-changefreq">
<span class="changefreq desc">
CHANGE FREQUENCY
</span>
</xsl:if>
<xsl:if test="$has-image">
<span class="image desc">
IMAGES
</span>
</xsl:if>
</li>
<xsl:for-each select="sitemap:urlset/sitemap:url">
<li>
<span class="loc">
<a href="{sitemap:loc}">
<xsl:value-of select="sitemap:loc"/>
</a>
</span>
<xsl:if test="$has-lastmod">
<span class="lastmod">
<xsl:value-of select="concat(substring(sitemap:lastmod,0,5), '/', substring(sitemap:lastmod,6,2), '/', substring(sitemap:lastmod,9,2), concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
</span>
</xsl:if>
<xsl:if test="$has-changefreq">
<span class="changefreq">
<xsl:value-of select="sitemap:changefreq"/>
</span>
</xsl:if>
<xsl:if test="$has-image">
<span class="image">
<xsl:value-of select="count(image:image)"/>
</span>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</xsl:if>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
<?php
namespace ArrayIterator\WP\Components\Sitemap;
use WP_Sitemaps;
class Sitemaps
{
/**
* @return Sitemap|WP_Sitemaps
*/
public function sitemap()
{
global $wp_sitemaps;
if (!$wp_sitemaps) {
$wp_sitemaps = new Sitemap();
$wp_sitemaps->init();
do_action( 'wp_sitemaps_init', $wp_sitemaps );
}
return $wp_sitemaps;
}
}
@ArrayIterator
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment