PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.4
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.4
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/Bloat/BloatModule.php +107 -15 1.0.21.3.4 View file →
@@ -21,8 +21,10 @@
21 21 declare(strict_types=1);
22 22
23 23 namespace XSpeed\Modules\Bloat;
24 24
25 +defined( 'ABSPATH' ) || exit;
26 +
25 27 use XSpeed\Module;
26 28 use XSpeed\Settings_Manager;
27 29
28 30 final class BloatModule extends Module {
@@ -32,11 +34,11 @@
32 34 public const VERSION = '1.0.0';
33 35
34 36 public function ui_metadata(): array {
35 37 return array(
36 - 'label' => 'Disable Bloat',
38 + 'label' => __( 'Bloat Control', 'xspeed' ),
37 39 'icon' => 'Sliders',
38 - 'description' => 'Turn off WordPress defaults you do not use — saves bytes, requests, and attack surface.',
40 + 'description' => __( 'Turn off WordPress defaults you do not use — saves bytes, requests, and attack surface.', 'xspeed' ),
39 41 );
40 42 }
41 43
42 44 public function settings_schema(): array {
@@ -43,45 +45,70 @@
43 45 return array(
44 46 'disable_dashicons_frontend' => array(
45 47 'type' => 'bool',
46 48 'default' => false,
47 - 'label' => 'Disable Dashicons on Frontend',
48 - 'description' => 'Drop the dashicons stylesheet from non-admin pages. Most themes do not need it. Saves ~45 KB per visitor.',
49 + 'label' => __( 'Disable Dashicons on Frontend', 'xspeed' ),
50 + 'description' => __( 'Drop the dashicons stylesheet from non-admin pages. Most themes do not need it. Saves ~45 KB per visitor.', 'xspeed' ),
49 51 ),
50 52 'disable_oembed' => array(
51 53 'type' => 'bool',
52 54 'default' => false,
53 - 'label' => 'Disable oEmbed Discovery + wp-embed.min.js',
54 - 'description' => 'Strip the auto-embed handlers + the embed script. Posts that paste a YouTube URL will no longer auto-render the player — embed it via a block instead. Saves a request per page.',
55 + 'label' => __( 'Disable oEmbed Discovery + wp-embed.min.js', 'xspeed' ),
56 + 'description' => __( 'Strip the auto-embed handlers + the embed script. Posts that paste a YouTube URL will no longer auto-render the player — embed it via a block instead. Saves a request per page.', 'xspeed' ),
55 57 ),
56 58 'disable_rss_feeds' => array(
57 59 'type' => 'bool',
58 60 'default' => false,
59 - 'label' => 'Disable RSS Feeds',
60 - 'description' => 'Return a 404 on /feed/ and similar endpoints. Useful for sites that do not publish feeds and want to cut feed-fetcher traffic.',
61 + 'label' => __( 'Disable RSS Feeds', 'xspeed' ),
62 + 'description' => __( 'Return a 404 on /feed/ and similar endpoints. Useful for sites that do not publish feeds and want to cut feed-fetcher traffic.', 'xspeed' ),
61 63 ),
62 64 'disable_xmlrpc' => array(
63 65 'type' => 'bool',
64 66 'default' => false,
65 - 'label' => 'Disable XML-RPC',
66 - 'description' => 'Disable the legacy xmlrpc.php endpoint. Cuts pingback brute-force noise; safe to disable unless you use a remote WP client (Jetpack, WordPress mobile app).',
67 + 'label' => __( 'Disable XML-RPC', 'xspeed' ),
68 + 'description' => __( 'Disable the legacy xmlrpc.php endpoint. Cuts pingback brute-force noise; safe to disable unless you use a remote WP client (Jetpack, WordPress mobile app).', 'xspeed' ),
67 69 ),
68 70 'strip_jquery_migrate' => array(
69 71 'type' => 'bool',
70 72 'default' => false,
71 - 'label' => 'Strip jQuery Migrate on Frontend',
72 - 'description' => 'Remove the jquery-migrate compatibility shim from non-admin pages. Saves ~10 KB; safe on modern themes / plugins.',
73 + 'label' => __( 'Strip jQuery Migrate on Frontend', 'xspeed' ),
74 + 'description' => __( 'Remove the jquery-migrate compatibility shim from non-admin pages. Saves ~10 KB; safe on modern themes / plugins.', 'xspeed' ),
73 75 ),
76 + 'strip_editor_styles' => array(
77 + 'type' => 'bool',
78 + 'default' => false,
79 + 'label' => __( 'Strip Block-Editor Styles on Frontend', 'xspeed' ),
80 + 'description' => __( 'Drop editor-only stylesheets (wp-editor, wp-components, and friends) from anonymous pages. A plugin that enqueues them on the frontend usually does so by accident — they can add hundreds of KB of render-blocking CSS. Frontend block styles (wp-block-library) are never touched.', 'xspeed' ),
81 + ),
74 82 'restrict_rest_to_authed' => array(
75 83 'type' => 'bool',
76 84 'default' => false,
77 - 'label' => 'Restrict REST API to Logged-In Users',
78 - 'description' => 'Block /wp-json/ for anonymous requests. WooCommerce checkout, contact-form submissions, and many block-editor previews need anonymous REST — keep this off unless you know your site does not depend on it.',
85 + 'label' => __( 'Restrict REST API to Logged-In Users', 'xspeed' ),
86 + 'description' => __( 'Block /wp-json/ for anonymous requests. WooCommerce checkout, contact-form submissions, and many block-editor previews need anonymous REST — keep this off unless you know your site does not depend on it.', 'xspeed' ),
79 87 ),
80 88 );
81 89 }
82 90
83 91 public function boot(): void {
92 + /*
93 + * Deferred to `init` priority 0. This reads the module's settings,
94 + * which builds settings_schema(), whose labels go through __(), and
95 + * boot() runs on `plugins_loaded` — before `after_setup_theme`, the
96 + * earliest point WordPress 6.7+ treats as safe to translate.
97 + *
98 + * Priority 0 (not the default 10) because the body itself registers
99 + * an `init` callback at priority 9: adding a hook to the action that
100 + * is currently running only takes effect if the new priority is still
101 + * ahead of the running position, so we have to be first. Every other
102 + * hook it registers fires later than `init`.
103 + */
104 + add_action( 'init', array( $this, 'boot_on_init' ), 0 );
105 + }
106 +
107 + /**
108 + * The real boot body — see boot() for why it runs on `init`.
109 + */
110 + public function boot_on_init(): void {
84 111 $opts = Settings_Manager::get( self::SLUG );
85 112
86 113 if ( ! empty( $opts['disable_dashicons_frontend'] ) ) {
87 114 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'dequeue_dashicons' ), 100 );
@@ -110,8 +137,13 @@
110 137 if ( ! empty( $opts['strip_jquery_migrate'] ) ) {
111 138 add_action( 'wp_default_scripts', array( __CLASS__, 'strip_jquery_migrate' ) );
112 139 }
113 140
141 + if ( ! empty( $opts['strip_editor_styles'] ) ) {
142 + // Late, so anything enqueued at normal priority is already queued.
143 + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'dequeue_editor_styles' ), PHP_INT_MAX );
144 + }
145 +
114 146 if ( ! empty( $opts['restrict_rest_to_authed'] ) ) {
115 147 add_filter( 'rest_authentication_errors', array( __CLASS__, 'restrict_rest' ) );
116 148 }
117 149 }
@@ -153,8 +185,56 @@
153 185 }
154 186 );
155 187 }
156 188
189 + /**
190 + * Editor-only style handles that have no business on an anonymous
191 + * frontend page. Deliberately NOT wp-block-library /
192 + * wp-block-library-theme / global-styles — those style the blocks
193 + * visitors actually see. Observed live: a plugin pulled wp-editor +
194 + * wp-components (and their deps) onto a marketing homepage, several
195 + * hundred KB of render-blocking CSS nothing on the page used.
196 + */
197 + private const EDITOR_STYLE_HANDLES = array(
198 + 'wp-editor',
199 + 'wp-block-editor',
200 + 'wp-block-directory',
201 + 'wp-components',
202 + 'wp-preferences',
203 + 'wp-media-utils',
204 + 'wp-reusable-blocks',
205 + 'wp-patterns',
206 + 'wp-edit-blocks',
207 + 'wp-edit-post',
208 + 'wp-edit-site',
209 + 'wp-edit-widgets',
210 + 'wp-format-library',
211 + 'wp-list-reusable-blocks',
212 + 'wp-nux',
213 + );
214 +
215 + public static function dequeue_editor_styles(): void {
216 + // Logged-in views legitimately reach editor surfaces (front-end
217 + // editing, admin bar flows), and a builder editing screen is a
218 + // front-end URL — same guard set as the other frontend strips.
219 + if ( is_user_logged_in() || is_admin() || \XSpeed\Builder_Editor::is_active() ) {
220 + return;
221 + }
222 + $styles = wp_styles();
223 + foreach ( self::EDITOR_STYLE_HANDLES as $handle ) {
224 + wp_dequeue_style( $handle );
225 + }
226 + // Dequeue alone is not enough: dependencies are resolved again at
227 + // print time, so any queued sheet that lists one of these as a dep
228 + // pulls it straight back. Strip the handles from every registered
229 + // sheet's deps too — same technique strip_jquery_migrate() uses.
230 + foreach ( $styles->registered as $dependency ) {
231 + if ( is_array( $dependency->deps ?? null ) && array_intersect( $dependency->deps, self::EDITOR_STYLE_HANDLES ) ) {
232 + $dependency->deps = array_values( array_diff( $dependency->deps, self::EDITOR_STYLE_HANDLES ) );
233 + }
234 + }
235 + }
236 +
157 237 public static function block_feed(): void {
158 238 wp_die(
159 239 esc_html__( 'Feeds are disabled.', 'xspeed' ),
160 240 '',
@@ -176,9 +256,12 @@
176 256 /**
177 257 * @param \WP_Scripts $scripts
178 258 */
179 259 public static function strip_jquery_migrate( $scripts ): void {
180 - if ( is_admin() || ! isset( $scripts->registered['jquery'] ) ) {
260 + // Builders and their add-ons still rely on jQuery Migrate shims; a
261 + // builder editing screen is a front-end URL, so is_admin() misses it
262 + // and the editor loses methods it calls. (#281)
263 + if ( is_admin() || \XSpeed\Builder_Editor::is_active() || ! isset( $scripts->registered['jquery'] ) ) {
181 264 return;
182 265 }
183 266 $jquery = $scripts->registered['jquery'];
184 267 if ( is_array( $jquery->deps ?? null ) ) {
@@ -212,8 +295,9 @@
212 295 array(
213 296 'name' => 'xspeed bloat',
214 297 'callback' => array( $this, 'cli_handler' ),
215 298 'shortdesc' => 'Show which bloat-removal toggles are active.',
299 + 'ai_hint' => 'What unnecessary WordPress output is being stripped (emojis, embeds, jQuery Migrate, dashicons)? Use when asked why extra scripts still load on the frontend, or before recommending bloat removal.',
216 300 'synopsis' => array(),
217 301 ),
218 302 );
219 303 }
@@ -222,6 +306,14 @@
222 306 $opts = Settings_Manager::get( self::SLUG );
223 307 foreach ( $opts as $key => $value ) {
224 308 \WP_CLI::log( sprintf( '%-30s %s', $key, $value ? 'on' : 'off' ) );
225 309 }
310 + }
311 +
312 + /**
313 + * Bloat has no master switch -- it is on when any of its disable_* /
314 + * strip_* / restrict_* flags is set. (#363)
315 + */
316 + public function is_active(): ?bool {
317 + return $this->any_bool_flag_on();
226 318 }
227 319 }