sitemap.php

I’ll build this as an advanced inc/seo/sitemap.php module for your RX theme, designed to work from functions.php and create XML sitemaps without needing a plugin.

Below is an advanced sitemap.php file for your RX theme.

Place it here:

rx-theme/inc/seo/sitemap.php

Then include it from your functions.php:

require_once get_template_directory() . '/inc/seo/sitemap.php';

After adding it, go to:

WordPress Dashboard → Settings → Permalinks → Save Changes

This refreshes rewrite rules.

<?php
/**
 * RX Theme Advanced XML Sitemap System
 *
 * File: inc/seo/sitemap.php
 *
 * Features:
 * - Sitemap index
 * - Post sitemap
 * - Page sitemap
 * - Custom post type sitemap
 * - Category sitemap
 * - Tag sitemap
 * - Custom taxonomy sitemap
 * - Author sitemap
 * - Image sitemap support
 * - News-style recent sitemap support
 * - Automatic rewrite rules
 * - Query vars
 * - XML headers
 * - Excludes noindex content
 * - Yoast/Rank Math/AIOSEO noindex compatibility checks
 * - Cache with transients
 * - Cache clearing on post/category/tag/term changes
 * - robots.txt sitemap line
 * - Admin helper links
 */

if (!defined('ABSPATH')) {
    exit;
}

if (!class_exists('RX_Theme_Advanced_Sitemap')) :

final class RX_Theme_Advanced_Sitemap {

    /**
     * Main sitemap slug.
     *
     * Example:
     * https://example.com/rx-sitemap.xml
     */
    const INDEX_SLUG = 'rx-sitemap.xml';

    /**
     * Query variable.
     */
    const QUERY_VAR = 'rx_sitemap';

    /**
     * Cache key prefix.
     */
    const CACHE_PREFIX = 'rx_theme_sitemap_';

    /**
     * Cache duration.
     *
     * 12 hours.
     */
    const CACHE_TTL = 43200;

    /**
     * Maximum URLs per sitemap.
     *
     * Search engines usually allow up to 50,000 URLs,
     * but smaller chunks are safer.
     */
    const MAX_URLS = 1000;

    /**
     * Start class.
     */
    public static function init() {
        add_action('init', [__CLASS__, 'add_rewrite_rules']);
        add_filter('query_vars', [__CLASS__, 'add_query_vars']);
        add_action('template_redirect', [__CLASS__, 'render_sitemap']);

        add_action('save_post', [__CLASS__, 'clear_cache']);
        add_action('deleted_post', [__CLASS__, 'clear_cache']);
        add_action('trashed_post', [__CLASS__, 'clear_cache']);
        add_action('transition_post_status', [__CLASS__, 'clear_cache']);

        add_action('created_term', [__CLASS__, 'clear_cache']);
        add_action('edited_term', [__CLASS__, 'clear_cache']);
        add_action('delete_term', [__CLASS__, 'clear_cache']);

        add_filter('robots_txt', [__CLASS__, 'add_sitemap_to_robots'], 20, 2);

        add_action('admin_bar_menu', [__CLASS__, 'admin_bar_link'], 100);
        add_action('admin_notices', [__CLASS__, 'admin_notice_after_theme_switch']);

        add_action('after_switch_theme', [__CLASS__, 'activate']);
    }

    /**
     * Run after theme activation.
     */
    public static function activate() {
        self::add_rewrite_rules();
        flush_rewrite_rules();
        self::clear_cache();
    }

    /**
     * Add pretty rewrite rules.
     */
    public static function add_rewrite_rules() {
        add_rewrite_rule(
            '^rx-sitemap\.xml$',
            'index.php?' . self::QUERY_VAR . '=index',
            'top'
        );

        add_rewrite_rule(
            '^rx-sitemap-([a-zA-Z0-9_-]+)-([0-9]+)\.xml$',
            'index.php?' . self::QUERY_VAR . '=$matches[1]&paged=$matches[2]',
            'top'
        );

        add_rewrite_rule(
            '^rx-sitemap-([a-zA-Z0-9_-]+)\.xml$',
            'index.php?' . self::QUERY_VAR . '=$matches[1]',
            'top'
        );
    }

