tags to just before * and skips any tag carrying data-jetpack-boost="ignore". We stamp that * attribute onto GF tags via `script_loader_tag`, and additionally feed GF's * known handles to Boost's own `jetpack_boost_render_blocking_js_exclude_handles` * filter (which also covers GF's inline before/after blocks). * * When GF's core script (gform_gravityforms - it defines window.gform / * gform.addAction) is deferred or concatenated out of order, the inline init runs * before gform exists ("gform.addAction is not a function") and the AJAX submit / * post-submit redirect never fire. Protecting the gform / gravityforms handle * family (plus reCAPTCHA and jQuery) fixes both, form- and version-agnostically. * * Every hook here is a no-op when Boost is absent (the Boost-specific filters never * fire; the script_loader_tag attribute is inert HTML), so the shim is always safe. * * @package ESP\Instanda */ declare(strict_types=1); namespace ESP\Instanda; final class Boost_Compat { /** * Handle prefixes that identify Gravity Forms' own scripts across versions * (2.5 Orbital -> 2.10): the file names drift but these handle stems are stable. */ private const GF_PREFIXES = ['gform', 'gforms', 'gravityforms', 'gravity_form']; /** * Exact extra handles to protect beyond the GF pattern: GF's hard dependencies * that, if deferred, silently break the form (deferring jQuery alone makes GF * forms disappear), plus the reCAPTCHA loader. */ private const EXTRA_HANDLES = [ 'jquery', 'jquery-core', 'jquery-migrate', 'google-recaptcha', // GF's JS layer uses the wp.* runtime; protect the whole closure so a // protected script never runs before a deferred dependency (e.g. wp-a11y // calling wp-dom-ready/wp-i18n before they load). 'wp-hooks', 'wp-i18n', 'wp-a11y', 'wp-dom-ready', 'wp-polyfill', ]; /** * GF handles to hand to Boost's defer exclude-handles filter. script_loader_tag * only sees the main tag; this list lets Boost also exempt the inline-attached * (-js-before / -js-after) blocks bound to these handles. */ private const GF_KNOWN_HANDLES = [ 'gform_gravityforms', 'gform_gravityforms_utils', 'gform_gravityforms_vendors', 'gform_gravityforms_theme', 'gform_gravityforms_theme_reset', 'gform_conditional_logic', 'gform_datepicker_init', 'gform_recaptcha', ]; /** Wire the compatibility filters. Called from Instanda_AddOn::init(). */ public static function register(): void { // Concatenate JS - the only runtime hook Boost's concat engine consults. add_filter('js_do_concat', [self::class, 'filter_js_do_concat'], 10, 2); // Defer / Render Blocking JS - stamp the ignore attribute on the front end only // (the attribute is meaningless in wp-admin and Boost never defers there). if (!is_admin()) { add_filter('script_loader_tag', [self::class, 'filter_script_loader_tag'], 10, 2); } // Secondary defer safety: Boost's own handle-list exclusion, which also // covers GF's inline before/after script blocks. add_filter('jetpack_boost_render_blocking_js_exclude_handles', [self::class, 'filter_exclude_handles']); } /** * The protected exact-handle set, filterable so the site can extend it (e.g. a * custom GF add-on with an unusual handle) without editing the plugin. * * @return list */ public static function protected_handles(): array { $handles = self::EXTRA_HANDLES; if (function_exists('apply_filters')) { $handles = (array) apply_filters('esp_instanda_boost_protected_handles', $handles); } return array_values(array_filter(array_map('strval', $handles))); } /** Is this a Gravity Forms (or GF hard-dependency) script handle that must not be optimized? */ public static function is_protected(string $handle): bool { $h = strtolower($handle); if ($h === '') { return false; } foreach (self::GF_PREFIXES as $prefix) { if (str_starts_with($h, $prefix)) { return true; } } if (str_contains($h, 'gravityforms')) { return true; } foreach (self::protected_handles() as $exact) { if ($h === strtolower($exact)) { return true; } } return false; } /** * `js_do_concat` filter: returning false keeps the handle out of Boost's * concatenated JS bundle. Unrelated handles pass through unchanged. */ public static function filter_js_do_concat($do_concat, $handle = ''): bool { if (is_string($handle) && self::is_protected($handle)) { return false; } return (bool) $do_concat; } /** * `script_loader_tag` filter: stamp data-jetpack-boost="ignore" onto GF tags so * Boost's Defer module leaves them in place. Idempotent and byte-preserving for * everything else. */ public static function filter_script_loader_tag($tag, $handle = '') { if (!is_string($tag) || !is_string($handle) || !self::is_protected($handle)) { return $tag; } if (str_contains($tag, 'data-jetpack-boost')) { return $tag; // already marked - never double-stamp } return str_ireplace(' */ public static function filter_exclude_handles($handles): array { $base = is_array($handles) ? array_map('strval', $handles) : []; $add = array_merge(self::GF_KNOWN_HANDLES, self::protected_handles()); return array_values(array_unique(array_merge($base, $add))); } }