PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 10.0
Jetpack – WP Security, Backup, Speed, & Growth v10.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 in Jetpack – WP Security, Backup, Speed, & Growth 10.0, at class.jetpack-gutenberg.php

1,166 lines 36.2 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\Blocks;
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Constants;
12 use Automattic\Jetpack\Status;
13
14 /**
15 * Wrapper function to safely register a gutenberg block type
16 *
17 * @deprecated 9.1.0 Use Automattic\\Jetpack\\Blocks::jetpack_register_block instead
18 *
19 * @see register_block_type
20 *
21 * @since 6.7.0
22 *
23 * @param string $slug Slug of the block.
24 * @param array $args Arguments that are passed into register_block_type.
25 *
26 * @return WP_Block_Type|false The registered block type on success, or false on failure.
27 */
28 function jetpack_register_block( $slug, $args = array() ) {
29 _deprecated_function( __METHOD__, '9.1.0', 'Automattic\\Jetpack\\Blocks::jetpack_register_block' );
30 return Blocks::jetpack_register_block( $slug, $args );
31 }
32
33 /**
34 * Helper function to register a Jetpack Gutenberg plugin
35 *
36 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
37 *
38 * @param string $slug Slug of the plugin.
39 *
40 * @since 6.9.0
41 *
42 * @return void
43 */
44 function jetpack_register_plugin( $slug ) {
45 _deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
46
47 Jetpack_Gutenberg::register_plugin( $slug );
48 }
49
50 /**
51 * Set the reason why an extension (block or plugin) is unavailable
52 *
53 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_unavailable() instead
54 *
55 * @param string $slug Slug of the block.
56 * @param string $reason A string representation of why the extension is unavailable.
57 *
58 * @since 7.0.0
59 *
60 * @return void
61 */
62 function jetpack_set_extension_unavailability_reason( $slug, $reason ) {
63 _deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
64
65 Jetpack_Gutenberg::set_extension_unavailability_reason( $slug, $reason );
66 }
67
68 /**
69 * General Gutenberg editor specific functionality
70 */
71 class Jetpack_Gutenberg {
72
73 /**
74 * Only these extensions can be registered. Used to control availability of beta blocks.
75 *
76 * @var array Extensions allowed list.
77 */
78 private static $extensions = array();
79
80 /**
81 * Keeps track of the reasons why a given extension is unavailable.
82 *
83 * @var array Extensions availability information
84 */
85 private static $availability = array();
86
87 /**
88 * A cached array of the fully processed availability data. Keeps track of
89 * reasons why an extension is unavailable or missing.
90 *
91 * @var array Extensions availability information.
92 */
93 private static $cached_availability = null;
94
95 /**
96 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
97 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
98 * optionally fall back to that.
99 *
100 * @param array $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
101 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
102 *
103 * @since 8.3.0
104 *
105 * @return boolean True if the version of gutenberg required by the block or plugin is available.
106 */
107 public static function is_gutenberg_version_available( $version_requirements, $slug ) {
108 global $wp_version;
109
110 // Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
111 if ( empty( $version_requirements['gutenberg'] ) ) {
112 return false;
113 }
114
115 // If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
116 if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
117 return true;
118 }
119
120 $version_available = false;
121
122 // If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
123 // with required version of Gutenberg is known check that.
124 if ( defined( 'GUTENBERG_VERSION' ) ) {
125 $version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
126 } elseif ( ! empty( $version_requirements['wp'] ) ) {
127 $version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
128 }
129
130 if ( ! $version_available ) {
131 self::set_extension_unavailable(
132 $slug,
133 'incorrect_gutenberg_version',
134 array(
135 'required_feature' => $slug,
136 'required_version' => $version_requirements,
137 'current_version' => array(
138 'wp' => $wp_version,
139 'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
140 ),
141 )
142 );
143 }
144
145 return $version_available;
146 }
147
148 /**
149 * Prepend the 'jetpack/' prefix to a block name
150 *
151 * @param string $block_name The block name.
152 *
153 * @return string The prefixed block name.
154 */
155 private static function prepend_block_prefix( $block_name ) {
156 return 'jetpack/' . $block_name;
157 }
158
159 /**
160 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
161 *
162 * @param string $extension_name The extension name.
163 *
164 * @return string The unprefixed extension name.
165 */
166 public static function remove_extension_prefix( $extension_name ) {
167 if ( 0 === strpos( $extension_name, 'jetpack/' ) || 0 === strpos( $extension_name, 'jetpack-' ) ) {
168 return substr( $extension_name, strlen( 'jetpack/' ) );
169 }
170 return $extension_name;
171 }
172
173 /**
174 * Whether two arrays share at least one item
175 *
176 * @param array $a An array.
177 * @param array $b Another array.
178 *
179 * @return boolean True if $a and $b share at least one item
180 */
181 protected static function share_items( $a, $b ) {
182 return count( array_intersect( $a, $b ) ) > 0;
183 }
184
185 /**
186 * Register a plugin
187 *
188 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
189 *
190 * @param string $slug Slug of the plugin.
191 */
192 public static function register_plugin( $slug ) {
193 _deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
194
195 self::set_extension_available( $slug );
196 }
197
198 /**
199 * Set a (non-block) extension as available
200 *
201 * @param string $slug Slug of the extension.
202 */
203 public static function set_extension_available( $slug ) {
204 self::$availability[ self::remove_extension_prefix( $slug ) ] = true;
205 }
206
207 /**
208 * Set the reason why an extension (block or plugin) is unavailable
209 *
210 * @param string $slug Slug of the extension.
211 * @param string $reason A string representation of why the extension is unavailable.
212 * @param array $details A free-form array containing more information on why the extension is unavailable.
213 */
214 public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
215 if (
216 // Extensions that require a plan may be eligible for upgrades.
217 'missing_plan' === $reason
218 && (
219 /**
220 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
221 * to disable paid feature upgrade nudges in the block editor.
222 *
223 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
224 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
225 *
226 * @since 7.7.0
227 *
228 * @param boolean
229 */
230 ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
231 /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
232 || ! apply_filters( 'jetpack_show_promotions', true )
233 )
234 ) {
235 // The block editor may apply an upgrade nudge if `missing_plan` is the reason.
236 // Add a descriptive suffix to disable behavior but provide informative reason.
237 $reason .= '__nudge_disabled';
238 }
239
240 self::$availability[ self::remove_extension_prefix( $slug ) ] = array(
241 'reason' => $reason,
242 'details' => $details,
243 );
244 }
245
246 /**
247 * Set the reason why an extension (block or plugin) is unavailable
248 *
249 * @deprecated 7.1.0 Use set_extension_unavailable() instead
250 *
251 * @param string $slug Slug of the extension.
252 * @param string $reason A string representation of why the extension is unavailable.
253 */
254 public static function set_extension_unavailability_reason( $slug, $reason ) {
255 _deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
256
257 self::set_extension_unavailable( $slug, $reason );
258 }
259
260 /**
261 * Set up a list of allowed block editor extensions
262 *
263 * @return void
264 */
265 public static function init() {
266 if ( ! self::should_load() ) {
267 return;
268 }
269
270 /**
271 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
272 *
273 * @since 6.9.0
274 *
275 * @param boolean
276 */
277 if ( apply_filters( 'jetpack_load_beta_blocks', false ) ) {
278 Constants::set_constant( 'JETPACK_BETA_BLOCKS', true );
279 }
280
281 /**
282 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
283 *
284 * @since 8.4.0
285 *
286 * @param boolean
287 */
288 if ( apply_filters( 'jetpack_load_experimental_blocks', false ) ) {
289 Constants::set_constant( 'JETPACK_EXPERIMENTAL_BLOCKS', true );
290 }
291
292 /**
293 * Filter the list of block editor extensions that are available through Jetpack.
294 *
295 * @since 7.0.0
296 *
297 * @param array
298 */
299 self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
300
301 /**
302 * Filter the list of block editor plugins that are available through Jetpack.
303 *
304 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
305 *
306 * @since 6.8.0
307 *
308 * @param array
309 */
310 self::$extensions = apply_filters( 'jetpack_set_available_blocks', self::$extensions );
311
312 /**
313 * Filter the list of block editor plugins that are available through Jetpack.
314 *
315 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
316 *
317 * @since 6.9.0
318 *
319 * @param array
320 */
321 self::$extensions = apply_filters( 'jetpack_set_available_plugins', self::$extensions );
322 }
323
324 /**
325 * Resets the class to its original state
326 *
327 * Used in unit tests
328 *
329 * @return void
330 */
331 public static function reset() {
332 self::$extensions = array();
333 self::$availability = array();
334 self::$cached_availability = null;
335 }
336
337 /**
338 * Return the Gutenberg extensions (blocks and plugins) directory
339 *
340 * @return string The Gutenberg extensions directory
341 */
342 public static function get_blocks_directory() {
343 /**
344 * Filter to select Gutenberg blocks directory
345 *
346 * @since 6.9.0
347 *
348 * @param string default: '_inc/blocks/'
349 */
350 return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
351 }
352
353 /**
354 * Checks for a given .json file in the blocks folder.
355 *
356 * @param string $preset The name of the .json file to look for.
357 *
358 * @return bool True if the file is found.
359 */
360 public static function preset_exists( $preset ) {
361 return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
362 }
363
364 /**
365 * Decodes JSON loaded from a preset file in the blocks folder
366 *
367 * @param string $preset The name of the .json file to load.
368 *
369 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
370 */
371 public static function get_preset( $preset ) {
372 return json_decode(
373 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
374 file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' )
375 );
376 }
377
378 /**
379 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
380 *
381 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
382 */
383 public static function get_jetpack_gutenberg_extensions_allowed_list() {
384 $preset_extensions_manifest = self::preset_exists( 'index' )
385 ? self::get_preset( 'index' )
386 : (object) array();
387 $blocks_variation = self::blocks_variation();
388
389 return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
390 }
391
392 /**
393 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
394 *
395 * @deprecated 8.7.0 Use get_jetpack_gutenberg_extensions_allowed_list()
396 *
397 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
398 */
399 public static function get_jetpack_gutenberg_extensions_whitelist() {
400 _deprecated_function( __FUNCTION__, 'jetpack-8.7.0', 'Jetpack_Gutenberg::get_jetpack_gutenberg_extensions_allowed_list' );
401 return self::get_jetpack_gutenberg_extensions_allowed_list();
402 }
403
404 /**
405 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
406 *
407 * @param array $allowed_extensions An array of allowed extensions.
408 *
409 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
410 */
411 public static function get_available_extensions( $allowed_extensions = null ) {
412 $exclusions = get_option( 'jetpack_excluded_extensions', array() );
413 $allowed_extensions = is_null( $allowed_extensions ) ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
414
415 return array_diff( $allowed_extensions, $exclusions );
416 }
417
418 /**
419 * Return true if the extension has been registered and there's nothing in the availablilty array.
420 *
421 * @param string $extension The name of the extension.
422 *
423 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
424 */
425 public static function is_registered_and_no_entry_in_availability( $extension ) {
426 return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
427 }
428
429 /**
430 * Return true if the extension has a true entry in the availablilty array.
431 *
432 * @param string $extension The name of the extension.
433 *
434 * @return bool whether the extension has a true entry in the availablilty array.
435 */
436 public static function is_available( $extension ) {
437 return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
438 }
439
440 /**
441 * Get the availability of each block / plugin, or return the cached availability
442 * if it has already been calculated. Avoids re-registering extensions when not
443 * necessary.
444 *
445 * @return array A list of block and plugins and their availability status.
446 */
447 public static function get_cached_availability() {
448 if ( null === self::$cached_availability ) {
449 self::$cached_availability = self::get_availability();
450 }
451 return self::$cached_availability;
452 }
453
454 /**
455 * Get availability of each block / plugin.
456 *
457 * @return array A list of block and plugins and their availablity status
458 */
459 public static function get_availability() {
460 /**
461 * Fires before Gutenberg extensions availability is computed.
462 *
463 * In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
464 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
465 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
466 * but marked as unavailable).
467 *
468 * @since 7.0.0
469 */
470 do_action( 'jetpack_register_gutenberg_extensions' );
471
472 $available_extensions = array();
473
474 foreach ( self::$extensions as $extension ) {
475 $is_available = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
476 $available_extensions[ $extension ] = array(
477 'available' => $is_available,
478 );
479
480 if ( ! $is_available ) {
481 $reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
482 $details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
483 $available_extensions[ $extension ]['unavailable_reason'] = $reason;
484 $available_extensions[ $extension ]['details'] = $details;
485 }
486 }
487
488 return $available_extensions;
489 }
490
491 /**
492 * Check if an extension/block is already registered
493 *
494 * @since 7.2
495 *
496 * @param string $slug Name of extension/block to check.
497 *
498 * @return bool
499 */
500 public static function is_registered( $slug ) {
501 return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
502 }
503
504 /**
505 * Check if Gutenberg editor is available
506 *
507 * @since 6.7.0
508 *
509 * @return bool
510 */
511 public static function is_gutenberg_available() {
512 return true;
513 }
514
515 /**
516 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
517 *
518 * Loading blocks and plugins is enabled by default and may be disabled via filter:
519 * add_filter( 'jetpack_gutenberg', '__return_false' );
520 *
521 * @since 6.9.0
522 *
523 * @return bool
524 */
525 public static function should_load() {
526 if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
527 return false;
528 }
529
530 /**
531 * Filter to disable Gutenberg blocks
532 *
533 * @since 6.5.0
534 *
535 * @param bool true Whether to load Gutenberg blocks
536 */
537 return (bool) apply_filters( 'jetpack_gutenberg', true );
538 }
539
540 /**
541 * Only enqueue block assets when needed.
542 *
543 * @param string $type Slug of the block.
544 * @param array $script_dependencies Script dependencies. Will be merged with automatically
545 * detected script dependencies from the webpack build.
546 *
547 * @return void
548 */
549 public static function load_assets_as_required( $type, $script_dependencies = array() ) {
550 if ( is_admin() ) {
551 // A block's view assets will not be required in wp-admin.
552 return;
553 }
554
555 $type = sanitize_title_with_dashes( $type );
556 self::load_styles_as_required( $type );
557 self::load_scripts_as_required( $type, $script_dependencies );
558 }
559
560 /**
561 * Only enqueue block sytles when needed.
562 *
563 * @param string $type Slug of the block.
564 *
565 * @since 7.2.0
566 *
567 * @return void
568 */
569 public static function load_styles_as_required( $type ) {
570 if ( is_admin() ) {
571 // A block's view assets will not be required in wp-admin.
572 return;
573 }
574
575 // Enqueue styles.
576 $style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
577 if ( self::block_has_asset( $style_relative_path ) ) {
578 $style_version = self::get_asset_version( $style_relative_path );
579 $view_style = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
580
581 // If this is a customizer preview, render the style directly to the preview after autosave.
582 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
583 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
584 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
585 echo '<link rel="stylesheet" id="jetpack-block-' . esc_attr( $type ) . '" href="' . esc_attr( $view_style ) . '?ver=' . esc_attr( $style_version ) . '" media="all">';
586 } else {
587 wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
588 }
589 }
590
591 }
592
593 /**
594 * Only enqueue block scripts when needed.
595 *
596 * @param string $type Slug of the block.
597 * @param array $script_dependencies Script dependencies. Will be merged with automatically
598 * detected script dependencies from the webpack build.
599 *
600 * @since 7.2.0
601 *
602 * @return void
603 */
604 public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
605 if ( is_admin() ) {
606 // A block's view assets will not be required in wp-admin.
607 return;
608 }
609
610 // Enqueue script.
611 $script_relative_path = self::get_blocks_directory() . $type . '/view.js';
612 $script_deps_path = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
613 $script_dependencies[] = 'wp-polyfill';
614 if ( file_exists( $script_deps_path ) ) {
615 $asset_manifest = include $script_deps_path;
616 $script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
617 }
618
619 if ( ! Blocks::is_amp_request() && self::block_has_asset( $script_relative_path ) ) {
620 $script_version = self::get_asset_version( $script_relative_path );
621 $view_script = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
622
623 // Enqueue dependencies.
624 wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
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 ) . '?ver=' . esc_attr( $script_version ) . '"></script>';
638 }
639 }
640 }
641
642 wp_localize_script(
643 'jetpack-block-' . $type,
644 'Jetpack_Block_Assets_Base_Url',
645 array(
646 'url' => plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE ),
647 )
648 );
649 }
650
651 /**
652 * Check if an asset exists for a block.
653 *
654 * @param string $file Path of the file we are looking for.
655 *
656 * @return bool $block_has_asset Does the file exist.
657 */
658 public static function block_has_asset( $file ) {
659 return file_exists( JETPACK__PLUGIN_DIR . $file );
660 }
661
662 /**
663 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
664 *
665 * @param string $file Path of the file we are looking for.
666 *
667 * @return string $script_version Version number.
668 */
669 public static function get_asset_version( $file ) {
670 return Jetpack::is_development_version() && self::block_has_asset( $file )
671 ? filemtime( JETPACK__PLUGIN_DIR . $file )
672 : JETPACK__VERSION;
673 }
674
675 /**
676 * Load Gutenberg editor assets
677 *
678 * @since 6.7.0
679 *
680 * @return void
681 */
682 public static function enqueue_block_editor_assets() {
683 if ( ! self::should_load() ) {
684 return;
685 }
686
687 $status = new Status();
688
689 // Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
690 if ( ! $status->is_offline_mode() && Jetpack::is_connection_ready() ) {
691 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
692 }
693
694 $rtl = is_rtl() ? '.rtl' : '';
695 $blocks_dir = self::get_blocks_directory();
696 $blocks_variation = self::blocks_variation();
697
698 if ( 'production' !== $blocks_variation ) {
699 $blocks_env = '-' . esc_attr( $blocks_variation );
700 } else {
701 $blocks_env = '';
702 }
703
704 $editor_script = plugins_url( "{$blocks_dir}editor{$blocks_env}.js", JETPACK__PLUGIN_FILE );
705 $editor_style = plugins_url( "{$blocks_dir}editor{$blocks_env}{$rtl}.css", JETPACK__PLUGIN_FILE );
706
707 $editor_deps_path = JETPACK__PLUGIN_DIR . $blocks_dir . "editor{$blocks_env}.asset.php";
708 $editor_deps = array( 'wp-polyfill' );
709 if ( file_exists( $editor_deps_path ) ) {
710 $asset_manifest = include $editor_deps_path;
711 $editor_deps = $asset_manifest['dependencies'];
712 }
713
714 $version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
715 ? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
716 : JETPACK__VERSION;
717
718 wp_enqueue_script(
719 'jetpack-blocks-editor',
720 $editor_script,
721 $editor_deps,
722 $version,
723 false
724 );
725
726 wp_localize_script(
727 'jetpack-blocks-editor',
728 'Jetpack_Block_Assets_Base_Url',
729 array(
730 'url' => plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE ),
731 )
732 );
733
734 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
735 $user = wp_get_current_user();
736 $user_data = array(
737 'userid' => $user->ID,
738 'username' => $user->user_login,
739 );
740 $blog_id = get_current_blog_id();
741 $is_current_user_connected = true;
742 } else {
743 $user_data = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
744 $blog_id = Jetpack_Options::get_option( 'id', 0 );
745 $is_current_user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected();
746 }
747
748 wp_localize_script(
749 'jetpack-blocks-editor',
750 'Jetpack_Editor_Initial_State',
751 array(
752 'available_blocks' => self::get_availability(),
753 'jetpack' => array(
754 'is_active' => Jetpack::is_connection_ready(),
755 'is_current_user_connected' => $is_current_user_connected,
756 /** This filter is documented in class.jetpack-gutenberg.php */
757 'enable_upgrade_nudge' => apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false ),
758 'is_private_site' => '-1' === get_option( 'blog_public' ),
759 ),
760 'siteFragment' => $status->get_site_suffix(),
761 'adminUrl' => esc_url( admin_url() ),
762 'tracksUserData' => $user_data,
763 'wpcomBlogId' => $blog_id,
764 'allowedMimeTypes' => wp_get_mime_types(),
765 )
766 );
767
768 wp_set_script_translations( 'jetpack-blocks-editor', 'jetpack' );
769
770 wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
771 }
772
773 /**
774 * Some blocks do not depend on a specific module,
775 * and can consequently be loaded outside of the usual modules.
776 * We will look for such modules in the extensions/ directory.
777 *
778 * @since 7.1.0
779 */
780 public static function load_independent_blocks() {
781 if ( self::should_load() ) {
782 /**
783 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
784 * If available, load them.
785 */
786 foreach ( self::$extensions as $extension ) {
787 $extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
788 if ( ! empty( $extension_file_glob ) ) {
789 include_once $extension_file_glob[0];
790 }
791 }
792 }
793 }
794
795 /**
796 * Loads PHP components of extended-blocks.
797 *
798 * @since 8.9.0
799 */
800 public static function load_extended_blocks() {
801 if ( self::should_load() ) {
802 $extended_blocks = glob( JETPACK__PLUGIN_DIR . 'extensions/extended-blocks/*' );
803
804 foreach ( $extended_blocks as $block ) {
805 $name = basename( $block );
806 $path = JETPACK__PLUGIN_DIR . 'extensions/extended-blocks/' . $name . '/' . $name . '.php';
807
808 if ( file_exists( $path ) ) {
809 include_once $path;
810 }
811 }
812 }
813 }
814
815 /**
816 * Get CSS classes for a block.
817 *
818 * @since 7.7.0
819 *
820 * @param string $slug Block slug.
821 * @param array $attr Block attributes.
822 * @param array $extra Potential extra classes you may want to provide.
823 *
824 * @return string $classes List of CSS classes for a block.
825 */
826 public static function block_classes( $slug, $attr, $extra = array() ) {
827 _deprecated_function( __METHOD__, '9.0.0', 'Automattic\\Jetpack\\Blocks::classes' );
828 return Blocks::classes( $slug, $attr, $extra );
829 }
830
831 /**
832 * Determine whether a site should use the default set of blocks, or a custom set.
833 * Possible variations are currently beta, experimental, and production.
834 *
835 * @since 8.1.0
836 *
837 * @return string $block_varation production|beta|experimental
838 */
839 public static function blocks_variation() {
840 // Default to production blocks.
841 $block_varation = 'production';
842
843 if ( Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
844 $block_varation = 'beta';
845 }
846
847 /*
848 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant.
849 */
850 if ( Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' ) ) {
851 $block_varation = 'experimental';
852 }
853
854 /**
855 * Allow customizing the variation of blocks in use on a site.
856 *
857 * @since 8.1.0
858 *
859 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
860 */
861 return apply_filters( 'jetpack_blocks_variation', $block_varation );
862 }
863
864 /**
865 * Get a list of extensions available for the variation you chose.
866 *
867 * @since 8.1.0
868 *
869 * @param obj $preset_extensions_manifest List of extensions available in Jetpack.
870 * @param string $blocks_variation Subset of blocks. production|beta|experimental.
871 *
872 * @return array $preset_extensions Array of extensions for that variation
873 */
874 public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
875 $preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
876 ? (array) $preset_extensions_manifest->{ $blocks_variation }
877 : array();
878
879 /*
880 * Experimental and Beta blocks need the production blocks as well.
881 */
882 if (
883 'experimental' === $blocks_variation
884 || 'beta' === $blocks_variation
885 ) {
886 $production_extensions = isset( $preset_extensions_manifest->production )
887 ? (array) $preset_extensions_manifest->production
888 : array();
889
890 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
891 }
892
893 /*
894 * Beta blocks need the experimental blocks as well.
895 *
896 * If you've chosen to see Beta blocks,
897 * we want to make all blocks available to you:
898 * - Production
899 * - Experimental
900 * - Beta
901 */
902 if ( 'beta' === $blocks_variation ) {
903 $production_extensions = isset( $preset_extensions_manifest->experimental )
904 ? (array) $preset_extensions_manifest->experimental
905 : array();
906
907 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
908 }
909
910 return $preset_extensions;
911 }
912
913 /**
914 * Validate a URL used in a SSR block.
915 *
916 * @since 8.3.0
917 *
918 * @param string $url URL saved as an attribute in block.
919 * @param array $allowed Array of allowed hosts for that block, or regexes to check against.
920 * @param bool $is_regex Array of regexes matching the URL that could be used in block.
921 *
922 * @return bool|string
923 */
924 public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
925 if (
926 empty( $url )
927 || ! is_array( $allowed )
928 || empty( $allowed )
929 ) {
930 return false;
931 }
932
933 $url_components = wp_parse_url( $url );
934
935 // Bail early if we cannot find a host.
936 if ( empty( $url_components['host'] ) ) {
937 return false;
938 }
939
940 // Normalize URL.
941 $url = sprintf(
942 '%s://%s%s%s',
943 isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
944 $url_components['host'],
945 isset( $url_components['path'] ) ? $url_components['path'] : '/',
946 isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
947 );
948
949 if ( ! empty( $url_components['fragment'] ) ) {
950 $url = $url . '#' . rawurlencode( $url_components['fragment'] );
951 }
952
953 /*
954 * If we're using an allowed list of hosts,
955 * check if the URL belongs to one of the domains allowed for that block.
956 */
957 if (
958 false === $is_regex
959 && in_array( $url_components['host'], $allowed, true )
960 ) {
961 return $url;
962 }
963
964 /*
965 * If we are using an array of regexes to check against,
966 * loop through that.
967 */
968 if ( true === $is_regex ) {
969 foreach ( $allowed as $regex ) {
970 if ( 1 === preg_match( $regex, $url ) ) {
971 return $url;
972 }
973 }
974 }
975
976 return false;
977 }
978
979 /**
980 * Determines whether a preview of the block with an upgrade nudge should
981 * be displayed for admins on the site frontend.
982 *
983 * @since 8.4.0
984 *
985 * @param array $availability_for_block The availability for the block.
986 *
987 * @return bool
988 */
989 public static function should_show_frontend_preview( $availability_for_block ) {
990 return (
991 isset( $availability_for_block['details']['required_plan'] )
992 && current_user_can( 'manage_options' )
993 && ! is_feed()
994 );
995 }
996
997 /**
998 * Output an UpgradeNudge Component on the frontend of a site.
999 *
1000 * @since 8.4.0
1001 *
1002 * @param string $plan The plan that users need to purchase to make the block work.
1003 *
1004 * @return string
1005 */
1006 public static function upgrade_nudge( $plan ) {
1007 jetpack_require_lib( 'components' );
1008 return Jetpack_Components::render_upgrade_nudge(
1009 array(
1010 'plan' => $plan,
1011 )
1012 );
1013 }
1014
1015 /**
1016 * Output a notice within a block.
1017 *
1018 * @since 8.6.0
1019 *
1020 * @param string $message Notice we want to output.
1021 * @param string $status Status of the notice. Can be one of success, info, warning, error. info by default.
1022 * @param string $classes List of CSS classes.
1023 *
1024 * @return string
1025 */
1026 public static function notice( $message, $status = 'info', $classes = '' ) {
1027 if (
1028 empty( $message )
1029 || ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1030 ) {
1031 return '';
1032 }
1033
1034 $color = '';
1035 switch ( $status ) {
1036 case 'success':
1037 $color = '#00a32a';
1038 break;
1039 case 'warning':
1040 $color = '#dba617';
1041 break;
1042 case 'error':
1043 $color = '#d63638';
1044 break;
1045 case 'info':
1046 default:
1047 $color = '#72aee6';
1048 break;
1049 }
1050
1051 return sprintf(
1052 '<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1053 esc_attr( $status ),
1054 wp_kses(
1055 $message,
1056 array(
1057 'br' => array(),
1058 'p' => array(),
1059 )
1060 ),
1061 esc_attr( $classes ),
1062 sanitize_hex_color( $color )
1063 );
1064 }
1065
1066 /**
1067 * Set the availability of the block as the editor
1068 * is loaded.
1069 *
1070 * @param string $slug Slug of the block.
1071 */
1072 public static function set_availability_for_plan( $slug ) {
1073 $is_available = true;
1074 $plan = '';
1075 $slug = self::remove_extension_prefix( $slug );
1076 $features_data = array();
1077 $is_simple_site = defined( 'IS_WPCOM' ) && IS_WPCOM;
1078 $is_atomic_site = jetpack_is_atomic_site();
1079
1080 // Check feature availability for Simple and Atomic sites.
1081 if ( $is_simple_site || $is_atomic_site ) {
1082
1083 // Simple sites.
1084 if ( $is_simple_site ) {
1085 if ( ! class_exists( 'Store_Product_List' ) ) {
1086 require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1087 }
1088 $features_data = Store_Product_List::get_site_specific_features_data();
1089 } else {
1090 // Atomic sites.
1091 $option = get_option( 'jetpack_active_plan' );
1092 if ( isset( $option['features'] ) ) {
1093 $features_data = $option['features'];
1094 }
1095 }
1096
1097 $is_available = isset( $features_data['active'] ) && in_array( $slug, $features_data['active'], true );
1098 if ( ! empty( $features_data['available'][ $slug ] ) ) {
1099 $plan = $features_data['available'][ $slug ][0];
1100 }
1101 } else {
1102 // Jetpack sites.
1103 $is_available = Jetpack_Plan::supports( $slug );
1104 $plan = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1105 }
1106
1107 if ( $is_available ) {
1108 self::set_extension_available( $slug );
1109 } else {
1110 self::set_extension_unavailable(
1111 $slug,
1112 'missing_plan',
1113 array(
1114 'required_feature' => $slug,
1115 'required_plan' => $plan,
1116 )
1117 );
1118 }
1119 }
1120
1121 /**
1122 * Wraps the suplied render_callback in a function to check
1123 * the availability of the block before rendering it.
1124 *
1125 * @param string $slug The block slug, used to check for availability.
1126 * @param callable $render_callback The render_callback that will be called if the block is available.
1127 */
1128 public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1129 return function ( $prepared_attributes, $block_content, $block ) use ( $render_callback, $slug ) {
1130 $availability = self::get_cached_availability();
1131 $bare_slug = self::remove_extension_prefix( $slug );
1132 if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1133 return call_user_func( $render_callback, $prepared_attributes, $block_content );
1134 }
1135
1136 // A preview of the block is rendered for admins on the frontend with an upgrade nudge.
1137 if ( isset( $availability[ $bare_slug ] ) ) {
1138 if ( self::should_show_frontend_preview( $availability[ $bare_slug ] ) ) {
1139 $block_preview = call_user_func( $render_callback, $prepared_attributes, $block_content );
1140
1141 // If the upgrade nudge isn't already being displayed by a parent block, display the nudge.
1142 if ( isset( $block->attributes['shouldDisplayFrontendBanner'] ) && $block->attributes['shouldDisplayFrontendBanner'] ) {
1143 $upgrade_nudge = self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1144 return $upgrade_nudge . $block_preview;
1145 }
1146
1147 return $block_preview;
1148 }
1149 }
1150
1151 return null;
1152 };
1153 }
1154 }
1155
1156 /*
1157 * Enable upgrade nudge for Atomic sites.
1158 * This feature is false as default,
1159 * so let's enable it through this filter.
1160 *
1161 * More doc: https://github.com/Automattic/jetpack/tree/master/projects/plugins/jetpack/extensions#upgrades-for-blocks
1162 */
1163 if ( jetpack_is_atomic_site() ) {
1164 add_filter( 'jetpack_block_editor_enable_upgrade_nudge', '__return_true' );
1165 }
1166