    /**
     * Add custom query variable.
     */
    public static function add_query_vars($vars) {
        $vars[] = self::QUERY_VAR;
        return $vars;
    }

    /**
     * Render sitemap based on query var.
     */
    public static function render_sitemap() {
        $type = get_query_var(self::QUERY_VAR);

        if (empty($type)) {
            return;
        }

        nocache_headers();

        header('Content-Type: application/xml; charset=UTF-8');
        header('X-Robots-Tag: noindex, follow', true);

        $paged = absint(get_query_var('paged'));
        if ($paged < 1) {
            $paged = 1;
        }

        if ($type === 'index') {
            echo self::get_cached_xml('index', function () {
                return self::build_sitemap_index();
            });
            exit;
        }

        echo self::get_cached_xml($type . '_' . $paged, function () use ($type, $paged) {
            return self::build_sitemap_by_type($type, $paged);
        });

        exit;
    }

    /**
     * Cache wrapper.
     */
    private static function get_cached_xml($key, $callback) {
        $cache_key = self::CACHE_PREFIX . sanitize_key($key);

        $cached = get_transient($cache_key);

        if ($cached !== false && !is_user_logged_in()) {
            return $cached;
        }

        $xml = call_user_func($callback);

        set_transient($cache_key, $xml, self::CACHE_TTL);

        return $xml;
    }

    /**
     * Build sitemap index.
     */
    private static function build_sitemap_index() {
        $items = [];

        $items[] = [
            'loc'     => self::sitemap_url('post'),
            'lastmod' => self::get_latest_post_modified('post'),
        ];

        $items[] = [
            'loc'     => self::sitemap_url('page'),
            'lastmod' => self::get_latest_post_modified('page'),
        ];

        foreach (self::get_public_post_types() as $post_type) {
            if (in_array($post_type, ['post', 'page'], true)) {
                continue;
            }

            $count = self::count_posts_for_sitemap($post_type);

            if ($count > 0) {
                $pages = ceil($count / self::MAX_URLS);

                for ($i = 1; $i <= $pages; $i++) {
                    $items[] = [
                        'loc'     => self::sitemap_url($post_type, $i),
                        'lastmod' => self::get_latest_post_modified($post_type),
                    ];
                }
            }
        }

        if (self::has_public_terms('category')) {
            $items[] = [
                'loc'     => self::sitemap_url('category'),
                'lastmod' => current_time('c'),
            ];
        }

        if (self::has_public_terms('post_tag')) {
            $items[] = [
                'loc'     => self::sitemap_url('post_tag'),
                'lastmod' => current_time('c'),
            ];
        }

        foreach (self::get_public_taxonomies() as $taxonomy) {
            if (in_array($taxonomy, ['category', 'post_tag'], true)) {
                continue;
            }

            if (self::has_public_terms($taxonomy)) {
                $items[] = [
                    'loc'     => self::sitemap_url($taxonomy),
                    'lastmod' => current_time('c'),
                ];
            }
        }

        if (self::has_authors()) {
            $items[] = [
                'loc'     => self::sitemap_url('author'),
                'lastmod' => current_time('c'),
            ];
        }

        $items[] = [
            'loc'     => self::sitemap_url('image'),
            'lastmod' => self::get_latest_post_modified('post'),
        ];

        $items[] = [
            'loc'     => self::sitemap_url('news'),
            'lastmod' => current_time('c'),
        ];

        $xml  = self::xml_header();
        $xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

        foreach ($items as $item) {
            if (empty($item['loc'])) {
                continue;
            }

            $xml .= "\t<sitemap>\n";
            $xml .= "\t\t<loc>" . esc_url($item['loc']) . "</loc>\n";

            if (!empty($item['lastmod'])) {
                $xml .= "\t\t<lastmod>" . esc_html(self::format_lastmod($item['lastmod'])) . "</lastmod>\n";
            }

            $xml .= "\t</sitemap>\n";
        }

        $xml .= '</sitemapindex>';

        return $xml;
    }

