PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 15.0
Jetpack – WP Security, Backup, Speed, & Growth v15.0
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack-gutenberg.php
class.jetpack-gutenberg.php
1,441 lines 46.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Handles server-side registration and use of all blocks and plugins available in Jetpack for the block editor, aka Gutenberg.
4 * Works in tandem with client-side block registration via `index.json`
5 *
6 * @package automattic/jetpack
7 */
8
9 use Automattic\Jetpack\Assets;
10 use Automattic\Jetpack\Blocks;
11 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
12 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13 use Automattic\Jetpack\Constants;
14 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
15 use Automattic\Jetpack\Modules;
16 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
17 use Automattic\Jetpack\Status;
18 use Automattic\Jetpack\Status\Host;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit( 0 );
22 }
23
24 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move the functions and such to some other file.
25
26 /**
27 * General Gutenberg editor specific functionality
28 */
29 class Jetpack_Gutenberg {
30
31 /**
32 * Only these extensions can be registered. Used to control availability of beta blocks.
33 *
34 * @var array|null Extensions allowed list or `null` if not initialized yet.
35 * @see static::get_extensions()
36 */
37 private static $extensions = null;
38
39 /**
40 * Keeps track of the reasons why a given extension is unavailable.
41 *
42 * @var array Extensions availability information
43 */
44 private static $availability = array();
45
46 /**
47 * A cached array of the fully processed availability data. Keeps track of
48 * reasons why an extension is unavailable or missing.
49 *
50 * @var array Extensions availability information.
51 */
52 private static $cached_availability = null;
53
54 /**
55 * Site-specific features available.
56 * Their calculation can be expensive and slow, so we're caching it for the request.
57 *
58 * @var array Site-specific features
59 */
60 private static $site_specific_features = array();
61
62 /**
63 * List of deprecated blocks.
64 *
65 * @var array List of deprecated blocks.
66 */
67 private static $deprecated_blocks = array(
68 'jetpack/revue',
69 );
70
71 /**
72 * Storing the contents of the preset file.
73 *
74 * Already been json_decode.
75 *
76 * @var null|object JSON decoded object after first usage.
77 */
78 private static $preset_cache = null;
79
80 /**
81 * Keep track of JS loading strategies for each block that needs it.
82 *
83 * @var array<string, array|bool>
84 *
85 * @since 15.0
86 */
87 private static $block_js_loading_strategies = array();
88
89 /**
90 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
91 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
92 * optionally fall back to that.
93 *
94 * @param array $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
95 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
96 *
97 * @since 8.3.0
98 *
99 * @return boolean True if the version of gutenberg required by the block or plugin is available.
100 */
101 public static function is_gutenberg_version_available( $version_requirements, $slug ) {
102 global $wp_version;
103
104 // Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
105 if ( empty( $version_requirements['gutenberg'] ) ) {
106 return false;
107 }
108
109 // If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
110 if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
111 return true;
112 }
113
114 $version_available = false;
115
116 // If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
117 // with required version of Gutenberg is known check that.
118 if ( defined( 'GUTENBERG_VERSION' ) ) {
119 $version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
120 } elseif ( ! empty( $version_requirements['wp'] ) ) {
121 $version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
122 }
123
124 if ( ! $version_available ) {
125 $slug = self::remove_extension_prefix( $slug );
126 self::set_extension_unavailable(
127 $slug,
128 'incorrect_gutenberg_version',
129 array(
130 'required_feature' => $slug,
131 'required_version' => $version_requirements,
132 'current_version' => array(
133 'wp' => $wp_version,
134 'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
135 ),
136 )
137 );
138 }
139
140 return $version_available;
141 }
142
143 /**
144 * Prepend the 'jetpack/' prefix to a block name
145 *
146 * @param string $block_name The block name.
147 *
148 * @return string The prefixed block name.
149 */
150 private static function prepend_block_prefix( $block_name ) {
151 return 'jetpack/' . $block_name;
152 }
153
154 /**
155 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
156 *
157 * @param string $extension_name The extension name.
158 *
159 * @return string The unprefixed extension name.
160 */
161 public static function remove_extension_prefix( $extension_name ) {
162 if ( str_starts_with( $extension_name, 'jetpack/' ) || str_starts_with( $extension_name, 'jetpack-' ) ) {
163 return substr( $extension_name, strlen( 'jetpack/' ) );
164 }
165 return $extension_name;
166 }
167
168 /**
169 * Whether two arrays share at least one item
170 *
171 * @param array $a An array.
172 * @param array $b Another array.
173 *
174 * @return boolean True if $a and $b share at least one item
175 */
176 protected static function share_items( $a, $b ) {
177 return array_intersect( $a, $b ) !== array();
178 }
179
180 /**
181 * Set a (non-block) extension as available
182 *
183 * @param string $slug Slug of the extension.
184 */
185 public static function set_extension_available( $slug ) {
186 $slug = self::remove_extension_prefix( $slug );
187 self::$availability[ $slug ] = true;
188 }
189
190 /**
191 * Set the reason why an extension (block or plugin) is unavailable
192 *
193 * @param string $slug Slug of the extension.
194 * @param string $reason A string representation of why the extension is unavailable.
195 * @param array $details A free-form array containing more information on why the extension is unavailable.
196 */
197 public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
198 if (
199 // Extensions that require a plan may be eligible for upgrades.
200 'missing_plan' === $reason
201 && (
202 /**
203 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
204 * to disable paid feature upgrade nudges in the block editor.
205 *
206 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
207 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
208 *
209 * @since 7.7.0
210 *
211 * @param boolean
212 */
213 ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
214 /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
215 || ! apply_filters( 'jetpack_show_promotions', true )
216 )
217 ) {
218 // The block editor may apply an upgrade nudge if `missing_plan` is the reason.
219 // Add a descriptive suffix to disable behavior but provide informative reason.
220 $reason .= '__nudge_disabled';
221 }
222 $slug = self::remove_extension_prefix( $slug );
223 self::$availability[ $slug ] = array(
224 'reason' => $reason,
225 'details' => $details,
226 );
227 }
228
229 /**
230 * Used to initialize the class, no longer in use.
231 *
232 * @return void
233 * @deprecated 12.2 No longer needed.
234 */
235 public static function init() {
236 _deprecated_function( __METHOD__, '12.2' );
237 }
238
239 /**
240 * Resets the class to its original state
241 *
242 * Used in unit tests
243 *
244 * @return void
245 */
246 public static function reset() {
247 self::$extensions = null;
248 self::$availability = array();
249 self::$cached_availability = null;
250 self::$block_js_loading_strategies = array();
251 }
252
253 /**
254 * Return the Gutenberg extensions (blocks and plugins) directory
255 *
256 * @return string The Gutenberg extensions directory
257 */
258 public static function get_blocks_directory() {
259 /**
260 * Filter to select Gutenberg blocks directory
261 *
262 * @since 6.9.0
263 *
264 * @param string default: '_inc/blocks/'
265 */
266 return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
267 }
268
269 /**
270 * Checks for a given .json file in the blocks folder.
271 *
272 * @deprecated 14.3
273 *
274 * @param string $preset The name of the .json file to look for.
275 *
276 * @return bool True if the file is found.
277 */
278 public static function preset_exists( $preset ) {
279 _deprecated_function( __METHOD__, '14.3' );
280 return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
281 }
282
283 /**
284 * Decodes JSON loaded from the preset file in the blocks folder
285 *
286 * @since 14.3 Deprecated argument. Only one value is ever used.
287 *
288 * @param null $deprecated No longer used.
289 *
290 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
291 */
292 public static function get_preset( $deprecated = null ) {
293 if ( $deprecated ) {
294 _deprecated_argument( __METHOD__, '14.3', 'The $preset argument is no longer needed or used.' );
295 }
296
297 if ( self::$preset_cache ) {
298 return self::$preset_cache;
299 }
300
301 self::$preset_cache = json_decode(
302 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
303 file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . 'index.json' )
304 );
305 return self::$preset_cache;
306 }
307
308 /**
309 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
310 *
311 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
312 */
313 public static function get_jetpack_gutenberg_extensions_allowed_list() {
314 $preset_extensions_manifest = ( defined( 'TESTING_IN_JETPACK' ) && TESTING_IN_JETPACK ) ? array() : self::get_preset();
315 $blocks_variation = self::blocks_variation();
316
317 return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
318 }
319
320 /**
321 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
322 *
323 * @param array $allowed_extensions An array of allowed extensions.
324 *
325 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
326 */
327 public static function get_available_extensions( $allowed_extensions = null ) {
328 $exclusions = get_option( 'jetpack_excluded_extensions', array() );
329 $allowed_extensions = $allowed_extensions === null ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
330
331 // Avoid errors if option data is not as expected.
332 if ( ! is_array( $exclusions ) ) {
333 $exclusions = array();
334 }
335
336 return array_diff( $allowed_extensions, $exclusions );
337 }
338
339 /**
340 * Return true if the extension has been registered and there's nothing in the availablilty array.
341 *
342 * @param string $extension The name of the extension.
343 *
344 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
345 */
346 public static function is_registered_and_no_entry_in_availability( $extension ) {
347 return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
348 }
349
350 /**
351 * Return true if the extension has a true entry in the availablilty array.
352 *
353 * @param string $extension The name of the extension.
354 *
355 * @return bool whether the extension has a true entry in the availablilty array.
356 */
357 public static function is_available( $extension ) {
358 return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
359 }
360
361 /**
362 * Get the availability of each block / plugin, or return the cached availability
363 * if it has already been calculated. Avoids re-registering extensions when not
364 * necessary.
365 *
366 * @return array A list of block and plugins and their availability status.
367 */
368 public static function get_cached_availability() {
369 if ( null === self::$cached_availability ) {
370 self::$cached_availability = self::get_availability();
371 }
372 return self::$cached_availability;
373 }
374
375 /**
376 * Get availability of each block / plugin.
377 *
378 * @return array A list of block and plugins and their availablity status
379 */
380 public static function get_availability() {
381 /**
382 * Fires before Gutenberg extensions availability is computed.
383 *
384 * In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
385 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
386 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
387 * but marked as unavailable).
388 *
389 * @since 7.0.0
390 */
391 do_action( 'jetpack_register_gutenberg_extensions' );
392
393 $available_extensions = array();
394
395 foreach ( static::get_extensions() as $extension ) {
396 $is_available = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
397 $available_extensions[ $extension ] = array(
398 'available' => $is_available,
399 );
400
401 if ( ! $is_available ) {
402 $reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
403 $details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
404 $available_extensions[ $extension ]['unavailable_reason'] = $reason;
405 $available_extensions[ $extension ]['details'] = $details;
406 }
407 }
408
409 return $available_extensions;
410 }
411
412 /**
413 * Return the list of extensions that are available.
414 *
415 * @since 11.9
416 *
417 * @return array A list of block and plugins and their availability status.
418 */
419 public static function get_extensions() {
420 if ( ! static::should_load() ) {
421 return array();
422 }
423
424 if ( null === self::$extensions ) {
425 /**
426 * Filter the list of block editor extensions that are available through Jetpack.
427 *
428 * @since 7.0.0
429 *
430 * @param array
431 */
432 self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
433
434 if ( ! is_array( self::$extensions ) ) {
435 _doing_it_wrong( __METHOD__, esc_html__( 'The jetpack_set_available_extensions filter must return an array.', 'jetpack' ), '14.9' );
436 self::$extensions = array();
437 }
438 }
439
440 return self::$extensions;
441 }
442
443 /**
444 * Check if an extension/block is already registered
445 *
446 * @since 7.2
447 *
448 * @param string $slug Name of extension/block to check.
449 *
450 * @return bool
451 */
452 public static function is_registered( $slug ) {
453 return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
454 }
455
456 /**
457 * Check if Gutenberg editor is available
458 *
459 * @since 6.7.0
460 *
461 * @return bool
462 */
463 public static function is_gutenberg_available() {
464 return true;
465 }
466
467 /**
468 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
469 *
470 * Loading blocks and plugins is enabled by default and may be disabled via filter:
471 * add_filter( 'jetpack_gutenberg', '__return_false' );
472 *
473 * @since 6.9.0
474 *
475 * @return bool
476 */
477 public static function should_load() {
478 if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
479 return false;
480 }
481
482 $return = true;
483
484 if ( ! ( new Modules() )->is_active( 'blocks' ) ) {
485 $return = false;
486 }
487
488 /**
489 * Filter to enable Gutenberg blocks.
490 *
491 * Defaults to true if (connected or in offline mode) and the Blocks module is active.
492 *
493 * @since 6.5.0
494 * @since 13.9 Filter is able to activate or deactivate Gutenberg blocks.
495 *
496 * @param bool true Whether to load Gutenberg blocks
497 */
498 return (bool) apply_filters( 'jetpack_gutenberg', $return );
499 }
500
501 /**
502 * Queue a script to set `Jetpack_Block_Assets_Base_Url`.
503 *
504 * In certain cases Webpack needs to know a base to load additional assets from.
505 * Normally it can determine that itself, but when JS concatenation is involved that tends to confuse it.
506 * We work around that by explicitly outputting a variable with the correct URL.
507 * We set that as its own "script" so we can reliably only output it once.
508 */
509 private static function register_blocks_assets_base_url() {
510 if ( ! wp_script_is( 'jetpack-blocks-assets-base-url', 'registered' ) ) {
511 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- No actual script, so no version needed.
512 wp_register_script( 'jetpack-blocks-assets-base-url', false, array(), null, array( 'in_footer' => false ) );
513 $json_encode_flags = JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP;
514 if ( get_option( 'blog_charset' ) === 'UTF-8' ) {
515 $json_encode_flags |= JSON_UNESCAPED_UNICODE;
516 }
517 wp_add_inline_script(
518 'jetpack-blocks-assets-base-url',
519 'var Jetpack_Block_Assets_Base_Url=' . wp_json_encode( plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE ), $json_encode_flags ) . ';',
520 'before'
521 );
522 }
523 }
524
525 /**
526 * Only enqueue block assets when needed.
527 *
528 * @param string $type Slug of the block or absolute path to the block source code directory.
529 * @param array $script_dependencies Script dependencies. Will be merged with automatically
530 * detected script dependencies from the webpack build.
531 *
532 * @return void
533 */
534 public static function load_assets_as_required( $type, $script_dependencies = array() ) {
535 if ( is_admin() ) {
536 // A block's view assets will not be required in wp-admin.
537 return;
538 }
539
540 // Retrieve the feature from block.json if a path is passed.
541 if ( path_is_absolute( $type ) ) {
542 $metadata = Blocks::get_block_metadata_from_file( Blocks::get_path_to_block_metadata( $type ) );
543 $feature = Blocks::get_block_feature_from_metadata( $metadata );
544
545 if ( ! empty( $feature ) ) {
546 $type = $feature;
547 }
548 }
549
550 $type = sanitize_title_with_dashes( $type );
551 self::load_styles_as_required( $type );
552 self::load_scripts_as_required( $type, $script_dependencies );
553 }
554
555 /**
556 * Only enqueue block sytles when needed.
557 *
558 * @param string $type Slug of the block.
559 *
560 * @since 7.2.0
561 *
562 * @return void
563 */
564 public static function load_styles_as_required( $type ) {
565 if ( is_admin() ) {
566 // A block's view assets will not be required in wp-admin.
567 return;
568 }
569
570 // Enqueue styles.
571 $style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
572 if ( self::block_has_asset( $style_relative_path ) ) {
573 $style_version = self::get_asset_version( $style_relative_path );
574 $view_style = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
575 $view_style = add_query_arg( 'minify', 'false', $view_style );
576
577 // If this is a customizer preview, render the style directly to the preview after autosave.
578 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
579 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
580 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
581 echo '<link rel="stylesheet" id="jetpack-block-' . esc_attr( $type ) . '" href="' . esc_attr( $view_style ) . '&amp;ver=' . esc_attr( $style_version ) . '" media="all">';
582 } else {
583 wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
584 wp_style_add_data( 'jetpack-block-' . $type, 'path', JETPACK__PLUGIN_DIR . $style_relative_path );
585 }
586 }
587 }
588
589 /**
590 * Only enqueue block scripts when needed.
591 *
592 * @param string $type Slug of the block.
593 * @param array $script_dependencies Script dependencies. Will be merged with automatically
594 * detected script dependencies from the webpack build.
595 *
596 * @since 7.2.0
597 *
598 * @return void
599 */
600 public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
601 if ( is_admin() ) {
602 // A block's view assets will not be required in wp-admin.
603 return;
604 }
605
606 self::register_blocks_assets_base_url();
607
608 // Enqueue script.
609 $script_relative_path = self::get_blocks_directory() . $type . '/view.js';
610 $script_deps_path = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
611 $script_dependencies[] = 'jetpack-blocks-assets-base-url';
612 if ( file_exists( $script_deps_path ) ) {
613 $asset_manifest = include $script_deps_path;
614 $script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
615 }
616
617 if ( ! Blocks::is_amp_request() && self::block_has_asset( $script_relative_path ) ) {
618 $script_version = self::get_asset_version( $script_relative_path );
619 $view_script = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
620 $view_script = add_query_arg( 'minify', 'false', $view_script );
621 $strategy = self::get_block_js_loading_strategy( $type );
622
623 // Enqueue dependencies.
624 wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, $strategy );
625
626 // If this is a customizer preview, enqueue the dependencies and render the script directly to the preview after autosave.
627 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
628 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
629 // The Map block is dependent on wp-element, and it doesn't appear to to be possible to load
630 // this dynamically into the customizer iframe currently.
631 if ( 'map' === $type ) {
632 echo '<div>' . esc_html_e( 'No map preview available. Publish and refresh to see this widget.', 'jetpack' ) . '</div>';
633 echo '<script>';
634 echo 'Array.from(document.getElementsByClassName(\'wp-block-jetpack-map\')).forEach(function(element){element.style.display = \'none\';})';
635 echo '</script>';
636 } else {
637 echo '<script id="jetpack-block-' . esc_attr( $type ) . '" src="' . esc_attr( $view_script ) . '&amp;ver=' . esc_attr( $script_version ) . '"></script>';
638 }
639 }
640 }
641 }
642
643 /**
644 * Check if an asset exists for a block.
645 *
646 * @param string $file Path of the file we are looking for.
647 *
648 * @return bool $block_has_asset Does the file exist.
649 */
650 public static function block_has_asset( $file ) {
651 return file_exists( JETPACK__PLUGIN_DIR . $file );
652 }
653
654 /**
655 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
656 *
657 * @param string $file Path of the file we are looking for.
658 *
659 * @return string $script_version Version number.
660 */
661 public static function get_asset_version( $file ) {
662 return Jetpack::is_development_version() && self::block_has_asset( $file )
663 ? filemtime( JETPACK__PLUGIN_DIR . $file )
664 : JETPACK__VERSION;
665 }
666
667 /**
668 * Load Gutenberg editor assets
669 *
670 * @since 6.7.0
671 *
672 * @return void
673 */
674 public static function enqueue_block_editor_assets() {
675 if ( ! self::should_load() ) {
676 return;
677 }
678
679 $status = new Status();
680
681 // Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
682 if ( ! $status->is_offline_mode() && Jetpack::is_connection_ready() ) {
683 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
684 }
685
686 $blocks_dir = self::get_blocks_directory();
687 $blocks_variation = self::blocks_variation();
688
689 if ( 'production' !== $blocks_variation ) {
690 $blocks_env = '-' . esc_attr( $blocks_variation );
691 } else {
692 $blocks_env = '';
693 }
694
695 self::register_blocks_assets_base_url();
696
697 Assets::register_script(
698 'jetpack-blocks-editor',
699 "{$blocks_dir}editor{$blocks_env}.js",
700 JETPACK__PLUGIN_FILE,
701 array(
702 'textdomain' => 'jetpack',
703 'dependencies' => array( 'jetpack-blocks-assets-base-url' ),
704 )
705 );
706
707 /**
708 * This can be called multiple times per page load in the admin, during the `enqueue_block_assets` action.
709 * These assets are necessary for the admin for editing but are not necessary for each pattern preview.
710 * Therefore we dequeue them, so they don't load for each pattern preview iframe.
711 */
712 if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
713 wp_dequeue_script( 'jp-tracks' );
714 wp_dequeue_script( 'jetpack-blocks-editor' );
715
716 return;
717 }
718
719 // Hack around #20357 (specifically, that the editor bundle depends on
720 // wp-edit-post but wp-edit-post's styles break the Widget Editor and
721 // Site Editor) until a real fix gets unblocked.
722 // @todo Remove this once #20357 is properly fixed.
723 $wp_styles_fix = wp_styles()->query( 'jetpack-blocks-editor', 'registered' );
724 if ( empty( $wp_styles_fix ) ) {
725 wp_die( 'Your installation of Jetpack is incomplete. Please run "jetpack build plugins/jetpack" in your dev env.' );
726 }
727 wp_styles()->query( 'jetpack-blocks-editor', 'registered' )->deps = array();
728
729 Assets::enqueue_script( 'jetpack-blocks-editor' );
730
731 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
732 $user = wp_get_current_user();
733 $user_data = array(
734 'email' => $user->user_email,
735 'userid' => $user->ID,
736 'username' => $user->user_login,
737 );
738 $blog_id = get_current_blog_id();
739 $is_current_user_connected = true;
740 } else {
741 $user_data = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
742 $blog_id = Jetpack_Options::get_option( 'id', 0 );
743 $is_current_user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected();
744 }
745
746 if ( $blocks_variation === 'beta' && $is_current_user_connected ) {
747 wp_enqueue_style( 'recoleta-font', '//s1.wp.com/i/fonts/recoleta/css/400.min.css', array(), Constants::get_constant( 'JETPACK__VERSION' ) );
748 }
749 // AI Assistant
750 $ai_assistant_state = array(
751 'is-enabled' => apply_filters( 'jetpack_ai_enabled', true ),
752 );
753
754 $screen_base = null;
755 if ( function_exists( 'get_current_screen' ) ) {
756 $current_screen = get_current_screen();
757 $screen_base = $current_screen ? $current_screen->base : null;
758 }
759
760 $modules = array();
761 if ( class_exists( 'Jetpack_Core_API_Module_List_Endpoint' ) ) {
762 $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
763 $modules = $module_list_endpoint->get_modules();
764 }
765
766 $jetpack_plan = Jetpack_Plan::get();
767 $initial_state = array(
768 'available_blocks' => self::get_availability(),
769 'blocks_variation' => $blocks_variation,
770 'modules' => $modules,
771 'jetpack' => array(
772 'is_active' => Jetpack::is_connection_ready(),
773 'is_current_user_connected' => $is_current_user_connected,
774 /** This filter is documented in class.jetpack-gutenberg.php */
775 'enable_upgrade_nudge' => apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false ),
776 'is_private_site' => $status->is_private_site(),
777 'is_coming_soon' => $status->is_coming_soon(),
778 'is_offline_mode' => $status->is_offline_mode(),
779 'is_newsletter_feature_enabled' => class_exists( '\Jetpack_Memberships' ),
780 // this is the equivalent of JP initial state siteData.showMyJetpack (class-jetpack-redux-state-helper)
781 // used to determine if we can link to My Jetpack from the block editor
782 'is_my_jetpack_available' => My_Jetpack_Initializer::should_initialize(),
783 'jetpack_plan' => array(
784 'data' => $jetpack_plan['product_slug'],
785 ),
786 /**
787 * Enable the RePublicize UI in the block editor context.
788 *
789 * @module publicize
790 *
791 * @since 10.3.0
792 * @deprecated 11.5 This is a feature flag that is no longer used.
793 *
794 * @param bool true Enable the RePublicize UI in the block editor context. Defaults to true.
795 */
796 'republicize_enabled' => apply_filters( 'jetpack_block_editor_republicize_feature', true ),
797 ),
798 'siteFragment' => $status->get_site_suffix(),
799 'adminUrl' => esc_url( admin_url() ),
800 'tracksUserData' => $user_data,
801 'wpcomBlogId' => $blog_id,
802 'allowedMimeTypes' => wp_get_mime_types(),
803 'siteLocale' => str_replace( '_', '-', get_locale() ),
804 'ai-assistant' => $ai_assistant_state,
805 'screenBase' => $screen_base,
806 /**
807 * Add your own feature flags to the block editor.
808 *
809 * You can access the feature flags in the block editor via hasFeatureFlag( 'your-feature-flag' ) function.
810 *
811 * @since 14.8
812 *
813 * @param array true Enable the RePublicize UI in the block editor context. Defaults to true.
814 */
815 'feature_flags' => apply_filters( 'jetpack_block_editor_feature_flags', array() ),
816 'pluginBasePath' => plugins_url( '', Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) ),
817 );
818
819 wp_localize_script(
820 'jetpack-blocks-editor',
821 'Jetpack_Editor_Initial_State',
822 $initial_state
823 );
824
825 // Adds Connection package initial state.
826 Connection_Initial_State::render_script( 'jetpack-blocks-editor' );
827
828 // Register and enqueue the Jetpack Chrome AI token script
829 wp_register_script(
830 'jetpack-chrome-ai-token',
831 'https://widgets.wp.com/jetpack-chrome-ai/v1/3p-token.js',
832 array(),
833 gmdate( 'Ymd' ) . floor( (int) gmdate( 'G' ) / 12 ), // Cache buster: changes twice daily (morning/afternoon) in case we need to rotate the tokens
834 true
835 );
836 wp_enqueue_script( 'jetpack-chrome-ai-token' );
837 }
838
839 /**
840 * Some blocks do not depend on a specific module,
841 * and can consequently be loaded outside of the usual modules.
842 * We will look for such modules in the extensions/ directory.
843 *
844 * @since 7.1.0
845 * @see wp_common_block_scripts_and_styles()
846 */
847 public static function load_independent_blocks() {
848 if ( self::should_load() ) {
849 /**
850 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
851 * If available, load them.
852 */
853 $directories = array( 'blocks', 'plugins', 'extended-blocks' );
854
855 foreach ( static::get_extensions() as $extension ) {
856 foreach ( $directories as $dirname ) {
857 $path = JETPACK__PLUGIN_DIR . "extensions/{$dirname}/{$extension}/{$extension}.php";
858
859 if ( file_exists( $path ) ) {
860 include_once $path;
861 continue 2;
862 }
863 }
864 }
865 }
866 }
867
868 /**
869 * Loads PHP components of block editor extensions.
870 *
871 * @since 8.9.0
872 */
873 public static function load_block_editor_extensions() {
874 if ( self::should_load() ) {
875 // Block editor extensions to load.
876 $extensions_to_load = array(
877 'extended-blocks',
878 'plugins',
879 );
880
881 // Collect the extension paths.
882 foreach ( $extensions_to_load as $extension_to_load ) {
883 $extensions_folder = glob( JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/*' );
884
885 // Require each of the extension files, in case it exists.
886 foreach ( $extensions_folder as $extension_folder ) {
887 $name = basename( $extension_folder );
888 $extension_file_path = JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/' . $name . '/' . $name . '.php';
889
890 if ( file_exists( $extension_file_path ) ) {
891 include_once $extension_file_path;
892 }
893 }
894 }
895 }
896 }
897
898 /**
899 * Determine whether a site should use the default set of blocks, or a custom set.
900 * Possible variations are currently beta, experimental, and production.
901 *
902 * @since 8.1.0
903 *
904 * @return string $block_varation production|beta|experimental
905 */
906 public static function blocks_variation() {
907 // Default to production blocks.
908 $block_varation = 'production';
909
910 /*
911 * Prefer to use this JETPACK_BLOCKS_VARIATION constant
912 * or the jetpack_blocks_variation filter
913 * to set the block variation in your code.
914 */
915 $default = Constants::get_constant( 'JETPACK_BLOCKS_VARIATION' );
916 if ( ! empty( $default ) && in_array( $default, array( 'beta', 'experimental', 'production' ), true ) ) {
917 $block_varation = $default;
918 }
919
920 /**
921 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
922 *
923 * @since 6.9.0
924 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
925 *
926 * @param boolean
927 */
928 $is_beta = apply_filters_deprecated(
929 'jetpack_load_beta_blocks',
930 array( false ),
931 'jetpack-11.8.0',
932 'jetpack_blocks_variation'
933 );
934
935 /*
936 * Switch to beta blocks if you use the JETPACK_BETA_BLOCKS constant
937 * or the deprecated jetpack_load_beta_blocks filter.
938 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
939 */
940 if (
941 empty( $default )
942 && (
943 $is_beta
944 || Constants::is_true( 'JETPACK_BETA_BLOCKS' )
945 )
946 ) {
947 $block_varation = 'beta';
948 }
949
950 /**
951 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
952 *
953 * @since 6.9.0
954 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
955 *
956 * @param boolean
957 */
958 $is_experimental = apply_filters_deprecated(
959 'jetpack_load_experimental_blocks',
960 array( false ),
961 'jetpack-11.8.0',
962 'jetpack_blocks_variation'
963 );
964
965 /*
966 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant
967 * or the deprecated jetpack_load_experimental_blocks filter.
968 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
969 */
970 if (
971 empty( $default )
972 && (
973 $is_experimental
974 || Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' )
975 )
976 ) {
977 $block_varation = 'experimental';
978 }
979
980 /**
981 * Allow customizing the variation of blocks in use on a site.
982 * Overwrites any previously set values, whether by constant or filter.
983 *
984 * @since 8.1.0
985 *
986 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
987 */
988 return apply_filters( 'jetpack_blocks_variation', $block_varation );
989 }
990
991 /**
992 * Get a list of extensions available for the variation you chose.
993 *
994 * @since 8.1.0
995 *
996 * @param object $preset_extensions_manifest List of extensions available in Jetpack.
997 * @param string $blocks_variation Subset of blocks. production|beta|experimental.
998 *
999 * @return array $preset_extensions Array of extensions for that variation
1000 */
1001 public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
1002 $preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
1003 ? (array) $preset_extensions_manifest->{ $blocks_variation }
1004 : array();
1005
1006 /*
1007 * Experimental and Beta blocks need the production blocks as well.
1008 */
1009 if (
1010 'experimental' === $blocks_variation
1011 || 'beta' === $blocks_variation
1012 ) {
1013 $production_extensions = isset( $preset_extensions_manifest->production )
1014 ? (array) $preset_extensions_manifest->production
1015 : array();
1016
1017 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1018 }
1019
1020 /*
1021 * Beta blocks need the experimental blocks as well.
1022 *
1023 * If you've chosen to see Beta blocks,
1024 * we want to make all blocks available to you:
1025 * - Production
1026 * - Experimental
1027 * - Beta
1028 */
1029 if ( 'beta' === $blocks_variation ) {
1030 $production_extensions = isset( $preset_extensions_manifest->experimental )
1031 ? (array) $preset_extensions_manifest->experimental
1032 : array();
1033
1034 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1035 }
1036
1037 return $preset_extensions;
1038 }
1039
1040 /**
1041 * Validate a URL used in a SSR block.
1042 *
1043 * @since 8.3.0
1044 *
1045 * @param string $url URL saved as an attribute in block.
1046 * @param array $allowed Array of allowed hosts for that block, or regexes to check against.
1047 * @param bool $is_regex Array of regexes matching the URL that could be used in block.
1048 *
1049 * @return bool|string
1050 */
1051 public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
1052 if (
1053 empty( $url )
1054 || ! is_array( $allowed )
1055 || empty( $allowed )
1056 ) {
1057 return false;
1058 }
1059
1060 $url_components = wp_parse_url( $url );
1061
1062 // Bail early if we cannot find a host.
1063 if ( empty( $url_components['host'] ) ) {
1064 return false;
1065 }
1066
1067 // Normalize URL.
1068 $url = sprintf(
1069 '%s://%s%s%s',
1070 isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
1071 $url_components['host'],
1072 isset( $url_components['path'] ) ? $url_components['path'] : '/',
1073 isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
1074 );
1075
1076 if ( ! empty( $url_components['fragment'] ) ) {
1077 $url = $url . '#' . rawurlencode( $url_components['fragment'] );
1078 }
1079
1080 /*
1081 * If we're using an allowed list of hosts,
1082 * check if the URL belongs to one of the domains allowed for that block.
1083 */
1084 if (
1085 false === $is_regex
1086 && in_array( $url_components['host'], $allowed, true )
1087 ) {
1088 return $url;
1089 }
1090
1091 /*
1092 * If we are using an array of regexes to check against,
1093 * loop through that.
1094 */
1095 if ( true === $is_regex ) {
1096 foreach ( $allowed as $regex ) {
1097 if ( 1 === preg_match( $regex, $url ) ) {
1098 return $url;
1099 }
1100 }
1101 }
1102
1103 return false;
1104 }
1105
1106 /**
1107 * Determines whether a preview of the block with an upgrade nudge should
1108 * be displayed for admins on the site frontend.
1109 *
1110 * @since 8.4.0
1111 *
1112 * @param array $availability_for_block The availability for the block.
1113 *
1114 * @return bool
1115 */
1116 public static function should_show_frontend_preview( $availability_for_block ) {
1117 return (
1118 isset( $availability_for_block['details']['required_plan'] )
1119 && current_user_can( 'manage_options' )
1120 && ! is_feed()
1121 );
1122 }
1123
1124 /**
1125 * Output an UpgradeNudge Component on the frontend of a site.
1126 *
1127 * @since 8.4.0
1128 *
1129 * @param string $plan The plan that users need to purchase to make the block work.
1130 *
1131 * @return string
1132 */
1133 public static function upgrade_nudge( $plan ) {
1134 require_once JETPACK__PLUGIN_DIR . '_inc/lib/components.php';
1135 return Jetpack_Components::render_upgrade_nudge(
1136 array(
1137 'plan' => $plan,
1138 )
1139 );
1140 }
1141
1142 /**
1143 * Output a notice within a block.
1144 *
1145 * @since 8.6.0
1146 *
1147 * @param string $message Notice we want to output.
1148 * @param string $status Status of the notice. Can be one of success, info, warning, error. info by default.
1149 * @param string $classes List of CSS classes.
1150 *
1151 * @return string
1152 */
1153 public static function notice( $message, $status = 'info', $classes = '' ) {
1154 if (
1155 empty( $message )
1156 || ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1157 ) {
1158 return '';
1159 }
1160
1161 $color = '';
1162 switch ( $status ) {
1163 case 'success':
1164 $color = '#00a32a';
1165 break;
1166 case 'warning':
1167 $color = '#dba617';
1168 break;
1169 case 'error':
1170 $color = '#d63638';
1171 break;
1172 case 'info':
1173 default:
1174 $color = '#72aee6';
1175 break;
1176 }
1177
1178 return sprintf(
1179 '<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1180 esc_attr( $status ),
1181 wp_kses(
1182 $message,
1183 array(
1184 'br' => array(),
1185 'p' => array(),
1186 'a' => array(
1187 'href' => array(),
1188 'target' => array(),
1189 'rel' => array(),
1190 ),
1191 )
1192 ),
1193 esc_attr( $classes ),
1194 sanitize_hex_color( $color )
1195 );
1196 }
1197
1198 /**
1199 * Retrieve site-specific features for Simple sites.
1200 *
1201 * We're caching the data for the lifetime of the request, because it can be slow to calculate,
1202 * and it can be called multiple times per single request.
1203 *
1204 * We intentionally don't use object caching or any other type of persistent caching,
1205 * in order to avoid complex cache invalidation on subscription addition or removal.
1206 *
1207 * @since 10.7
1208 *
1209 * @return array
1210 */
1211 private static function get_site_specific_features() {
1212 $current_blog_id = get_current_blog_id();
1213
1214 if ( isset( self::$site_specific_features[ $current_blog_id ] ) ) {
1215 return self::$site_specific_features[ $current_blog_id ];
1216 }
1217
1218 if ( ! class_exists( 'Store_Product_List' ) ) {
1219 require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1220 }
1221
1222 $site_specific_features = Store_Product_List::get_site_specific_features_data( $current_blog_id );
1223 self::$site_specific_features[ $current_blog_id ] = $site_specific_features;
1224
1225 return $site_specific_features;
1226 }
1227
1228 /**
1229 * Set the availability of the block as the editor
1230 * is loaded.
1231 *
1232 * @param string $slug Slug of the block.
1233 */
1234 public static function set_availability_for_plan( $slug ) {
1235 $slug = self::remove_extension_prefix( $slug );
1236
1237 if ( Jetpack_Plan::supports( $slug ) ) {
1238 self::set_extension_available( $slug );
1239 return;
1240 }
1241
1242 // Check what's the minimum plan where the feature is available.
1243 $plan = '';
1244 $features_data = array();
1245 $is_simple_site = defined( 'IS_WPCOM' ) && IS_WPCOM;
1246 $is_atomic_site = ( new Host() )->is_woa_site();
1247
1248 if ( $is_simple_site || $is_atomic_site ) {
1249 // Simple sites.
1250 if ( $is_simple_site ) {
1251 $features_data = self::get_site_specific_features();
1252 } else {
1253 // Atomic sites.
1254 $option = get_option( 'jetpack_active_plan' );
1255 if ( isset( $option['features'] ) ) {
1256 $features_data = $option['features'];
1257 }
1258 }
1259
1260 if ( ! empty( $features_data['available'][ $slug ] ) ) {
1261 $plan = $features_data['available'][ $slug ][0];
1262 }
1263 } else {
1264 // Jetpack sites.
1265 $plan = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1266 }
1267
1268 self::set_extension_unavailable(
1269 $slug,
1270 'missing_plan',
1271 array(
1272 'required_feature' => $slug,
1273 'required_plan' => $plan,
1274 )
1275 );
1276 }
1277
1278 /**
1279 * Wraps the suplied render_callback in a function to check
1280 * the availability of the block before rendering it.
1281 *
1282 * @param string $slug The block slug, used to check for availability.
1283 * @param callable $render_callback The render_callback that will be called if the block is available.
1284 */
1285 public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1286 return function ( $prepared_attributes, $block_content, $block ) use ( $render_callback, $slug ) {
1287 $availability = self::get_cached_availability();
1288 $bare_slug = self::remove_extension_prefix( $slug );
1289 if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1290 return call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1291 }
1292
1293 // A preview of the block is rendered for admins on the frontend with an upgrade nudge.
1294 if ( isset( $availability[ $bare_slug ] ) ) {
1295 if ( self::should_show_frontend_preview( $availability[ $bare_slug ] ) ) {
1296 $block_preview = call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1297
1298 // If the upgrade nudge isn't already being displayed by a parent block, display the nudge.
1299 if ( isset( $block->attributes['shouldDisplayFrontendBanner'] ) && $block->attributes['shouldDisplayFrontendBanner'] ) {
1300 $upgrade_nudge = self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1301 return $upgrade_nudge . $block_preview;
1302 }
1303
1304 return $block_preview;
1305 }
1306 }
1307
1308 return null;
1309 };
1310 }
1311
1312 /**
1313 * Display a message to site editors and roles above when a block is no longer supported.
1314 * This is only displayed on the frontend.
1315 *
1316 * @since 12.3
1317 *
1318 * @param string $block_content The block content.
1319 * @param array $block The full block, including name and attributes.
1320 *
1321 * @return string
1322 */
1323 public static function display_deprecated_block_message( $block_content, $block ) {
1324 if ( isset( $block['blockName'] ) && in_array( $block['blockName'], self::$deprecated_blocks, true ) ) {
1325 if ( current_user_can( 'edit_posts' ) ) {
1326 $block_content = self::notice(
1327 __( 'This block is no longer supported. Its contents will no longer be displayed to your visitors and as such this block should be removed.', 'jetpack' ),
1328 'warning',
1329 'jetpack-block-deprecated'
1330 );
1331 } else {
1332 $block_content = '';
1333 }
1334 }
1335
1336 return $block_content;
1337 }
1338
1339 /**
1340 * Temporarily bypasses _doing_it_wrong() notices for block metadata collection registration.
1341 *
1342 * WordPress 6.7 introduced block metadata collections (with strict path validation).
1343 * Any sites using symlinks for plugins will fail the validation which causes the metadata
1344 * collection to not be registered. However, the blocks will still fall back to the regular
1345 * registration and no functionality is affected.
1346 * While this validation is being discussed in WordPress Core (#62140),
1347 * this method allows registration to proceed by temporarily disabling
1348 * the relevant notice.
1349 *
1350 * @since 14.2
1351 *
1352 * @param bool $trigger Whether to trigger the error.
1353 * @param string $function The function that was called.
1354 * @param string $message A message explaining what was done incorrectly.
1355 * @param string $version The version of WordPress where the message was added.
1356 * @return bool Whether to trigger the error.
1357 */
1358 public static function bypass_block_metadata_doing_it_wrong( $trigger, $function, $message, $version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1359 if ( $function === 'WP_Block_Metadata_Registry::register_collection' ) {
1360 return false;
1361 }
1362 return $trigger;
1363 }
1364
1365 /**
1366 * Register block metadata collection for Jetpack blocks.
1367 * This allows for more efficient block metadata loading by avoiding
1368 * individual block.json file reads at runtime.
1369 *
1370 * Uses wp_register_block_metadata_collection() if available (WordPress 6.7+)
1371 * and if the manifest file exists. The manifest file is auto-generated
1372 * during the build process.
1373 *
1374 * Runs on plugins_loaded to ensure registration happens before individual
1375 * blocks register themselves on init.
1376 *
1377 * @static
1378 * @since 14.1
1379 * @return void
1380 */
1381 public static function register_block_metadata_collection() {
1382 $meta_file_path = JETPACK__PLUGIN_DIR . '_inc/blocks/blocks-manifest.php';
1383 if ( function_exists( 'wp_register_block_metadata_collection' ) && file_exists( $meta_file_path ) ) {
1384 add_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10, 4 );
1385
1386 // @phan-suppress-next-line PhanUndeclaredFunction -- New in WP 6.7. We're checking if it exists first. @phan-suppress-current-line UnusedPluginSuppression
1387 wp_register_block_metadata_collection(
1388 JETPACK__PLUGIN_DIR . '_inc/blocks/',
1389 $meta_file_path
1390 );
1391
1392 remove_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10 );
1393 }
1394 }
1395
1396 /**
1397 * Set the JS loading strategy for a block.
1398 *
1399 * @param string $block_name The block name.
1400 * @param array|bool $strategy The JS loading strategy.
1401 *
1402 * @since 15.0
1403 */
1404 public static function set_block_js_loading_strategy( $block_name, $strategy ) {
1405 self::$block_js_loading_strategies[ $block_name ] = $strategy;
1406 }
1407
1408 /**
1409 * Get the JS loading strategy for a block.
1410 *
1411 * @param string $block_name The block name.
1412 *
1413 * @return array|bool The JS loading strategy for the block.
1414 *
1415 * @since 15.0
1416 */
1417 public static function get_block_js_loading_strategy( $block_name ) {
1418 $strategy = array(
1419 'strategy' => 'defer',
1420 'in_footer' => true,
1421 );
1422
1423 if ( isset( self::$block_js_loading_strategies[ $block_name ] ) ) {
1424 $strategy = self::$block_js_loading_strategies[ $block_name ];
1425 }
1426
1427 return $strategy;
1428 }
1429 }
1430
1431 if ( ( new Host() )->is_woa_site() ) {
1432 /**
1433 * Enable upgrade nudge for Atomic sites.
1434 * This feature is false as default,
1435 * so let's enable it through this filter.
1436 *
1437 * More doc: https://github.com/Automattic/jetpack/blob/trunk/projects/plugins/jetpack/extensions/README.md#upgrades-for-blocks
1438 */
1439 add_filter( 'jetpack_block_editor_enable_upgrade_nudge', '__return_true' );
1440 }
1441