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