    /**
     * Build sitemap by type.
     */
    private static function build_sitemap_by_type($type, $paged = 1) {
        if ($type === 'category') {
            return self::build_taxonomy_sitemap('category');
        }

        if ($type === 'post_tag') {
            return self::build_taxonomy_sitemap('post_tag');
        }

        if ($type === 'author') {
            return self::build_author_sitemap();
        }

        if ($type === 'image') {
            return self::build_image_sitemap($paged);
        }

        if ($type === 'news') {
            return self::build_news_sitemap();
        }

        $public_post_types = self::get_public_post_types();

        if (in_array($type, $public_post_types, true)) {
            return self::build_post_type_sitemap($type, $paged);
        }

        $public_taxonomies = self::get_public_taxonomies();

        if (in_array($type, $public_taxonomies, true)) {
            return self::build_taxonomy_sitemap($type);
        }

        status_header(404);

        return self::xml_header() . '<error>Sitemap not found</error>';
    }

    /**
     * Build post type sitemap.
     */
    private static function build_post_type_sitemap($post_type = 'post', $paged = 1) {
        $query = new WP_Query([
            'post_type'              => $post_type,
            'post_status'            => 'publish',
            'posts_per_page'         => self::MAX_URLS,
            'paged'                  => $paged,
            'orderby'                => 'modified',
            'order'                  => 'DESC',
            'no_found_rows'          => true,
            'ignore_sticky_posts'    => true,
            'update_post_meta_cache' => true,
            'update_post_term_cache' => false,
        ]);

        $urls = [];

        if ($query->have_posts()) {
            foreach ($query->posts as $post) {
                if (!self::is_post_indexable($post->ID)) {
                    continue;
                }

                $urls[] = [
                    'loc'        => get_permalink($post),
                    'lastmod'    => get_post_modified_time('c', true, $post),
                    'changefreq' => self::get_changefreq_for_post($post),
                    'priority'   => self::get_priority_for_post($post),
                    'images'     => self::get_post_images($post->ID),
                ];
            }
        }

        wp_reset_postdata();

        return self::build_urlset($urls, true);
    }

    /**
     * Build taxonomy sitemap.
     */
    private static function build_taxonomy_sitemap($taxonomy) {
        $terms = get_terms([
            'taxonomy'   => $taxonomy,
            'hide_empty' => true,
            'number'     => self::MAX_URLS,
        ]);

        $urls = [];

        if (!is_wp_error($terms) && !empty($terms)) {
            foreach ($terms as $term) {
                if (!self::is_term_indexable($term)) {
                    continue;
                }

                $term_link = get_term_link($term);

                if (is_wp_error($term_link)) {
                    continue;
                }

                $urls[] = [
                    'loc'        => $term_link,
                    'lastmod'    => current_time('c'),
                    'changefreq' => 'weekly',
                    'priority'   => '0.60',
                ];
            }
        }

        return self::build_urlset($urls, false);
    }

    /**
     * Build author sitemap.
     */
    private static function build_author_sitemap() {
        $users = get_users([
            'has_published_posts' => true,
            'number'              => self::MAX_URLS,
            'fields'              => ['ID', 'display_name'],
        ]);

        $urls = [];

        if (!empty($users)) {
            foreach ($users as $user) {
                if (!self::is_author_indexable($user->ID)) {
                    continue;
                }

                $urls[] = [
                    'loc'        => get_author_posts_url($user->ID),
                    'lastmod'    => current_time('c'),
                    'changefreq' => 'monthly',
                    'priority'   => '0.40',
                ];
            }
        }

        return self::build_urlset($urls, false);
    }

