initial
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
<?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)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user