Files
hub-insurance/wp-content/plugins/esp-instanda-integration/includes/class-boost-compat.php
T
2026-07-02 15:54:39 -06:00

162 lines
6.8 KiB
PHP

<?php
/**
* Boost_Compat - keeps Gravity Forms' front-end scripts out of Jetpack Boost's
* JavaScript optimizations, which otherwise break the form.
*
* Two independent Boost modules each break GF in a different way, so each needs a
* different, primary-source-verified hook (verified against Automattic/jetpack
* trunk, Boost ~3.x/4.x):
*
* 1. "Concatenate JS" (Minify) bundles enqueued scripts. Its saved exclude list
* lives in Boost's Data Sync store with NO public PHP filter, but the engine
* consults `js_do_concat` (bool, $handle) per script at runtime - returning
* false there force-excludes a handle from the bundle. The
* data-jetpack-boost="ignore" attribute does NOT affect concatenation.
*
* 2. "Defer / Render Blocking JS" relocates <script> tags to just before </body>
* 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<string>
*/
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('<script', '<script data-jetpack-boost="ignore"', $tag);
}
/**
* `jetpack_boost_render_blocking_js_exclude_handles` filter: append GF's known
* handles (and the protected extras) to Boost's defer exclusion list. Deduped;
* tolerant of a non-array input.
*
* @return list<string>
*/
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)));
}
}