    /**
     * Build image sitemap.
     */
    private static function build_image_sitemap($paged = 1) {
        $query = new WP_Query([
            'post_type'              => self::get_public_post_types(),
            'post_status'            => 'publish',
            'posts_per_page'         => self::MAX_URLS,
            'paged'                  => $paged,
            'orderby'                => 'modified',
            'order'                  => 'DESC',
            'no_found_rows'          => true,
            'ignore_sticky_posts'    => true,
            'update_post_meta_cache' => true,
            'update_post_term_cache' => false,
        ]);

        $urls = [];

        if ($query->have_posts()) {
            foreach ($query->posts as $post) {
                if (!self::is_post_indexable($post->ID)) {
                    continue;
                }

                $images = self::get_post_images($post->ID);

                if (empty($images)) {
                    continue;
                }

                $urls[] = [
                    'loc'        => get_permalink($post),
                    'lastmod'    => get_post_modified_time('c', true, $post),
                    'changefreq' => self::get_changefreq_for_post($post),
                    'priority'   => self::get_priority_for_post($post),
                    'images'     => $images,
                ];
            }
        }

        wp_reset_postdata();

        return self::build_urlset($urls, true);
    }

    /**
     * Build simple news sitemap.
     *
     * Important:
     * Real Google News sitemap requires your site to be accepted in Google Publisher Center.
     * This creates a recent-post XML sitemap structure only.
     */
    private static function build_news_sitemap() {
        $query = new WP_Query([
            'post_type'              => 'post',
            'post_status'            => 'publish',
            'posts_per_page'         => 100,
            'date_query'             => [
                [
                    'after' => '2 days ago',
                ],
            ],
            'orderby'                => 'date',
            'order'                  => 'DESC',
            'no_found_rows'          => true,
            'ignore_sticky_posts'    => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ]);

        $xml  = self::xml_header();
        $xml .= '<urlset ';
        $xml .= 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ';
        $xml .= 'xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">';
        $xml .= "\n";

        if ($query->have_posts()) {
            foreach ($query->posts as $post) {
                if (!self::is_post_indexable($post->ID)) {
                    continue;
                }

                $title = get_the_title($post);
                $title = wp_strip_all_tags($title);

                $xml .= "\t<url>\n";
                $xml .= "\t\t<loc>" . esc_url(get_permalink($post)) . "</loc>\n";
                $xml .= "\t\t<news:news>\n";
                $xml .= "\t\t\t<news:publication>\n";
                $xml .= "\t\t\t\t<news:name>" . esc_html(get_bloginfo('name')) . "</news:name>\n";
                $xml .= "\t\t\t\t<news:language>" . esc_html(substr(get_bloginfo('language'), 0, 2)) . "</news:language>\n";
                $xml .= "\t\t\t</news:publication>\n";
                $xml .= "\t\t\t<news:publication_date>" . esc_html(get_the_date('c', $post)) . "</news:publication_date>\n";
                $xml .= "\t\t\t<news:title>" . esc_html($title) . "</news:title>\n";
                $xml .= "\t\t</news:news>\n";
                $xml .= "\t</url>\n";
            }
        }

        wp_reset_postdata();

        $xml .= '</urlset>';

        return $xml;
    }

    /**
     * Build URL set.
     */
    private static function build_urlset($urls, $include_images = false) {
        $xml  = self::xml_header();

        $xml .= '<urlset ';
        $xml .= 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';

        if ($include_images) {
            $xml .= ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
        }

        $xml .= '>' . "\n";

        foreach ($urls as $url) {
            if (empty($url['loc'])) {
                continue;
            }

            $xml .= "\t<url>\n";
            $xml .= "\t\t<loc>" . esc_url($url['loc']) . "</loc>\n";

            if (!empty($url['lastmod'])) {
                $xml .= "\t\t<lastmod>" . esc_html(self::format_lastmod($url['lastmod'])) . "</lastmod>\n";
            }

            if (!empty($url['changefreq'])) {
                $xml .= "\t\t<changefreq>" . esc_html($url['changefreq']) . "</changefreq>\n";
            }

            if (!empty($url['priority'])) {
                $xml .= "\t\t<priority>" . esc_html($url['priority']) . "</priority>\n";
            }

            if ($include_images && !empty($url['images']) && is_array($url['images'])) {
                foreach ($url['images'] as $image) {
                    if (empty($image['loc'])) {
                        continue;
                    }

                    $xml .= "\t\t<image:image>\n";
                    $xml .= "\t\t\t<image:loc>" . esc_url($image['loc']) . "</image:loc>\n";

                    if (!empty($image['title'])) {
                        $xml .= "\t\t\t<image:title>" . esc_html($image['title']) . "</image:title>\n";
                    }

                    if (!empty($image['caption'])) {
                        $xml .= "\t\t\t<image:caption>" . esc_html($image['caption']) . "</image:caption>\n";
                    }

                    $xml .= "\t\t</image:image>\n";
                }
            }

            $xml .= "\t</url>\n";
        }

        $xml .= '</urlset>';

        return $xml;
    }

