From c96448d654fbf1dbfe9be325fbfa2faf1daf4ca1 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Mon, 28 Sep 2026 14:57:28 +0545 Subject: [PATCH 1/5] Fix - Kirki downloading Google Fonts on every front-end request Kirki's bundled webfont module tries to save Google Fonts locally so visitors never contact Google directly, but it does that download inline while a real visitor's page is rendering, and doesn't remember a failed attempt: if wp-content/fonts isn't writable, every single request re-attempts the same failed download (6+ seconds and a batch of PHP warnings, per visitor, forever). - kirki_enqueue_google_fonts is filtered to skip local generation entirely when the fonts directory (or its parent, if not created yet) isn't writable, so those installs get a fast page with a system-font fallback instead of a slow one with repeated failed downloads. - On a writable install, the first-ever download after a Customizer save no longer happens on a real visitor's request: a background WP-Cron tick (scheduled a few seconds after save) warms the local files instead, via Kirki's own normal front-end code path. - Removed the block editor's direct request to fonts.googleapis.com; it's the one request in this whole flow the front end already avoids by design, and dropping it is a one-line fix. Verified: an isolated test of the writability filter and the prewarm-scheduling logic covers "not yet created", "exists but unwritable", "writable" and "already scheduled" cases. See #88. Co-Authored-By: Claude Sonnet 5 --- functions.php | 2 +- inc/kirki-local-fonts.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 inc/kirki-local-fonts.php diff --git a/functions.php b/functions.php index 88d274a..b68c267 100644 --- a/functions.php +++ b/functions.php @@ -121,7 +121,6 @@ function flash_setup() { * @since Flash 1.3.5 */ function flash_block_editor_styles() { - wp_enqueue_style( 'flash-editor-googlefonts', '//fonts.googleapis.com/css?family=Montserrat:400,700&display=swap' ); wp_enqueue_style( 'flash-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css' ); } add_action( 'enqueue_block_editor_assets', 'flash_block_editor_styles', 1, 1 ); @@ -325,6 +324,7 @@ function flash_scripts() { // Kirki Toolkit. require get_template_directory() . '/inc/kirki/kirki.php'; +require get_template_directory() . '/inc/kirki-local-fonts.php'; // Customizer additions.s require get_template_directory() . '/inc/customizer.php'; diff --git a/inc/kirki-local-fonts.php b/inc/kirki-local-fonts.php new file mode 100644 index 0000000..553dafc --- /dev/null +++ b/inc/kirki-local-fonts.php @@ -0,0 +1,30 @@ + 15, 'sslverify' => false ) ); +} +add_action( 'flash_kirki_prewarm_fonts', 'flash_kirki_prewarm_fonts' ); From 6f6e83109c7a877b265f321463b9e320475861de Mon Sep 17 00:00:00 2001 From: rg755421 Date: Mon, 28 Sep 2026 15:20:37 +0545 Subject: [PATCH 2/5] Fix - Two problems found reviewing the previous commit Same corrections as themegrill/flash-pro's matching commit: 1. The writability check discarded fonts that were already successfully downloaded before the directory became read-only (e.g. "build with write access, deploy read-only" hosting). Read access and write access are different things; a site with existing local files should keep serving them regardless of current writability. The gate now only skips generation when nothing has ever been cached AND the directory is unwritable -- the actual severe case from #88 -- not whenever it's merely unwritable right now. 2. Reverted removing the block editor's Google Fonts request. Checked whether Kirki's own async webfont loader covers the block editor's preview area (which would have made the removal safe) and it doesn't: Async.php only enqueues its loader when $wp_customize or is_customize_preview() is true, neither of which is true on the block editor screen. Removing that line would have silently broken the heading-font preview in the editor with nothing replacing it -- a real regression for something the issue itself rates as a non-functional consistency nicety, not worth that trade-off. Verified with an expanded isolated test: the "something was already cached before it became unwritable" case now correctly keeps serving fonts instead of discarding them, on top of the existing four cases. Co-Authored-By: Claude Sonnet 5 --- functions.php | 1 + inc/kirki-local-fonts.php | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/functions.php b/functions.php index b68c267..50e0209 100644 --- a/functions.php +++ b/functions.php @@ -121,6 +121,7 @@ function flash_setup() { * @since Flash 1.3.5 */ function flash_block_editor_styles() { + wp_enqueue_style( 'flash-editor-googlefonts', '//fonts.googleapis.com/css?family=Montserrat:400,700&display=swap' ); wp_enqueue_style( 'flash-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css' ); } add_action( 'enqueue_block_editor_assets', 'flash_block_editor_styles', 1, 1 ); diff --git a/inc/kirki-local-fonts.php b/inc/kirki-local-fonts.php index 553dafc..6ff1f61 100644 --- a/inc/kirki-local-fonts.php +++ b/inc/kirki-local-fonts.php @@ -2,19 +2,23 @@ defined( 'ABSPATH' ) || exit; -function flash_kirki_local_fonts_writable() { +function flash_kirki_local_fonts_ready() { $dir = WP_CONTENT_DIR . '/fonts'; - return file_exists( $dir ) ? wp_is_writable( $dir ) : wp_is_writable( WP_CONTENT_DIR ); + if ( ! file_exists( $dir ) ) { + return wp_is_writable( WP_CONTENT_DIR ); + } + + return wp_is_writable( $dir ) || (bool) get_option( 'kirki_downloaded_font_files' ); } function flash_kirki_skip_fonts_when_unwritable( $fonts ) { - return flash_kirki_local_fonts_writable() ? $fonts : array(); + return flash_kirki_local_fonts_ready() ? $fonts : array(); } add_filter( 'kirki_enqueue_google_fonts', 'flash_kirki_skip_fonts_when_unwritable' ); function flash_kirki_schedule_font_prewarm() { - if ( ! flash_kirki_local_fonts_writable() ) { + if ( ! flash_kirki_local_fonts_ready() ) { return; } From da06de46918e3d88701fc27eaa7829d50216ecf1 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Mon, 28 Sep 2026 15:28:30 +0545 Subject: [PATCH 3/5] Fix - Font prewarm depended on passive WP-Cron timing wp_schedule_single_event() only queues an event; WordPress's default pseudo-cron only checks for due events on a subsequent front-end request, so on a low-traffic site the prewarm could sit unfired for an unbounded amount of time. Schedule the event for "now" instead of 5 seconds out, and call WordPress's own spawn_cron() right after so the loopback dispatch is triggered immediately rather than waited for. spawn_cron() is non-blocking (fire-and-forget, ~0.01s to open the socket), so this adds no measurable delay to the Customizer save request itself. If spawn_cron() is rate-limited by WP_CRON_LOCK_TIMEOUT, the already-persisted event still fires on the next normal cron check, same as before - so this is a strict improvement with no new failure mode. Co-Authored-By: Claude Sonnet 5 --- inc/kirki-local-fonts.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/kirki-local-fonts.php b/inc/kirki-local-fonts.php index 6ff1f61..2c22971 100644 --- a/inc/kirki-local-fonts.php +++ b/inc/kirki-local-fonts.php @@ -23,8 +23,10 @@ function flash_kirki_schedule_font_prewarm() { } if ( ! wp_next_scheduled( 'flash_kirki_prewarm_fonts' ) ) { - wp_schedule_single_event( time() + 5, 'flash_kirki_prewarm_fonts' ); + wp_schedule_single_event( time(), 'flash_kirki_prewarm_fonts' ); } + + spawn_cron(); } add_action( 'customize_save_after', 'flash_kirki_schedule_font_prewarm' ); From 4f315d78a0bbfe943a4af04426ab5238fbbcf3fd Mon Sep 17 00:00:00 2001 From: rg755421 Date: Mon, 28 Sep 2026 17:03:36 +0545 Subject: [PATCH 4/5] Fix - Ready gate trusted a stale DB option instead of checking real files Live Playwright/WP-CLI reproduction of issue #88's unwritable-directory case (chmod-equivalent via icacls on Windows) exposed a real regression: flash_kirki_local_fonts_ready() treated a non-empty kirki_downloaded_font_files option as proof that cached fonts exist, but that option lives in the database and can outlive the actual files - e.g. a container redeploy resets the filesystem while the DB persists. With a stale option and an empty unwritable directory, the gate said "ready", so Kirki still hit the same synchronous download-and-fail loop on every request that issue #88 reports (confirmed live: 500-file every request, identical PHP warnings in the log, until this fix). Replaced the option check with a direct glob() for physical files under the fonts directory - the only source of truth that can't go stale independently of the filesystem it's describing. Re-verified live: same unwritable+stale-option scenario now resolves in ~1-2s with zero warnings, and the original regression guard (unwritable dir that still has genuinely-cached files on disk) still returns ready, confirmed with an actual cached file on a write-denied directory. Co-Authored-By: Claude Sonnet 5 --- inc/kirki-local-fonts.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/inc/kirki-local-fonts.php b/inc/kirki-local-fonts.php index 2c22971..f5e89ee 100644 --- a/inc/kirki-local-fonts.php +++ b/inc/kirki-local-fonts.php @@ -9,7 +9,11 @@ function flash_kirki_local_fonts_ready() { return wp_is_writable( WP_CONTENT_DIR ); } - return wp_is_writable( $dir ) || (bool) get_option( 'kirki_downloaded_font_files' ); + if ( wp_is_writable( $dir ) ) { + return true; + } + + return (bool) glob( $dir . '/*/*.*' ); } function flash_kirki_skip_fonts_when_unwritable( $fonts ) { From cae20856e3b7d1946acab78148c01e1640493ab3 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Tue, 29 Sep 2026 17:57:16 +0545 Subject: [PATCH 5/5] Fix - Review: shared font filter blocked Editor/Customizer previews kirki_enqueue_google_fonts is used by both Embed.php (the real, front- end-only download path this fix targets) and Async.php (webfontloader.js, loaded on admin/customize-preview pages - never touches the filesystem). Emptying the font list whenever the fonts dir was unwritable blocked the Async path too, breaking font previews in the block editor and Customizer on exactly the hosts this fix was meant to help. Now a no-op whenever is_admin() or is_customize_preview() is true, mirroring Kirki's own Webfonts::get_method() gate between the two paths. Also scoped the readiness check to the specific font(s) being requested instead of "any font is cached anywhere" - a host with one font already cached but an unwritable directory would otherwise still attempt (and retry-loop on) every newly selected font. Co-Authored-By: Claude Sonnet 5 --- inc/kirki-local-fonts.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/inc/kirki-local-fonts.php b/inc/kirki-local-fonts.php index f5e89ee..a716f5f 100644 --- a/inc/kirki-local-fonts.php +++ b/inc/kirki-local-fonts.php @@ -2,7 +2,7 @@ defined( 'ABSPATH' ) || exit; -function flash_kirki_local_fonts_ready() { +function flash_kirki_local_fonts_ready( $fonts = array() ) { $dir = WP_CONTENT_DIR . '/fonts'; if ( ! file_exists( $dir ) ) { @@ -13,11 +13,26 @@ function flash_kirki_local_fonts_ready() { return true; } - return (bool) glob( $dir . '/*/*.*' ); + if ( empty( $fonts ) ) { + return (bool) glob( $dir . '/*/*.*' ); + } + + foreach ( array_keys( $fonts ) as $family ) { + $folder = $dir . '/' . sanitize_key( strtolower( str_replace( ' ', '-', $family ) ) ); + if ( ! glob( $folder . '/*.*' ) ) { + return false; + } + } + + return true; } function flash_kirki_skip_fonts_when_unwritable( $fonts ) { - return flash_kirki_local_fonts_ready() ? $fonts : array(); + if ( is_admin() || is_customize_preview() ) { + return $fonts; + } + + return flash_kirki_local_fonts_ready( $fonts ) ? $fonts : array(); } add_filter( 'kirki_enqueue_google_fonts', 'flash_kirki_skip_fonts_when_unwritable' );