PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.3
1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
← All changes | includes/modules/Heartbeat/HeartbeatModule.php +59 -21 1.0.11.3.3 View file →
@@ -15,8 +15,10 @@
15 15 declare(strict_types=1);
16 16
17 17 namespace XSpeed\Modules\Heartbeat;
18 18
19 +defined( 'ABSPATH' ) || exit;
20 +
19 21 use XSpeed\Module;
20 22
21 23 final class HeartbeatModule extends Module {
22 24
@@ -32,11 +34,11 @@
32 34 private const CONTEXTS = array( 'dashboard', 'editor', 'frontend' );
33 35
34 36 public function ui_metadata(): array {
35 37 return array(
36 - 'label' => 'Heartbeat',
38 + 'label' => __( 'Heartbeat', 'xspeed' ),
37 39 'icon' => 'Activity',
38 - 'description' => 'Control the WordPress Heartbeat API per context.',
40 + 'description' => __( 'Control the WordPress Heartbeat API per context.', 'xspeed' ),
39 41 );
40 42 }
41 43
42 44 /**
@@ -58,10 +60,10 @@
58 60 'type' => 'enum',
59 61 'default' => self::BEHAVIOR_THROTTLE,
60 62 'options' => $behavior_options,
61 63 'option_labels' => $behavior_option_labels,
62 - 'label' => 'Dashboard',
63 - 'description' => 'Heartbeat behavior on /wp-admin/ screens (autosave, notifications).',
64 + 'label' => __( 'Dashboard', 'xspeed' ),
65 + 'description' => __( 'Heartbeat behavior on /wp-admin/ screens (autosave, notifications).', 'xspeed' ),
64 66 ),
65 67 'behavior_editor' => array(
66 68 'type' => 'enum',
67 69 'default' => self::BEHAVIOR_THROTTLE,
@@ -66,10 +68,10 @@
66 68 'type' => 'enum',
67 69 'default' => self::BEHAVIOR_THROTTLE,
68 70 'options' => $behavior_options,
69 71 'option_labels' => $behavior_option_labels,
70 - 'label' => 'Editor',
71 - 'description' => 'Heartbeat in the post / block editor. Disable only if you do not need autosave or co-edit locks.',
72 + 'label' => __( 'Editor', 'xspeed' ),
73 + 'description' => __( 'Heartbeat in the post / block editor. Disable only if you do not need autosave or co-edit locks.', 'xspeed' ),
72 74 ),
73 75 'behavior_frontend' => array(
74 76 'type' => 'enum',
75 77 'default' => self::BEHAVIOR_DISABLE,
@@ -74,10 +76,10 @@
74 76 'type' => 'enum',
75 77 'default' => self::BEHAVIOR_DISABLE,
76 78 'options' => $behavior_options,
77 79 'option_labels' => $behavior_option_labels,
78 - 'label' => 'Frontend',
79 - 'description' => 'Heartbeat on the public site. Recommended off — most themes never need it and it costs admin-ajax requests per visitor.',
80 + 'label' => __( 'Frontend', 'xspeed' ),
81 + 'description' => __( 'Controls the Heartbeat API on the public site. Only takes effect when a plugin or theme actually loads heartbeat on the frontend (e.g. WooCommerce cart fragments, membership/notification plugins) — a default WordPress site loads none there, so this has no visible effect on such sites. Recommended: Disable, to stop the admin-ajax polling those plugins add.', 'xspeed' ),
80 82 ),
81 83 'frequency' => array(
82 84 'type' => 'int',
83 85 'default' => 60,
@@ -82,10 +84,10 @@
82 84 'type' => 'int',
83 85 'default' => 60,
84 86 'min' => 15,
85 87 'max' => 300,
86 - 'label' => 'Throttle Frequency',
87 - 'description' => 'Interval in seconds for contexts set to Throttle. 60 is a sane default; lower = faster sync but more requests.',
88 + 'label' => __( 'Throttle Frequency', 'xspeed' ),
89 + 'description' => __( 'Interval in seconds for contexts set to Throttle. 60 is a sane default; lower = faster sync but more requests.', 'xspeed' ),
88 90 ),
89 91 );
90 92 }
91 93
@@ -97,8 +99,9 @@
97 99 array(
98 100 'name' => 'xspeed heartbeat',
99 101 'callback' => array( $this, 'cli_handler' ),
100 102 'shortdesc' => 'Inspect or modify xSpeed heartbeat settings.',
103 + 'ai_hint' => 'Inspect or change WordPress Heartbeat (admin-ajax polling) frequency. Use for high admin-ajax.php CPU load, or hosting warnings about too many background requests.',
101 104 'synopsis' => array(
102 105 array(
103 106 'type' => 'positional',
104 107 'name' => 'action',
@@ -104,19 +107,25 @@
104 107 'name' => 'action',
105 108 'options' => array( 'show', 'set' ),
106 109 'optional' => false,
107 110 ),
111 + // Use `--for` (not `--context`): WP-CLI silently swallows a
112 + // `--context` assoc arg before it reaches the command, so
113 + // `set --context= --behavior=` always lost the context and
114 + // errored "Nothing to set". `--for` reads naturally
115 + // (set --for=editor --behavior=disable) and is collision-
116 + // free. No `options` constraint either — an options list
117 + // also dropped args from $assoc; we validate in
118 + // cli_handler() instead. (FBS-82153 Bug 2.)
108 119 array(
109 120 'type' => 'assoc',
110 - 'name' => 'context',
121 + 'name' => 'for',
111 122 'optional' => true,
112 - 'options' => self::CONTEXTS,
113 123 ),
114 124 array(
115 125 'type' => 'assoc',
116 126 'name' => 'behavior',
117 127 'optional' => true,
118 - 'options' => array( self::BEHAVIOR_KEEP, self::BEHAVIOR_THROTTLE, self::BEHAVIOR_DISABLE ),
119 128 ),
120 129 array(
121 130 'type' => 'assoc',
122 131 'name' => 'frequency',
@@ -127,12 +136,23 @@
127 136 );
128 137 }
129 138
130 139 public function boot(): void {
131 - // `init` is early enough to register our filters before Heartbeat
132 - // itself enqueues. Stay low priority to defer to plugins that ran
133 - // at plugins_loaded.
134 - add_action( 'init', array( $this, 'apply_settings' ), 5 );
140 + // Context resolution depends on WHEN we can classify the request:
141 + // - Frontend: known at `init` (is_admin() is reliable there), and we
142 + // must act before wp_enqueue_scripts (priority 10) registers the
143 + // heartbeat script.
144 + // - Admin: dashboard-vs-editor needs get_current_screen(), which is
145 + // NULL at `init` and only populated from the `current_screen`
146 + // action onward. Resolving context at init always returned
147 + // "dashboard" on editor screens, so behavior_editor was dead.
148 + // (FBS-82153 Bug 1.) Hook admin on `current_screen` instead, which
149 + // fires before admin_enqueue_scripts so we can still dequeue.
150 + if ( is_admin() ) {
151 + add_action( 'current_screen', array( $this, 'apply_settings' ) );
152 + } else {
153 + add_action( 'init', array( $this, 'apply_settings' ), 5 );
154 + }
135 155 }
136 156
137 157 /**
138 158 * Hook handler — translates our settings into Heartbeat behavior. Runs
@@ -203,17 +223,35 @@
203 223 return;
204 224 }
205 225
206 226 if ( 'set' === $action ) {
207 - $patch = array();
208 - if ( isset( $assoc['context'], $assoc['behavior'] ) ) {
209 - $patch[ 'behavior_' . $assoc['context'] ] = $assoc['behavior'];
227 + $patch = array();
228 + $behaviors = array( self::BEHAVIOR_KEEP, self::BEHAVIOR_THROTTLE, self::BEHAVIOR_DISABLE );
229 +
230 + $has_context = isset( $assoc['for'] ) && '' !== $assoc['for'];
231 + $has_behavior = isset( $assoc['behavior'] ) && '' !== $assoc['behavior'];
232 +
233 + // --for and --behavior are a pair: both or neither.
234 + if ( $has_context xor $has_behavior ) {
235 + \WP_CLI::error( 'Pass --for and --behavior together.' );
236 + return;
210 237 }
238 + if ( $has_context && $has_behavior ) {
239 + if ( ! in_array( $assoc['for'], self::CONTEXTS, true ) ) {
240 + \WP_CLI::error( 'Invalid --for. Use one of: ' . implode( ', ', self::CONTEXTS ) . '.' );
241 + return;
242 + }
243 + if ( ! in_array( $assoc['behavior'], $behaviors, true ) ) {
244 + \WP_CLI::error( 'Invalid --behavior. Use one of: ' . implode( ', ', $behaviors ) . '.' );
245 + return;
246 + }
247 + $patch[ 'behavior_' . $assoc['for'] ] = $assoc['behavior'];
248 + }
211 249 if ( isset( $assoc['frequency'] ) ) {
212 250 $patch['frequency'] = (int) $assoc['frequency'];
213 251 }
214 252 if ( empty( $patch ) ) {
215 - \WP_CLI::error( 'Nothing to set. Provide --context= --behavior= and/or --frequency=' );
253 + \WP_CLI::error( 'Nothing to set. Provide --for= --behavior= and/or --frequency=' );
216 254 return;
217 255 }
218 256 $this->update_settings( $patch );
219 257 \WP_CLI::success( 'Updated heartbeat settings.' );