PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.2
1.3.4 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 All 30 releases
← All changes | includes/modules/Cloudflare/CloudflareModule.php +124 -27 1.2.41.3.2 View file →
@@ -36,11 +36,11 @@
36 36 private const HEALTH_OPTION = 'xspeed_cloudflare_health';
37 37
38 38 public function ui_metadata(): array {
39 39 return array(
40 - 'label' => 'Cloudflare',
40 + 'label' => __( 'Cloudflare', 'xspeed' ),
41 41 'icon' => 'Cloud',
42 - 'description' => 'Connect a Cloudflare zone for automatic edge purging when xSpeed clears its cache, plus a dev-mode toggle.',
42 + 'description' => __( 'Connect a Cloudflare zone for automatic edge purging when xSpeed clears its cache, plus a dev-mode toggle.', 'xspeed' ),
43 43 'custom_panel' => 'CloudflarePanel',
44 44 );
45 45 }
46 46
@@ -61,10 +61,10 @@
61 61 return array(
62 62 'enabled' => array(
63 63 'type' => 'bool',
64 64 'default' => false,
65 - 'label' => 'Enable Cloudflare integration',
66 - 'description' => 'Use the credentials below to verify your zone and run purges.',
65 + 'label' => __( 'Enable Cloudflare integration', 'xspeed' ),
66 + 'description' => __( 'Use the credentials below to verify your zone and run purges.', 'xspeed' ),
67 67 ),
68 68 'auth_method' => array(
69 69 'type' => 'enum',
70 70 'default' => 'token',
@@ -72,17 +72,17 @@
72 72 'option_labels' => array(
73 73 'token' => 'API Token',
74 74 'key' => 'Global API Key',
75 75 ),
76 - 'label' => 'Authentication',
77 - 'description' => 'API Tokens (scoped, recommended) or the legacy Global API Key with your account email.',
76 + 'label' => __( 'Authentication', 'xspeed' ),
77 + 'description' => __( 'API Tokens (scoped, recommended) or the legacy Global API Key with your account email.', 'xspeed' ),
78 78 'dependsOn' => array( 'field' => 'enabled' ),
79 79 ),
80 80 'api_token' => array(
81 81 'type' => 'secret',
82 82 'default' => '',
83 - 'label' => 'API Token',
84 - 'description' => 'Create a token at dash.cloudflare.com → My Profile → API Tokens. Needs "Zone → Cache Purge" + "Zone Settings" permissions.',
83 + 'label' => __( 'API Token', 'xspeed' ),
84 + 'description' => __( 'Create a token at dash.cloudflare.com → My Profile → API Tokens. Needs "Zone → Cache Purge" + "Zone Settings" permissions.', 'xspeed' ),
85 85 // Only the token auth branch (and only while CF is enabled, via
86 86 // the transitive gate on auth_method → enabled).
87 87 'dependsOn' => array( 'field' => 'auth_method', 'value' => 'token' ),
88 88 ),
@@ -88,31 +88,31 @@
88 88 ),
89 89 'email' => array(
90 90 'type' => 'string',
91 91 'default' => '',
92 - 'label' => 'Account Email',
93 - 'description' => 'Only used when Authentication is set to Global API Key.',
92 + 'label' => __( 'Account Email', 'xspeed' ),
93 + 'description' => __( 'Only used when Authentication is set to Global API Key.', 'xspeed' ),
94 94 'dependsOn' => array( 'field' => 'auth_method', 'value' => 'key' ),
95 95 ),
96 96 'api_key' => array(
97 97 'type' => 'secret',
98 98 'default' => '',
99 - 'label' => 'Global API Key',
100 - 'description' => 'Found at dash.cloudflare.com → My Profile → API Tokens → Global API Key.',
99 + 'label' => __( 'Global API Key', 'xspeed' ),
100 + 'description' => __( 'Found at dash.cloudflare.com → My Profile → API Tokens → Global API Key.', 'xspeed' ),
101 101 'dependsOn' => array( 'field' => 'auth_method', 'value' => 'key' ),
102 102 ),
103 103 'zone_id' => array(
104 104 'type' => 'string',
105 105 'default' => '',
106 - 'label' => 'Zone ID',
107 - 'description' => 'The 32-character hex Zone ID from your domain overview page.',
106 + 'label' => __( 'Zone ID', 'xspeed' ),
107 + 'description' => __( 'The 32-character hex Zone ID from your domain overview page.', 'xspeed' ),
108 108 'dependsOn' => array( 'field' => 'enabled' ),
109 109 ),
110 110 'auto_purge_on_update' => array(
111 111 'type' => 'bool',
112 112 'default' => true,
113 - 'label' => 'Auto-purge Cloudflare on xSpeed purge',
114 - 'description' => 'When xSpeed clears its own cache (post save, settings change, manual purge), trigger a Cloudflare purge too.',
113 + 'label' => __( 'Auto-purge Cloudflare on xSpeed purge', 'xspeed' ),
114 + 'description' => __( 'When xSpeed clears its own cache (post save, settings change, manual purge), trigger a Cloudflare purge too.', 'xspeed' ),
115 115 'dependsOn' => array( 'field' => 'enabled' ),
116 116 ),
117 117 );
118 118 }
@@ -171,8 +171,27 @@
171 171 );
172 172 }
173 173
174 174 public function boot(): void {
175 + /*
176 + * Deferred to `init`. This module reads its own settings to decide
177 + * what to hook, and reading settings builds settings_schema(), whose
178 + * labels are declared through __(). boot() runs on `plugins_loaded`,
179 + * before `after_setup_theme` — the point WordPress 6.7+ treats as the
180 + * earliest safe moment to translate — so doing that here fires
181 + * _load_textdomain_just_in_time on every request AND resolves the
182 + * labels against a domain that is not loaded yet.
183 + *
184 + * Everything below hooks actions that fire after `init`, so running
185 + * one hook later is equivalent.
186 + */
187 + add_action( 'init', array( $this, 'boot_on_init' ) );
188 + }
189 +
190 + /**
191 + * The real boot body — see boot() for why it runs on `init`.
192 + */
193 + public function boot_on_init(): void {
175 194 $opts = $this->get_settings();
176 195 if ( empty( $opts['enabled'] ) ) {
177 196 return;
178 197 }
@@ -184,14 +203,64 @@
184 203 }
185 204 }
186 205
187 206 public function on_xspeed_purge(): void {
207 + /*
208 + * `wp xspeed purge` purges the edge itself, as its own reported line
209 + * item, and the page step it runs first fires this action. Without
210 + * this guard the zone is purged twice per command, and the SECOND
211 + * call's outcome — the one nobody reported — is what lands in the
212 + * health record the panel reads.
213 + *
214 + * Gated on covers(), not merely is_running(): on `--type=page` the
215 + * action still fires but no edge target runs, so standing down there
216 + * would leave the zone stale with nothing in the report to say so.
217 + * That run is exactly the one this listener exists for.
218 + */
219 + if ( class_exists( '\\XSpeed\\Purge_Runner' ) && \XSpeed\Purge_Runner::covers( 'cloudflare' ) ) {
220 + return;
221 + }
222 + if ( true !== $this->can_purge_edge() ) {
223 + return;
224 + }
225 + $this->purge_edge( 'auto-purge' );
226 + }
227 +
228 + /**
229 + * Whether this site can purge its Cloudflare zone right now.
230 + *
231 + * @return true|string True, or the reason it cannot — for the skip line
232 + * in `wp xspeed purge`, which has to explain itself
233 + * rather than silently do nothing.
234 + */
235 + public function can_purge_edge() {
188 236 $opts = $this->get_settings();
189 - if ( empty( $opts['enabled'] ) || empty( $opts['zone_id'] ) ) {
190 - return;
237 + if ( empty( $opts['enabled'] ) ) {
238 + return __( 'the Cloudflare integration is switched off', 'xspeed' );
191 239 }
192 - $result = Cloudflare::purge_all( $opts );
240 + if ( ! $this->has_credentials( $opts ) ) {
241 + return __( 'no zone ID or API credentials are configured', 'xspeed' );
242 + }
243 +
244 + return true;
245 + }
246 +
247 + /**
248 + * Purge the whole zone and record the outcome.
249 + *
250 + * The one edge-purge path: the auto-purge listener, `wp xspeed cf purge`
251 + * and `wp xspeed purge` all land here, so the health record and the
252 + * activity log say the same thing whichever one ran.
253 + *
254 + * @param string $cause Who asked.
255 + * @return array{ok:bool,reason:string,status:int,body:mixed} The engine
256 + * result plus a normalised `reason`, so the `cf` command can
257 + * still print the raw body it always has.
258 + */
259 + public function purge_edge( string $cause = 'manual' ): array {
260 + $result = Cloudflare::purge_all( $this->get_settings() );
193 261 $ok = ! empty( $result['ok'] );
262 + $reason = $ok ? '' : $this->message_of( $result );
194 263
195 264 // A GET /zones verify can pass with a token that still lacks the
196 265 // "Zone → Cache Purge" permission, so the real purge is the only
197 266 // authoritative signal for purge capability. Record it either way so
@@ -196,17 +265,41 @@
196 265 // "Zone → Cache Purge" permission, so the real purge is the only
197 266 // authoritative signal for purge capability. Record it either way so
198 267 // a silent auth failure becomes a visible, unresolved warning on the
199 268 // module rather than an entry buried in the activity log. (#119)
200 - $this->record_health( $ok, 'purge', $ok ? '' : $this->message_of( $result ) );
269 + $this->record_health( $ok, 'purge', $reason );
201 270
202 - if ( ! $ok && class_exists( '\\XSpeed\\Activity_Log' ) ) {
203 - \XSpeed\Activity_Log::record(
204 - 'cloudflare_purge_failed',
205 - 'Cloudflare auto-purge failed: ' . ( $result['body']['message'] ?? 'unknown error' ),
206 - \XSpeed\Activity_Log::WARN
207 - );
271 + if ( class_exists( '\\XSpeed\\Activity_Log' ) ) {
272 + if ( $ok ) {
273 + \XSpeed\Activity_Log::record(
274 + 'cache_purged',
275 + sprintf(
276 + /* translators: %s: what asked for the purge. */
277 + __( 'Purged the Cloudflare edge cache (%s)', 'xspeed' ),
278 + $cause
279 + ),
280 + \XSpeed\Activity_Log::INFO
281 + );
282 + } else {
283 + \XSpeed\Activity_Log::record(
284 + 'cloudflare_purge_failed',
285 + sprintf(
286 + /* translators: 1: what asked for the purge, 2: failure reason. */
287 + __( 'Cloudflare purge failed (%1$s): %2$s', 'xspeed' ),
288 + $cause,
289 + $reason ? $reason : __( 'unknown error', 'xspeed' )
290 + ),
291 + \XSpeed\Activity_Log::WARN
292 + );
293 + }
208 294 }
295 +
296 + return array(
297 + 'ok' => $ok,
298 + 'reason' => $reason,
299 + 'status' => (int) ( $result['status'] ?? 0 ),
300 + 'body' => $result['body'] ?? array(),
301 + );
209 302 }
210 303
211 304 /**
212 305 * Persist any settings sent with the save, then verify the credentials
@@ -381,9 +474,13 @@
381 474 case 'verify':
382 475 $res = Cloudflare::verify( $opts );
383 476 break;
384 477 case 'purge':
385 - $res = Cloudflare::purge_all( $opts );
478 + // Through purge_edge() so a CLI purge records the same health
479 + // and activity-log entries as an auto-purge or `wp xspeed
480 + // purge`. Calling the engine directly left the panel's health
481 + // record showing whatever the last NON-CLI call found.
482 + $res = $this->purge_edge( 'CLI' );
386 483 break;
387 484 case 'dev-on':
388 485 $res = Cloudflare::set_dev_mode( $opts, true );
389 486 break;