    /**
     * XML header.
     */
    private static function xml_header() {
        return '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    }

    /**
     * Generate sitemap URL.
     */
    private static function sitemap_url($type, $paged = 1) {
        $type = sanitize_key($type);

        if ($type === 'index') {
            return home_url('/rx-sitemap.xml');
        }

        if ($paged > 1) {
            return home_url('/rx-sitemap-' . $type . '-' . absint($paged) . '.xml');
        }

        return home_url('/rx-sitemap-' . $type . '.xml');
    }

    /**
     * Get public post types.
     */
    private static function get_public_post_types() {
        $post_types = get_post_types([
            'public'             => true,
            'publicly_queryable' => true,
        ], 'names');

        unset($post_types['attachment']);

        /**
         * Filter post types included in RX sitemap.
         */
        $post_types = apply_filters('rx_theme_sitemap_post_types', $post_types);

        return array_values(array_unique($post_types));
    }

    /**
     * Get public taxonomies.
     */
    private static function get_public_taxonomies() {
        $taxonomies = get_taxonomies([
            'public' => true,
        ], 'names');

        $taxonomies = apply_filters('rx_theme_sitemap_taxonomies', $taxonomies);

        return array_values(array_unique($taxonomies));
    }

    /**
     * Count posts.
     */
    private static function count_posts_for_sitemap($post_type) {
        $counts = wp_count_posts($post_type);

        if (!isset($counts->publish)) {
            return 0;
        }

        return absint($counts->publish);
    }

    /**
     * Latest modified post date.
     */
    private static function get_latest_post_modified($post_type = 'post') {
        global $wpdb;

        $post_type = sanitize_key($post_type);

        $date = $wpdb->get_var(
            $wpdb->prepare(
                "
                SELECT post_modified_gmt
                FROM {$wpdb->posts}
                WHERE post_type = %s
                AND post_status = 'publish'
                ORDER BY post_modified_gmt DESC
                LIMIT 1
                ",
                $post_type
            )
        );

        if (empty($date)) {
            return current_time('c');
        }

        return mysql2date('c', $date, false);
    }

    /**
     * Format lastmod date.
     */
    private static function format_lastmod($date) {
        $timestamp = strtotime($date);

        if (!$timestamp) {
            return current_time('c');
        }

        return gmdate('c', $timestamp);
    }

    /**
     * Decide post priority.
     */
    private static function get_priority_for_post($post) {
        if (!$post instanceof WP_Post) {
            return '0.60';
        }

        if ($post->post_type === 'page') {
            if ((int) get_option('page_on_front') === (int) $post->ID) {
                return '1.00';
            }

            return '0.80';
        }

        if ($post->post_type === 'post') {
            return '0.70';
        }

        return '0.60';
    }

    /**
     * Decide change frequency.
     */
    private static function get_changefreq_for_post($post) {
        if (!$post instanceof WP_Post) {
            return 'weekly';
        }

        $modified = strtotime($post->post_modified_gmt);
        $now      = time();
        $days     = ($now - $modified) / DAY_IN_SECONDS;

        if ($days <= 2) {
            return 'daily';
        }

        if ($days <= 30) {
            return 'weekly';
        }

        if ($days <= 180) {
            return 'monthly';
        }

        return 'yearly';
    }

    /**
     * Get images from post.
     */
    private static function get_post_images($post_id) {
        $images = [];

        $thumbnail_id = get_post_thumbnail_id($post_id);

        if ($thumbnail_id) {
            $image = self::get_attachment_image_data($thumbnail_id);

            if (!empty($image)) {
                $images[] = $image;
            }
        }

        $content = get_post_field('post_content', $post_id);

        if (!empty($content)) {
            preg_match_all('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches);

            if (!empty($matches[1])) {
                foreach ($matches[1] as $src) {
                    $src = esc_url_raw($src);

                    if (empty($src)) {
                        continue;
                    }

                    $images[] = [
                        'loc'     => $src,
                        'title'   => '',
                        'caption' => '',
                    ];
                }
            }
        }

        $images = self::unique_images($images);

        /**
         * Filter images included in sitemap.
         */
        return apply_filters('rx_theme_sitemap_post_images', $images, $post_id);
    }

    /**
     * Attachment image data.
     */
    private static function get_attachment_image_data($attachment_id) {
        $src = wp_get_attachment_image_url($attachment_id, 'full');

        if (!$src) {
            return [];
        }

        $title   = get_the_title($attachment_id);
        $caption = wp_get_attachment_caption($attachment_id);

        return [
            'loc'     => esc_url_raw($src),
            'title'   => wp_strip_all_tags($title),
            'caption' => wp_strip_all_tags($caption),
        ];
    }

    /**
     * Remove duplicate images.
     */
    private static function unique_images($images) {
        $unique = [];
        $seen   = [];

        foreach ($images as $image) {
            if (empty($image['loc'])) {
                continue;
            }

            $key = md5($image['loc']);

            if (isset($seen[$key])) {
                continue;
            }

            $seen[$key] = true;
            $unique[]   = $image;
        }

        return $unique;
    }

    /**
     * Check if post should be indexed.
     */
    private static function is_post_indexable($post_id) {
        $post_id = absint($post_id);

        if (!$post_id) {
            return false;
        }

        if (get_post_status($post_id) !== 'publish') {
            return false;
        }

        if (post_password_required($post_id)) {
            return false;
        }

        /**
         * RX custom noindex field.
         *
         * Add custom field:
         * rx_noindex = 1
         */
        if (get_post_meta($post_id, 'rx_noindex', true)) {
            return false;
        }

        /**
         * Yoast SEO noindex support.
         */
        $yoast_noindex = get_post_meta($post_id, '_yoast_wpseo_meta-robots-noindex', true);

        if ($yoast_noindex === '1') {
            return false;
        }

        /**
         * Rank Math noindex support.
         */
        $rank_math_robots = get_post_meta($post_id, 'rank_math_robots', true);

        if (is_array($rank_math_robots) && in_array('noindex', $rank_math_robots, true)) {
            return false;
        }

        /**
         * AIOSEO noindex support.
         */
        $aioseo_robots = get_post_meta($post_id, '_aioseo_robots_default', true);
        $aioseo_noindex = get_post_meta($post_id, '_aioseo_robots_noindex', true);

        if ($aioseo_robots === '0' && $aioseo_noindex === '1') {
            return false;
        }

        return (bool) apply_filters('rx_theme_sitemap_is_post_indexable', true, $post_id);
    }

    /**
     * Check if term should be indexed.
     */
    private static function is_term_indexable($term) {
        if (!$term || empty($term->term_id)) {
            return false;
        }

        /**
         * RX custom term noindex.
         */
        $rx_noindex = get_term_meta($term->term_id, 'rx_noindex', true);

        if ($rx_noindex) {
            return false;
        }

        /**
         * Yoast term noindex support.
         */
        $yoast_noindex = get_term_meta($term->term_id, 'wpseo_noindex', true);

        if ($yoast_noindex === 'noindex') {
            return false;
        }

        /**
         * Rank Math term noindex support.
         */
        $rank_math_robots = get_term_meta($term->term_id, 'rank_math_robots', true);

        if (is_array($rank_math_robots) && in_array('noindex', $rank_math_robots, true)) {
            return false;
        }

        return (bool) apply_filters('rx_theme_sitemap_is_term_indexable', true, $term);
    }

    /**
     * Check if author should be indexed.
     */
    private static function is_author_indexable($user_id) {
        $user_id = absint($user_id);

        if (!$user_id) {
            return false;
        }

        $noindex = get_user_meta($user_id, 'rx_noindex_author', true);

        if ($noindex) {
            return false;
        }

        return (bool) apply_filters('rx_theme_sitemap_is_author_indexable', true, $user_id);
    }

    /**
     * Check if taxonomy has public terms.
     */
    private static function has_public_terms($taxonomy) {
        $terms = get_terms([
            'taxonomy'   => $taxonomy,
            'hide_empty' => true,
            'number'     => 1,
            'fields'     => 'ids',
        ]);

        return !is_wp_error($terms) && !empty($terms);
    }

    /**
     * Check if site has authors.
     */
    private static function has_authors() {
        $users = get_users([
            'has_published_posts' => true,
            'number'              => 1,
            'fields'              => 'ID',
        ]);

        return !empty($users);
    }

    /**
     * Add sitemap to robots.txt.
     */
    public static function add_sitemap_to_robots($output, $public) {
        if ('0' === (string) $public) {
            return $output;
        }

        $output .= "\n";
        $output .= 'Sitemap: ' . esc_url_raw(home_url('/rx-sitemap.xml')) . "\n";

        return $output;
    }

    /**
     * Clear sitemap cache.
     */
    public static function clear_cache() {
        global $wpdb;

        $like = $wpdb->esc_like('_transient_' . self::CACHE_PREFIX) . '%';

        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                $like
            )
        );

        $timeout_like = $wpdb->esc_like('_transient_timeout_' . self::CACHE_PREFIX) . '%';

        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                $timeout_like
            )
        );
    }

    /**
     * Admin bar sitemap link.
     */
    public static function admin_bar_link($wp_admin_bar) {
        if (!current_user_can('manage_options')) {
            return;
        }

        $wp_admin_bar->add_node([
            'id'    => 'rx-theme-sitemap',
            'title' => 'RX Sitemap',
            'href'  => home_url('/rx-sitemap.xml'),
            'meta'  => [
                'target' => '_blank',
            ],
        ]);
    }

    /**
     * Admin notice after theme switch.
     */
    public static function admin_notice_after_theme_switch() {
        if (!current_user_can('manage_options')) {
            return;
        }

        if (!isset($_GET['activated'])) {
            return;
        }

        echo '<div class="notice notice-info is-dismissible">';
        echo '<p><strong>RX Theme Sitemap:</strong> Please visit <a href="' . esc_url(admin_url('options-permalink.php')) . '">Settings → Permalinks</a> and click <strong>Save Changes</strong> once to activate sitemap URLs.</p>';
        echo '</div>';
    }
}

RX_Theme_Advanced_Sitemap::init();

endif;

Your main sitemap URL will be:

https://your-domain.com/rx-sitemap.xml

Example child sitemaps:

https://your-domain.com/rx-sitemap-post.xml
https://your-domain.com/rx-sitemap-page.xml
https://your-domain.com/rx-sitemap-category.xml
https://your-domain.com/rx-sitemap-post_tag.xml
https://your-domain.com/rx-sitemap-author.xml
https://your-domain.com/rx-sitemap-image.xml
https://your-domain.com/rx-sitemap-news.xml

One important note: if you already use Yoast SEO, Rank Math, AIOSEO, or another SEO plugin sitemap, do not use both at the same time. Use either the plugin sitemap or this RX theme sitemap, otherwise Google may see duplicate sitemap systems.

Leave a Reply

Your email address will not be published. Required fields are marked *