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.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/Preloader/PreloaderModule.php +85 -16 1.0.21.3.2 View file →
@@ -14,8 +14,10 @@
14 14 declare(strict_types=1);
15 15
16 16 namespace XSpeed\Modules\Preloader;
17 17
18 +defined( 'ABSPATH' ) || exit;
19 +
18 20 use XSpeed\Module;
19 21 use XSpeed\Preloader;
20 22 use XSpeed\Settings_Manager;
21 23
@@ -26,15 +28,16 @@
26 28 public const VERSION = '1.0.0';
27 29
28 30 public function ui_metadata(): array {
29 31 return array(
30 - 'label' => 'Preloader',
32 + 'label' => __( 'Preloader', 'xspeed' ),
33 + 'tab_label' => __( 'Crawl Now', 'xspeed' ), // its own tab on the Preloader page
31 34 'icon' => 'Wand2',
32 - 'description' => 'Crawl the sitemap to warm cache so visitors never hit a cold MISS.',
35 + 'description' => __( 'Crawl the sitemap to warm cache so visitors never hit a cold MISS.', 'xspeed' ),
33 36 // Custom panel wraps the schema-driven settings with a
34 37 // Start/Stop control surface + a live status readout
35 38 // (queue depth, last URL, recent errors).
36 - 'custom_panel' => 'PreloaderPanel',
39 + 'custom_panel' => 'PreloaderHost',
37 40 );
38 41 }
39 42
40 43 public function settings_schema(): array {
@@ -41,10 +44,10 @@
41 44 return array(
42 45 'enabled' => array(
43 46 'type' => 'bool',
44 47 'default' => false,
45 - 'label' => 'Enable Preloader',
46 - 'description' => 'When on, xSpeed crawls the sitemap on the schedule below and warms the page cache.',
48 + 'label' => __( 'Enable Preloader', 'xspeed' ),
49 + 'description' => __( 'When on, xSpeed crawls the sitemap on the schedule below and warms the page cache.', 'xspeed' ),
47 50 ),
48 51 'schedule' => array(
49 52 'type' => 'enum',
50 53 'default' => 'manual',
@@ -54,10 +57,11 @@
54 57 'hourly' => 'Hourly',
55 58 'daily' => 'Daily',
56 59 'weekly' => 'Weekly',
57 60 ),
58 - 'label' => 'Schedule',
59 - 'description' => 'How often to start a fresh crawl. Manual means you trigger it from the dashboard.',
61 + 'label' => __( 'Schedule', 'xspeed' ),
62 + 'description' => __( 'How often to start a fresh crawl. Manual means you trigger it from the dashboard.', 'xspeed' ),
63 + 'dependsOn' => array( 'field' => 'enabled' ),
60 64 ),
61 65 'batch_size' => array(
62 66 'type' => 'int',
63 67 'default' => 5,
@@ -62,28 +66,32 @@
62 66 'type' => 'int',
63 67 'default' => 5,
64 68 'min' => 1,
65 69 'max' => 50,
66 - 'label' => 'Batch Size',
67 - 'description' => 'URLs warmed per cron tick. Higher = faster crawl, more load on the origin.',
70 + 'label' => __( 'Batch Size', 'xspeed' ),
71 + 'description' => __( 'URLs warmed per cron tick. Higher = faster crawl, more load on the origin.', 'xspeed' ),
72 + 'dependsOn' => array( 'field' => 'enabled' ),
68 73 ),
69 74 'sitemap_url' => array(
70 75 'type' => 'string',
71 76 'default' => '',
72 - 'label' => 'Sitemap URL (optional)',
73 - 'description' => 'Override the auto-detected WordPress core sitemap (/wp-sitemap.xml). Leave blank for default.',
77 + 'label' => __( 'Sitemap URL (optional)', 'xspeed' ),
78 + 'description' => __( 'Override the auto-detected WordPress core sitemap (/wp-sitemap.xml). Leave blank for default.', 'xspeed' ),
79 + 'dependsOn' => array( 'field' => 'enabled' ),
74 80 ),
75 81 'warm_on_publish' => array(
76 82 'type' => 'bool',
77 83 'default' => true,
78 - 'label' => 'Warm new content immediately',
79 - 'description' => 'When a post or page is published, fetch it once so the first visitor sees a cache HIT, not a cold MISS.',
84 + 'label' => __( 'Warm new content immediately', 'xspeed' ),
85 + 'description' => __( 'When a post or page is published, fetch it once so the first visitor sees a cache HIT, not a cold MISS.', 'xspeed' ),
86 + 'dependsOn' => array( 'field' => 'enabled' ),
80 87 ),
81 88 'warm_on_comment' => array(
82 89 'type' => 'bool',
83 90 'default' => false,
84 - 'label' => 'Re-warm after comments',
85 - 'description' => 'Re-warm a page after a comment is posted (Cache purges the page on comment; this fetches it back into cache).',
91 + 'label' => __( 'Re-warm after comments', 'xspeed' ),
92 + 'description' => __( 'Re-warm a page after a comment is posted (Cache purges the page on comment; this fetches it back into cache).', 'xspeed' ),
93 + 'dependsOn' => array( 'field' => 'enabled' ),
86 94 ),
87 95 );
88 96 }
89 97
@@ -118,8 +126,9 @@
118 126 array(
119 127 'name' => 'xspeed preloader',
120 128 'callback' => array( $this, 'cli_handler' ),
121 129 'shortdesc' => 'Drive the cache preloader (start | stop | status).',
130 + 'ai_hint' => 'Cache warming: crawl the site so visitors hit a warm cache instead of paying for the first render. Use after a full purge, or when the first visitor to each page reports a slow load.',
122 131 'synopsis' => array(
123 132 array(
124 133 'type' => 'positional',
125 134 'name' => 'action',
@@ -131,8 +140,27 @@
131 140 );
132 141 }
133 142
134 143 public function boot(): void {
144 + /*
145 + * Deferred to `init`. This module reads its own settings to decide
146 + * what to hook, and reading settings builds settings_schema(), whose
147 + * labels are declared through __(). boot() runs on `plugins_loaded`,
148 + * before `after_setup_theme` — the point WordPress 6.7+ treats as the
149 + * earliest safe moment to translate — so doing that here fires
150 + * _load_textdomain_just_in_time on every request AND resolves the
151 + * labels against a domain that is not loaded yet.
152 + *
153 + * Everything below hooks actions that fire after `init`, so running
154 + * one hook later is equivalent.
155 + */
156 + add_action( 'init', array( $this, 'boot_on_init' ) );
157 + }
158 +
159 + /**
160 + * The real boot body — see boot() for why it runs on `init`.
161 + */
162 + public function boot_on_init(): void {
135 163 add_action( Preloader::CRON_HOOK, array( Preloader::class, 'tick' ) );
136 164 add_action( 'xspeed_preloader_recurring', array( Preloader::class, 'recurring_kickoff' ) );
137 165
138 166 // Apply schedule changes immediately whenever this module's
@@ -262,8 +290,30 @@
262 290 \WP_CLI::error( 'Preloader is disabled. Enable it via wp xspeed preloader set --enabled=1 first.' );
263 291 return;
264 292 }
265 293 $state = Preloader::start();
294 + // Don't print a green Success over a crawl that queued
295 + // nothing — that exit-0 was the whole complaint in #142.
296 + $sitemap_error = (string) ( $state['sitemap_error'] ?? '' );
297 + if ( 0 === (int) $state['total'] ) {
298 + \WP_CLI::error(
299 + '' !== $sitemap_error
300 + ? sprintf( 'Queued 0 URLs. %s', $sitemap_error )
301 + : 'Queued 0 URLs — nothing to warm. Check the sitemap URL and the cache exclusion rules.'
302 + );
303 + return;
304 + }
305 + if ( 'fallback' === ( $state['source'] ?? '' ) ) {
306 + \WP_CLI::warning( $sitemap_error );
307 + \WP_CLI::success(
308 + sprintf(
309 + 'Queued %d URL%s from the site content instead of the sitemap.',
310 + $state['total'],
311 + 1 === $state['total'] ? '' : 's'
312 + )
313 + );
314 + return;
315 + }
266 316 \WP_CLI::success( sprintf( 'Queued %d URL%s.', $state['total'], 1 === $state['total'] ? '' : 's' ) );
267 317 return;
268 318
269 319 case 'stop':
@@ -280,9 +330,28 @@
280 330 }
281 331 if ( ! empty( $state['errors'] ) ) {
282 332 \WP_CLI::log( 'Errors : ' . count( $state['errors'] ) );
283 333 foreach ( array_slice( $state['errors'], -5 ) as $e ) {
284 - \WP_CLI::log( ' ' . $e['url'] . ' — ' . $e['error'] );
334 + // Tolerate a bare string as well as the {url, error}
335 + // shape. A string entry fataled this command outright
336 + // ("Cannot access offset of type string on string"),
337 + // which also took MCP's get_preloader_status down with
338 + // it — an agent asking why a preload failed got a type
339 + // error instead of the reason. The writer is fixed, but
340 + // `status` is a diagnostic: it should survive whatever
341 + // it is handed rather than die reporting on it. (QA F1)
342 + if ( is_array( $e ) ) {
343 + $url = isset( $e['url'] ) ? (string) $e['url'] : '';
344 + $msg = isset( $e['error'] ) ? (string) $e['error'] : '';
345 + // The sitemap message already names the URL, so
346 + // prefixing it would print the URL twice on one line.
347 + $line = ( '' !== $url && false === strpos( $msg, $url ) )
348 + ? $url . ' — ' . $msg
349 + : $msg;
350 + } else {
351 + $line = (string) $e;
352 + }
353 + \WP_CLI::log( ' ' . $line );
285 354 }
286 355 }
287 356 return;
288 357