??????????????
??????????????
??????????????
??????????????
Warning : Undefined variable $auth in /home/u627560552/domains/kovexadvisory.com/public_html/666.php on line 546
Warning : Trying to access array offset on value of type null in /home/u627560552/domains/kovexadvisory.com/public_html/666.php on line 546
??????????????
??????????????
??????????????
??????????????
File Manager
✏️ Edit File: /home/mklsvubc/am1atec.co.uk/wp-content/plugins/surerank/inc/sitemap/xml-sitemap.php
⬅ Kembali
<?php
/**
* Common Meta Data
*
* This file handles functionality to generate sitemap in frontend.
*
* @package surerank
* @since 0.0.1
*/
namespace SureRank\Inc\Sitemap;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use SureRank\Inc\BatchProcess\Sync_Archives;
use SureRank\Inc\Functions\Cache;
use SureRank\Inc\Functions\Compat;
use SureRank\Inc\Functions\Cron;
use SureRank\Inc\Functions\Settings;
use SureRank\Inc\Traits\Get_Instance;
/**
* XML Sitemap
* Handles functionality to generate XML sitemaps.
*
* @since 1.0.0
*/
class Xml_Sitemap extends Sitemap {
use Get_Instance;
/**
* Staleness threshold for self-healing rebuilds — 2 days.
*
* Not a filter: historical tuning happens in code, not via site
* configuration, so a filter hook without customer evidence is a
* speculative extension point.
*
* @since 1.7.2
*/
private const STALE_REBUILD_THRESHOLD = 2 * DAY_IN_SECONDS;
/**
* Sitemap slug to be used across the class.
*
* @var string
*/
private static $sitemap_slug = 'sitemap_index';
/**
* Constructor
*
* Sets up the sitemap functionality if XML sitemaps are enabled in settings.
*
* @since 1.0.0
* @return void
*/
public function __construct() {
add_filter(
'surerank_flush_rewrite_settings',
[ $this, 'flush_settings' ],
10,
1
);
if ( ! Settings::get( 'enable_xml_sitemap' ) ) {
return;
}
add_filter( 'wp_sitemaps_enabled', '__return_false' );
add_action( 'template_redirect', [ $this, 'template_redirect' ] );
add_action( 'parse_query', [ $this, 'parse_query' ], 1 );
}
/**
* Array of settings to flush rewrite rules on update settings
*
* @param array<string, mixed> $settings Existing settings to flush.
* @since 1.0.0
* @return array<string, mixed>
*/
public function flush_settings( $settings ) {
$settings[] = 'enable_xml_sitemap';
$settings[] = 'enable_xml_image_sitemap';
return $settings;
}
/**
* Returns the sitemap slug.
*
* @since 1.0.0
* @return string
*/
public static function get_slug(): string {
$sitemap_slug = apply_filters( 'surerank_sitemap_slug', self::$sitemap_slug );
$sitemap_slug = empty( $sitemap_slug ) ? self::$sitemap_slug : $sitemap_slug;
return $sitemap_slug . '.xml';
}
/**
* Redirects default WordPress sitemap requests to custom sitemap URLs.
*
* Uses HTTP 302 (not 301) because 301s are aggressively cached by
* browsers, proxies, and CDNs. If SureRank's sitemap URL ever changes
* (via filter, plugin uninstall, or another SEO plugin taking over), a
* cached 301 would continue redirecting users to a URL that may no
* longer exist. 302 avoids that long-tail cache invalidation hazard.
*
* @since 1.0.0
* @return void
*/
public function template_redirect() {
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
return;
}
$current_url = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
$current_url = explode( '/', $current_url );
$last_url = end( $current_url );
$sitemap = [
'sitemap.xml',
'wp-sitemap.xml',
'index.xml',
];
if ( in_array( $last_url, $sitemap, true ) ) {
wp_safe_redirect( '/' . self::get_slug(), 302 );
exit;
}
}
/**
* Parses custom query variables and triggers sitemap generation.
*
* @param \WP_Query $query Current query object.
* @since 1.0.0
* @return void
*/
public function parse_query( \WP_Query $query ) {
if ( ! $query->is_main_query() && ! is_admin() ) {
return;
}
$type = sanitize_text_field( get_query_var( 'surerank_sitemap' ) );
$style = sanitize_text_field( get_query_var( 'surerank_sitemap_type' ) );
if ( ! $type && ! $style ) {
return;
}
if ( $style ) {
Utils::output_stylesheet( $style );
}
$page = absint( get_query_var( 'surerank_sitemap_page' ) ) ? absint( get_query_var( 'surerank_sitemap_page' ) ) : 1;
$threshold = apply_filters( 'surerank_sitemap_threshold', 200 );
do_action( 'surerank_sitemap_before_generation', $type, $page, $threshold );
$this->generate_sitemap( $type, $page, $threshold );
do_action( 'surerank_sitemap_after_generation', $type, $page, $threshold );
}
/**
* Generates the appropriate sitemap based on the requested type.
*
* Dispatch order, each exiting on success:
* 1. Live cache (the authoritative sitemap)
* 2. Stale-while-revalidate from the rebuild backup (sitemap.old/)
* 3. Miss response (503 + valid empty <sitemapindex/>)
*
* Stale-while-revalidate ensures Googlebot and other crawlers see the
* last known-good sitemap with HTTP 200 during a rebuild window rather
* than 503. That materially reduces the chance of a rebuild triggering
* transient sitemap errors in Google Search Console.
*
* @param string $type Sitemap type requested.
* @param int $page Current page number for paginated sitemaps.
* @param int $threshold Threshold for splitting sitemaps.
* @since 1.0.0
* @return void
*/
public function generate_sitemap( string $type, int $page, $threshold ): void {
// Self-healing: if the 6-hourly cron has silently stopped firing
// and the sitemap is stale past the threshold, schedule a one-shot
// recovery rebuild. Called once (not per dispatch branch) to avoid
// the duplicate option-read on cache-miss hits.
$this->maybe_schedule_stale_rebuild();
// 1. Live cache — exits on success, falls through on miss.
if ( '1' === $type ) {
$sitemap_index = Cache::get_file( 'sitemap/sitemap_index.json' );
if ( $sitemap_index ) {
$sitemap = json_decode( $sitemap_index, true );
if ( is_array( $sitemap ) ) {
$this->sitemapindex( $sitemap );
}
}
} elseif ( Sync_Archives::TYPE === $type ) {
$this->generate_archive_sitemap();
} else {
$this->generate_main_sitemap( $type, $page, $threshold );
}
// 2. Stale-while-revalidate: serve the rebuild backup if available.
if ( Cache::has_rebuild_backup( 'sitemap' ) ) {
$this->serve_from_backup( $type, $page );
}
// 3. Miss — cache absent or unreadable. Emit a valid 503.
$this->send_miss_response();
}
/**
* Generates the main sitemap for a specific type, page, and offset.
*
* Returns (without exiting) when the cache is missing, so the caller
* (generate_sitemap) can fall through to stale or miss handling.
* Exits on success.
*
* @param string $type Post type or taxonomy.
* @param int $page Current page number.
* @param int $offset Number of posts to retrieve.
* @since 1.0.0
* @return void
*/
public function generate_main_sitemap( string $type, int $page, int $offset = 1000 ) {
remove_all_actions( 'parse_query' );
if ( ! Cache::file_exists( 'sitemap/sitemap_index.json' ) ) {
return;
}
$prefix_param = sanitize_text_field( get_query_var( 'surerank_prefix' ) );
$sitemap = $this->get_sitemap_from_cache( $type, $page, $prefix_param );
$this->generate_main_sitemap_xml( $sitemap );
}
/**
* Generate the post-type archive sitemap from its cached chunk.
*
* The archive chunk has no type prefix, so it is served directly here
* rather than through the prefix-based get_sitemap_from_cache() path.
* Returns (without exiting) on a cache miss so the caller can fall
* through to stale/miss handling. Exits on success.
*
* @since 1.9.0
* @return void
*/
public function generate_archive_sitemap(): void {
// Single chunk by design: archive URLs number one per public
// has_archive post type, far below the split threshold, so they are
// never paginated (see Sync_Archives).
$chunk = Cache::get_file( 'sitemap/' . Sync_Archives::TYPE . '-chunk-1.json' );
if ( ! $chunk ) {
return;
}
$entries = json_decode( $chunk, true );
if ( is_array( $entries ) ) {
$this->generate_main_sitemap_xml( $entries );
}
}
/**
* Emit a minimal valid <sitemapindex/> XML with HTTP 503 + Retry-After.
*
* Called when the pre-built sitemap cache is missing (e.g., first install
* before the first rebuild completes, or after a failed rebuild). Googlebot
* treats 503 with Retry-After as a transient condition and does not remove
* URLs from its index. Critically, this path MUST exit so WordPress does
* not fall through to its default query dispatch and render the blog page.
*
* Retry-After seconds are filterable via `surerank_sitemap_miss_retry_after`
* (default 300 seconds / 5 minutes).
*
* @since 1.7.2
* @return void
*/
public function send_miss_response(): void {
$retry_after = (int) apply_filters( 'surerank_sitemap_miss_retry_after', 300 );
$this->send_xml_response(
$this->render_miss_response(),
503,
[
'Retry-After' => (string) max( 1, $retry_after ),
'X-Robots-Tag' => 'noindex',
]
);
}
/**
* Build a minimal valid <sitemapindex/> document for the miss response.
*
* Pure: returns a string; does not send headers or exit.
*
* @since 1.7.2
* @return string
*/
public function render_miss_response(): string {
return Utils::sitemap_index( [] );
}
/**
* Outputs the sitemap index as XML.
*
* @param array<string, mixed>|array<int, string> $sitemap Sitemap index data.
* @since 1.0.0
* @return void
*/
public function sitemapindex( array $sitemap ) {
$this->send_xml_response( Utils::sitemap_index( $sitemap ) );
}
/**
* Outputs the main sitemap as XML.
*
* @param array<string, mixed>|array<int, string> $sitemap Sitemap data for main sitemap.
* @since 1.0.0
* @return void
*/
public function generate_main_sitemap_xml( array $sitemap ) {
$this->send_xml_response( Utils::sitemap_main( $sitemap ) );
}
/**
* Get sitemap url
*
* @return string
*/
public function get_sitemap_url() {
return home_url( self::get_slug() );
}
/**
* Public, read-only accessor for a sub-sitemap's combined entries.
*
* Lets the headless JSON sitemap (Headless_Sitemap) serve the exact same
* cached entries the XML sitemap renders, guaranteeing parity (same items,
* same noindex/exclusion filtering applied at build time). Pure read: does
* not schedule rebuilds or emit any output.
*
* @param string $base Sub-sitemap base token, e.g. "post-type-page" — the
* chunk-file prefix shared by "{base}-chunk-{n}.json".
* @param int $page Page number within the sub-sitemap (1-based).
* @return array<int, mixed> Entry rows (each an array of link, updated, …).
* @since 1.9.0
*/
public function get_entries_for_base( string $base, int $page ): array {
return $this->combine_chunks( $base, max( 1, $page ) );
}
/**
* Schedule a one-shot recovery rebuild when the last successful
* rebuild is older than STALE_REBUILD_THRESHOLD.
*
* Gated on `manage_options` (not `is_admin()`, which is true for
* any authenticated admin-ajax call and so not a security
* boundary) or cron context. No-op on first install (let
* Cron::ensure_cron_scheduled bootstrap instead) and when a
* rebuild is already queued.
*
* @since 1.7.2
* @return void
*/
protected function maybe_schedule_stale_rebuild(): void {
if ( ! current_user_can( 'manage_options' ) && ! wp_doing_cron() ) {
return;
}
if ( wp_next_scheduled( Cron::SITEMAP_CRON_EVENT ) ) {
return;
}
$last = (int) get_option( 'surerank_sitemap_last_successful_rebuild', 0 );
if ( $last <= 0 || ( time() - $last ) < self::STALE_REBUILD_THRESHOLD ) {
return;
}
// Respect the probe rate limit (false) so a blackholing loopback
// doesn't freeze admin page rendering for 5 seconds.
Compat::refresh_loopback_probe( false );
wp_schedule_single_event(
time() + MINUTE_IN_SECONDS,
Cron::SITEMAP_CRON_EVENT,
[ 'yes' ]
);
}
/**
* Attempt to serve the request from the rebuild backup (sitemap.old/).
*
* Called when the live cache is unavailable but an atomic rebuild is
* in progress and the previous generation's cache is preserved. Returns
* (without exiting) when no matching data is in the backup either, so
* the caller can continue to the miss handler.
*
* @param string $type Sitemap type requested.
* @param int $page Current page number.
* @since 1.7.2
* @return void
*/
protected function serve_from_backup( string $type, int $page ): void {
if ( '1' === $type ) {
$stale = Cache::read_rebuild_backup( 'sitemap/sitemap_index.json' );
if ( $stale ) {
$sitemap = json_decode( $stale, true );
if ( is_array( $sitemap ) ) {
$this->sitemapindex( $sitemap );
}
}
return;
}
$prefix_param = sanitize_text_field( get_query_var( 'surerank_prefix' ) );
$sitemap = $this->get_sitemap_from_cache( $type, $page, $prefix_param, true );
if ( ! empty( $sitemap ) ) {
$this->generate_main_sitemap_xml( $sitemap );
}
}
/**
* Send an XML response with the given body, status, and optional extra headers.
*
* Centralizes transport (headers + echo + exit) so render methods can stay pure.
*
* @param string $xml XML body to emit.
* @param int $status HTTP status code. Defaults to 200.
* @param array<string,string> $extra_headers Optional additional response headers.
* @since 1.7.2
* @return void
*/
protected function send_xml_response( string $xml, int $status = 200, array $extra_headers = [] ): void {
// Cleanup buffers before any header() so headers_sent() is honest.
$xml = Utils::strip_leading_noise( $xml );
if ( ! headers_sent() ) {
http_response_code( $status );
}
Utils::output_headers();
foreach ( $extra_headers as $name => $value ) {
if ( ! headers_sent() ) {
header( "{$name}: {$value}" );
}
}
echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML is safely generated by Utils::sitemap_index/sitemap_main
exit;
}
/**
* Get sitemap from cache
*
* @param string $type Sitemap type.
* @param int $page Page number.
* @param string $prefix_param Prefix name.
* @param bool $from_backup When true, read chunks from the rebuild
* backup (sitemap.old/) for stale-while-revalidate.
* @return array<string, mixed>|array<int, string>
*/
private function get_sitemap_from_cache( string $type, int $page, string $prefix_param, bool $from_backup = false ) {
return $this->combine_chunks( $prefix_param . '-' . $type, $page, $from_backup );
}
/**
* Combine the cached JSON chunks that make up one sub-sitemap page.
*
* @param string $base Chunk-file prefix, e.g. "post-type-page".
* @param int $page Page number (1-based).
* @param bool $from_backup Read from the rebuild backup (sitemap.old/).
* @return array<int, array<string, mixed>>|array<int, string>
* @since 1.9.0
*/
private function combine_chunks( string $base, int $page, bool $from_backup = false ) {
// Calculate which chunks belong to this page based on threshold and chunk size.
$sitemap_threshold = apply_filters( 'surerank_sitemap_threshold', 200 );
$chunk_size = apply_filters( 'surerank_sitemap_json_chunk_size', 20 );
$chunks_per_sitemap = (int) ceil( $sitemap_threshold / $chunk_size );
$start_chunk = ( $page - 1 ) * $chunks_per_sitemap + 1;
$end_chunk = $page * $chunks_per_sitemap;
$combined_sitemap = [];
for ( $chunk_number = $start_chunk; $chunk_number <= $end_chunk; $chunk_number++ ) {
$cache_path = 'sitemap/' . $base . '-chunk-' . $chunk_number . '.json';
$cache_file_data = $from_backup
? Cache::read_rebuild_backup( $cache_path )
: Cache::get_file( $cache_path );
if ( ! $cache_file_data ) {
continue;
}
$chunk_data = json_decode( $cache_file_data, true );
if ( is_array( $chunk_data ) ) {
$combined_sitemap = array_merge( $combined_sitemap, $chunk_data );
}
}
return $combined_sitemap;
}
}
Nama
Tipe
Ukuran
Diubah
Aksi
🐘 checksum.php
php
6 KB
2026-06-21 06:40
🐘 sitemap.php
php
8.2 KB
2026-06-21 06:40
🐘 stylesheet.php
php
10.5 KB
2026-06-21 06:40
🐘 utils.php
php
11.8 KB
2026-06-21 06:40
🐘 xml-sitemap.php
php
15.4 KB
2026-06-21 06:40