| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
namespace Yatra\Sitemap; |
| 6 |
|
| 7 |
/** |
| 8 |
* Serves Yatra's single consolidated sitemap at /yatra-sitemap.xml — every |
| 9 |
* trip, destination, activity, category and listing page in one <urlset>. |
| 10 |
* |
| 11 |
* Discovery is guaranteed two ways: a rewrite rule (clean routing) and a |
| 12 |
* raw-path fallback in template_redirect (so it works immediately on existing |
| 13 |
* installs before rewrite rules are re-flushed). The URL is also added to |
| 14 |
* robots.txt, which every search engine honours regardless of SEO plugin. |
| 15 |
*/ |
| 16 |
class SitemapRouter |
| 17 |
{ |
| 18 |
private const QUERY_VAR = 'yatra_sitemap'; |
| 19 |
private const SLUG = 'yatra-sitemap'; |
| 20 |
private const REWRITE_OPTION = 'yatra_sitemap_rewrite_version'; |
| 21 |
private const REWRITE_VERSION = '2'; |
| 22 |
|
| 23 |
private SitemapService $service; |
| 24 |
private SitemapRenderer $renderer; |
| 25 |
|
| 26 |
public function __construct(SitemapService $service, SitemapRenderer $renderer) |
| 27 |
{ |
| 28 |
$this->service = $service; |
| 29 |
$this->renderer = $renderer; |
| 30 |
} |
| 31 |
|
| 32 |
public function register(): void |
| 33 |
{ |
| 34 |
add_action('init', [$this, 'addRewriteRules']); |
| 35 |
add_filter('query_vars', [$this, 'addQueryVar']); |
| 36 |
add_action('template_redirect', [$this, 'maybeRender'], 0); |
| 37 |
add_filter('robots_txt', [$this, 'addToRobots'], 10, 2); |
| 38 |
} |
| 39 |
|
| 40 |
public function addRewriteRules(): void |
| 41 |
{ |
| 42 |
add_rewrite_rule('^' . self::SLUG . '\.xml$', 'index.php?' . self::QUERY_VAR . '=1', 'top'); |
| 43 |
|
| 44 |
// One-time flush so the pretty URL routes natively on existing installs |
| 45 |
// (new installs flush on activation). The template_redirect fallback |
| 46 |
// covers the window before this runs. |
| 47 |
if (get_option(self::REWRITE_OPTION) !== self::REWRITE_VERSION) { |
| 48 |
flush_rewrite_rules(false); |
| 49 |
update_option(self::REWRITE_OPTION, self::REWRITE_VERSION, false); |
| 50 |
} |
| 51 |
} |
| 52 |
|
| 53 |
/** |
| 54 |
* @param string[] $vars |
| 55 |
* @return string[] |
| 56 |
*/ |
| 57 |
public function addQueryVar(array $vars): array |
| 58 |
{ |
| 59 |
$vars[] = self::QUERY_VAR; |
| 60 |
return $vars; |
| 61 |
} |
| 62 |
|
| 63 |
public function maybeRender(): void |
| 64 |
{ |
| 65 |
$requested = get_query_var(self::QUERY_VAR) !== ''; |
| 66 |
|
| 67 |
// Fallback: resolve straight off the request path when the rewrite rule |
| 68 |
// has not been flushed yet (or is shadowed by another plugin). |
| 69 |
if (!$requested) { |
| 70 |
$path = (string) wp_parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH); |
| 71 |
$requested = (bool) preg_match('~/' . self::SLUG . '\.xml$~', $path); |
| 72 |
} |
| 73 |
|
| 74 |
if (!$requested) { |
| 75 |
return; |
| 76 |
} |
| 77 |
|
| 78 |
if (!headers_sent()) { |
| 79 |
status_header(200); |
| 80 |
nocache_headers(); |
| 81 |
header('Content-Type: application/xml; charset=UTF-8'); |
| 82 |
header('X-Robots-Tag: noindex, follow', true); |
| 83 |
} |
| 84 |
|
| 85 |
echo $this->renderer->urlset($this->service->getAllEntries()); // phpcs:ignore WordPress.Security.EscapeOutput -- renderer escapes each node. |
| 86 |
exit; |
| 87 |
} |
| 88 |
|
| 89 |
/** |
| 90 |
* Absolute URL of the sitemap (robots.txt, admin UI, SEO integrations). |
| 91 |
* Static so callers don't need to build the service/renderer just for a URL. |
| 92 |
*/ |
| 93 |
public static function sitemapUrl(): string |
| 94 |
{ |
| 95 |
// Plain permalinks: fall back to the query-var form, which always works. |
| 96 |
if (get_option('permalink_structure') === '') { |
| 97 |
return add_query_arg(self::QUERY_VAR, '1', home_url('/')); |
| 98 |
} |
| 99 |
|
| 100 |
return home_url('/' . self::SLUG . '.xml'); |
| 101 |
} |
| 102 |
|
| 103 |
/** |
| 104 |
* @param string $output Existing robots.txt body. |
| 105 |
* @param bool $public Whether the site is set to be indexed. |
| 106 |
*/ |
| 107 |
public function addToRobots(string $output, $public): string |
| 108 |
{ |
| 109 |
if (!$public) { |
| 110 |
return $output; |
| 111 |
} |
| 112 |
|
| 113 |
return rtrim($output) . "\nSitemap: " . esc_url(self::sitemapUrl()) . "\n"; |
| 114 |
} |
| 115 |
} |
| 116 |
|