# xspeed/1.0.8/includes/class-rest-api.php

xSpeed Cache: AI-Powered Performance Hub with MCP, Caching &amp; CDN, version 1.0.8. 307 lines.

- Page: https://pluginprobe.com/plugins/xspeed/1.0.8/code/includes/class-rest-api.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.8/raw/includes/class-rest-api.php
- Modified: 2026-07-14T10:10:50+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/xspeed/1.0.8/code/includes/class-rest-api.php#L10-L20`.

```php
<?php
/**
 * REST API endpoints.
 *
 * @package XSpeed
 */

namespace XSpeed;

defined( 'ABSPATH' ) || exit;

class Rest_Api {

	const NAMESPACE_V1 = 'xspeed/v1';

	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register' ) );
	}

	public function register() {
		register_rest_route(
			self::NAMESPACE_V1,
			'/status',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_status' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/settings',
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'get_settings' ),
					'permission_callback' => array( $this, 'permissions' ),
				),
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'update_settings' ),
					'permission_callback' => array( $this, 'permissions' ),
				),
			)
		);

		// Resolved white-label branding. The dashboard refetches this after
		// a white-label save so the chrome (sidebar name/logo, footer)
		// updates live without a reload. (FBS white-label-onboarding)
		register_rest_route(
			self::NAMESPACE_V1,
			'/branding',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_branding' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/cache/purge',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'purge' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/cache/toggle',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'toggle_cache' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/cache/benchmark',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'benchmark' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/audit/pro',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'pro_audit' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/modules',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_modules' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		// On-demand desktop-vs-mobile HTML equality probe (FBS-83145). POST so
		// it's never triggered by a prefetch/GET; runs only from the dashboard
		// "Check now" button behind manage_options.
		register_rest_route(
			self::NAMESPACE_V1,
			'/cache/mobile-probe',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'mobile_probe' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);

		// Dismiss the Separate-Mobile-Cache review prompt (FBS-83145).
		register_rest_route(
			self::NAMESPACE_V1,
			'/cache/mobile-review-dismiss',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'mobile_review_dismiss' ),
				'permission_callback' => array( $this, 'permissions' ),
			)
		);
	}

	/**
	 * Run the on-demand mobile-equality probe and return the fresh /status
	 * mobile_separate block so the dashboard can update the callout in place.
	 */
	public function mobile_probe( $request ) {
		unset( $request );
		return rest_ensure_response( Cache::probe_mobile_equality() );
	}

	/**
	 * Clear the migration review flag so the callout stops nagging.
	 */
	public function mobile_review_dismiss( $request ) {
		unset( $request );
		Cache::clear_mobile_separate_review();
		return rest_ensure_response( array( 'dismissed' => true ) );
	}

	/**
	 * The registered-module descriptors — same payload baked into the
	 * admin bootstrap (Admin::modules_payload), re-evaluated live. The
	 * dashboard re-fetches this after a license activate/deactivate so a
	 * Pro module's custom_panel flips between its real surface and
	 * LicenseLockedPanel (decided server-side via the
	 * xspeed_module_descriptor filter) WITHOUT a full page reload.
	 */
	public function get_modules() {
		return rest_ensure_response( Admin::modules_payload() );
	}

	/**
	 * Run the Pro audit — scans current settings + cache stats,
	 * returns a personalized list of Pro features that would help
	 * THIS site. See Pro_Audit::run() for the rule set.
	 */
	public function pro_audit( $request ) {
		unset( $request );
		return rest_ensure_response( array( 'suggestions' => Pro_Audit::run() ) );
	}

	/**
	 * Cache before/after benchmark — fetches home_url() twice (with +
	 * without the bypass header) and returns side-by-side timings for
	 * the dashboard widget.
	 */
	public function benchmark( $request ) {
		unset( $request );
		return rest_ensure_response( Cache_Benchmark::run() );
	}

	public function permissions() {
		return current_user_can( 'manage_options' );
	}

	public function get_status() {
		$opts  = Settings::get();
		$stats = Cache::get_stats();

		// rewrite_probe + nginx_server_block mirror the admin bootstrap
		// payload (Admin::bootstrap_data). The dashboard re-fetches /status
		// after every module save to refresh the consolidated nginx
		// server-block snippet without a full page reload — if these were
		// omitted here, the snippet would only ever update on reload (the
		// QA bug: "Server config snippet requires full page reload to
		// reflect toggle changes"). Keep this in sync with Admin.
		$server_type     = Server::type();
		// LiteSpeed deliberately serves hits via the PHP drop-in (its
		// .htaccess can't add the HIT header or log a static hit), so the
		// static-rewrite probe is N/A there — surfacing it would pop the
		// "PHP fallback" nag for a setup that's working as designed. Only
		// nginx + Apache use a server-level rewrite worth probing.
		$rewrite_capable = ( $server_type === Server::NGINX || $server_type === Server::APACHE );
		$rewrite_probe   = null;
		if ( $opts['cache_enabled'] && $rewrite_capable ) {
			$probe         = Cache::probe_static_rewrite();
			$rewrite_probe = array(
				'active'       => (bool) ( $probe['active'] ?? false ),
				'server_type'  => $server_type,
				'snippet'      => Cache::nginx_snippet(),
				'topology'     => Server::rewrite_topology(),
				'behind_proxy' => Server::is_behind_proxy(),
			);
		}

		return rest_ensure_response(
			array(
				'enabled'            => (bool) $opts['cache_enabled'],
				'stats'              => $stats,
				'server'             => array(
					'type'           => $server_type,
					'gzip_mode'      => Server::gzip_mode(),
					'gzip_active'    => Gzip::probe_active(),
					'nginx_snippet'  => Gzip::nginx_snippet(),
				),
				'rewrite_probe'      => $rewrite_probe,
				'nginx_server_block' => Cache::full_nginx_server_block(),
				// Separate Mobile Cache visibility (FBS-83145). `blocking` is
				// true when mobile_separate is what's keeping the device-blind
				// static fast path from installing on a rewrite-capable server;
				// `needs_review` is true when a migration turned it on for us and
				// the user hasn't confirmed they actually need it. The dashboard
				// renders a callout (+ "Check now" equality probe) from these.
				'mobile_separate'    => array(
					'enabled'      => ! empty( Settings::get()['cache_enabled'] ) ? (bool) ( Settings_Manager::get( 'cache' )['mobile_separate'] ?? false ) : false,
					'blocking'     => $rewrite_capable && 'mobile_separate' === Cache::static_rewrite_block_reason(),
					'needs_review' => Cache::mobile_separate_needs_review(),
				),
			)
		);
	}

	public function get_settings() {
		return rest_ensure_response( Settings::get() );
	}

	public function update_settings( \WP_REST_Request $request ) {
		$params  = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = $request->get_params();
		}
		// `cache_enabled` is the trigger for drop-in install / wp-config.php
		// edit and must only flow through the dedicated /cache/toggle
		// endpoint. Strip it here so generic settings updates can never
		// implicitly write a drop-in or modify wp-config.php.
		unset( $params['cache_enabled'] );
		$updated = Settings::update( $params );
		return rest_ensure_response( $updated );
	}

	public function purge() {
		Cache::purge_all();
		return rest_ensure_response( array( 'stats' => Cache::get_stats() ) );
	}

	/**
	 * Resolved branding ({name, footer_credit, hide_help_links, logo_svg}).
	 * Runs the `xspeed_branding` filter so Pro's white-label override is
	 * reflected. Consumed by the dashboard's post-save branding refresh.
	 */
	public function get_branding() {
		return rest_ensure_response( Admin::branding() );
	}

	public function toggle_cache( \WP_REST_Request $request ) {
		$params  = $request->get_json_params();
		$enabled = isset( $params['enabled'] ) ? (bool) $params['enabled'] : false;

		// User-explicit drop-in install / wp-config.php edit happens here.
		// permission_callback above already enforced current_user_can(
		// 'manage_options' ); the REST nonce is verified by core via the
		// X-WP-Nonce header.
		$state   = Cache::toggle( $enabled );
		$updated = Settings::update( array( 'cache_enabled' => $state['enabled'] ) );

		// Recompute the unified nginx block AFTER cache_enabled is persisted.
		// Cache::toggle() computes it inline, but cache_enabled isn't written
		// until the Settings::update() above — so the block inside $state
		// reflects the PRE-toggle state (CacheModule::nginx_directives() gates
		// on cache_enabled). Regenerate here so the dashboard's optimistic
		// update shows the snippet for the state the user just selected.
		$state['nginx_server_block'] = Cache::full_nginx_server_block();

		return rest_ensure_response(
			array(
				'enabled'       => $updated['cache_enabled'],
				'stats'         => Cache::get_stats(),
				'install_state' => $state,
			)
		);
	}
}

```
