| @@ -24,11 +24,12 @@ | ||
| 24 | 24 | public const VERSION = '1.0.0'; |
| 25 | 25 | |
| 26 | 26 | public function ui_metadata(): array { |
| 27 | 27 | return array( |
| 28 | - 'label' => 'CDN', | |
| 28 | + 'label' => __( 'CDN', 'xspeed' ), | |
| 29 | 29 | 'icon' => 'Globe', |
| 30 | - 'description' => 'Serve static assets (images, fonts, CSS, JS) from a pull-zone CDN host like BunnyCDN, KeyCDN, or your own.', | |
| 30 | + 'description' => __( 'Serve images, fonts, CSS and JS from a CDN such as BunnyCDN or KeyCDN.', 'xspeed' ), | |
| 31 | + 'group' => 'network', | |
| 31 | 32 | ); |
| 32 | 33 | } |
| 33 | 34 | |
| 34 | 35 | public function settings_schema(): array { |
| @@ -35,16 +36,16 @@ | ||
| 35 | 36 | return array( |
| 36 | 37 | 'enabled' => array( |
| 37 | 38 | 'type' => 'bool', |
| 38 | 39 | 'default' => false, |
| 39 | - 'label' => 'Enable CDN', | |
| 40 | - 'description' => 'Rewrite static asset URLs to the CDN hostname below. Your CDN must be a pull-zone configured to fetch from this site.', | |
| 40 | + 'label' => __( 'Enable CDN', 'xspeed' ), | |
| 41 | + 'description' => __( 'Load static files from the CDN address below. Set up the CDN to pull files from this site first.', 'xspeed' ), | |
| 41 | 42 | ), |
| 42 | 43 | 'cdn_url' => array( |
| 43 | 44 | 'type' => 'string', |
| 44 | 45 | 'default' => '', |
| 45 | - 'label' => 'CDN URL', | |
| 46 | - 'description' => 'CDN hostname, e.g. cdn.example.com. https:// and trailing slashes are stripped automatically.', | |
| 46 | + 'label' => __( 'CDN URL', 'xspeed' ), | |
| 47 | + 'description' => __( 'The CDN address, for example cdn.example.com. xSpeed removes https:// and any trailing slash.', 'xspeed' ), | |
| 47 | 48 | 'dependsOn' => array( 'field' => 'enabled' ), |
| 48 | 49 | ), |
| 49 | 50 | 'included_extensions' => array( |
| 50 | 51 | 'type' => 'list', |
| @@ -49,10 +50,10 @@ | ||
| 49 | 50 | 'included_extensions' => array( |
| 50 | 51 | 'type' => 'list', |
| 51 | 52 | 'default' => Cdn_Rewriter::DEFAULT_EXTENSIONS, |
| 52 | 53 | 'item_type' => 'string', |
| 53 | - 'label' => 'Included File Extensions', | |
| 54 | - 'description' => 'Only URLs ending in these extensions are rewritten. Defaults cover images, fonts, CSS, JS, and common media.', | |
| 54 | + 'label' => __( 'File types to serve', 'xspeed' ), | |
| 55 | + 'description' => __( 'Only files with these extensions load from the CDN. The defaults cover images, fonts, CSS, JS and common media.', 'xspeed' ), | |
| 55 | 56 | 'dependsOn' => array( 'field' => 'enabled' ), |
| 56 | 57 | ), |
| 57 | 58 | 'excluded_patterns' => array( |
| 58 | 59 | 'type' => 'list', |
| @@ -57,10 +58,10 @@ | ||
| 57 | 58 | 'excluded_patterns' => array( |
| 58 | 59 | 'type' => 'list', |
| 59 | 60 | 'default' => array(), |
| 60 | 61 | 'item_type' => 'string', |
| 61 | - 'label' => 'Excluded Patterns', | |
| 62 | - 'description' => 'Glob patterns matched against the URL path. Matching URLs stay on the origin. Examples: /wp-admin/*, *.pdf, /private/*', | |
| 62 | + 'label' => __( 'Excluded paths', 'xspeed' ), | |
| 63 | + 'description' => __( 'Files whose path matches a pattern load from your server, not the CDN. Use * as a wildcard, for example /private/* or *.pdf.', 'xspeed' ), | |
| 63 | 64 | 'dependsOn' => array( 'field' => 'enabled' ), |
| 64 | 65 | ), |
| 65 | 66 | ); |
| 66 | 67 | } |
| @@ -76,8 +77,27 @@ | ||
| 76 | 77 | ); |
| 77 | 78 | } |
| 78 | 79 | |
| 79 | 80 | public function boot(): void { |
| 81 | + /* | |
| 82 | + * Deferred to `init`. This module reads its own settings to decide | |
| 83 | + * what to hook, and reading settings builds settings_schema(), whose | |
| 84 | + * labels are declared through __(). boot() runs on `plugins_loaded`, | |
| 85 | + * before `after_setup_theme` — the point WordPress 6.7+ treats as the | |
| 86 | + * earliest safe moment to translate — so doing that here fires | |
| 87 | + * _load_textdomain_just_in_time on every request AND resolves the | |
| 88 | + * labels against a domain that is not loaded yet. | |
| 89 | + * | |
| 90 | + * Everything below hooks actions that fire after `init`, so running | |
| 91 | + * one hook later is equivalent. | |
| 92 | + */ | |
| 93 | + add_action( 'init', array( $this, 'boot_on_init' ) ); | |
| 94 | + } | |
| 95 | + | |
| 96 | + /** | |
| 97 | + * The real boot body — see boot() for why it runs on `init`. | |
| 98 | + */ | |
| 99 | + public function boot_on_init(): void { | |
| 80 | 100 | // Always-on: normalize cdn_url on save (admin context too). |
| 81 | 101 | add_filter( 'pre_update_option_xspeed_module_cdn', array( $this, 'normalize_on_save' ), 10, 1 ); |
| 82 | 102 | |
| 83 | 103 | // CDN URLs are baked into cached HTML, so a settings change that |
| @@ -89,8 +109,14 @@ | ||
| 89 | 109 | |
| 90 | 110 | if ( is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { |
| 91 | 111 | return; |
| 92 | 112 | } |
| 113 | + | |
| 114 | + // Rewriting asset hosts under a builder editor sends the editor's own | |
| 115 | + // scripts to the CDN, where the copy can be stale or absent. (#281) | |
| 116 | + if ( \XSpeed\Builder_Editor::is_active() ) { | |
| 117 | + return; | |
| 118 | + } | |
| 93 | 119 | $opts = $this->get_settings(); |
| 94 | 120 | if ( empty( $opts['enabled'] ) || empty( $opts['cdn_url'] ) ) { |
| 95 | 121 | return; |
| 96 | 122 | } |
| @@ -237,8 +263,18 @@ | ||
| 237 | 263 | public function on_settings_change(): void { |
| 238 | 264 | $this->sync_font_cors(); |
| 239 | 265 | if ( class_exists( '\\XSpeed\\Cache' ) ) { |
| 240 | 266 | \XSpeed\Cache::purge_all( 'cdn settings change' ); |
| 267 | + // purge_all() only reaches what we wrote. The attachment-URL | |
| 268 | + // filter below runs DURING render, so a page builder that caches | |
| 269 | + // rendered output has already stored the old host — Elementor | |
| 270 | + // keeps it in `_elementor_element_cache` for 24 h and in | |
| 271 | + // `uploads/elementor/css/post-<id>.css` with no expiry at all. | |
| 272 | + // Without this, turning the CDN OFF keeps serving the dead host | |
| 273 | + // (images 404 once the pull zone lapses) and turning it ON leaves | |
| 274 | + // the LCP hero on the origin — both for a day or more, both after | |
| 275 | + // a purge the user watched succeed. | |
| 276 | + \XSpeed\Cache::purge_render_caches( 'cdn settings change' ); | |
| 241 | 277 | } |
| 242 | 278 | } |
| 243 | 279 | |
| 244 | 280 | /** |
| @@ -379,8 +415,9 @@ | ||
| 379 | 415 | array( |
| 380 | 416 | 'name' => 'xspeed cdn', |
| 381 | 417 | 'callback' => array( $this, 'cli_handler' ), |
| 382 | 418 | 'shortdesc' => 'Show CDN settings + test rewriting a URL.', |
| 419 | + 'ai_hint' => 'Is a CDN configured, and does URL rewriting work? Use to check whether assets are served from the CDN, or to test what a given URL rewrites to before trusting the setting.', | |
| 383 | 420 | 'synopsis' => array( |
| 384 | 421 | array( |
| 385 | 422 | 'type' => 'positional', |
| 386 | 423 | 'name' => 'action', |