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

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