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

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