PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.5
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-wp-build-polyfills / src / class-wp-build-polyfills.php
jetpack / jetpack_vendor / automattic / jetpack-wp-build-polyfills / src Last commit date
internal 3 months ago class-wp-build-polyfills.php 2 months ago
class-wp-build-polyfills.php
271 lines
1 <?php
2 /**
3 * Polyfill registration for Core packages not available or incomplete in older WordPress versions.
4 *
5 * Conditionally registers wp-notices, wp-private-apis, wp-theme (classic scripts) and
6 * `@wordpress/boot`, `@wordpress/route`, `@wordpress/a11y` (script modules)
7 * ONLY when they are not already provided by Core or Gutenberg.
8 *
9 * @package automattic/jetpack-wp-build-polyfills
10 */
11
12 namespace Automattic\Jetpack\WP_Build_Polyfills;
13
14 /**
15 * Registers polyfill scripts and modules for WordPress Core packages.
16 */
17 class WP_Build_Polyfills {
18
19 /**
20 * Available polyfill handles for classic scripts.
21 */
22 const SCRIPT_HANDLES = array( 'wp-notices', 'wp-private-apis', 'wp-theme', 'wp-views' );
23
24 /**
25 * Available polyfill module IDs.
26 */
27 const MODULE_IDS = array( '@wordpress/boot', '@wordpress/route', '@wordpress/a11y' );
28
29 /**
30 * Minimum Gutenberg plugin version known to ship a private-apis allowlist
31 * that includes the dashboard packages used by this package's current build.
32 */
33 const GUTENBERG_PRIVATE_APIS_MIN_VERSION = '23.5.0';
34
35 /**
36 * Tracks which polyfills have been requested and by which consumers.
37 *
38 * Keys are polyfill handles/module IDs, values are arrays of consumer names.
39 *
40 * @var array<string, string[]>
41 */
42 private static $requested = array();
43
44 /**
45 * Whether the wp_default_scripts hook has already been added.
46 *
47 * @var bool
48 */
49 private static $hooked = false;
50
51 /**
52 * The WordPress version below which force-replacements are applied.
53 * When multiple consumers call register() with different thresholds,
54 * the highest threshold wins (most conservative approach).
55 *
56 * @var string
57 */
58 private static $wp_version_threshold = '7.0';
59
60 /**
61 * Register polyfill scripts and modules.
62 *
63 * Call this early (e.g. during plugin load) — it hooks into wp_default_scripts
64 * at priority 20 so Core (default) and Gutenberg (priority 10) register first.
65 *
66 * When multiple consumers call this method with different thresholds, the
67 * highest threshold wins (most conservative — polyfills active on more versions).
68 *
69 * @param string $consumer A unique identifier for the consumer (e.g. plugin slug).
70 * @param string[] $polyfills List of polyfill handles/module IDs to register.
71 * Use class constants SCRIPT_HANDLES and MODULE_IDS for reference.
72 * @param string $wp_version_threshold The WordPress version below which force-replacements
73 * are applied. Defaults to '7.0'.
74 */
75 public static function register( $consumer, $polyfills, $wp_version_threshold = '7.0' ) {
76 foreach ( $polyfills as $handle ) {
77 if ( ! in_array( $handle, self::SCRIPT_HANDLES, true ) && ! in_array( $handle, self::MODULE_IDS, true ) ) {
78 continue;
79 }
80 if ( ! isset( self::$requested[ $handle ] ) ) {
81 self::$requested[ $handle ] = array();
82 }
83 if ( ! in_array( $consumer, self::$requested[ $handle ], true ) ) {
84 self::$requested[ $handle ][] = $consumer;
85 }
86 }
87
88 if ( version_compare( $wp_version_threshold, self::$wp_version_threshold, '>' ) ) {
89 self::$wp_version_threshold = $wp_version_threshold;
90 }
91
92 if ( self::$hooked ) {
93 return;
94 }
95 self::$hooked = true;
96
97 $package_root = dirname( __DIR__ );
98 $build_dir = $package_root . '/build';
99 $base_file = $package_root . '/composer.json';
100
101 // `wp_default_scripts` fires once when the WP_Scripts singleton is
102 // instantiated. If something has already initialized `wp_scripts()` —
103 // common on admin requests where WP or other plugins register scripts
104 // before `admin_menu` priority 1 runs — adding this hook here is too
105 // late and the polyfills never register. Detect that case and run the
106 // registration synchronously so consumers can rely on the script
107 // handles and module IDs being available regardless of init order.
108 if ( did_action( 'wp_default_scripts' ) ) {
109 self::register_scripts( wp_scripts(), $build_dir, $base_file, self::$wp_version_threshold );
110 self::register_modules( $build_dir, $base_file );
111 return;
112 }
113
114 add_action(
115 'wp_default_scripts',
116 function ( $scripts ) use ( $build_dir, $base_file ) {
117 self::register_scripts( $scripts, $build_dir, $base_file, self::$wp_version_threshold );
118 self::register_modules( $build_dir, $base_file );
119 },
120 20
121 );
122 }
123
124 /**
125 * Get the map of requested polyfills and their consumers.
126 *
127 * @return array<string, string[]> Keys are polyfill handles/module IDs, values are consumer names.
128 */
129 public static function get_consumers() {
130 return self::$requested;
131 }
132
133 /**
134 * Register polyfill classic scripts.
135 *
136 * @param \WP_Scripts $scripts The WP_Scripts instance.
137 * @param string $build_dir Absolute path to the build directory.
138 * @param string $base_file File path for plugins_url() computation.
139 * @param string $wp_version_threshold WP version below which force-replacements apply.
140 */
141 private static function register_scripts( $scripts, $build_dir, $base_file, $wp_version_threshold ) {
142 // Force-replace only when Core's bundled scripts are incomplete and
143 // Gutenberg cannot be trusted to provide a compatible implementation.
144 $gutenberg_version = defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null;
145
146 $polyfills = array(
147 'wp-notices' => array(
148 'path' => 'notices',
149 'force_threshold' => '7.0',
150 // Only force-replace on older WP without Gutenberg: older Core
151 // versions ship notices without SnackbarNotices and InlineNotices
152 // component exports that @wordpress/boot depends on.
153 ),
154 'wp-private-apis' => array(
155 'path' => 'private-apis',
156 'force_threshold' => '7.1',
157 'gutenberg_min_version' => self::GUTENBERG_PRIVATE_APIS_MIN_VERSION,
158 // WP 7.0 and older versions ship private-apis with an incomplete
159 // allowlist that rejects @wordpress/theme, @wordpress/route, and
160 // newer dashboard packages. Active Gutenberg is only a safe
161 // substitute once its private-apis allowlist includes those
162 // dashboard packages too.
163 ),
164 'wp-theme' => array(
165 'path' => 'theme',
166 ),
167 'wp-views' => array(
168 'path' => 'views',
169 ),
170 );
171
172 foreach ( $polyfills as $handle => $data ) {
173 if ( ! isset( self::$requested[ $handle ] ) ) {
174 continue;
175 }
176
177 $asset_file = $build_dir . '/scripts/' . $data['path'] . '/index.asset.php';
178
179 if ( ! file_exists( $asset_file ) ) {
180 continue;
181 }
182
183 $force_threshold = $data['force_threshold'] ?? null;
184 if ( null !== $force_threshold && version_compare( $wp_version_threshold, $force_threshold, '>' ) ) {
185 $force_threshold = $wp_version_threshold;
186 }
187
188 $force = null !== $force_threshold
189 && ! self::is_gutenberg_version_safe( $data['gutenberg_min_version'] ?? null, $gutenberg_version )
190 && version_compare( $GLOBALS['wp_version'] ?? '0', $force_threshold, '<' );
191
192 if ( ! $force && $scripts->query( $handle, 'registered' ) ) {
193 continue;
194 }
195
196 // Deregister first when forcing replacement of an existing registration.
197 if ( $force && $scripts->query( $handle, 'registered' ) ) {
198 $scripts->remove( $handle );
199 }
200
201 $asset = require $asset_file;
202
203 $scripts->add(
204 $handle,
205 plugins_url( 'build/scripts/' . $data['path'] . '/index.js', $base_file ),
206 $asset['dependencies'],
207 $asset['version']
208 );
209 }
210 }
211
212 /**
213 * Check whether the active Gutenberg plugin can satisfy a forced script.
214 *
215 * @param string|null $minimum_version Minimum Gutenberg version required for the script, or null when any active Gutenberg is sufficient.
216 * @param string|null $gutenberg_version Active Gutenberg version, or null when Gutenberg is inactive.
217 * @return bool True when Gutenberg is active and new enough.
218 */
219 private static function is_gutenberg_version_safe( $minimum_version, $gutenberg_version ) {
220 if ( null === $gutenberg_version ) {
221 return false;
222 }
223
224 if ( null === $minimum_version ) {
225 return true;
226 }
227
228 return version_compare( $gutenberg_version, $minimum_version, '>=' );
229 }
230
231 /**
232 * Register polyfill script modules.
233 *
234 * Call to wp_register_script_module() silently ignores duplicate registrations (first wins),
235 * so no explicit is_registered check is needed.
236 *
237 * @param string $build_dir Absolute path to the build directory.
238 * @param string $base_file File path for plugins_url() computation.
239 */
240 private static function register_modules( $build_dir, $base_file ) {
241 if ( ! function_exists( 'wp_register_script_module' ) ) {
242 return;
243 }
244
245 $modules = array( 'boot', 'route', 'a11y' );
246
247 foreach ( $modules as $name ) {
248 $module_id = '@wordpress/' . $name;
249
250 if ( ! isset( self::$requested[ $module_id ] ) ) {
251 continue;
252 }
253
254 $asset_file = $build_dir . '/modules/' . $name . '/index.asset.php';
255
256 if ( ! file_exists( $asset_file ) ) {
257 continue;
258 }
259
260 $asset = require $asset_file;
261
262 wp_register_script_module(
263 $module_id,
264 plugins_url( 'build/modules/' . $name . '/index.js', $base_file ),
265 $asset['module_dependencies'] ?? array(),
266 $asset['version']
267 );
268 }
269 }
270 }
271