<?php
/**
 * Ownership-proof challenge — .well-known exposure surface.
 *
 * Serves the VPS-issued one-time verification nonce at a fixed, predictable path
 * (ACME / site-verification style) so the VPS can fetch it and confirm this site
 * controls its own domain before (re-)issuing the api_key. The value served is a
 * short-lived, single-use token — NOT the api_key — and the path returns 404
 * whenever no handshake is pending.
 *
 * Uses the proven init + parse_request priority-0 interception pattern (the same
 * mechanism as includes/sitemap.php and includes/indexnow-key-file.php), so it
 * needs no rewrite rule and no flush on activation.
 *
 * @package TamRank
 */

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

class TamRank_WellKnown_Verify
{
    /**
     * Fixed exposure path. Must match what the VPS fetches.
     */
    const PATH = '/.well-known/tamrank-challenge.txt';

    public function __construct()
    {
        // Priority 0 + both hooks so we intercept before WordPress routes the
        // request (mirrors sitemap.php / indexnow-key-file.php).
        add_action('init', array($this, 'handle'), 0);
        add_action('parse_request', array($this, 'handle'), 0);
    }

    /**
     * Serve the pending nonce as plaintext, or 404 when none is live.
     *
     * @return void
     */
    public function handle()
    {
        static $checked = false;
        if ($checked) {
            return;
        }
        $checked = true;

        $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $path = wp_parse_url($request_uri, PHP_URL_PATH);
        if ($path !== self::PATH) {
            return;
        }

        // Never let a page cache / CDN serve this. nocache_headers() covers the
        // HTTP layer; DONOTCACHEPAGE asks page-cache drop-ins to skip it. (The VPS
        // also appends a random cache-buster to its fetch URL as the primary
        // defence against caches that run before WordPress boots.)
        if (!defined('DONOTCACHEPAGE')) {
            define('DONOTCACHEPAGE', true); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress-ecosystem standard constant read by caching plugins; must not be prefixed.
        }

        // Drain any output buffer a pre-init BOM / PHP notice / stray early-loading
        // plugin may have opened, so the body is EXACTLY the nonce with nothing
        // prepended — a leading byte survives the VPS-side trim() and would break
        // hash_equals. Mirrors includes/indexnow-key-file.php and includes/sitemap.php.
        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        if (!headers_sent()) {
            nocache_headers();
            // Strip headers WordPress may have queued so the VPS sees a clean body.
            header_remove('X-Pingback');
            header_remove('Link');
        }

        $nonce = function_exists('tamrank_get_pending_verify_nonce') ? tamrank_get_pending_verify_nonce() : '';

        if ($nonce === '') {
            status_header(404);
            header('Content-Type: text/plain; charset=UTF-8');
            exit;
        }

        status_header(200);
        header('Content-Type: text/plain; charset=UTF-8');
        header('X-Robots-Tag: noindex');
        header('Content-Length: ' . strlen($nonce));
        // Raw, single value (sanitized at storage time). No surrounding markup so
        // the VPS can match exactly after a trim. This is a public one-time token.
        echo $nonce; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- text/plain body, value is a sanitized opaque token; HTML-escaping would corrupt the match
        exit;
    }
}

// Initialize at file-load time (this file is required early, before 'init') so
// both the init@0 and parse_request@0 hooks register before those actions
// dispatch — same proven pattern as TamRank_IndexNow_Key_File.
new TamRank_WellKnown_Verify();
