wp/wp-includes/blocks.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    15  * @param string $asset_handle_or_path Asset handle or prefixed path.
    15  * @param string $asset_handle_or_path Asset handle or prefixed path.
    16  * @return string Path without the prefix or the original value.
    16  * @return string Path without the prefix or the original value.
    17  */
    17  */
    18 function remove_block_asset_path_prefix( $asset_handle_or_path ) {
    18 function remove_block_asset_path_prefix( $asset_handle_or_path ) {
    19 	$path_prefix = 'file:';
    19 	$path_prefix = 'file:';
    20 	if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) {
    20 	if ( ! str_starts_with( $asset_handle_or_path, $path_prefix ) ) {
    21 		return $asset_handle_or_path;
    21 		return $asset_handle_or_path;
    22 	}
    22 	}
    23 	$path = substr(
    23 	$path = substr(
    24 		$asset_handle_or_path,
    24 		$asset_handle_or_path,
    25 		strlen( $path_prefix )
    25 		strlen( $path_prefix )
    26 	);
    26 	);
    27 	if ( strpos( $path, './' ) === 0 ) {
    27 	if ( str_starts_with( $path, './' ) ) {
    28 		$path = substr( $path, 2 );
    28 		$path = substr( $path, 2 );
    29 	}
    29 	}
    30 	return $path;
    30 	return $path;
    31 }
    31 }
    32 
    32 
    33 /**
    33 /**
    34  * Generates the name for an asset based on the name of the block
    34  * Generates the name for an asset based on the name of the block
    35  * and the field name provided.
    35  * and the field name provided.
    36  *
    36  *
    37  * @since 5.5.0
    37  * @since 5.5.0
       
    38  * @since 6.1.0 Added `$index` parameter.
       
    39  * @since 6.5.0 Added support for `viewScriptModule` field.
    38  *
    40  *
    39  * @param string $block_name Name of the block.
    41  * @param string $block_name Name of the block.
    40  * @param string $field_name Name of the metadata field.
    42  * @param string $field_name Name of the metadata field.
       
    43  * @param int    $index      Optional. Index of the asset when multiple items passed.
       
    44  *                           Default 0.
    41  * @return string Generated asset name for the block's field.
    45  * @return string Generated asset name for the block's field.
    42  */
    46  */
    43 function generate_block_asset_handle( $block_name, $field_name ) {
    47 function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) {
    44 	if ( 0 === strpos( $block_name, 'core/' ) ) {
    48 	if ( str_starts_with( $block_name, 'core/' ) ) {
    45 		$asset_handle = str_replace( 'core/', 'wp-block-', $block_name );
    49 		$asset_handle = str_replace( 'core/', 'wp-block-', $block_name );
    46 		if ( 0 === strpos( $field_name, 'editor' ) ) {
    50 		if ( str_starts_with( $field_name, 'editor' ) ) {
    47 			$asset_handle .= '-editor';
    51 			$asset_handle .= '-editor';
    48 		}
    52 		}
    49 		if ( 0 === strpos( $field_name, 'view' ) ) {
    53 		if ( str_starts_with( $field_name, 'view' ) ) {
    50 			$asset_handle .= '-view';
    54 			$asset_handle .= '-view';
    51 		}
    55 		}
       
    56 		if ( str_ends_with( strtolower( $field_name ), 'scriptmodule' ) ) {
       
    57 			$asset_handle .= '-script-module';
       
    58 		}
       
    59 		if ( $index > 0 ) {
       
    60 			$asset_handle .= '-' . ( $index + 1 );
       
    61 		}
    52 		return $asset_handle;
    62 		return $asset_handle;
    53 	}
    63 	}
    54 
    64 
    55 	$field_mappings = array(
    65 	$field_mappings = array(
    56 		'editorScript' => 'editor-script',
    66 		'editorScript'     => 'editor-script',
    57 		'script'       => 'script',
    67 		'editorStyle'      => 'editor-style',
    58 		'viewScript'   => 'view-script',
    68 		'script'           => 'script',
    59 		'editorStyle'  => 'editor-style',
    69 		'style'            => 'style',
    60 		'style'        => 'style',
    70 		'viewScript'       => 'view-script',
       
    71 		'viewScriptModule' => 'view-script-module',
       
    72 		'viewStyle'        => 'view-style',
    61 	);
    73 	);
    62 	return str_replace( '/', '-', $block_name ) .
    74 	$asset_handle   = str_replace( '/', '-', $block_name ) .
    63 		'-' . $field_mappings[ $field_name ];
    75 		'-' . $field_mappings[ $field_name ];
    64 }
    76 	if ( $index > 0 ) {
    65 
    77 		$asset_handle .= '-' . ( $index + 1 );
    66 /**
    78 	}
    67  * Finds a script handle for the selected block metadata field. It detects
    79 	return $asset_handle;
    68  * when a path to file was provided and finds a corresponding asset file
    80 }
    69  * with details necessary to register the script under automatically
    81 
    70  * generated handle name. It returns unprocessed script handle otherwise.
    82 /**
    71  *
    83  * Gets the URL to a block asset.
    72  * @since 5.5.0
    84  *
       
    85  * @since 6.4.0
       
    86  *
       
    87  * @param string $path A normalized path to a block asset.
       
    88  * @return string|false The URL to the block asset or false on failure.
       
    89  */
       
    90 function get_block_asset_url( $path ) {
       
    91 	if ( empty( $path ) ) {
       
    92 		return false;
       
    93 	}
       
    94 
       
    95 	// Path needs to be normalized to work in Windows env.
       
    96 	static $wpinc_path_norm = '';
       
    97 	if ( ! $wpinc_path_norm ) {
       
    98 		$wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) );
       
    99 	}
       
   100 
       
   101 	if ( str_starts_with( $path, $wpinc_path_norm ) ) {
       
   102 		return includes_url( str_replace( $wpinc_path_norm, '', $path ) );
       
   103 	}
       
   104 
       
   105 	static $template_paths_norm = array();
       
   106 
       
   107 	$template = get_template();
       
   108 	if ( ! isset( $template_paths_norm[ $template ] ) ) {
       
   109 		$template_paths_norm[ $template ] = wp_normalize_path( realpath( get_template_directory() ) );
       
   110 	}
       
   111 
       
   112 	if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $template ] ) ) ) {
       
   113 		return get_theme_file_uri( str_replace( $template_paths_norm[ $template ], '', $path ) );
       
   114 	}
       
   115 
       
   116 	if ( is_child_theme() ) {
       
   117 		$stylesheet = get_stylesheet();
       
   118 		if ( ! isset( $template_paths_norm[ $stylesheet ] ) ) {
       
   119 			$template_paths_norm[ $stylesheet ] = wp_normalize_path( realpath( get_stylesheet_directory() ) );
       
   120 		}
       
   121 
       
   122 		if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $stylesheet ] ) ) ) {
       
   123 			return get_theme_file_uri( str_replace( $template_paths_norm[ $stylesheet ], '', $path ) );
       
   124 		}
       
   125 	}
       
   126 
       
   127 	return plugins_url( basename( $path ), $path );
       
   128 }
       
   129 
       
   130 /**
       
   131  * Finds a script module ID for the selected block metadata field. It detects
       
   132  * when a path to file was provided and optionally finds a corresponding asset
       
   133  * file with details necessary to register the script module under with an
       
   134  * automatically generated module ID. It returns unprocessed script module
       
   135  * ID otherwise.
       
   136  *
       
   137  * @since 6.5.0
    73  *
   138  *
    74  * @param array  $metadata   Block metadata.
   139  * @param array  $metadata   Block metadata.
    75  * @param string $field_name Field name to pick from metadata.
   140  * @param string $field_name Field name to pick from metadata.
       
   141  * @param int    $index      Optional. Index of the script module ID to register when multiple
       
   142  *                           items passed. Default 0.
       
   143  * @return string|false Script module ID or false on failure.
       
   144  */
       
   145 function register_block_script_module_id( $metadata, $field_name, $index = 0 ) {
       
   146 	if ( empty( $metadata[ $field_name ] ) ) {
       
   147 		return false;
       
   148 	}
       
   149 
       
   150 	$module_id = $metadata[ $field_name ];
       
   151 	if ( is_array( $module_id ) ) {
       
   152 		if ( empty( $module_id[ $index ] ) ) {
       
   153 			return false;
       
   154 		}
       
   155 		$module_id = $module_id[ $index ];
       
   156 	}
       
   157 
       
   158 	$module_path = remove_block_asset_path_prefix( $module_id );
       
   159 	if ( $module_id === $module_path ) {
       
   160 		return $module_id;
       
   161 	}
       
   162 
       
   163 	$path                  = dirname( $metadata['file'] );
       
   164 	$module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) );
       
   165 	$module_id             = generate_block_asset_handle( $metadata['name'], $field_name, $index );
       
   166 	$module_asset_path     = wp_normalize_path(
       
   167 		realpath( $module_asset_raw_path )
       
   168 	);
       
   169 
       
   170 	$module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) );
       
   171 	$module_uri       = get_block_asset_url( $module_path_norm );
       
   172 
       
   173 	$module_asset        = ! empty( $module_asset_path ) ? require $module_asset_path : array();
       
   174 	$module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array();
       
   175 	$block_version       = isset( $metadata['version'] ) ? $metadata['version'] : false;
       
   176 	$module_version      = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version;
       
   177 
       
   178 	wp_register_script_module(
       
   179 		$module_id,
       
   180 		$module_uri,
       
   181 		$module_dependencies,
       
   182 		$module_version
       
   183 	);
       
   184 
       
   185 	return $module_id;
       
   186 }
       
   187 
       
   188 /**
       
   189  * Finds a script handle for the selected block metadata field. It detects
       
   190  * when a path to file was provided and optionally finds a corresponding asset
       
   191  * file with details necessary to register the script under automatically
       
   192  * generated handle name. It returns unprocessed script handle otherwise.
       
   193  *
       
   194  * @since 5.5.0
       
   195  * @since 6.1.0 Added `$index` parameter.
       
   196  * @since 6.5.0 The asset file is optional. Added script handle support in the asset file.
       
   197  *
       
   198  * @param array  $metadata   Block metadata.
       
   199  * @param string $field_name Field name to pick from metadata.
       
   200  * @param int    $index      Optional. Index of the script to register when multiple items passed.
       
   201  *                           Default 0.
    76  * @return string|false Script handle provided directly or created through
   202  * @return string|false Script handle provided directly or created through
    77  *                      script's registration, or false on failure.
   203  *                      script's registration, or false on failure.
    78  */
   204  */
    79 function register_block_script_handle( $metadata, $field_name ) {
   205 function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
    80 	if ( empty( $metadata[ $field_name ] ) ) {
   206 	if ( empty( $metadata[ $field_name ] ) ) {
    81 		return false;
   207 		return false;
    82 	}
   208 	}
    83 	$script_handle = $metadata[ $field_name ];
   209 
    84 	$script_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
   210 	$script_handle_or_path = $metadata[ $field_name ];
    85 	if ( $script_handle === $script_path ) {
   211 	if ( is_array( $script_handle_or_path ) ) {
       
   212 		if ( empty( $script_handle_or_path[ $index ] ) ) {
       
   213 			return false;
       
   214 		}
       
   215 		$script_handle_or_path = $script_handle_or_path[ $index ];
       
   216 	}
       
   217 
       
   218 	$script_path = remove_block_asset_path_prefix( $script_handle_or_path );
       
   219 	if ( $script_handle_or_path === $script_path ) {
       
   220 		return $script_handle_or_path;
       
   221 	}
       
   222 
       
   223 	$path                  = dirname( $metadata['file'] );
       
   224 	$script_asset_raw_path = $path . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) );
       
   225 	$script_asset_path     = wp_normalize_path(
       
   226 		realpath( $script_asset_raw_path )
       
   227 	);
       
   228 
       
   229 	// Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460.
       
   230 	$script_asset  = ! empty( $script_asset_path ) ? require $script_asset_path : array();
       
   231 	$script_handle = isset( $script_asset['handle'] ) ?
       
   232 		$script_asset['handle'] :
       
   233 		generate_block_asset_handle( $metadata['name'], $field_name, $index );
       
   234 	if ( wp_script_is( $script_handle, 'registered' ) ) {
    86 		return $script_handle;
   235 		return $script_handle;
    87 	}
   236 	}
    88 
   237 
    89 	$script_handle     = generate_block_asset_handle( $metadata['name'], $field_name );
   238 	$script_path_norm    = wp_normalize_path( realpath( $path . '/' . $script_path ) );
    90 	$script_asset_path = wp_normalize_path(
   239 	$script_uri          = get_block_asset_url( $script_path_norm );
    91 		realpath(
       
    92 			dirname( $metadata['file'] ) . '/' .
       
    93 			substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
       
    94 		)
       
    95 	);
       
    96 	if ( ! file_exists( $script_asset_path ) ) {
       
    97 		_doing_it_wrong(
       
    98 			__FUNCTION__,
       
    99 			sprintf(
       
   100 				/* translators: 1: Field name, 2: Block name. */
       
   101 				__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.' ),
       
   102 				$field_name,
       
   103 				$metadata['name']
       
   104 			),
       
   105 			'5.5.0'
       
   106 		);
       
   107 		return false;
       
   108 	}
       
   109 	// Path needs to be normalized to work in Windows env.
       
   110 	$wpinc_path_norm  = wp_normalize_path( realpath( ABSPATH . WPINC ) );
       
   111 	$theme_path_norm  = wp_normalize_path( get_theme_file_path() );
       
   112 	$script_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $script_path ) );
       
   113 	$is_core_block    = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm );
       
   114 	$is_theme_block   = 0 === strpos( $script_path_norm, $theme_path_norm );
       
   115 
       
   116 	$script_uri = plugins_url( $script_path, $metadata['file'] );
       
   117 	if ( $is_core_block ) {
       
   118 		$script_uri = includes_url( str_replace( $wpinc_path_norm, '', $script_path_norm ) );
       
   119 	} elseif ( $is_theme_block ) {
       
   120 		$script_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $script_path_norm ) );
       
   121 	}
       
   122 
       
   123 	$script_asset        = require $script_asset_path;
       
   124 	$script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array();
   240 	$script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array();
   125 	$result              = wp_register_script(
   241 	$block_version       = isset( $metadata['version'] ) ? $metadata['version'] : false;
       
   242 	$script_version      = isset( $script_asset['version'] ) ? $script_asset['version'] : $block_version;
       
   243 	$script_args         = array();
       
   244 	if ( 'viewScript' === $field_name && $script_uri ) {
       
   245 		$script_args['strategy'] = 'defer';
       
   246 	}
       
   247 
       
   248 	$result = wp_register_script(
   126 		$script_handle,
   249 		$script_handle,
   127 		$script_uri,
   250 		$script_uri,
   128 		$script_dependencies,
   251 		$script_dependencies,
   129 		isset( $script_asset['version'] ) ? $script_asset['version'] : false
   252 		$script_version,
       
   253 		$script_args
   130 	);
   254 	);
   131 	if ( ! $result ) {
   255 	if ( ! $result ) {
   132 		return false;
   256 		return false;
   133 	}
   257 	}
   134 
   258 
   143  * Finds a style handle for the block metadata field. It detects when a path
   267  * Finds a style handle for the block metadata field. It detects when a path
   144  * to file was provided and registers the style under automatically
   268  * to file was provided and registers the style under automatically
   145  * generated handle name. It returns unprocessed style handle otherwise.
   269  * generated handle name. It returns unprocessed style handle otherwise.
   146  *
   270  *
   147  * @since 5.5.0
   271  * @since 5.5.0
       
   272  * @since 6.1.0 Added `$index` parameter.
   148  *
   273  *
   149  * @param array  $metadata   Block metadata.
   274  * @param array  $metadata   Block metadata.
   150  * @param string $field_name Field name to pick from metadata.
   275  * @param string $field_name Field name to pick from metadata.
       
   276  * @param int    $index      Optional. Index of the style to register when multiple items passed.
       
   277  *                           Default 0.
   151  * @return string|false Style handle provided directly or created through
   278  * @return string|false Style handle provided directly or created through
   152  *                      style's registration, or false on failure.
   279  *                      style's registration, or false on failure.
   153  */
   280  */
   154 function register_block_style_handle( $metadata, $field_name ) {
   281 function register_block_style_handle( $metadata, $field_name, $index = 0 ) {
   155 	if ( empty( $metadata[ $field_name ] ) ) {
   282 	if ( empty( $metadata[ $field_name ] ) ) {
   156 		return false;
   283 		return false;
   157 	}
   284 	}
   158 	$wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) );
   285 
   159 	$theme_path_norm = wp_normalize_path( get_theme_file_path() );
   286 	$style_handle = $metadata[ $field_name ];
   160 	$is_core_block   = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm );
   287 	if ( is_array( $style_handle ) ) {
       
   288 		if ( empty( $style_handle[ $index ] ) ) {
       
   289 			return false;
       
   290 		}
       
   291 		$style_handle = $style_handle[ $index ];
       
   292 	}
       
   293 
       
   294 	$style_handle_name = generate_block_asset_handle( $metadata['name'], $field_name, $index );
       
   295 	// If the style handle is already registered, skip re-registering.
       
   296 	if ( wp_style_is( $style_handle_name, 'registered' ) ) {
       
   297 		return $style_handle_name;
       
   298 	}
       
   299 
       
   300 	static $wpinc_path_norm = '';
       
   301 	if ( ! $wpinc_path_norm ) {
       
   302 		$wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) );
       
   303 	}
       
   304 
       
   305 	$is_core_block = isset( $metadata['file'] ) && str_starts_with( $metadata['file'], $wpinc_path_norm );
       
   306 	// Skip registering individual styles for each core block when a bundled version provided.
   161 	if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) {
   307 	if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) {
   162 		return false;
   308 		return false;
   163 	}
   309 	}
   164 
   310 
       
   311 	$style_path      = remove_block_asset_path_prefix( $style_handle );
       
   312 	$is_style_handle = $style_handle === $style_path;
       
   313 	// Allow only passing style handles for core blocks.
       
   314 	if ( $is_core_block && ! $is_style_handle ) {
       
   315 		return false;
       
   316 	}
       
   317 	// Return the style handle unless it's the first item for every core block that requires special treatment.
       
   318 	if ( $is_style_handle && ! ( $is_core_block && 0 === $index ) ) {
       
   319 		return $style_handle;
       
   320 	}
       
   321 
   165 	// Check whether styles should have a ".min" suffix or not.
   322 	// Check whether styles should have a ".min" suffix or not.
   166 	$suffix = SCRIPT_DEBUG ? '' : '.min';
   323 	$suffix = SCRIPT_DEBUG ? '' : '.min';
   167 
       
   168 	$style_handle = $metadata[ $field_name ];
       
   169 	$style_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
       
   170 
       
   171 	if ( $style_handle === $style_path && ! $is_core_block ) {
       
   172 		return $style_handle;
       
   173 	}
       
   174 
       
   175 	$style_uri = plugins_url( $style_path, $metadata['file'] );
       
   176 	if ( $is_core_block ) {
   324 	if ( $is_core_block ) {
   177 		$style_path = "style$suffix.css";
   325 		$style_path = ( 'editorStyle' === $field_name ) ? "editor{$suffix}.css" : "style{$suffix}.css";
   178 		$style_uri  = includes_url( 'blocks/' . str_replace( 'core/', '', $metadata['name'] ) . "/style$suffix.css" );
       
   179 	}
   326 	}
   180 
   327 
   181 	$style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) );
   328 	$style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) );
   182 	$is_theme_block  = 0 === strpos( $style_path_norm, $theme_path_norm );
   329 	$style_uri       = get_block_asset_url( $style_path_norm );
   183 
   330 
   184 	if ( $is_theme_block ) {
   331 	$version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
   185 		$style_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $style_path_norm ) );
   332 	$result  = wp_register_style(
   186 	}
   333 		$style_handle_name,
   187 
       
   188 	$style_handle   = generate_block_asset_handle( $metadata['name'], $field_name );
       
   189 	$block_dir      = dirname( $metadata['file'] );
       
   190 	$style_file     = realpath( "$block_dir/$style_path" );
       
   191 	$has_style_file = false !== $style_file;
       
   192 	$version        = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
       
   193 	$style_uri      = $has_style_file ? $style_uri : false;
       
   194 	$result         = wp_register_style(
       
   195 		$style_handle,
       
   196 		$style_uri,
   334 		$style_uri,
   197 		array(),
   335 		array(),
   198 		$version
   336 		$version
   199 	);
   337 	);
   200 	if ( file_exists( str_replace( '.css', '-rtl.css', $style_file ) ) ) {
   338 	if ( ! $result ) {
   201 		wp_style_add_data( $style_handle, 'rtl', 'replace' );
   339 		return false;
   202 	}
   340 	}
   203 	if ( $has_style_file ) {
   341 
   204 		wp_style_add_data( $style_handle, 'path', $style_file );
   342 	if ( $style_uri ) {
   205 	}
   343 		wp_style_add_data( $style_handle_name, 'path', $style_path_norm );
   206 
   344 
   207 	$rtl_file = str_replace( "$suffix.css", "-rtl$suffix.css", $style_file );
   345 		if ( $is_core_block ) {
   208 	if ( is_rtl() && file_exists( $rtl_file ) ) {
   346 			$rtl_file = str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $style_path_norm );
   209 		wp_style_add_data( $style_handle, 'path', $rtl_file );
   347 		} else {
   210 	}
   348 			$rtl_file = str_replace( '.css', '-rtl.css', $style_path_norm );
   211 
   349 		}
   212 	return $result ? $style_handle : false;
   350 
       
   351 		if ( is_rtl() && file_exists( $rtl_file ) ) {
       
   352 			wp_style_add_data( $style_handle_name, 'rtl', 'replace' );
       
   353 			wp_style_add_data( $style_handle_name, 'suffix', $suffix );
       
   354 			wp_style_add_data( $style_handle_name, 'path', $rtl_file );
       
   355 		}
       
   356 	}
       
   357 
       
   358 	return $style_handle_name;
   213 }
   359 }
   214 
   360 
   215 /**
   361 /**
   216  * Gets i18n schema for block's metadata read from `block.json` file.
   362  * Gets i18n schema for block's metadata read from `block.json` file.
   217  *
   363  *
   233  * Registers a block type from the metadata stored in the `block.json` file.
   379  * Registers a block type from the metadata stored in the `block.json` file.
   234  *
   380  *
   235  * @since 5.5.0
   381  * @since 5.5.0
   236  * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields.
   382  * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields.
   237  * @since 5.9.0 Added support for `variations` and `viewScript` fields.
   383  * @since 5.9.0 Added support for `variations` and `viewScript` fields.
       
   384  * @since 6.1.0 Added support for `render` field.
       
   385  * @since 6.3.0 Added `selectors` field.
       
   386  * @since 6.4.0 Added support for `blockHooks` field.
       
   387  * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields.
   238  *
   388  *
   239  * @param string $file_or_folder Path to the JSON file with metadata definition for
   389  * @param string $file_or_folder Path to the JSON file with metadata definition for
   240  *                               the block or path to the folder where the `block.json` file is located.
   390  *                               the block or path to the folder where the `block.json` file is located.
   241  *                               If providing the path to a JSON file, the filename must end with `block.json`.
   391  *                               If providing the path to a JSON file, the filename must end with `block.json`.
   242  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
   392  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
   243  *                               of `WP_Block_Type`. See WP_Block_Type::__construct() for information
   393  *                               of `WP_Block_Type`. See WP_Block_Type::__construct() for information
   244  *                               on accepted arguments. Default empty array.
   394  *                               on accepted arguments. Default empty array.
   245  * @return WP_Block_Type|false The registered block type on success, or false on failure.
   395  * @return WP_Block_Type|false The registered block type on success, or false on failure.
   246  */
   396  */
   247 function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
   397 function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
   248 	$filename      = 'block.json';
   398 	/*
   249 	$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
   399 	 * Get an array of metadata from a PHP file.
   250 		trailingslashit( $file_or_folder ) . $filename :
   400 	 * This improves performance for core blocks as it's only necessary to read a single PHP file
       
   401 	 * instead of reading a JSON file per-block, and then decoding from JSON to PHP.
       
   402 	 * Using a static variable ensures that the metadata is only read once per request.
       
   403 	 */
       
   404 	static $core_blocks_meta;
       
   405 	if ( ! $core_blocks_meta ) {
       
   406 		$core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php';
       
   407 	}
       
   408 
       
   409 	$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
       
   410 		trailingslashit( $file_or_folder ) . 'block.json' :
   251 		$file_or_folder;
   411 		$file_or_folder;
   252 	if ( ! file_exists( $metadata_file ) ) {
   412 
       
   413 	$is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC );
       
   414 	// If the block is not a core block, the metadata file must exist.
       
   415 	$metadata_file_exists = $is_core_block || file_exists( $metadata_file );
       
   416 	if ( ! $metadata_file_exists && empty( $args['name'] ) ) {
   253 		return false;
   417 		return false;
   254 	}
   418 	}
   255 
   419 
   256 	$metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
   420 	// Try to get metadata from the static cache for core blocks.
   257 	if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
   421 	$metadata = array();
       
   422 	if ( $is_core_block ) {
       
   423 		$core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder );
       
   424 		if ( ! empty( $core_blocks_meta[ $core_block_name ] ) ) {
       
   425 			$metadata = $core_blocks_meta[ $core_block_name ];
       
   426 		}
       
   427 	}
       
   428 
       
   429 	// If metadata is not found in the static cache, read it from the file.
       
   430 	if ( $metadata_file_exists && empty( $metadata ) ) {
       
   431 		$metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
       
   432 	}
       
   433 
       
   434 	if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
   258 		return false;
   435 		return false;
   259 	}
   436 	}
   260 	$metadata['file'] = wp_normalize_path( realpath( $metadata_file ) );
   437 
       
   438 	$metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null;
   261 
   439 
   262 	/**
   440 	/**
   263 	 * Filters the metadata provided for registering a block type.
   441 	 * Filters the metadata provided for registering a block type.
   264 	 *
   442 	 *
   265 	 * @since 5.7.0
   443 	 * @since 5.7.0
   267 	 * @param array $metadata Metadata for registering a block type.
   445 	 * @param array $metadata Metadata for registering a block type.
   268 	 */
   446 	 */
   269 	$metadata = apply_filters( 'block_type_metadata', $metadata );
   447 	$metadata = apply_filters( 'block_type_metadata', $metadata );
   270 
   448 
   271 	// Add `style` and `editor_style` for core blocks if missing.
   449 	// Add `style` and `editor_style` for core blocks if missing.
   272 	if ( ! empty( $metadata['name'] ) && 0 === strpos( $metadata['name'], 'core/' ) ) {
   450 	if ( ! empty( $metadata['name'] ) && str_starts_with( $metadata['name'], 'core/' ) ) {
   273 		$block_name = str_replace( 'core/', '', $metadata['name'] );
   451 		$block_name = str_replace( 'core/', '', $metadata['name'] );
   274 
   452 
   275 		if ( ! isset( $metadata['style'] ) ) {
   453 		if ( ! isset( $metadata['style'] ) ) {
   276 			$metadata['style'] = "wp-block-$block_name";
   454 			$metadata['style'] = "wp-block-$block_name";
       
   455 		}
       
   456 		if ( current_theme_supports( 'wp-block-styles' ) && wp_should_load_separate_core_block_assets() ) {
       
   457 			$metadata['style']   = (array) $metadata['style'];
       
   458 			$metadata['style'][] = "wp-block-{$block_name}-theme";
   277 		}
   459 		}
   278 		if ( ! isset( $metadata['editorStyle'] ) ) {
   460 		if ( ! isset( $metadata['editorStyle'] ) ) {
   279 			$metadata['editorStyle'] = "wp-block-{$block_name}-editor";
   461 			$metadata['editorStyle'] = "wp-block-{$block_name}-editor";
   280 		}
   462 		}
   281 	}
   463 	}
   282 
   464 
   283 	$settings          = array();
   465 	$settings          = array();
   284 	$property_mappings = array(
   466 	$property_mappings = array(
   285 		'apiVersion'      => 'api_version',
   467 		'apiVersion'      => 'api_version',
       
   468 		'name'            => 'name',
   286 		'title'           => 'title',
   469 		'title'           => 'title',
   287 		'category'        => 'category',
   470 		'category'        => 'category',
   288 		'parent'          => 'parent',
   471 		'parent'          => 'parent',
   289 		'ancestor'        => 'ancestor',
   472 		'ancestor'        => 'ancestor',
   290 		'icon'            => 'icon',
   473 		'icon'            => 'icon',
   291 		'description'     => 'description',
   474 		'description'     => 'description',
   292 		'keywords'        => 'keywords',
   475 		'keywords'        => 'keywords',
   293 		'attributes'      => 'attributes',
   476 		'attributes'      => 'attributes',
   294 		'providesContext' => 'provides_context',
   477 		'providesContext' => 'provides_context',
   295 		'usesContext'     => 'uses_context',
   478 		'usesContext'     => 'uses_context',
       
   479 		'selectors'       => 'selectors',
   296 		'supports'        => 'supports',
   480 		'supports'        => 'supports',
   297 		'styles'          => 'styles',
   481 		'styles'          => 'styles',
   298 		'variations'      => 'variations',
   482 		'variations'      => 'variations',
   299 		'example'         => 'example',
   483 		'example'         => 'example',
       
   484 		'allowedBlocks'   => 'allowed_blocks',
   300 	);
   485 	);
   301 	$textdomain        = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null;
   486 	$textdomain        = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null;
   302 	$i18n_schema       = get_block_metadata_i18n_schema();
   487 	$i18n_schema       = get_block_metadata_i18n_schema();
   303 
   488 
   304 	foreach ( $property_mappings as $key => $mapped_key ) {
   489 	foreach ( $property_mappings as $key => $mapped_key ) {
   305 		if ( isset( $metadata[ $key ] ) ) {
   490 		if ( isset( $metadata[ $key ] ) ) {
   306 			$settings[ $mapped_key ] = $metadata[ $key ];
   491 			$settings[ $mapped_key ] = $metadata[ $key ];
   307 			if ( $textdomain && isset( $i18n_schema->$key ) ) {
   492 			if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) {
   308 				$settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain );
   493 				$settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain );
   309 			}
   494 			}
   310 		}
   495 		}
   311 	}
   496 	}
   312 
   497 
   313 	if ( ! empty( $metadata['editorScript'] ) ) {
   498 	if ( ! empty( $metadata['render'] ) ) {
   314 		$settings['editor_script'] = register_block_script_handle(
   499 		$template_path = wp_normalize_path(
   315 			$metadata,
   500 			realpath(
   316 			'editorScript'
   501 				dirname( $metadata['file'] ) . '/' .
       
   502 				remove_block_asset_path_prefix( $metadata['render'] )
       
   503 			)
   317 		);
   504 		);
   318 	}
   505 		if ( $template_path ) {
   319 
   506 			/**
   320 	if ( ! empty( $metadata['script'] ) ) {
   507 			 * Renders the block on the server.
   321 		$settings['script'] = register_block_script_handle(
   508 			 *
   322 			$metadata,
   509 			 * @since 6.1.0
   323 			'script'
   510 			 *
       
   511 			 * @param array    $attributes Block attributes.
       
   512 			 * @param string   $content    Block default content.
       
   513 			 * @param WP_Block $block      Block instance.
       
   514 			 *
       
   515 			 * @return string Returns the block content.
       
   516 			 */
       
   517 			$settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) {
       
   518 				ob_start();
       
   519 				require $template_path;
       
   520 				return ob_get_clean();
       
   521 			};
       
   522 		}
       
   523 	}
       
   524 
       
   525 	$settings = array_merge( $settings, $args );
       
   526 
       
   527 	$script_fields = array(
       
   528 		'editorScript' => 'editor_script_handles',
       
   529 		'script'       => 'script_handles',
       
   530 		'viewScript'   => 'view_script_handles',
       
   531 	);
       
   532 	foreach ( $script_fields as $metadata_field_name => $settings_field_name ) {
       
   533 		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
       
   534 			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
       
   535 		}
       
   536 		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
       
   537 			$scripts           = $metadata[ $metadata_field_name ];
       
   538 			$processed_scripts = array();
       
   539 			if ( is_array( $scripts ) ) {
       
   540 				for ( $index = 0; $index < count( $scripts ); $index++ ) {
       
   541 					$result = register_block_script_handle(
       
   542 						$metadata,
       
   543 						$metadata_field_name,
       
   544 						$index
       
   545 					);
       
   546 					if ( $result ) {
       
   547 						$processed_scripts[] = $result;
       
   548 					}
       
   549 				}
       
   550 			} else {
       
   551 				$result = register_block_script_handle(
       
   552 					$metadata,
       
   553 					$metadata_field_name
       
   554 				);
       
   555 				if ( $result ) {
       
   556 					$processed_scripts[] = $result;
       
   557 				}
       
   558 			}
       
   559 			$settings[ $settings_field_name ] = $processed_scripts;
       
   560 		}
       
   561 	}
       
   562 
       
   563 	$module_fields = array(
       
   564 		'viewScriptModule' => 'view_script_module_ids',
       
   565 	);
       
   566 	foreach ( $module_fields as $metadata_field_name => $settings_field_name ) {
       
   567 		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
       
   568 			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
       
   569 		}
       
   570 		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
       
   571 			$modules           = $metadata[ $metadata_field_name ];
       
   572 			$processed_modules = array();
       
   573 			if ( is_array( $modules ) ) {
       
   574 				for ( $index = 0; $index < count( $modules ); $index++ ) {
       
   575 					$result = register_block_script_module_id(
       
   576 						$metadata,
       
   577 						$metadata_field_name,
       
   578 						$index
       
   579 					);
       
   580 					if ( $result ) {
       
   581 						$processed_modules[] = $result;
       
   582 					}
       
   583 				}
       
   584 			} else {
       
   585 				$result = register_block_script_module_id(
       
   586 					$metadata,
       
   587 					$metadata_field_name
       
   588 				);
       
   589 				if ( $result ) {
       
   590 					$processed_modules[] = $result;
       
   591 				}
       
   592 			}
       
   593 			$settings[ $settings_field_name ] = $processed_modules;
       
   594 		}
       
   595 	}
       
   596 
       
   597 	$style_fields = array(
       
   598 		'editorStyle' => 'editor_style_handles',
       
   599 		'style'       => 'style_handles',
       
   600 		'viewStyle'   => 'view_style_handles',
       
   601 	);
       
   602 	foreach ( $style_fields as $metadata_field_name => $settings_field_name ) {
       
   603 		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
       
   604 			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
       
   605 		}
       
   606 		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
       
   607 			$styles           = $metadata[ $metadata_field_name ];
       
   608 			$processed_styles = array();
       
   609 			if ( is_array( $styles ) ) {
       
   610 				for ( $index = 0; $index < count( $styles ); $index++ ) {
       
   611 					$result = register_block_style_handle(
       
   612 						$metadata,
       
   613 						$metadata_field_name,
       
   614 						$index
       
   615 					);
       
   616 					if ( $result ) {
       
   617 						$processed_styles[] = $result;
       
   618 					}
       
   619 				}
       
   620 			} else {
       
   621 				$result = register_block_style_handle(
       
   622 					$metadata,
       
   623 					$metadata_field_name
       
   624 				);
       
   625 				if ( $result ) {
       
   626 					$processed_styles[] = $result;
       
   627 				}
       
   628 			}
       
   629 			$settings[ $settings_field_name ] = $processed_styles;
       
   630 		}
       
   631 	}
       
   632 
       
   633 	if ( ! empty( $metadata['blockHooks'] ) ) {
       
   634 		/**
       
   635 		 * Map camelCased position string (from block.json) to snake_cased block type position.
       
   636 		 *
       
   637 		 * @var array
       
   638 		 */
       
   639 		$position_mappings = array(
       
   640 			'before'     => 'before',
       
   641 			'after'      => 'after',
       
   642 			'firstChild' => 'first_child',
       
   643 			'lastChild'  => 'last_child',
   324 		);
   644 		);
   325 	}
   645 
   326 
   646 		$settings['block_hooks'] = array();
   327 	if ( ! empty( $metadata['viewScript'] ) ) {
   647 		foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) {
   328 		$settings['view_script'] = register_block_script_handle(
   648 			// Avoid infinite recursion (hooking to itself).
   329 			$metadata,
   649 			if ( $metadata['name'] === $anchor_block_name ) {
   330 			'viewScript'
   650 				_doing_it_wrong(
   331 		);
   651 					__METHOD__,
   332 	}
   652 					__( 'Cannot hook block to itself.' ),
   333 
   653 					'6.4.0'
   334 	if ( ! empty( $metadata['editorStyle'] ) ) {
   654 				);
   335 		$settings['editor_style'] = register_block_style_handle(
   655 				continue;
   336 			$metadata,
   656 			}
   337 			'editorStyle'
   657 
   338 		);
   658 			if ( ! isset( $position_mappings[ $position ] ) ) {
   339 	}
   659 				continue;
   340 
   660 			}
   341 	if ( ! empty( $metadata['style'] ) ) {
   661 
   342 		$settings['style'] = register_block_style_handle(
   662 			$settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ];
   343 			$metadata,
   663 		}
   344 			'style'
       
   345 		);
       
   346 	}
   664 	}
   347 
   665 
   348 	/**
   666 	/**
   349 	 * Filters the settings determined from the block type metadata.
   667 	 * Filters the settings determined from the block type metadata.
   350 	 *
   668 	 *
   351 	 * @since 5.7.0
   669 	 * @since 5.7.0
   352 	 *
   670 	 *
   353 	 * @param array $settings Array of determined settings for registering a block type.
   671 	 * @param array $settings Array of determined settings for registering a block type.
   354 	 * @param array $metadata Metadata provided for registering a block type.
   672 	 * @param array $metadata Metadata provided for registering a block type.
   355 	 */
   673 	 */
   356 	$settings = apply_filters(
   674 	$settings = apply_filters( 'block_type_metadata_settings', $settings, $metadata );
   357 		'block_type_metadata_settings',
   675 
   358 		array_merge(
   676 	$metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name'];
   359 			$settings,
       
   360 			$args
       
   361 		),
       
   362 		$metadata
       
   363 	);
       
   364 
   677 
   365 	return WP_Block_Type_Registry::get_instance()->register(
   678 	return WP_Block_Type_Registry::get_instance()->register(
   366 		$metadata['name'],
   679 		$metadata['name'],
   367 		$settings
   680 		$settings
   368 	);
   681 	);
   423  * @return bool Whether the post has blocks.
   736  * @return bool Whether the post has blocks.
   424  */
   737  */
   425 function has_blocks( $post = null ) {
   738 function has_blocks( $post = null ) {
   426 	if ( ! is_string( $post ) ) {
   739 	if ( ! is_string( $post ) ) {
   427 		$wp_post = get_post( $post );
   740 		$wp_post = get_post( $post );
   428 		if ( $wp_post instanceof WP_Post ) {
   741 
   429 			$post = $wp_post->post_content;
   742 		if ( ! $wp_post instanceof WP_Post ) {
   430 		}
   743 			return false;
   431 	}
   744 		}
   432 
   745 
   433 	return false !== strpos( (string) $post, '<!-- wp:' );
   746 		$post = $wp_post->post_content;
       
   747 	}
       
   748 
       
   749 	return str_contains( (string) $post, '<!-- wp:' );
   434 }
   750 }
   435 
   751 
   436 /**
   752 /**
   437  * Determines whether a $post or a string contains a specific block type.
   753  * Determines whether a $post or a string contains a specific block type.
   438  *
   754  *
   439  * This test optimizes for performance rather than strict accuracy, detecting
   755  * This test optimizes for performance rather than strict accuracy, detecting
   440  * whether the block type exists but not validating its structure and not checking
   756  * whether the block type exists but not validating its structure and not checking
   441  * reusable blocks. For strict accuracy, you should use the block parser on post content.
   757  * synced patterns (formerly called reusable blocks). For strict accuracy,
       
   758  * you should use the block parser on post content.
   442  *
   759  *
   443  * @since 5.0.0
   760  * @since 5.0.0
   444  *
   761  *
   445  * @see parse_blocks()
   762  * @see parse_blocks()
   446  *
   763  *
   464 	/*
   781 	/*
   465 	 * Normalize block name to include namespace, if provided as non-namespaced.
   782 	 * Normalize block name to include namespace, if provided as non-namespaced.
   466 	 * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
   783 	 * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
   467 	 * their serialized names.
   784 	 * their serialized names.
   468 	 */
   785 	 */
   469 	if ( false === strpos( $block_name, '/' ) ) {
   786 	if ( ! str_contains( $block_name, '/' ) ) {
   470 		$block_name = 'core/' . $block_name;
   787 		$block_name = 'core/' . $block_name;
   471 	}
   788 	}
   472 
   789 
   473 	// Test for existence of block by its fully qualified name.
   790 	// Test for existence of block by its fully qualified name.
   474 	$has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' );
   791 	$has_block = str_contains( $post, '<!-- wp:' . $block_name . ' ' );
   475 
   792 
   476 	if ( ! $has_block ) {
   793 	if ( ! $has_block ) {
   477 		/*
   794 		/*
   478 		 * If the given block name would serialize to a different name, test for
   795 		 * If the given block name would serialize to a different name, test for
   479 		 * existence by the serialized form.
   796 		 * existence by the serialized form.
   480 		 */
   797 		 */
   481 		$serialized_block_name = strip_core_block_namespace( $block_name );
   798 		$serialized_block_name = strip_core_block_namespace( $block_name );
   482 		if ( $serialized_block_name !== $block_name ) {
   799 		if ( $serialized_block_name !== $block_name ) {
   483 			$has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' );
   800 			$has_block = str_contains( $post, '<!-- wp:' . $serialized_block_name . ' ' );
   484 		}
   801 		}
   485 	}
   802 	}
   486 
   803 
   487 	return $has_block;
   804 	return $has_block;
   488 }
   805 }
   503 			$dynamic_block_names[] = $block_type->name;
   820 			$dynamic_block_names[] = $block_type->name;
   504 		}
   821 		}
   505 	}
   822 	}
   506 
   823 
   507 	return $dynamic_block_names;
   824 	return $dynamic_block_names;
       
   825 }
       
   826 
       
   827 /**
       
   828  * Retrieves block types hooked into the given block, grouped by anchor block type and the relative position.
       
   829  *
       
   830  * @since 6.4.0
       
   831  *
       
   832  * @return array[] Array of block types grouped by anchor block type and the relative position.
       
   833  */
       
   834 function get_hooked_blocks() {
       
   835 	$block_types   = WP_Block_Type_Registry::get_instance()->get_all_registered();
       
   836 	$hooked_blocks = array();
       
   837 	foreach ( $block_types as $block_type ) {
       
   838 		if ( ! ( $block_type instanceof WP_Block_Type ) || ! is_array( $block_type->block_hooks ) ) {
       
   839 			continue;
       
   840 		}
       
   841 		foreach ( $block_type->block_hooks as $anchor_block_type => $relative_position ) {
       
   842 			if ( ! isset( $hooked_blocks[ $anchor_block_type ] ) ) {
       
   843 				$hooked_blocks[ $anchor_block_type ] = array();
       
   844 			}
       
   845 			if ( ! isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) ) {
       
   846 				$hooked_blocks[ $anchor_block_type ][ $relative_position ] = array();
       
   847 			}
       
   848 			$hooked_blocks[ $anchor_block_type ][ $relative_position ][] = $block_type->name;
       
   849 		}
       
   850 	}
       
   851 
       
   852 	return $hooked_blocks;
       
   853 }
       
   854 
       
   855 /**
       
   856  * Returns the markup for blocks hooked to the given anchor block in a specific relative position.
       
   857  *
       
   858  * @since 6.5.0
       
   859  * @access private
       
   860  *
       
   861  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
       
   862  * @param string                          $relative_position   The relative position of the hooked blocks.
       
   863  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
       
   864  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
       
   865  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
       
   866  * @return string
       
   867  */
       
   868 function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
       
   869 	$anchor_block_type  = $parsed_anchor_block['blockName'];
       
   870 	$hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] )
       
   871 		? $hooked_blocks[ $anchor_block_type ][ $relative_position ]
       
   872 		: array();
       
   873 
       
   874 	/**
       
   875 	 * Filters the list of hooked block types for a given anchor block type and relative position.
       
   876 	 *
       
   877 	 * @since 6.4.0
       
   878 	 *
       
   879 	 * @param string[]                        $hooked_block_types The list of hooked block types.
       
   880 	 * @param string                          $relative_position  The relative position of the hooked blocks.
       
   881 	 *                                                            Can be one of 'before', 'after', 'first_child', or 'last_child'.
       
   882 	 * @param string                          $anchor_block_type  The anchor block type.
       
   883 	 * @param WP_Block_Template|WP_Post|array $context            The block template, template part, `wp_navigation` post type,
       
   884 	 *                                                            or pattern that the anchor block belongs to.
       
   885 	 */
       
   886 	$hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context );
       
   887 
       
   888 	$markup = '';
       
   889 	foreach ( $hooked_block_types as $hooked_block_type ) {
       
   890 		$parsed_hooked_block = array(
       
   891 			'blockName'    => $hooked_block_type,
       
   892 			'attrs'        => array(),
       
   893 			'innerBlocks'  => array(),
       
   894 			'innerContent' => array(),
       
   895 		);
       
   896 
       
   897 		/**
       
   898 		 * Filters the parsed block array for a given hooked block.
       
   899 		 *
       
   900 		 * @since 6.5.0
       
   901 		 *
       
   902 		 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
       
   903 		 * @param string                          $hooked_block_type   The hooked block type name.
       
   904 		 * @param string                          $relative_position   The relative position of the hooked block.
       
   905 		 * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
       
   906 		 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, `wp_navigation` post type,
       
   907 		 *                                                             or pattern that the anchor block belongs to.
       
   908 		 */
       
   909 		$parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
       
   910 
       
   911 		/**
       
   912 		 * Filters the parsed block array for a given hooked block.
       
   913 		 *
       
   914 		 * The dynamic portion of the hook name, `$hooked_block_type`, refers to the block type name of the specific hooked block.
       
   915 		 *
       
   916 		 * @since 6.5.0
       
   917 		 *
       
   918 		 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
       
   919 		 * @param string                          $hooked_block_type   The hooked block type name.
       
   920 		 * @param string                          $relative_position   The relative position of the hooked block.
       
   921 		 * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
       
   922 		 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, `wp_navigation` post type,
       
   923 		 *                                                             or pattern that the anchor block belongs to.
       
   924 		 */
       
   925 		$parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
       
   926 
       
   927 		if ( null === $parsed_hooked_block ) {
       
   928 			continue;
       
   929 		}
       
   930 
       
   931 		// It's possible that the filter returned a block of a different type, so we explicitly
       
   932 		// look for the original `$hooked_block_type` in the `ignoredHookedBlocks` metadata.
       
   933 		if (
       
   934 			! isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) ||
       
   935 			! in_array( $hooked_block_type, $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'], true )
       
   936 		) {
       
   937 			$markup .= serialize_block( $parsed_hooked_block );
       
   938 		}
       
   939 	}
       
   940 
       
   941 	return $markup;
       
   942 }
       
   943 
       
   944 /**
       
   945  * Adds a list of hooked block types to an anchor block's ignored hooked block types.
       
   946  *
       
   947  * This function is meant for internal use only.
       
   948  *
       
   949  * @since 6.5.0
       
   950  * @access private
       
   951  *
       
   952  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
       
   953  * @param string                          $relative_position   The relative position of the hooked blocks.
       
   954  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
       
   955  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
       
   956  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
       
   957  * @return string Empty string.
       
   958  */
       
   959 function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
       
   960 	$anchor_block_type  = $parsed_anchor_block['blockName'];
       
   961 	$hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] )
       
   962 		? $hooked_blocks[ $anchor_block_type ][ $relative_position ]
       
   963 		: array();
       
   964 
       
   965 	/** This filter is documented in wp-includes/blocks.php */
       
   966 	$hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context );
       
   967 	if ( empty( $hooked_block_types ) ) {
       
   968 		return '';
       
   969 	}
       
   970 
       
   971 	foreach ( $hooked_block_types as $index => $hooked_block_type ) {
       
   972 		$parsed_hooked_block = array(
       
   973 			'blockName'    => $hooked_block_type,
       
   974 			'attrs'        => array(),
       
   975 			'innerBlocks'  => array(),
       
   976 			'innerContent' => array(),
       
   977 		);
       
   978 
       
   979 		/** This filter is documented in wp-includes/blocks.php */
       
   980 		$parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
       
   981 
       
   982 		/** This filter is documented in wp-includes/blocks.php */
       
   983 		$parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
       
   984 
       
   985 		if ( null === $parsed_hooked_block ) {
       
   986 			unset( $hooked_block_types[ $index ] );
       
   987 		}
       
   988 	}
       
   989 
       
   990 	$previously_ignored_hooked_blocks = isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] )
       
   991 		? $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks']
       
   992 		: array();
       
   993 
       
   994 	$parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] = array_unique(
       
   995 		array_merge(
       
   996 			$previously_ignored_hooked_blocks,
       
   997 			$hooked_block_types
       
   998 		)
       
   999 	);
       
  1000 
       
  1001 	// Markup for the hooked blocks has already been created (in `insert_hooked_blocks`).
       
  1002 	return '';
       
  1003 }
       
  1004 
       
  1005 /**
       
  1006  * Runs the hooked blocks algorithm on the given content.
       
  1007  *
       
  1008  * @since 6.6.0
       
  1009  * @access private
       
  1010  *
       
  1011  * @param string                          $content  Serialized content.
       
  1012  * @param WP_Block_Template|WP_Post|array $context  A block template, template part, `wp_navigation` post object,
       
  1013  *                                                  or pattern that the blocks belong to.
       
  1014  * @param callable                        $callback A function that will be called for each block to generate
       
  1015  *                                                  the markup for a given list of blocks that are hooked to it.
       
  1016  *                                                  Default: 'insert_hooked_blocks'.
       
  1017  * @return string The serialized markup.
       
  1018  */
       
  1019 function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) {
       
  1020 	$hooked_blocks = get_hooked_blocks();
       
  1021 	if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
       
  1022 		return $content;
       
  1023 	}
       
  1024 
       
  1025 	$blocks = parse_blocks( $content );
       
  1026 
       
  1027 	$before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback );
       
  1028 	$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $context, $callback );
       
  1029 
       
  1030 	return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
       
  1031 }
       
  1032 
       
  1033 /**
       
  1034  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
       
  1035  *
       
  1036  * @since 6.6.0
       
  1037  * @access private
       
  1038  *
       
  1039  * @param string $serialized_block The serialized markup of a block and its inner blocks.
       
  1040  * @return string The serialized markup of the inner blocks.
       
  1041  */
       
  1042 function remove_serialized_parent_block( $serialized_block ) {
       
  1043 	$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
       
  1044 	$end   = strrpos( $serialized_block, '<!--' );
       
  1045 	return substr( $serialized_block, $start, $end - $start );
       
  1046 }
       
  1047 
       
  1048 /**
       
  1049  * Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content.
       
  1050  * Currently only supports `wp_navigation` post types.
       
  1051  *
       
  1052  * @since 6.6.0
       
  1053  * @access private
       
  1054  *
       
  1055  * @param stdClass $post Post object.
       
  1056  * @return stdClass The updated post object.
       
  1057  */
       
  1058 function update_ignored_hooked_blocks_postmeta( $post ) {
       
  1059 	/*
       
  1060 	 * In this scenario the user has likely tried to create a navigation via the REST API.
       
  1061 	 * In which case we won't have a post ID to work with and store meta against.
       
  1062 	 */
       
  1063 	if ( empty( $post->ID ) ) {
       
  1064 		return $post;
       
  1065 	}
       
  1066 
       
  1067 	/*
       
  1068 	 * Skip meta generation when consumers intentionally update specific Navigation fields
       
  1069 	 * and omit the content update.
       
  1070 	 */
       
  1071 	if ( ! isset( $post->post_content ) ) {
       
  1072 		return $post;
       
  1073 	}
       
  1074 
       
  1075 	/*
       
  1076 	 * Skip meta generation when the post content is not a navigation block.
       
  1077 	 */
       
  1078 	if ( ! isset( $post->post_type ) || 'wp_navigation' !== $post->post_type ) {
       
  1079 		return $post;
       
  1080 	}
       
  1081 
       
  1082 	$attributes = array();
       
  1083 
       
  1084 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1085 	if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1086 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
       
  1087 		$attributes['metadata'] = array(
       
  1088 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
       
  1089 		);
       
  1090 	}
       
  1091 
       
  1092 	$markup = get_comment_delimited_block_content(
       
  1093 		'core/navigation',
       
  1094 		$attributes,
       
  1095 		$post->post_content
       
  1096 	);
       
  1097 
       
  1098 	$serialized_block = apply_block_hooks_to_content( $markup, get_post( $post->ID ), 'set_ignored_hooked_blocks_metadata' );
       
  1099 	$root_block       = parse_blocks( $serialized_block )[0];
       
  1100 
       
  1101 	$ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
       
  1102 		? $root_block['attrs']['metadata']['ignoredHookedBlocks']
       
  1103 		: array();
       
  1104 
       
  1105 	if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1106 		$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1107 		if ( ! empty( $existing_ignored_hooked_blocks ) ) {
       
  1108 			$existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
       
  1109 			$ignored_hooked_blocks          = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
       
  1110 		}
       
  1111 		update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
       
  1112 	}
       
  1113 
       
  1114 	$post->post_content = remove_serialized_parent_block( $serialized_block );
       
  1115 	return $post;
       
  1116 }
       
  1117 
       
  1118 /**
       
  1119  * Returns the markup for blocks hooked to the given anchor block in a specific relative position and then
       
  1120  * adds a list of hooked block types to an anchor block's ignored hooked block types.
       
  1121  *
       
  1122  * This function is meant for internal use only.
       
  1123  *
       
  1124  * @since 6.6.0
       
  1125  * @access private
       
  1126  *
       
  1127  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
       
  1128  * @param string                          $relative_position   The relative position of the hooked blocks.
       
  1129  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
       
  1130  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
       
  1131  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
       
  1132  * @return string
       
  1133  */
       
  1134 function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
       
  1135 	$markup  = insert_hooked_blocks( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
       
  1136 	$markup .= set_ignored_hooked_blocks_metadata( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
       
  1137 
       
  1138 	return $markup;
       
  1139 }
       
  1140 
       
  1141 /**
       
  1142  * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
       
  1143  *
       
  1144  * @since 6.6.0
       
  1145  *
       
  1146  * @param WP_REST_Response $response The response object.
       
  1147  * @param WP_Post          $post     Post object.
       
  1148  * @return WP_REST_Response The response object.
       
  1149  */
       
  1150 function insert_hooked_blocks_into_rest_response( $response, $post ) {
       
  1151 	if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
       
  1152 		return $response;
       
  1153 	}
       
  1154 
       
  1155 	$attributes            = array();
       
  1156 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1157 	if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1158 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
       
  1159 		$attributes['metadata'] = array(
       
  1160 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
       
  1161 		);
       
  1162 	}
       
  1163 	$content = get_comment_delimited_block_content(
       
  1164 		'core/navigation',
       
  1165 		$attributes,
       
  1166 		$response->data['content']['raw']
       
  1167 	);
       
  1168 
       
  1169 	$content = apply_block_hooks_to_content( $content, $post );
       
  1170 
       
  1171 	// Remove mock Navigation block wrapper.
       
  1172 	$content = remove_serialized_parent_block( $content );
       
  1173 
       
  1174 	$response->data['content']['raw'] = $content;
       
  1175 
       
  1176 	/** This filter is documented in wp-includes/post-template.php */
       
  1177 	$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
       
  1178 
       
  1179 	return $response;
       
  1180 }
       
  1181 
       
  1182 /**
       
  1183  * Returns a function that injects the theme attribute into, and hooked blocks before, a given block.
       
  1184  *
       
  1185  * The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`,
       
  1186  * where it will inject the `theme` attribute into all Template Part blocks, and prepend the markup for
       
  1187  * any blocks hooked `before` the given block and as its parent's `first_child`, respectively.
       
  1188  *
       
  1189  * This function is meant for internal use only.
       
  1190  *
       
  1191  * @since 6.4.0
       
  1192  * @since 6.5.0 Added $callback argument.
       
  1193  * @access private
       
  1194  *
       
  1195  * @param array                           $hooked_blocks An array of blocks hooked to another given block.
       
  1196  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, `wp_navigation` post object,
       
  1197  *                                                       or pattern that the blocks belong to.
       
  1198  * @param callable                        $callback      A function that will be called for each block to generate
       
  1199  *                                                       the markup for a given list of blocks that are hooked to it.
       
  1200  *                                                       Default: 'insert_hooked_blocks'.
       
  1201  * @return callable A function that returns the serialized markup for the given block,
       
  1202  *                  including the markup for any hooked blocks before it.
       
  1203  */
       
  1204 function make_before_block_visitor( $hooked_blocks, $context, $callback = 'insert_hooked_blocks' ) {
       
  1205 	/**
       
  1206 	 * Injects hooked blocks before the given block, injects the `theme` attribute into Template Part blocks, and returns the serialized markup.
       
  1207 	 *
       
  1208 	 * If the current block is a Template Part block, inject the `theme` attribute.
       
  1209 	 * Furthermore, prepend the markup for any blocks hooked `before` the given block and as its parent's
       
  1210 	 * `first_child`, respectively, to the serialized markup for the given block.
       
  1211 	 *
       
  1212 	 * @param array $block        The block to inject the theme attribute into, and hooked blocks before. Passed by reference.
       
  1213 	 * @param array $parent_block The parent block of the given block. Passed by reference. Default null.
       
  1214 	 * @param array $prev         The previous sibling block of the given block. Default null.
       
  1215 	 * @return string The serialized markup for the given block, with the markup for any hooked blocks prepended to it.
       
  1216 	 */
       
  1217 	return function ( &$block, &$parent_block = null, $prev = null ) use ( $hooked_blocks, $context, $callback ) {
       
  1218 		_inject_theme_attribute_in_template_part_block( $block );
       
  1219 
       
  1220 		$markup = '';
       
  1221 
       
  1222 		if ( $parent_block && ! $prev ) {
       
  1223 			// Candidate for first-child insertion.
       
  1224 			$markup .= call_user_func_array(
       
  1225 				$callback,
       
  1226 				array( &$parent_block, 'first_child', $hooked_blocks, $context )
       
  1227 			);
       
  1228 		}
       
  1229 
       
  1230 		$markup .= call_user_func_array(
       
  1231 			$callback,
       
  1232 			array( &$block, 'before', $hooked_blocks, $context )
       
  1233 		);
       
  1234 
       
  1235 		return $markup;
       
  1236 	};
       
  1237 }
       
  1238 
       
  1239 /**
       
  1240  * Returns a function that injects the hooked blocks after a given block.
       
  1241  *
       
  1242  * The returned function can be used as `$post_callback` argument to `traverse_and_serialize_block(s)`,
       
  1243  * where it will append the markup for any blocks hooked `after` the given block and as its parent's
       
  1244  * `last_child`, respectively.
       
  1245  *
       
  1246  * This function is meant for internal use only.
       
  1247  *
       
  1248  * @since 6.4.0
       
  1249  * @since 6.5.0 Added $callback argument.
       
  1250  * @access private
       
  1251  *
       
  1252  * @param array                           $hooked_blocks An array of blocks hooked to another block.
       
  1253  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, `wp_navigation` post object,
       
  1254  *                                                       or pattern that the blocks belong to.
       
  1255  * @param callable                        $callback      A function that will be called for each block to generate
       
  1256  *                                                       the markup for a given list of blocks that are hooked to it.
       
  1257  *                                                       Default: 'insert_hooked_blocks'.
       
  1258  * @return callable A function that returns the serialized markup for the given block,
       
  1259  *                  including the markup for any hooked blocks after it.
       
  1260  */
       
  1261 function make_after_block_visitor( $hooked_blocks, $context, $callback = 'insert_hooked_blocks' ) {
       
  1262 	/**
       
  1263 	 * Injects hooked blocks after the given block, and returns the serialized markup.
       
  1264 	 *
       
  1265 	 * Append the markup for any blocks hooked `after` the given block and as its parent's
       
  1266 	 * `last_child`, respectively, to the serialized markup for the given block.
       
  1267 	 *
       
  1268 	 * @param array $block        The block to inject the hooked blocks after. Passed by reference.
       
  1269 	 * @param array $parent_block The parent block of the given block. Passed by reference. Default null.
       
  1270 	 * @param array $next         The next sibling block of the given block. Default null.
       
  1271 	 * @return string The serialized markup for the given block, with the markup for any hooked blocks appended to it.
       
  1272 	 */
       
  1273 	return function ( &$block, &$parent_block = null, $next = null ) use ( $hooked_blocks, $context, $callback ) {
       
  1274 		$markup = call_user_func_array(
       
  1275 			$callback,
       
  1276 			array( &$block, 'after', $hooked_blocks, $context )
       
  1277 		);
       
  1278 
       
  1279 		if ( $parent_block && ! $next ) {
       
  1280 			// Candidate for last-child insertion.
       
  1281 			$markup .= call_user_func_array(
       
  1282 				$callback,
       
  1283 				array( &$parent_block, 'last_child', $hooked_blocks, $context )
       
  1284 			);
       
  1285 		}
       
  1286 
       
  1287 		return $markup;
       
  1288 	};
   508 }
  1289 }
   509 
  1290 
   510 /**
  1291 /**
   511  * Given an array of attributes, returns a string in the serialized attributes
  1292  * Given an array of attributes, returns a string in the serialized attributes
   512  * format prepared for post content.
  1293  * format prepared for post content.
   540  * Returns the block name to use for serialization. This will remove the default
  1321  * Returns the block name to use for serialization. This will remove the default
   541  * "core/" namespace from a block name.
  1322  * "core/" namespace from a block name.
   542  *
  1323  *
   543  * @since 5.3.1
  1324  * @since 5.3.1
   544  *
  1325  *
   545  * @param string $block_name Original block name.
  1326  * @param string|null $block_name Optional. Original block name. Null if the block name is unknown,
       
  1327  *                                e.g. Classic blocks have their name set to null. Default null.
   546  * @return string Block name to use for serialization.
  1328  * @return string Block name to use for serialization.
   547  */
  1329  */
   548 function strip_core_block_namespace( $block_name = null ) {
  1330 function strip_core_block_namespace( $block_name = null ) {
   549 	if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
  1331 	if ( is_string( $block_name ) && str_starts_with( $block_name, 'core/' ) ) {
   550 		return substr( $block_name, 5 );
  1332 		return substr( $block_name, 5 );
   551 	}
  1333 	}
   552 
  1334 
   553 	return $block_name;
  1335 	return $block_name;
   554 }
  1336 }
   594  * `render_block`, this does not evaluate a block's `render_callback`, and will
  1376  * `render_block`, this does not evaluate a block's `render_callback`, and will
   595  * instead preserve the markup as parsed.
  1377  * instead preserve the markup as parsed.
   596  *
  1378  *
   597  * @since 5.3.1
  1379  * @since 5.3.1
   598  *
  1380  *
   599  * @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
  1381  * @param array $block {
       
  1382  *     A representative array of a single parsed block object. See WP_Block_Parser_Block.
       
  1383  *
       
  1384  *     @type string   $blockName    Name of block.
       
  1385  *     @type array    $attrs        Attributes from block comment delimiters.
       
  1386  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  1387  *                                  have the same structure as this one.
       
  1388  *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  1389  *     @type array    $innerContent List of string fragments and null markers where
       
  1390  *                                  inner blocks were found.
       
  1391  * }
   600  * @return string String of rendered HTML.
  1392  * @return string String of rendered HTML.
   601  */
  1393  */
   602 function serialize_block( $block ) {
  1394 function serialize_block( $block ) {
   603 	$block_content = '';
  1395 	$block_content = '';
   604 
  1396 
   617 		$block_content
  1409 		$block_content
   618 	);
  1410 	);
   619 }
  1411 }
   620 
  1412 
   621 /**
  1413 /**
   622  * Returns a joined string of the aggregate serialization of the given parsed
  1414  * Returns a joined string of the aggregate serialization of the given
   623  * blocks.
  1415  * parsed blocks.
   624  *
  1416  *
   625  * @since 5.3.1
  1417  * @since 5.3.1
   626  *
  1418  *
   627  * @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block().
  1419  * @param array[] $blocks {
       
  1420  *     Array of block structures.
       
  1421  *
       
  1422  *     @type array ...$0 {
       
  1423  *         A representative array of a single parsed block object. See WP_Block_Parser_Block.
       
  1424  *
       
  1425  *         @type string   $blockName    Name of block.
       
  1426  *         @type array    $attrs        Attributes from block comment delimiters.
       
  1427  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  1428  *                                      have the same structure as this one.
       
  1429  *         @type string   $innerHTML    HTML from inside block comment delimiters.
       
  1430  *         @type array    $innerContent List of string fragments and null markers where
       
  1431  *                                      inner blocks were found.
       
  1432  *     }
       
  1433  * }
   628  * @return string String of rendered HTML.
  1434  * @return string String of rendered HTML.
   629  */
  1435  */
   630 function serialize_blocks( $blocks ) {
  1436 function serialize_blocks( $blocks ) {
   631 	return implode( '', array_map( 'serialize_block', $blocks ) );
  1437 	return implode( '', array_map( 'serialize_block', $blocks ) );
   632 }
  1438 }
   633 
  1439 
   634 /**
  1440 /**
   635  * Filters and sanitizes block content to remove non-allowable HTML from
  1441  * Traverses a parsed block tree and applies callbacks before and after serializing it.
   636  * parsed block attribute values.
  1442  *
       
  1443  * Recursively traverses the block and its inner blocks and applies the two callbacks provided as
       
  1444  * arguments, the first one before serializing the block, and the second one after serializing it.
       
  1445  * If either callback returns a string value, it will be prepended and appended to the serialized
       
  1446  * block markup, respectively.
       
  1447  *
       
  1448  * The callbacks will receive a reference to the current block as their first argument, so that they
       
  1449  * can also modify it, and the current block's parent block as second argument. Finally, the
       
  1450  * `$pre_callback` receives the previous block, whereas the `$post_callback` receives
       
  1451  * the next block as third argument.
       
  1452  *
       
  1453  * Serialized blocks are returned including comment delimiters, and with all attributes serialized.
       
  1454  *
       
  1455  * This function should be used when there is a need to modify the saved block, or to inject markup
       
  1456  * into the return value. Prefer `serialize_block` when preparing a block to be saved to post content.
       
  1457  *
       
  1458  * This function is meant for internal use only.
       
  1459  *
       
  1460  * @since 6.4.0
       
  1461  * @access private
       
  1462  *
       
  1463  * @see serialize_block()
       
  1464  *
       
  1465  * @param array    $block         A representative array of a single parsed block object. See WP_Block_Parser_Block.
       
  1466  * @param callable $pre_callback  Callback to run on each block in the tree before it is traversed and serialized.
       
  1467  *                                It is called with the following arguments: &$block, $parent_block, $previous_block.
       
  1468  *                                Its string return value will be prepended to the serialized block markup.
       
  1469  * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
       
  1470  *                                It is called with the following arguments: &$block, $parent_block, $next_block.
       
  1471  *                                Its string return value will be appended to the serialized block markup.
       
  1472  * @return string Serialized block markup.
       
  1473  */
       
  1474 function traverse_and_serialize_block( $block, $pre_callback = null, $post_callback = null ) {
       
  1475 	$block_content = '';
       
  1476 	$block_index   = 0;
       
  1477 
       
  1478 	foreach ( $block['innerContent'] as $chunk ) {
       
  1479 		if ( is_string( $chunk ) ) {
       
  1480 			$block_content .= $chunk;
       
  1481 		} else {
       
  1482 			$inner_block = $block['innerBlocks'][ $block_index ];
       
  1483 
       
  1484 			if ( is_callable( $pre_callback ) ) {
       
  1485 				$prev = 0 === $block_index
       
  1486 					? null
       
  1487 					: $block['innerBlocks'][ $block_index - 1 ];
       
  1488 
       
  1489 				$block_content .= call_user_func_array(
       
  1490 					$pre_callback,
       
  1491 					array( &$inner_block, &$block, $prev )
       
  1492 				);
       
  1493 			}
       
  1494 
       
  1495 			if ( is_callable( $post_callback ) ) {
       
  1496 				$next = count( $block['innerBlocks'] ) - 1 === $block_index
       
  1497 					? null
       
  1498 					: $block['innerBlocks'][ $block_index + 1 ];
       
  1499 
       
  1500 				$post_markup = call_user_func_array(
       
  1501 					$post_callback,
       
  1502 					array( &$inner_block, &$block, $next )
       
  1503 				);
       
  1504 			}
       
  1505 
       
  1506 			$block_content .= traverse_and_serialize_block( $inner_block, $pre_callback, $post_callback );
       
  1507 			$block_content .= isset( $post_markup ) ? $post_markup : '';
       
  1508 
       
  1509 			++$block_index;
       
  1510 		}
       
  1511 	}
       
  1512 
       
  1513 	if ( ! is_array( $block['attrs'] ) ) {
       
  1514 		$block['attrs'] = array();
       
  1515 	}
       
  1516 
       
  1517 	return get_comment_delimited_block_content(
       
  1518 		$block['blockName'],
       
  1519 		$block['attrs'],
       
  1520 		$block_content
       
  1521 	);
       
  1522 }
       
  1523 
       
  1524 /**
       
  1525  * Replaces patterns in a block tree with their content.
       
  1526  *
       
  1527  * @since 6.6.0
       
  1528  *
       
  1529  * @param array $blocks An array blocks.
       
  1530  *
       
  1531  * @return array An array of blocks with patterns replaced by their content.
       
  1532  */
       
  1533 function resolve_pattern_blocks( $blocks ) {
       
  1534 	static $inner_content;
       
  1535 	// Keep track of seen references to avoid infinite loops.
       
  1536 	static $seen_refs = array();
       
  1537 	$i                = 0;
       
  1538 	while ( $i < count( $blocks ) ) {
       
  1539 		if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) {
       
  1540 			$attrs = $blocks[ $i ]['attrs'];
       
  1541 
       
  1542 			if ( empty( $attrs['slug'] ) ) {
       
  1543 				++$i;
       
  1544 				continue;
       
  1545 			}
       
  1546 
       
  1547 			$slug = $attrs['slug'];
       
  1548 
       
  1549 			if ( isset( $seen_refs[ $slug ] ) ) {
       
  1550 				// Skip recursive patterns.
       
  1551 				array_splice( $blocks, $i, 1 );
       
  1552 				continue;
       
  1553 			}
       
  1554 
       
  1555 			$registry = WP_Block_Patterns_Registry::get_instance();
       
  1556 			$pattern  = $registry->get_registered( $slug );
       
  1557 
       
  1558 			// Skip unknown patterns.
       
  1559 			if ( ! $pattern ) {
       
  1560 				++$i;
       
  1561 				continue;
       
  1562 			}
       
  1563 
       
  1564 			$blocks_to_insert   = parse_blocks( $pattern['content'] );
       
  1565 			$seen_refs[ $slug ] = true;
       
  1566 			$prev_inner_content = $inner_content;
       
  1567 			$inner_content      = null;
       
  1568 			$blocks_to_insert   = resolve_pattern_blocks( $blocks_to_insert );
       
  1569 			$inner_content      = $prev_inner_content;
       
  1570 			unset( $seen_refs[ $slug ] );
       
  1571 			array_splice( $blocks, $i, 1, $blocks_to_insert );
       
  1572 
       
  1573 			// If we have inner content, we need to insert nulls in the
       
  1574 			// inner content array, otherwise serialize_blocks will skip
       
  1575 			// blocks.
       
  1576 			if ( $inner_content ) {
       
  1577 				$null_indices  = array_keys( $inner_content, null, true );
       
  1578 				$content_index = $null_indices[ $i ];
       
  1579 				$nulls         = array_fill( 0, count( $blocks_to_insert ), null );
       
  1580 				array_splice( $inner_content, $content_index, 1, $nulls );
       
  1581 			}
       
  1582 
       
  1583 			// Skip inserted blocks.
       
  1584 			$i += count( $blocks_to_insert );
       
  1585 		} else {
       
  1586 			if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) {
       
  1587 				$prev_inner_content           = $inner_content;
       
  1588 				$inner_content                = $blocks[ $i ]['innerContent'];
       
  1589 				$blocks[ $i ]['innerBlocks']  = resolve_pattern_blocks(
       
  1590 					$blocks[ $i ]['innerBlocks']
       
  1591 				);
       
  1592 				$blocks[ $i ]['innerContent'] = $inner_content;
       
  1593 				$inner_content                = $prev_inner_content;
       
  1594 			}
       
  1595 			++$i;
       
  1596 		}
       
  1597 	}
       
  1598 	return $blocks;
       
  1599 }
       
  1600 
       
  1601 /**
       
  1602  * Given an array of parsed block trees, applies callbacks before and after serializing them and
       
  1603  * returns their concatenated output.
       
  1604  *
       
  1605  * Recursively traverses the blocks and their inner blocks and applies the two callbacks provided as
       
  1606  * arguments, the first one before serializing a block, and the second one after serializing.
       
  1607  * If either callback returns a string value, it will be prepended and appended to the serialized
       
  1608  * block markup, respectively.
       
  1609  *
       
  1610  * The callbacks will receive a reference to the current block as their first argument, so that they
       
  1611  * can also modify it, and the current block's parent block as second argument. Finally, the
       
  1612  * `$pre_callback` receives the previous block, whereas the `$post_callback` receives
       
  1613  * the next block as third argument.
       
  1614  *
       
  1615  * Serialized blocks are returned including comment delimiters, and with all attributes serialized.
       
  1616  *
       
  1617  * This function should be used when there is a need to modify the saved blocks, or to inject markup
       
  1618  * into the return value. Prefer `serialize_blocks` when preparing blocks to be saved to post content.
       
  1619  *
       
  1620  * This function is meant for internal use only.
       
  1621  *
       
  1622  * @since 6.4.0
       
  1623  * @access private
       
  1624  *
       
  1625  * @see serialize_blocks()
       
  1626  *
       
  1627  * @param array[]  $blocks        An array of parsed blocks. See WP_Block_Parser_Block.
       
  1628  * @param callable $pre_callback  Callback to run on each block in the tree before it is traversed and serialized.
       
  1629  *                                It is called with the following arguments: &$block, $parent_block, $previous_block.
       
  1630  *                                Its string return value will be prepended to the serialized block markup.
       
  1631  * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
       
  1632  *                                It is called with the following arguments: &$block, $parent_block, $next_block.
       
  1633  *                                Its string return value will be appended to the serialized block markup.
       
  1634  * @return string Serialized block markup.
       
  1635  */
       
  1636 function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) {
       
  1637 	$result       = '';
       
  1638 	$parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference.
       
  1639 
       
  1640 	foreach ( $blocks as $index => $block ) {
       
  1641 		if ( is_callable( $pre_callback ) ) {
       
  1642 			$prev = 0 === $index
       
  1643 				? null
       
  1644 				: $blocks[ $index - 1 ];
       
  1645 
       
  1646 			$result .= call_user_func_array(
       
  1647 				$pre_callback,
       
  1648 				array( &$block, &$parent_block, $prev )
       
  1649 			);
       
  1650 		}
       
  1651 
       
  1652 		if ( is_callable( $post_callback ) ) {
       
  1653 			$next = count( $blocks ) - 1 === $index
       
  1654 				? null
       
  1655 				: $blocks[ $index + 1 ];
       
  1656 
       
  1657 			$post_markup = call_user_func_array(
       
  1658 				$post_callback,
       
  1659 				array( &$block, &$parent_block, $next )
       
  1660 			);
       
  1661 		}
       
  1662 
       
  1663 		$result .= traverse_and_serialize_block( $block, $pre_callback, $post_callback );
       
  1664 		$result .= isset( $post_markup ) ? $post_markup : '';
       
  1665 	}
       
  1666 
       
  1667 	return $result;
       
  1668 }
       
  1669 
       
  1670 /**
       
  1671  * Filters and sanitizes block content to remove non-allowable HTML
       
  1672  * from parsed block attribute values.
   637  *
  1673  *
   638  * @since 5.3.1
  1674  * @since 5.3.1
   639  *
  1675  *
   640  * @param string         $text              Text that may contain block content.
  1676  * @param string         $text              Text that may contain block content.
   641  * @param array[]|string $allowed_html      An array of allowed HTML elements
  1677  * @param array[]|string $allowed_html      Optional. An array of allowed HTML elements and attributes,
   642  *                                          and attributes, or a context name
  1678  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   643  *                                          such as 'post'.
  1679  *                                          for the list of accepted context names. Default 'post'.
   644  * @param string[]       $allowed_protocols Array of allowed URL protocols.
  1680  * @param string[]       $allowed_protocols Optional. Array of allowed URL protocols.
       
  1681  *                                          Defaults to the result of wp_allowed_protocols().
   645  * @return string The filtered and sanitized content result.
  1682  * @return string The filtered and sanitized content result.
   646  */
  1683  */
   647 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
  1684 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
   648 	$result = '';
  1685 	$result = '';
       
  1686 
       
  1687 	if ( str_contains( $text, '<!--' ) && str_contains( $text, '--->' ) ) {
       
  1688 		$text = preg_replace_callback( '%<!--(.*?)--->%', '_filter_block_content_callback', $text );
       
  1689 	}
   649 
  1690 
   650 	$blocks = parse_blocks( $text );
  1691 	$blocks = parse_blocks( $text );
   651 	foreach ( $blocks as $block ) {
  1692 	foreach ( $blocks as $block ) {
   652 		$block   = filter_block_kses( $block, $allowed_html, $allowed_protocols );
  1693 		$block   = filter_block_kses( $block, $allowed_html, $allowed_protocols );
   653 		$result .= serialize_block( $block );
  1694 		$result .= serialize_block( $block );
   655 
  1696 
   656 	return $result;
  1697 	return $result;
   657 }
  1698 }
   658 
  1699 
   659 /**
  1700 /**
   660  * Filters and sanitizes a parsed block to remove non-allowable HTML from block
  1701  * Callback used for regular expression replacement in filter_block_content().
   661  * attribute values.
  1702  *
       
  1703  * @since 6.2.1
       
  1704  * @access private
       
  1705  *
       
  1706  * @param array $matches Array of preg_replace_callback matches.
       
  1707  * @return string Replacement string.
       
  1708  */
       
  1709 function _filter_block_content_callback( $matches ) {
       
  1710 	return '<!--' . rtrim( $matches[1], '-' ) . '-->';
       
  1711 }
       
  1712 
       
  1713 /**
       
  1714  * Filters and sanitizes a parsed block to remove non-allowable HTML
       
  1715  * from block attribute values.
   662  *
  1716  *
   663  * @since 5.3.1
  1717  * @since 5.3.1
   664  *
  1718  *
   665  * @param WP_Block_Parser_Block $block             The parsed block object.
  1719  * @param WP_Block_Parser_Block $block             The parsed block object.
   666  * @param array[]|string        $allowed_html      An array of allowed HTML
  1720  * @param array[]|string        $allowed_html      An array of allowed HTML elements and attributes,
   667  *                                                 elements and attributes, or a
  1721  *                                                 or a context name such as 'post'. See wp_kses_allowed_html()
   668  *                                                 context name such as 'post'.
  1722  *                                                 for the list of accepted context names.
   669  * @param string[]              $allowed_protocols Allowed URL protocols.
  1723  * @param string[]              $allowed_protocols Optional. Array of allowed URL protocols.
       
  1724  *                                                 Defaults to the result of wp_allowed_protocols().
   670  * @return array The filtered and sanitized block object result.
  1725  * @return array The filtered and sanitized block object result.
   671  */
  1726  */
   672 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
  1727 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
   673 	$block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
  1728 	$block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols, $block );
   674 
  1729 
   675 	if ( is_array( $block['innerBlocks'] ) ) {
  1730 	if ( is_array( $block['innerBlocks'] ) ) {
   676 		foreach ( $block['innerBlocks'] as $i => $inner_block ) {
  1731 		foreach ( $block['innerBlocks'] as $i => $inner_block ) {
   677 			$block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
  1732 			$block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
   678 		}
  1733 		}
   680 
  1735 
   681 	return $block;
  1736 	return $block;
   682 }
  1737 }
   683 
  1738 
   684 /**
  1739 /**
   685  * Filters and sanitizes a parsed block attribute value to remove non-allowable
  1740  * Filters and sanitizes a parsed block attribute value to remove
   686  * HTML.
  1741  * non-allowable HTML.
   687  *
  1742  *
   688  * @since 5.3.1
  1743  * @since 5.3.1
       
  1744  * @since 6.5.5 Added the `$block_context` parameter.
   689  *
  1745  *
   690  * @param string[]|string $value             The attribute value to filter.
  1746  * @param string[]|string $value             The attribute value to filter.
   691  * @param array[]|string  $allowed_html      An array of allowed HTML elements
  1747  * @param array[]|string  $allowed_html      An array of allowed HTML elements and attributes,
   692  *                                           and attributes, or a context name
  1748  *                                           or a context name such as 'post'. See wp_kses_allowed_html()
   693  *                                           such as 'post'.
  1749  *                                           for the list of accepted context names.
   694  * @param string[]        $allowed_protocols Array of allowed URL protocols.
  1750  * @param string[]        $allowed_protocols Optional. Array of allowed URL protocols.
       
  1751  *                                           Defaults to the result of wp_allowed_protocols().
       
  1752  * @param array           $block_context     Optional. The block the attribute belongs to, in parsed block array format.
   695  * @return string[]|string The filtered and sanitized result.
  1753  * @return string[]|string The filtered and sanitized result.
   696  */
  1754  */
   697 function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
  1755 function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array(), $block_context = null ) {
   698 	if ( is_array( $value ) ) {
  1756 	if ( is_array( $value ) ) {
   699 		foreach ( $value as $key => $inner_value ) {
  1757 		foreach ( $value as $key => $inner_value ) {
   700 			$filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
  1758 			$filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols, $block_context );
   701 			$filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
  1759 			$filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols, $block_context );
   702 
  1760 
       
  1761 			if ( isset( $block_context['blockName'] ) && 'core/template-part' === $block_context['blockName'] ) {
       
  1762 				$filtered_value = filter_block_core_template_part_attributes( $filtered_value, $filtered_key, $allowed_html );
       
  1763 			}
   703 			if ( $filtered_key !== $key ) {
  1764 			if ( $filtered_key !== $key ) {
   704 				unset( $value[ $key ] );
  1765 				unset( $value[ $key ] );
   705 			}
  1766 			}
   706 
  1767 
   707 			$value[ $filtered_key ] = $filtered_value;
  1768 			$value[ $filtered_key ] = $filtered_value;
   712 
  1773 
   713 	return $value;
  1774 	return $value;
   714 }
  1775 }
   715 
  1776 
   716 /**
  1777 /**
       
  1778  * Sanitizes the value of the Template Part block's `tagName` attribute.
       
  1779  *
       
  1780  * @since 6.5.5
       
  1781  *
       
  1782  * @param string         $attribute_value The attribute value to filter.
       
  1783  * @param string         $attribute_name  The attribute name.
       
  1784  * @param array[]|string $allowed_html    An array of allowed HTML elements and attributes,
       
  1785  *                                        or a context name such as 'post'. See wp_kses_allowed_html()
       
  1786  *                                        for the list of accepted context names.
       
  1787  * @return string The sanitized attribute value.
       
  1788  */
       
  1789 function filter_block_core_template_part_attributes( $attribute_value, $attribute_name, $allowed_html ) {
       
  1790 	if ( empty( $attribute_value ) || 'tagName' !== $attribute_name ) {
       
  1791 		return $attribute_value;
       
  1792 	}
       
  1793 	if ( ! is_array( $allowed_html ) ) {
       
  1794 		$allowed_html = wp_kses_allowed_html( $allowed_html );
       
  1795 	}
       
  1796 	return isset( $allowed_html[ $attribute_value ] ) ? $attribute_value : '';
       
  1797 }
       
  1798 
       
  1799 /**
   717  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
  1800  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
   718  *
  1801  *
   719  * As the excerpt should be a small string of text relevant to the full post content,
  1802  * As the excerpt should be a small string of text relevant to the full post content,
   720  * this function renders the blocks that are most likely to contain such text.
  1803  * this function renders the blocks that are most likely to contain such text.
   721  *
  1804  *
   723  *
  1806  *
   724  * @param string $content The content to parse.
  1807  * @param string $content The content to parse.
   725  * @return string The parsed and filtered content.
  1808  * @return string The parsed and filtered content.
   726  */
  1809  */
   727 function excerpt_remove_blocks( $content ) {
  1810 function excerpt_remove_blocks( $content ) {
       
  1811 	if ( ! has_blocks( $content ) ) {
       
  1812 		return $content;
       
  1813 	}
       
  1814 
   728 	$allowed_inner_blocks = array(
  1815 	$allowed_inner_blocks = array(
   729 		// Classic blocks have their blockName set to null.
  1816 		// Classic blocks have their blockName set to null.
   730 		null,
  1817 		null,
   731 		'core/freeform',
  1818 		'core/freeform',
   732 		'core/heading',
  1819 		'core/heading',
   798 
  1885 
   799 	return $output;
  1886 	return $output;
   800 }
  1887 }
   801 
  1888 
   802 /**
  1889 /**
   803  * Render inner blocks from the allowed wrapper blocks
  1890  * Parses footnotes markup out of a content string,
       
  1891  * and renders those appropriate for the excerpt.
       
  1892  *
       
  1893  * @since 6.3.0
       
  1894  *
       
  1895  * @param string $content The content to parse.
       
  1896  * @return string The parsed and filtered content.
       
  1897  */
       
  1898 function excerpt_remove_footnotes( $content ) {
       
  1899 	if ( ! str_contains( $content, 'data-fn=' ) ) {
       
  1900 		return $content;
       
  1901 	}
       
  1902 
       
  1903 	return preg_replace(
       
  1904 		'_<sup data-fn="[^"]+" class="[^"]+">\s*<a href="[^"]+" id="[^"]+">\d+</a>\s*</sup>_',
       
  1905 		'',
       
  1906 		$content
       
  1907 	);
       
  1908 }
       
  1909 
       
  1910 /**
       
  1911  * Renders inner blocks from the allowed wrapper blocks
   804  * for generating an excerpt.
  1912  * for generating an excerpt.
   805  *
  1913  *
   806  * @since 5.8.0
  1914  * @since 5.8.0
   807  * @access private
  1915  * @access private
   808  *
  1916  *
   831 /**
  1939 /**
   832  * Renders a single block into a HTML string.
  1940  * Renders a single block into a HTML string.
   833  *
  1941  *
   834  * @since 5.0.0
  1942  * @since 5.0.0
   835  *
  1943  *
   836  * @global WP_Post  $post     The post to edit.
  1944  * @global WP_Post $post The post to edit.
   837  *
  1945  *
   838  * @param array $parsed_block A single parsed block object.
  1946  * @param array $parsed_block {
       
  1947  *     A representative array of the block being rendered. See WP_Block_Parser_Block.
       
  1948  *
       
  1949  *     @type string   $blockName    Name of block.
       
  1950  *     @type array    $attrs        Attributes from block comment delimiters.
       
  1951  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  1952  *                                  have the same structure as this one.
       
  1953  *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  1954  *     @type array    $innerContent List of string fragments and null markers where
       
  1955  *                                  inner blocks were found.
       
  1956  * }
   839  * @return string String of rendered HTML.
  1957  * @return string String of rendered HTML.
   840  */
  1958  */
   841 function render_block( $parsed_block ) {
  1959 function render_block( $parsed_block ) {
   842 	global $post;
  1960 	global $post;
   843 	$parent_block = null;
  1961 	$parent_block = null;
   847 	 *
  1965 	 *
   848 	 * @since 5.1.0
  1966 	 * @since 5.1.0
   849 	 * @since 5.9.0 The `$parent_block` parameter was added.
  1967 	 * @since 5.9.0 The `$parent_block` parameter was added.
   850 	 *
  1968 	 *
   851 	 * @param string|null   $pre_render   The pre-rendered content. Default null.
  1969 	 * @param string|null   $pre_render   The pre-rendered content. Default null.
   852 	 * @param array         $parsed_block The block being rendered.
  1970 	 * @param array         $parsed_block {
       
  1971 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
       
  1972 	 *
       
  1973 	 *     @type string   $blockName    Name of block.
       
  1974 	 *     @type array    $attrs        Attributes from block comment delimiters.
       
  1975 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  1976 	 *                                  have the same structure as this one.
       
  1977 	 *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  1978 	 *     @type array    $innerContent List of string fragments and null markers where
       
  1979 	 *                                  inner blocks were found.
       
  1980 	 * }
   853 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  1981 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
   854 	 */
  1982 	 */
   855 	$pre_render = apply_filters( 'pre_render_block', null, $parsed_block, $parent_block );
  1983 	$pre_render = apply_filters( 'pre_render_block', null, $parsed_block, $parent_block );
   856 	if ( ! is_null( $pre_render ) ) {
  1984 	if ( ! is_null( $pre_render ) ) {
   857 		return $pre_render;
  1985 		return $pre_render;
   863 	 * Filters the block being rendered in render_block(), before it's processed.
  1991 	 * Filters the block being rendered in render_block(), before it's processed.
   864 	 *
  1992 	 *
   865 	 * @since 5.1.0
  1993 	 * @since 5.1.0
   866 	 * @since 5.9.0 The `$parent_block` parameter was added.
  1994 	 * @since 5.9.0 The `$parent_block` parameter was added.
   867 	 *
  1995 	 *
   868 	 * @param array         $parsed_block The block being rendered.
  1996 	 * @param array         $parsed_block {
   869 	 * @param array         $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
  1997 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
       
  1998 	 *
       
  1999 	 *     @type string   $blockName    Name of block.
       
  2000 	 *     @type array    $attrs        Attributes from block comment delimiters.
       
  2001 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  2002 	 *                                  have the same structure as this one.
       
  2003 	 *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  2004 	 *     @type array    $innerContent List of string fragments and null markers where
       
  2005 	 *                                  inner blocks were found.
       
  2006 	 * }
       
  2007 	 * @param array         $source_block {
       
  2008 	 *     An un-modified copy of `$parsed_block`, as it appeared in the source content.
       
  2009 	 *     See WP_Block_Parser_Block.
       
  2010 	 *
       
  2011 	 *     @type string   $blockName    Name of block.
       
  2012 	 *     @type array    $attrs        Attributes from block comment delimiters.
       
  2013 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  2014 	 *                                  have the same structure as this one.
       
  2015 	 *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  2016 	 *     @type array    $innerContent List of string fragments and null markers where
       
  2017 	 *                                  inner blocks were found.
       
  2018 	 * }
   870 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  2019 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
   871 	 */
  2020 	 */
   872 	$parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block, $parent_block );
  2021 	$parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block, $parent_block );
   873 
  2022 
   874 	$context = array();
  2023 	$context = array();
   890 	 *
  2039 	 *
   891 	 * @since 5.5.0
  2040 	 * @since 5.5.0
   892 	 * @since 5.9.0 The `$parent_block` parameter was added.
  2041 	 * @since 5.9.0 The `$parent_block` parameter was added.
   893 	 *
  2042 	 *
   894 	 * @param array         $context      Default context.
  2043 	 * @param array         $context      Default context.
   895 	 * @param array         $parsed_block Block being rendered, filtered by `render_block_data`.
  2044 	 * @param array         $parsed_block {
       
  2045 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
       
  2046 	 *
       
  2047 	 *     @type string   $blockName    Name of block.
       
  2048 	 *     @type array    $attrs        Attributes from block comment delimiters.
       
  2049 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  2050 	 *                                  have the same structure as this one.
       
  2051 	 *     @type string   $innerHTML    HTML from inside block comment delimiters.
       
  2052 	 *     @type array    $innerContent List of string fragments and null markers where
       
  2053 	 *                                  inner blocks were found.
       
  2054 	 * }
   896 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  2055 	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
   897 	 */
  2056 	 */
   898 	$context = apply_filters( 'render_block_context', $context, $parsed_block, $parent_block );
  2057 	$context = apply_filters( 'render_block_context', $context, $parsed_block, $parent_block );
   899 
  2058 
   900 	$block = new WP_Block( $parsed_block, $context );
  2059 	$block = new WP_Block( $parsed_block, $context );
   906  * Parses blocks out of a content string.
  2065  * Parses blocks out of a content string.
   907  *
  2066  *
   908  * @since 5.0.0
  2067  * @since 5.0.0
   909  *
  2068  *
   910  * @param string $content Post content.
  2069  * @param string $content Post content.
   911  * @return array[] Array of parsed block objects.
  2070  * @return array[] {
       
  2071  *     Array of block structures.
       
  2072  *
       
  2073  *     @type array ...$0 {
       
  2074  *         A representative array of a single parsed block object. See WP_Block_Parser_Block.
       
  2075  *
       
  2076  *         @type string   $blockName    Name of block.
       
  2077  *         @type array    $attrs        Attributes from block comment delimiters.
       
  2078  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
       
  2079  *                                      have the same structure as this one.
       
  2080  *         @type string   $innerHTML    HTML from inside block comment delimiters.
       
  2081  *         @type array    $innerContent List of string fragments and null markers where
       
  2082  *                                      inner blocks were found.
       
  2083  *     }
       
  2084  * }
   912  */
  2085  */
   913 function parse_blocks( $content ) {
  2086 function parse_blocks( $content ) {
   914 	/**
  2087 	/**
   915 	 * Filter to allow plugins to replace the server-side block parser
  2088 	 * Filter to allow plugins to replace the server-side block parser.
   916 	 *
  2089 	 *
   917 	 * @since 5.0.0
  2090 	 * @since 5.0.0
   918 	 *
  2091 	 *
   919 	 * @param string $parser_class Name of block parser class.
  2092 	 * @param string $parser_class Name of block parser class.
   920 	 */
  2093 	 */
   985 
  2158 
   986 /**
  2159 /**
   987  * Registers a new block style.
  2160  * Registers a new block style.
   988  *
  2161  *
   989  * @since 5.3.0
  2162  * @since 5.3.0
   990  *
  2163  * @since 6.6.0 Added support for registering styles for multiple block types.
   991  * @param string $block_name       Block type name including namespace.
  2164  *
   992  * @param array  $style_properties Array containing the properties of the style name,
  2165  * @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/
   993  *                                 label, style (name of the stylesheet to be enqueued),
  2166  *
   994  *                                 inline_style (string containing the CSS to be added).
  2167  * @param string|string[] $block_name       Block type name including namespace or array of namespaced block type names.
       
  2168  * @param array           $style_properties Array containing the properties of the style name, label,
       
  2169  *                                          style_handle (name of the stylesheet to be enqueued),
       
  2170  *                                          inline_style (string containing the CSS to be added),
       
  2171  *                                          style_data (theme.json-like array to generate CSS from).
       
  2172  *                                          See WP_Block_Styles_Registry::register().
   995  * @return bool True if the block style was registered with success and false otherwise.
  2173  * @return bool True if the block style was registered with success and false otherwise.
   996  */
  2174  */
   997 function register_block_style( $block_name, $style_properties ) {
  2175 function register_block_style( $block_name, $style_properties ) {
   998 	return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties );
  2176 	return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties );
   999 }
  2177 }
  1013 
  2191 
  1014 /**
  2192 /**
  1015  * Checks whether the current block type supports the feature requested.
  2193  * Checks whether the current block type supports the feature requested.
  1016  *
  2194  *
  1017  * @since 5.8.0
  2195  * @since 5.8.0
  1018  *
  2196  * @since 6.4.0 The `$feature` parameter now supports a string.
  1019  * @param WP_Block_Type $block_type Block type to check for support.
  2197  *
  1020  * @param string        $feature    Name of the feature to check support for.
  2198  * @param WP_Block_Type $block_type    Block type to check for support.
  1021  * @param mixed         $default    Optional. Fallback value for feature support. Default false.
  2199  * @param string|array  $feature       Feature slug, or path to a specific feature to check support for.
       
  2200  * @param mixed         $default_value Optional. Fallback value for feature support. Default false.
  1022  * @return bool Whether the feature is supported.
  2201  * @return bool Whether the feature is supported.
  1023  */
  2202  */
  1024 function block_has_support( $block_type, $feature, $default = false ) {
  2203 function block_has_support( $block_type, $feature, $default_value = false ) {
  1025 	$block_support = $default;
  2204 	$block_support = $default_value;
  1026 	if ( $block_type && property_exists( $block_type, 'supports' ) ) {
  2205 	if ( $block_type instanceof WP_Block_Type ) {
  1027 		$block_support = _wp_array_get( $block_type->supports, $feature, $default );
  2206 		if ( is_array( $feature ) && count( $feature ) === 1 ) {
       
  2207 			$feature = $feature[0];
       
  2208 		}
       
  2209 
       
  2210 		if ( is_array( $feature ) ) {
       
  2211 			$block_support = _wp_array_get( $block_type->supports, $feature, $default_value );
       
  2212 		} elseif ( isset( $block_type->supports[ $feature ] ) ) {
       
  2213 			$block_support = $block_type->supports[ $feature ];
       
  2214 		}
  1028 	}
  2215 	}
  1029 
  2216 
  1030 	return true === $block_support || is_array( $block_support );
  2217 	return true === $block_support || is_array( $block_support );
  1031 }
  2218 }
  1032 
  2219 
  1055 		'fontSize',
  2242 		'fontSize',
  1056 		'lineHeight',
  2243 		'lineHeight',
  1057 	);
  2244 	);
  1058 
  2245 
  1059 	foreach ( $typography_keys as $typography_key ) {
  2246 	foreach ( $typography_keys as $typography_key ) {
  1060 		$support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null );
  2247 		$support_for_key = isset( $metadata['supports'][ $typography_key ] ) ? $metadata['supports'][ $typography_key ] : null;
  1061 
  2248 
  1062 		if ( null !== $support_for_key ) {
  2249 		if ( null !== $support_for_key ) {
  1063 			_doing_it_wrong(
  2250 			_doing_it_wrong(
  1064 				'register_block_type_from_metadata()',
  2251 				'register_block_type_from_metadata()',
  1065 				sprintf(
  2252 				sprintf(
  1087  * a `Query` block properties.
  2274  * a `Query` block properties.
  1088  *
  2275  *
  1089  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
  2276  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
  1090  *
  2277  *
  1091  * @since 5.8.0
  2278  * @since 5.8.0
       
  2279  * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
  1092  *
  2280  *
  1093  * @param WP_Block $block Block instance.
  2281  * @param WP_Block $block Block instance.
  1094  * @param int      $page  Current query's page.
  2282  * @param int      $page  Current query's page.
  1095  *
  2283  *
  1096  * @return array Returns the constructed WP_Query arguments.
  2284  * @return array Returns the constructed WP_Query arguments.
  1111 			}
  2299 			}
  1112 		}
  2300 		}
  1113 		if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
  2301 		if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
  1114 			$sticky = get_option( 'sticky_posts' );
  2302 			$sticky = get_option( 'sticky_posts' );
  1115 			if ( 'only' === $block->context['query']['sticky'] ) {
  2303 			if ( 'only' === $block->context['query']['sticky'] ) {
  1116 				$query['post__in'] = $sticky;
  2304 				/*
       
  2305 				 * Passing an empty array to post__in will return have_posts() as true (and all posts will be returned).
       
  2306 				 * Logic should be used before hand to determine if WP_Query should be used in the event that the array
       
  2307 				 * being passed to post__in is empty.
       
  2308 				 *
       
  2309 				 * @see https://core.trac.wordpress.org/ticket/28099
       
  2310 				 */
       
  2311 				$query['post__in']            = ! empty( $sticky ) ? $sticky : array( 0 );
       
  2312 				$query['ignore_sticky_posts'] = 1;
  1117 			} else {
  2313 			} else {
  1118 				$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
  2314 				$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
  1119 			}
  2315 			}
  1120 		}
  2316 		}
  1121 		if ( ! empty( $block->context['query']['exclude'] ) ) {
  2317 		if ( ! empty( $block->context['query']['exclude'] ) ) {
  1179 		}
  2375 		}
  1180 		if ( isset( $block->context['query']['orderBy'] ) ) {
  2376 		if ( isset( $block->context['query']['orderBy'] ) ) {
  1181 			$query['orderby'] = $block->context['query']['orderBy'];
  2377 			$query['orderby'] = $block->context['query']['orderBy'];
  1182 		}
  2378 		}
  1183 		if (
  2379 		if (
  1184 			isset( $block->context['query']['author'] ) &&
  2380 			isset( $block->context['query']['author'] )
  1185 			(int) $block->context['query']['author'] > 0
       
  1186 		) {
  2381 		) {
  1187 			$query['author'] = (int) $block->context['query']['author'];
  2382 			if ( is_array( $block->context['query']['author'] ) ) {
       
  2383 				$query['author__in'] = array_filter( array_map( 'intval', $block->context['query']['author'] ) );
       
  2384 			} elseif ( is_string( $block->context['query']['author'] ) ) {
       
  2385 				$query['author__in'] = array_filter( array_map( 'intval', explode( ',', $block->context['query']['author'] ) ) );
       
  2386 			} elseif ( is_int( $block->context['query']['author'] ) && $block->context['query']['author'] > 0 ) {
       
  2387 				$query['author'] = $block->context['query']['author'];
       
  2388 			}
  1188 		}
  2389 		}
  1189 		if ( ! empty( $block->context['query']['search'] ) ) {
  2390 		if ( ! empty( $block->context['query']['search'] ) ) {
  1190 			$query['s'] = $block->context['query']['search'];
  2391 			$query['s'] = $block->context['query']['search'];
  1191 		}
  2392 		}
  1192 	}
  2393 		if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
  1193 	return $query;
  2394 			$query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) );
       
  2395 		}
       
  2396 	}
       
  2397 
       
  2398 	/**
       
  2399 	 * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
       
  2400 	 *
       
  2401 	 * Anything to this filter should be compatible with the `WP_Query` API to form
       
  2402 	 * the query context which will be passed down to the Query Loop Block's children.
       
  2403 	 * This can help, for example, to include additional settings or meta queries not
       
  2404 	 * directly supported by the core Query Loop Block, and extend its capabilities.
       
  2405 	 *
       
  2406 	 * Please note that this will only influence the query that will be rendered on the
       
  2407 	 * front-end. The editor preview is not affected by this filter. Also, worth noting
       
  2408 	 * that the editor preview uses the REST API, so, ideally, one should aim to provide
       
  2409 	 * attributes which are also compatible with the REST API, in order to be able to
       
  2410 	 * implement identical queries on both sides.
       
  2411 	 *
       
  2412 	 * @since 6.1.0
       
  2413 	 *
       
  2414 	 * @param array    $query Array containing parameters for `WP_Query` as parsed by the block context.
       
  2415 	 * @param WP_Block $block Block instance.
       
  2416 	 * @param int      $page  Current query's page.
       
  2417 	 */
       
  2418 	return apply_filters( 'query_loop_block_query_vars', $query, $block, $page );
  1194 }
  2419 }
  1195 
  2420 
  1196 /**
  2421 /**
  1197  * Helper function that returns the proper pagination arrow HTML for
  2422  * Helper function that returns the proper pagination arrow HTML for
  1198  * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based
  2423  * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based
  1201  * It's used in QueryPaginationNext and QueryPaginationPrevious blocks.
  2426  * It's used in QueryPaginationNext and QueryPaginationPrevious blocks.
  1202  *
  2427  *
  1203  * @since 5.9.0
  2428  * @since 5.9.0
  1204  *
  2429  *
  1205  * @param WP_Block $block   Block instance.
  2430  * @param WP_Block $block   Block instance.
  1206  * @param boolean  $is_next Flag for handling `next/previous` blocks.
  2431  * @param bool     $is_next Flag for handling `next/previous` blocks.
  1207  *
       
  1208  * @return string|null The pagination arrow HTML or null if there is none.
  2432  * @return string|null The pagination arrow HTML or null if there is none.
  1209  */
  2433  */
  1210 function get_query_pagination_arrow( $block, $is_next ) {
  2434 function get_query_pagination_arrow( $block, $is_next ) {
  1211 	$arrow_map = array(
  2435 	$arrow_map = array(
  1212 		'none'    => '',
  2436 		'none'    => '',
  1222 	if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) {
  2446 	if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) {
  1223 		$pagination_type = $is_next ? 'next' : 'previous';
  2447 		$pagination_type = $is_next ? 'next' : 'previous';
  1224 		$arrow_attribute = $block->context['paginationArrow'];
  2448 		$arrow_attribute = $block->context['paginationArrow'];
  1225 		$arrow           = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ];
  2449 		$arrow           = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ];
  1226 		$arrow_classes   = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  2450 		$arrow_classes   = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  1227 		return "<span class='$arrow_classes'>$arrow</span>";
  2451 		return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
  1228 	}
  2452 	}
  1229 	return null;
  2453 	return null;
  1230 }
  2454 }
  1231 
       
  1232 /**
       
  1233  * Allows multiple block styles.
       
  1234  *
       
  1235  * @since 5.9.0
       
  1236  *
       
  1237  * @param array $metadata Metadata for registering a block type.
       
  1238  * @return array Metadata for registering a block type.
       
  1239  */
       
  1240 function _wp_multiple_block_styles( $metadata ) {
       
  1241 	foreach ( array( 'style', 'editorStyle' ) as $key ) {
       
  1242 		if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) {
       
  1243 			$default_style = array_shift( $metadata[ $key ] );
       
  1244 			foreach ( $metadata[ $key ] as $handle ) {
       
  1245 				$args = array( 'handle' => $handle );
       
  1246 				if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) {
       
  1247 					$style_path      = remove_block_asset_path_prefix( $handle );
       
  1248 					$theme_path_norm = wp_normalize_path( get_theme_file_path() );
       
  1249 					$style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) );
       
  1250 					$is_theme_block  = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $theme_path_norm );
       
  1251 
       
  1252 					$style_uri = plugins_url( $style_path, $metadata['file'] );
       
  1253 
       
  1254 					if ( $is_theme_block ) {
       
  1255 						$style_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $style_path_norm ) );
       
  1256 					}
       
  1257 
       
  1258 					$args = array(
       
  1259 						'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ),
       
  1260 						'src'    => $style_uri,
       
  1261 					);
       
  1262 				}
       
  1263 
       
  1264 				wp_enqueue_block_style( $metadata['name'], $args );
       
  1265 			}
       
  1266 
       
  1267 			// Only return the 1st item in the array.
       
  1268 			$metadata[ $key ] = $default_style;
       
  1269 		}
       
  1270 	}
       
  1271 	return $metadata;
       
  1272 }
       
  1273 add_filter( 'block_type_metadata', '_wp_multiple_block_styles' );
       
  1274 
  2455 
  1275 /**
  2456 /**
  1276  * Helper function that constructs a comment query vars array from the passed
  2457  * Helper function that constructs a comment query vars array from the passed
  1277  * block properties.
  2458  * block properties.
  1278  *
  2459  *
  1279  * It's used with the Comment Query Loop inner blocks.
  2460  * It's used with the Comment Query Loop inner blocks.
  1280  *
  2461  *
  1281  * @since 6.0.0
  2462  * @since 6.0.0
  1282  *
  2463  *
  1283  * @param WP_Block $block Block instance.
  2464  * @param WP_Block $block Block instance.
  1284  *
       
  1285  * @return array Returns the comment query parameters to use with the
  2465  * @return array Returns the comment query parameters to use with the
  1286  *               WP_Comment_Query constructor.
  2466  *               WP_Comment_Query constructor.
  1287  */
  2467  */
  1288 function build_comment_query_vars_from_block( $block ) {
  2468 function build_comment_query_vars_from_block( $block ) {
  1289 
  2469 
  1350  * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks.
  2530  * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks.
  1351  *
  2531  *
  1352  * @since 6.0.0
  2532  * @since 6.0.0
  1353  *
  2533  *
  1354  * @param WP_Block $block           Block instance.
  2534  * @param WP_Block $block           Block instance.
  1355  * @param string   $pagination_type Type of the arrow we will be rendering.
  2535  * @param string   $pagination_type Optional. Type of the arrow we will be rendering.
  1356  *                                  Default 'next'. Accepts 'next' or 'previous'.
  2536  *                                  Accepts 'next' or 'previous'. Default 'next'.
  1357  *
       
  1358  * @return string|null The pagination arrow HTML or null if there is none.
  2537  * @return string|null The pagination arrow HTML or null if there is none.
  1359  */
  2538  */
  1360 function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) {
  2539 function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) {
  1361 	$arrow_map = array(
  2540 	$arrow_map = array(
  1362 		'none'    => '',
  2541 		'none'    => '',
  1371 	);
  2550 	);
  1372 	if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) {
  2551 	if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) {
  1373 		$arrow_attribute = $block->context['comments/paginationArrow'];
  2552 		$arrow_attribute = $block->context['comments/paginationArrow'];
  1374 		$arrow           = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ];
  2553 		$arrow           = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ];
  1375 		$arrow_classes   = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  2554 		$arrow_classes   = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  1376 		return "<span class='$arrow_classes'>$arrow</span>";
  2555 		return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
  1377 	}
  2556 	}
  1378 	return null;
  2557 	return null;
  1379 }
  2558 }
       
  2559 
       
  2560 /**
       
  2561  * Strips all HTML from the content of footnotes, and sanitizes the ID.
       
  2562  *
       
  2563  * This function expects slashed data on the footnotes content.
       
  2564  *
       
  2565  * @access private
       
  2566  * @since 6.3.2
       
  2567  *
       
  2568  * @param string $footnotes JSON-encoded string of an array containing the content and ID of each footnote.
       
  2569  * @return string Filtered content without any HTML on the footnote content and with the sanitized ID.
       
  2570  */
       
  2571 function _wp_filter_post_meta_footnotes( $footnotes ) {
       
  2572 	$footnotes_decoded = json_decode( $footnotes, true );
       
  2573 	if ( ! is_array( $footnotes_decoded ) ) {
       
  2574 		return '';
       
  2575 	}
       
  2576 	$footnotes_sanitized = array();
       
  2577 	foreach ( $footnotes_decoded as $footnote ) {
       
  2578 		if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
       
  2579 			$footnotes_sanitized[] = array(
       
  2580 				'id'      => sanitize_key( $footnote['id'] ),
       
  2581 				'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ),
       
  2582 			);
       
  2583 		}
       
  2584 	}
       
  2585 	return wp_json_encode( $footnotes_sanitized );
       
  2586 }
       
  2587 
       
  2588 /**
       
  2589  * Adds the filters for footnotes meta field.
       
  2590  *
       
  2591  * @access private
       
  2592  * @since 6.3.2
       
  2593  */
       
  2594 function _wp_footnotes_kses_init_filters() {
       
  2595 	add_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' );
       
  2596 }
       
  2597 
       
  2598 /**
       
  2599  * Removes the filters for footnotes meta field.
       
  2600  *
       
  2601  * @access private
       
  2602  * @since 6.3.2
       
  2603  */
       
  2604 function _wp_footnotes_remove_filters() {
       
  2605 	remove_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' );
       
  2606 }
       
  2607 
       
  2608 /**
       
  2609  * Registers the filter of footnotes meta field if the user does not have `unfiltered_html` capability.
       
  2610  *
       
  2611  * @access private
       
  2612  * @since 6.3.2
       
  2613  */
       
  2614 function _wp_footnotes_kses_init() {
       
  2615 	_wp_footnotes_remove_filters();
       
  2616 	if ( ! current_user_can( 'unfiltered_html' ) ) {
       
  2617 		_wp_footnotes_kses_init_filters();
       
  2618 	}
       
  2619 }
       
  2620 
       
  2621 /**
       
  2622  * Initializes the filters for footnotes meta field when imported data should be filtered.
       
  2623  *
       
  2624  * This filter is the last one being executed on {@see 'force_filtered_html_on_import'}.
       
  2625  * If the input of the filter is true, it means we are in an import situation and should
       
  2626  * enable kses, independently of the user capabilities. So in that case we call
       
  2627  * _wp_footnotes_kses_init_filters().
       
  2628  *
       
  2629  * @access private
       
  2630  * @since 6.3.2
       
  2631  *
       
  2632  * @param string $arg Input argument of the filter.
       
  2633  * @return string Input argument of the filter.
       
  2634  */
       
  2635 function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) {
       
  2636 	// If `force_filtered_html_on_import` is true, we need to init the global styles kses filters.
       
  2637 	if ( $arg ) {
       
  2638 		_wp_footnotes_kses_init_filters();
       
  2639 	}
       
  2640 	return $arg;
       
  2641 }