PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.0
1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 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 All 35 releases
← All changes | includes/modules/ResourceHints/ResourceHintsModule.php +87 -15 1.0.8 → 1.4.0 View file →
@@ -56,11 +56,18 @@
56 56 private static $buffering = false;
57 57
58 58 public function ui_metadata(): array {
59 59 return array(
60 - 'label' => 'Resource Hints',
61 - 'icon' => 'Zap',
62 - 'description' => 'Preload the LCP hero image and preconnect to font hosts so the largest element paints sooner.',
60 + 'label' => __( 'Resource Hints', 'xspeed' ),
61 + 'tab_label' => __( 'Hints', 'xspeed' ), // its own tab on the Resource Hints page
62 + 'icon' => 'Zap',
63 + 'description' => __( 'Tells the browser to fetch the main image and fonts early.', 'xspeed' ),
64 + 'group' => 'performance',
65 + // Host page: Hints (this module) + a Speculation Rules section
66 + // (SmartPredict, Pro). SmartPredict prefetches the next page in
67 + // the visitor's browser — same family as preload/preconnect, so
68 + // it belongs here, not under AI (FBS-83633).
69 + 'custom_panel' => 'ResourceHintsPanel',
63 70 );
64 71 }
65 72
66 73 public function settings_schema(): array {
@@ -67,16 +74,17 @@
67 74 return array(
68 75 'enabled' => array(
69 76 'type' => 'bool',
70 77 'default' => true,
71 - 'label' => 'Enable Resource Hints',
72 - 'description' => 'Master switch for the LCP-image preload + preconnect resource hints. On by default — these are safe, no-config optimizations that help every theme.',
78 + 'label' => __( 'Enable resource hints', 'xspeed' ),
79 + 'description' => __( 'Turns on all the hints below. They are safe on every theme, so this is on by default.', 'xspeed' ),
73 80 ),
74 81 'lcp_preload' => array(
75 82 'type' => 'bool',
76 83 'default' => true,
77 - 'label' => 'Preload LCP Image',
78 - 'description' => 'Detect the largest above-the-fold image and emit a <link rel="preload" as="image" fetchpriority="high"> in the head, plus fetchpriority="high" on the image. This is the highest-impact fix for Largest Contentful Paint — it beats any lazy-load the theme applied.',
84 + 'label' => __( 'Preload the main image', 'xspeed' ),
85 + 'description' => __( 'Finds the largest image at the top of the page and tells the browser to fetch it first. This usually gives the biggest LCP gain.', 'xspeed' ),
86 + 'dependsOn' => array( 'field' => 'enabled' ),
79 87 ),
80 88 'lcp_image_count' => array(
81 89 'type' => 'int',
82 90 'default' => 1,
@@ -81,35 +89,68 @@
81 89 'type' => 'int',
82 90 'default' => 1,
83 91 'min' => 0,
84 92 'max' => 3,
85 - 'label' => 'Images to Preload',
86 - 'description' => 'How many of the first images on the page to preload. 1 is right for most sites (the single hero). Raise it only if the fold shows a small gallery.',
93 + 'label' => __( 'Images to preload', 'xspeed' ),
94 + 'description' => __( 'How many images at the top of the page to preload. 1 suits most sites; raise it only if the top shows a small gallery.', 'xspeed' ),
95 + 'advanced' => true,
96 + 'dependsOn' => array( 'field' => 'lcp_preload' ),
87 97 ),
88 98 'lcp_exclusions' => array(
89 99 'type' => 'list',
90 100 'default' => array(),
91 101 'item_type' => 'string',
92 - 'label' => 'Exclude From Preload',
93 - 'description' => 'Substring patterns (filename or class) that, if found in an <img>, exempt it from being treated as the LCP image. Useful for tracking pixels, spacers, or a decorative first image that is not the hero.',
102 + 'label' => __( 'Excluded from preload', 'xspeed' ),
103 + 'description' => __( 'Images whose tag contains a line here, such as a file name or class, are never picked as the main image. Useful for tracking pixels and spacers.', 'xspeed' ),
104 + 'dependsOn' => array( 'field' => 'lcp_preload' ),
94 105 ),
106 + 'preload_images' => array(
107 + 'type' => 'list',
108 + 'default' => array(),
109 + 'item_type' => 'string',
110 + 'label' => __( 'Always preload these images', 'xspeed' ),
111 + 'description' => __( 'Image URLs to fetch first on every page, such as a hero background set in CSS. Keep it to one or two; only the first three are used.', 'xspeed' ),
112 + 'dependsOn' => array( 'field' => 'enabled' ),
113 + ),
95 114 'preconnect' => array(
96 115 'type' => 'bool',
97 116 'default' => true,
98 - 'label' => 'Preconnect to Font Hosts',
99 - 'description' => 'When Google Fonts are detected, emit <link rel="preconnect"> to fonts.googleapis.com and fonts.gstatic.com so the DNS + TLS handshake happens ahead of the font request instead of on the critical path.',
117 + 'label' => __( 'Connect early to Google Fonts', 'xspeed' ),
118 + 'description' => __( 'When the page uses Google Fonts, the browser connects to Google\'s servers early, so fonts arrive sooner.', 'xspeed' ),
119 + 'dependsOn' => array( 'field' => 'enabled' ),
100 120 ),
101 121 'preconnect_hosts' => array(
102 122 'type' => 'list',
103 123 'default' => array(),
104 124 'item_type' => 'url',
105 - 'label' => 'Extra Preconnect Hosts',
106 - 'description' => 'One origin per line (e.g. https://cdn.example.com) to preconnect in addition to the auto-detected font hosts. Use for a CDN or third-party origin that serves above-the-fold assets.',
125 + 'label' => __( 'Other domains to connect early', 'xspeed' ),
126 + 'description' => __( 'One address per line, such as https://cdn.example.com. Use for a CDN or other domain that serves files at the top of the page.', 'xspeed' ),
127 + 'advanced' => true,
128 + 'dependsOn' => array( 'field' => 'enabled' ),
107 129 ),
108 130 );
109 131 }
110 132
111 133 public function boot(): void {
134 + /*
135 + * Deferred to `init`. This module reads its own settings to decide
136 + * what to hook, and reading settings builds settings_schema(), whose
137 + * labels are declared through __(). boot() runs on `plugins_loaded`,
138 + * before `after_setup_theme` — the point WordPress 6.7+ treats as the
139 + * earliest safe moment to translate — so doing that here fires
140 + * _load_textdomain_just_in_time on every request AND resolves the
141 + * labels against a domain that is not loaded yet.
142 + *
143 + * Everything below hooks actions that fire after `init`, so running
144 + * one hook later is equivalent.
145 + */
146 + add_action( 'init', array( $this, 'boot_on_init' ) );
147 + }
148 +
149 + /**
150 + * The real boot body — see boot() for why it runs on `init`.
151 + */
152 + public function boot_on_init(): void {
112 153 // Frontend page renders only. Admin / feed / cron / AJAX / REST never
113 154 // produce an HTML document we should rewrite.
114 155 if ( is_admin()
115 156 || ( defined( 'DOING_AJAX' ) && DOING_AJAX )
@@ -114,8 +155,12 @@
114 155 if ( is_admin()
115 156 || ( defined( 'DOING_AJAX' ) && DOING_AJAX )
116 157 || ( defined( 'DOING_CRON' ) && DOING_CRON )
117 158 || ( defined( 'REST_REQUEST' ) && REST_REQUEST )
159 + // Builder editing screens are front-end URLs; injecting hints into
160 + // the editor document helps nobody and can preload the wrong
161 + // assets. (#281)
162 + || \XSpeed\Builder_Editor::is_active()
118 163 ) {
119 164 return;
120 165 }
121 166
@@ -195,11 +240,36 @@
195 240 if ( is_array( $lazy ) && ! empty( $lazy['lazy_images'] ) && ! empty( $lazy['excluded_images'] ) && is_array( $lazy['excluded_images'] ) ) {
196 241 $opts['eager_excluded_images'] = array_values( array_filter( array_map( 'strval', $lazy['excluded_images'] ) ) );
197 242 }
198 243 }
244 + $opts['page_url'] = self::current_page_url();
199 245 return $opts;
200 246 }
201 247
248 + /**
249 + * The URL of the page being served, for the LCP preload candidate seam.
250 + * Empty outside a request (CLI, tests).
251 + *
252 + * The site's scheme and host with the request path as the browser sent
253 + * it. Not home_url( REQUEST_URI ): on a site in a subfolder both carry the
254 + * folder, which then appeared twice. And not sanitize_text_field(), which
255 + * drops percent-encoded octets, so `/caf%C3%A9/` lost its é.
256 + */
257 + private static function current_page_url(): string {
258 + if ( ! isset( $_SERVER['REQUEST_URI'] ) || ! function_exists( 'home_url' ) ) {
259 + return '';
260 + }
261 + $uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
262 + if ( '' === $uri || '/' !== $uri[0] ) {
263 + return '';
264 + }
265 + $home = wp_parse_url( home_url( '/' ) );
266 + $scheme = isset( $home['scheme'] ) ? $home['scheme'] : 'https';
267 + $host = isset( $home['host'] ) ? $home['host'] : '';
268 + $port = isset( $home['port'] ) ? ':' . $home['port'] : '';
269 + return '' === $host ? '' : $scheme . '://' . $host . $port . $uri;
270 + }
271 +
202 272 public function cli_commands(): array {
203 273 return array(
204 274 array(
205 275 'name' => 'xspeed resource-hints',
@@ -204,8 +274,9 @@
204 274 array(
205 275 'name' => 'xspeed resource-hints',
206 276 'callback' => array( $this, 'cli_handler' ),
207 277 'shortdesc' => 'Show LCP-preload / preconnect resource-hint settings.',
278 + 'ai_hint' => 'Browser resource hints — preload, prefetch, preconnect — for the resources that block first paint. Use for LCP problems or "preconnect to required origins" in PageSpeed. Not the same as the Preloader, which warms the page cache.',
208 279 'synopsis' => array(),
209 280 ),
210 281 );
211 282 }
@@ -215,8 +286,9 @@
215 286 \WP_CLI::log( sprintf( '%-20s %s', 'enabled', ! empty( $opts['enabled'] ) ? 'on' : 'off' ) );
216 287 \WP_CLI::log( sprintf( '%-20s %s', 'lcp_preload', ! empty( $opts['lcp_preload'] ) ? 'on' : 'off' ) );
217 288 \WP_CLI::log( sprintf( '%-20s %d', 'lcp_image_count', (int) ( $opts['lcp_image_count'] ?? 1 ) ) );
218 289 \WP_CLI::log( sprintf( '%-20s %d pattern(s)', 'lcp_exclusions', count( (array) ( $opts['lcp_exclusions'] ?? array() ) ) ) );
290 + \WP_CLI::log( sprintf( '%-20s %d url(s)', 'preload_images', count( (array) ( $opts['preload_images'] ?? array() ) ) ) );
219 291 \WP_CLI::log( sprintf( '%-20s %s', 'preconnect', ! empty( $opts['preconnect'] ) ? 'on' : 'off' ) );
220 292 \WP_CLI::log( sprintf( '%-20s %d host(s)', 'preconnect_hosts', count( (array) ( $opts['preconnect_hosts'] ?? array() ) ) ) );
221 293 }
222 294 }