<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
* Handles server-side registration and use of all blocks and plugins available in Jetpack for the block editor, aka Gutenberg.
* Works in tandem with client-side block registration via `index.json`
* @package automattic/jetpack
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Blocks;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
if ( ! defined( 'ABSPATH' ) ) {
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move the functions and such to some other file.
* General Gutenberg editor specific functionality
class Jetpack_Gutenberg {
* Only these extensions can be registered. Used to control availability of beta blocks.
* @var array|null Extensions allowed list or `null` if not initialized yet.
* @see static::get_extensions()
private static $extensions = null;
* Keeps track of the reasons why a given extension is unavailable.
* @var array Extensions availability information
private static $availability = array();
* A cached array of the fully processed availability data. Keeps track of
* reasons why an extension is unavailable or missing.
* @var array Extensions availability information.
private static $cached_availability = null;
* Site-specific features available.
* Their calculation can be expensive and slow, so we're caching it for the request.
* @var array Site-specific features
private static $site_specific_features = array();
* List of deprecated blocks.
* @var array List of deprecated blocks.
private static $deprecated_blocks = array(
* Storing the contents of the preset file.
* Already been json_decode.
* @var null|object JSON decoded object after first usage.
private static $preset_cache = null;
* Keep track of JS loading strategies for each block that needs it.
* @var array<string, array|bool>
private static $block_js_loading_strategies = array();
* Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
* php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
* optionally fall back to that.
* @param array $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
* @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
* @return boolean True if the version of gutenberg required by the block or plugin is available.
public static function is_gutenberg_version_available( $version_requirements, $slug ) {
// Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
if ( empty( $version_requirements['gutenberg'] ) ) {
// If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
$version_available = false;
// If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
// with required version of Gutenberg is known check that.
if ( defined( 'GUTENBERG_VERSION' ) ) {
$version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
} elseif ( ! empty( $version_requirements['wp'] ) ) {
$version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
if ( ! $version_available ) {
$slug = self::remove_extension_prefix( $slug );
self::set_extension_unavailable(
'incorrect_gutenberg_version',
'required_feature' => $slug,
'required_version' => $version_requirements,
'current_version' => array(
'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
return $version_available;
* Prepend the 'jetpack/' prefix to a block name
* @param string $block_name The block name.
* @return string The prefixed block name.
private static function prepend_block_prefix( $block_name ) {
return 'jetpack/' . $block_name;
* Remove the 'jetpack/' or jetpack-' prefix from an extension name
* @param string $extension_name The extension name.
* @return string The unprefixed extension name.
public static function remove_extension_prefix( $extension_name ) {
if ( str_starts_with( $extension_name, 'jetpack/' ) || str_starts_with( $extension_name, 'jetpack-' ) ) {
return substr( $extension_name, strlen( 'jetpack/' ) );
* Whether two arrays share at least one item
* @param array $a An array.
* @param array $b Another array.
* @return boolean True if $a and $b share at least one item
protected static function share_items( $a, $b ) {
return array_intersect( $a, $b ) !== array();
* Set a (non-block) extension as available
* @param string $slug Slug of the extension.
public static function set_extension_available( $slug ) {
$slug = self::remove_extension_prefix( $slug );
self::$availability[ $slug ] = true;
* Set the reason why an extension (block or plugin) is unavailable
* @param string $slug Slug of the extension.
* @param string $reason A string representation of why the extension is unavailable.
* @param array $details A free-form array containing more information on why the extension is unavailable.
public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
// Extensions that require a plan may be eligible for upgrades.
'missing_plan' === $reason
* Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
* to disable paid feature upgrade nudges in the block editor.
* When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
* See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
|| ! apply_filters( 'jetpack_show_promotions', true )
// The block editor may apply an upgrade nudge if `missing_plan` is the reason.
// Add a descriptive suffix to disable behavior but provide informative reason.
$reason .= '__nudge_disabled';
$slug = self::remove_extension_prefix( $slug );
self::$availability[ $slug ] = array(
* Used to initialize the class, no longer in use.
* @deprecated 12.2 No longer needed.
public static function init() {
_deprecated_function( __METHOD__, '12.2' );
* Resets the class to its original state
public static function reset() {
self::$extensions = null;
self::$availability = array();
self::$cached_availability = null;
self::$block_js_loading_strategies = array();
* Return the Gutenberg extensions (blocks and plugins) directory
* @return string The Gutenberg extensions directory
public static function get_blocks_directory() {
* Filter to select Gutenberg blocks directory
* @param string default: '_inc/blocks/'
return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
* Checks for a given .json file in the blocks folder.
* @param string $preset The name of the .json file to look for.
* @return bool True if the file is found.
public static function preset_exists( $preset ) {
_deprecated_function( __METHOD__, '14.3' );
return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
* Decodes JSON loaded from the preset file in the blocks folder
* @since 14.3 Deprecated argument. Only one value is ever used.
* @param null $deprecated No longer used.
* @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
public static function get_preset( $deprecated = null ) {
_deprecated_argument( __METHOD__, '14.3', 'The $preset argument is no longer needed or used.' );
if ( self::$preset_cache ) {
return self::$preset_cache;
self::$preset_cache = json_decode(
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . 'index.json' )
return self::$preset_cache;
* Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
* @return array A list of blocks: eg [ 'publicize', 'markdown' ]
public static function get_jetpack_gutenberg_extensions_allowed_list() {
$preset_extensions_manifest = ( defined( 'TESTING_IN_JETPACK' ) && TESTING_IN_JETPACK ) ? array() : self::get_preset();
$blocks_variation = self::blocks_variation();
return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
* Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
* @param array $allowed_extensions An array of allowed extensions.
* @return array A list of blocks: eg array( 'publicize', 'markdown' )
public static function get_available_extensions( $allowed_extensions = null ) {
$exclusions = get_option( 'jetpack_excluded_extensions', array() );
$allowed_extensions = $allowed_extensions === null ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
// Avoid errors if option data is not as expected.
if ( ! is_array( $exclusions ) ) {
return array_diff( $allowed_extensions, $exclusions );
* Return true if the extension has been registered and there's nothing in the availablilty array.
* @param string $extension The name of the extension.
* @return bool whether the extension has been registered and there's nothing in the availablilty array.
public static function is_registered_and_no_entry_in_availability( $extension ) {
return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
* Return true if the extension has a true entry in the availablilty array.
* @param string $extension The name of the extension.
* @return bool whether the extension has a true entry in the availablilty array.
public static function is_available( $extension ) {
return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
* Get the availability of each block / plugin, or return the cached availability
* if it has already been calculated. Avoids re-registering extensions when not
* @return array A list of block and plugins and their availability status.
public static function get_cached_availability() {
if ( null === self::$cached_availability ) {
self::$cached_availability = self::get_availability();
return self::$cached_availability;
* Get availability of each block / plugin.
* @return array A list of block and plugins and their availablity status
public static function get_availability() {
* Fires before Gutenberg extensions availability is computed.
* In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
* Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
* `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
* but marked as unavailable).
do_action( 'jetpack_register_gutenberg_extensions' );
$available_extensions = array();
foreach ( static::get_extensions() as $extension ) {
$is_available = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
$available_extensions[ $extension ] = array(
'available' => $is_available,
$reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
$details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
$available_extensions[ $extension ]['unavailable_reason'] = $reason;
$available_extensions[ $extension ]['details'] = $details;
return $available_extensions;
* Return the list of extensions that are available.
* @return array A list of block and plugins and their availability status.
public static function get_extensions() {
if ( ! static::should_load() ) {
if ( null === self::$extensions ) {
* Filter the list of block editor extensions that are available through Jetpack.
self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
if ( ! is_array( self::$extensions ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'The jetpack_set_available_extensions filter must return an array.', 'jetpack' ), '14.9' );
self::$extensions = array();
return self::$extensions;
* Check if an extension/block is already registered
* @param string $slug Name of extension/block to check.
public static function is_registered( $slug ) {
return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
* Check if Gutenberg editor is available
public static function is_gutenberg_available() {
* Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
* Loading blocks and plugins is enabled by default and may be disabled via filter:
* add_filter( 'jetpack_gutenberg', '__return_false' );
public static function should_load() {
if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
if ( ! ( new Modules() )->is_active( 'blocks' ) ) {
* Filter to enable Gutenberg blocks.
* Defaults to true if (connected or in offline mode) and the Blocks module is active.
* @since 13.9 Filter is able to activate or deactivate Gutenberg blocks.
* @param bool true Whether to load Gutenberg blocks
return (bool) apply_filters( 'jetpack_gutenberg', $return );