'auto', 'exclude_types' => array(), 'taxonomies' => 'on'); $s = get_option(CITAMI_SITEMAP_SETTINGS_OPT, array()); if (is_array($s)) { if (isset($s['enabled']) && in_array($s['enabled'], array('auto', 'on', 'off'), true)) { $d['enabled'] = $s['enabled']; } if (isset($s['exclude_types']) && is_array($s['exclude_types'])) { $d['exclude_types'] = array_values(array_map('sanitize_key', $s['exclude_types'])); } if (isset($s['taxonomies']) && $s['taxonomies'] === 'off') { $d['taxonomies'] = 'off'; } } return $d; } function citami_sanitize_sitemap_settings($value) { $in = is_array($value) ? $value : array(); $out = array('enabled' => 'auto', 'exclude_types' => array(), 'taxonomies' => 'on'); if (isset($in['enabled']) && in_array($in['enabled'], array('auto', 'on', 'off'), true)) { $out['enabled'] = $in['enabled']; } if (isset($in['exclude_types']) && is_array($in['exclude_types'])) { $out['exclude_types'] = array_values(array_filter(array_map('sanitize_key', $in['exclude_types']))); } if (isset($in['taxonomies']) && $in['taxonomies'] === 'off') { $out['taxonomies'] = 'off'; } return $out; } add_action('admin_init', function () { register_setting('citami_seo', CITAMI_SITEMAP_SETTINGS_OPT, array( 'type' => 'array', 'sanitize_callback' => 'citami_sanitize_sitemap_settings', 'default' => array(), )); }); function citami_sitemap_enabled() { $s = citami_sitemap_settings(); if ($s['enabled'] === 'on') { return true; } if ($s['enabled'] === 'off') { return false; } return citami_active_seo_plugin() === null; } // WordPress core sitemap off while ours is on (never two indexes). add_filter('wp_sitemaps_enabled', function ($enabled) { return citami_sitemap_enabled() ? false : $enabled; }); /** Post types that go into the sitemap. */ function citami_sitemap_types() { $s = citami_sitemap_settings(); $types = array(); foreach (citami_seo_post_types() as $t) { if (!in_array($t, $s['exclude_types'], true)) { $types[] = $t; } } return $types; } /** Published, indexable posts of a type (ids + lastmod), page $n (1-based). */ function citami_sitemap_entries($type, $page = 1) { $posts = get_posts(array( 'post_type' => $type, 'post_status' => 'publish', 'numberposts' => CITAMI_SITEMAP_PER_PAGE, 'offset' => ($page - 1) * CITAMI_SITEMAP_PER_PAGE, 'orderby' => 'modified', 'order' => 'DESC', )); $out = array(); foreach ((array) $posts as $p) { if (!is_object($p)) { continue; } if ((string) get_post_meta($p->ID, CITAMI_META_ROBOTS_INDEX, true) === 'noindex') { continue; } if ((string) get_post_meta($p->ID, '_citami_faq_page', true) === '1' && false) { continue; // placeholder for future exclusions } $mod = !empty($p->post_modified_gmt) ? $p->post_modified_gmt : (!empty($p->post_modified) ? $p->post_modified : $p->post_date); // WP11 — [[hreflang, url], …] from the hreflang module (empty = none). $out[] = array('loc' => get_permalink($p), 'lastmod' => citami_schema_iso_date($mod), 'alternates' => apply_filters('citami_sitemap_alternates', array(), $p)); } return $out; } function citami_sitemap_count($type) { $posts = get_posts(array('post_type' => $type, 'post_status' => 'publish', 'numberposts' => -1, 'fields' => 'ids')); return is_array($posts) ? count($posts) : 0; } function citami_sitemap_xml_header() { return '' . "\n"; } /** The index: one entry per (type, page) + taxonomies. */ function citami_sitemap_index_xml() { $xml = citami_sitemap_xml_header() . '' . "\n"; foreach (citami_sitemap_types() as $type) { $count = citami_sitemap_count($type); if ($count === 0) { continue; } $pages = (int) ceil($count / CITAMI_SITEMAP_PER_PAGE); for ($n = 1; $n <= $pages; $n++) { $xml .= ' ' . esc_url(home_url('/sitemap-' . $type . '-' . $n . '.xml')) . '' . "\n"; } } if (citami_sitemap_settings()['taxonomies'] === 'on') { $xml .= ' ' . esc_url(home_url('/sitemap-taxonomies.xml')) . '' . "\n"; } return $xml . '' . "\n"; } function citami_sitemap_urlset_xml(array $entries) { $xml = citami_sitemap_xml_header() . '' . "\n"; foreach ($entries as $e) { $xml .= ' ' . esc_url($e['loc']) . ''; if (!empty($e['lastmod'])) { $xml .= '' . esc_html($e['lastmod']) . ''; } if (!empty($e['alternates']) && is_array($e['alternates'])) { foreach ($e['alternates'] as $a) { if (is_array($a) && isset($a[0], $a[1])) { $xml .= ''; } } } $xml .= '' . "\n"; } return $xml . '' . "\n"; } function citami_sitemap_taxonomy_entries() { $out = array(); if (!function_exists('get_terms') || !function_exists('get_term_link')) { return $out; } $taxonomies = function_exists('get_taxonomies') ? get_taxonomies(array('public' => true), 'names') : array('category', 'post_tag'); foreach ((array) $taxonomies as $tax) { $terms = get_terms(array('taxonomy' => $tax, 'hide_empty' => true)); if (!is_array($terms)) { continue; } foreach ($terms as $term) { $link = get_term_link($term); if (is_string($link) && $link !== '') { $out[] = array('loc' => $link, 'lastmod' => ''); } } } return $out; } /** Resolves a request path to sitemap XML (null = not a sitemap URL). */ function citami_sitemap_for_path($path) { $path = (string) $path; $q = strpos($path, '?'); if ($q !== false) { $path = substr($path, 0, $q); } if ($path === '/sitemap.xml' || $path === '/sitemap_index.xml') { return citami_sitemap_index_xml(); } if ($path === '/sitemap-taxonomies.xml') { return citami_sitemap_urlset_xml(citami_sitemap_taxonomy_entries()); } if (preg_match('#^/sitemap-([a-z0-9_]+)-(\d+)\.xml$#', $path, $m)) { $type = $m[1]; if (!in_array($type, citami_sitemap_types(), true)) { return null; } return citami_sitemap_urlset_xml(citami_sitemap_entries($type, max(1, (int) $m[2]))); } return null; } add_action('init', function () { if (is_admin() || !citami_sitemap_enabled() || empty($_SERVER['REQUEST_URI'])) { return; } $xml = citami_sitemap_for_path(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))); if ($xml === null) { return; } if (!headers_sent()) { header('Content-Type: application/xml; charset=UTF-8'); header('X-Robots-Tag: noindex'); } echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- application/xml response built with esc_xml() per value. exit; }, 1); // robots.txt advertises the index. add_filter('robots_txt', function ($output) { if (!citami_sitemap_enabled()) { return $output; } $line = 'Sitemap: ' . home_url('/sitemap.xml'); return strpos((string) $output, $line) !== false ? $output : rtrim((string) $output) . "\n\n" . $line . "\n"; }, 30); add_action('citami_seo_settings_rows', function () { $s = citami_sitemap_settings(); $opt = CITAMI_SITEMAP_SETTINGS_OPT; ?>