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

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