# xspeed/1.0.2/includes/modules/Heartbeat/HeartbeatModule.php

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

- Page: https://pluginprobe.com/plugins/xspeed/1.0.2/code/includes/modules/Heartbeat/HeartbeatModule.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.2/raw/includes/modules/Heartbeat/HeartbeatModule.php
- Modified: 2026-06-01T17:33:22+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.2/code/includes/modules/Heartbeat/HeartbeatModule.php#L10-L20`.

```php
<?php
/**
 * Heartbeat Control module.
 *
 * Controls the WordPress Heartbeat API per context (dashboard / editor /
 * frontend) and tunes its interval. Disabling heartbeat on the frontend
 * is one of the cheapest wins for sites that don't need polling there.
 *
 * Tier: Free (1:1 with LiteSpeed Cache).
 * Roadmap: §4.3.
 *
 * @package XSpeed
 */

declare(strict_types=1);

namespace XSpeed\Modules\Heartbeat;

use XSpeed\Module;

final class HeartbeatModule extends Module {

	public const SLUG    = 'heartbeat';
	public const TIER    = self::TIER_FREE;
	public const VERSION = '1.0.0';

	// Per-context behavior values stored under `behavior_<context>`.
	public const BEHAVIOR_KEEP     = 'keep';     // leave alone
	public const BEHAVIOR_THROTTLE = 'throttle'; // apply our `frequency`
	public const BEHAVIOR_DISABLE  = 'disable';  // turn off entirely

	private const CONTEXTS = array( 'dashboard', 'editor', 'frontend' );

	public function ui_metadata(): array {
		return array(
			'label'       => 'Heartbeat',
			'icon'        => 'Activity',
			'description' => 'Control the WordPress Heartbeat API per context.',
		);
	}

	/**
	 * Default profile mirrors the IMPLEMENTATION recommendation:
	 *   - frontend: off by default (most polling-heavy, rarely needed)
	 *   - editor: throttled to 60s (was 15s)
	 *   - dashboard: throttled to 60s (was 60s — left as is)
	 */
	public function settings_schema(): array {
		$behavior_options       = array( self::BEHAVIOR_KEEP, self::BEHAVIOR_THROTTLE, self::BEHAVIOR_DISABLE );
		$behavior_option_labels = array(
			self::BEHAVIOR_KEEP     => 'Keep',
			self::BEHAVIOR_THROTTLE => 'Throttle',
			self::BEHAVIOR_DISABLE  => 'Disable',
		);

		return array(
			'behavior_dashboard' => array(
				'type'          => 'enum',
				'default'       => self::BEHAVIOR_THROTTLE,
				'options'       => $behavior_options,
				'option_labels' => $behavior_option_labels,
				'label'         => 'Dashboard',
				'description'   => 'Heartbeat behavior on /wp-admin/ screens (autosave, notifications).',
			),
			'behavior_editor'    => array(
				'type'          => 'enum',
				'default'       => self::BEHAVIOR_THROTTLE,
				'options'       => $behavior_options,
				'option_labels' => $behavior_option_labels,
				'label'         => 'Editor',
				'description'   => 'Heartbeat in the post / block editor. Disable only if you do not need autosave or co-edit locks.',
			),
			'behavior_frontend'  => array(
				'type'          => 'enum',
				'default'       => self::BEHAVIOR_DISABLE,
				'options'       => $behavior_options,
				'option_labels' => $behavior_option_labels,
				'label'         => 'Frontend',
				'description'   => 'Heartbeat on the public site. Recommended off — most themes never need it and it costs admin-ajax requests per visitor.',
			),
			'frequency'          => array(
				'type'        => 'int',
				'default'     => 60,
				'min'         => 15,
				'max'         => 300,
				'label'       => 'Throttle Frequency',
				'description' => 'Interval in seconds for contexts set to Throttle. 60 is a sane default; lower = faster sync but more requests.',
			),
		);
	}

	// rest_routes — using the Module base-class default (GET + POST under
	// /xspeed/v1/heartbeat/ wired to rest_get_settings + rest_update_settings).

	public function cli_commands(): array {
		return array(
			array(
				'name'      => 'xspeed heartbeat',
				'callback'  => array( $this, 'cli_handler' ),
				'shortdesc' => 'Inspect or modify xSpeed heartbeat settings.',
				'synopsis'  => array(
					array(
						'type'     => 'positional',
						'name'     => 'action',
						'options'  => array( 'show', 'set' ),
						'optional' => false,
					),
					array(
						'type'     => 'assoc',
						'name'     => 'context',
						'optional' => true,
						'options'  => self::CONTEXTS,
					),
					array(
						'type'     => 'assoc',
						'name'     => 'behavior',
						'optional' => true,
						'options'  => array( self::BEHAVIOR_KEEP, self::BEHAVIOR_THROTTLE, self::BEHAVIOR_DISABLE ),
					),
					array(
						'type'     => 'assoc',
						'name'     => 'frequency',
						'optional' => true,
					),
				),
			),
		);
	}

	public function boot(): void {
		// `init` is early enough to register our filters before Heartbeat
		// itself enqueues. Stay low priority to defer to plugins that ran
		// at plugins_loaded.
		add_action( 'init', array( $this, 'apply_settings' ), 5 );
	}

	/**
	 * Hook handler — translates our settings into Heartbeat behavior. Runs
	 * on every request. The branches below are pure read + dispatch — no
	 * I/O so the cost is negligible even on cached requests.
	 */
	public function apply_settings(): void {
		$settings = $this->get_settings();
		$context  = $this->detect_context();
		$behavior = $settings[ 'behavior_' . $context ] ?? self::BEHAVIOR_KEEP;

		if ( self::BEHAVIOR_DISABLE === $behavior ) {
			// Dequeue the heartbeat script entirely. wp_deregister_script
			// runs on `init` priority 5 → before wp_default_scripts
			// (priority 10) re-registers, so we hook the actual enqueue
			// stage instead.
			add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_heartbeat' ), 1 );
			add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_heartbeat' ), 1 );
		} elseif ( self::BEHAVIOR_THROTTLE === $behavior ) {
			$interval = max( 15, min( 300, (int) ( $settings['frequency'] ?? 60 ) ) );
			add_filter(
				'heartbeat_settings',
				static function ( $hb_settings ) use ( $interval ) {
					$hb_settings['interval'] = $interval;
					return $hb_settings;
				}
			);
		}
		// BEHAVIOR_KEEP → no filter, WP defaults apply.
	}

	/**
	 * Dequeue + deregister the heartbeat script. Safe to call multiple
	 * times — both wp functions are idempotent.
	 */
	public function dequeue_heartbeat(): void {
		wp_dequeue_script( 'heartbeat' );
		wp_deregister_script( 'heartbeat' );
	}

	/**
	 * Classify the current request into dashboard / editor / frontend.
	 * Editor here means the block / classic post editor screens — they're
	 * the heaviest heartbeat consumer and worth their own bucket.
	 */
	private function detect_context(): string {
		if ( ! is_admin() ) {
			return 'frontend';
		}
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( $screen && in_array( $screen->base, array( 'post', 'post-new' ), true ) ) {
			return 'editor';
		}
		return 'dashboard';
	}

	// REST handlers — provided by Module base class
	// (rest_get_settings / rest_update_settings).

	public function cli_handler( array $args, array $assoc ): void {
		$action = $args[0] ?? 'show';

		if ( 'show' === $action ) {
			$opts = $this->get_settings();
			foreach ( $opts as $key => $value ) {
				\WP_CLI::log( sprintf( '%-22s %s', $key, is_scalar( $value ) ? (string) $value : wp_json_encode( $value ) ) );
			}
			return;
		}

		if ( 'set' === $action ) {
			$patch = array();
			if ( isset( $assoc['context'], $assoc['behavior'] ) ) {
				$patch[ 'behavior_' . $assoc['context'] ] = $assoc['behavior'];
			}
			if ( isset( $assoc['frequency'] ) ) {
				$patch['frequency'] = (int) $assoc['frequency'];
			}
			if ( empty( $patch ) ) {
				\WP_CLI::error( 'Nothing to set. Provide --context= --behavior= and/or --frequency=' );
				return;
			}
			$this->update_settings( $patch );
			\WP_CLI::success( 'Updated heartbeat settings.' );
			return;
		}

		\WP_CLI::error( "Unknown action: $action" );
	}
}

```
