angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

blocks.php (36535B)


      1 <?php
      2 /**
      3  * Functions related to registering and parsing blocks.
      4  *
      5  * @package WordPress
      6  * @subpackage Blocks
      7  * @since 5.0.0
      8  */
      9 
     10 /**
     11  * Removes the block asset's path prefix if provided.
     12  *
     13  * @since 5.5.0
     14  *
     15  * @param string $asset_handle_or_path Asset handle or prefixed path.
     16  * @return string Path without the prefix or the original value.
     17  */
     18 function remove_block_asset_path_prefix( $asset_handle_or_path ) {
     19 	$path_prefix = 'file:';
     20 	if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) {
     21 		return $asset_handle_or_path;
     22 	}
     23 	return substr(
     24 		$asset_handle_or_path,
     25 		strlen( $path_prefix )
     26 	);
     27 }
     28 
     29 /**
     30  * Generates the name for an asset based on the name of the block
     31  * and the field name provided.
     32  *
     33  * @since 5.5.0
     34  *
     35  * @param string $block_name Name of the block.
     36  * @param string $field_name Name of the metadata field.
     37  * @return string Generated asset name for the block's field.
     38  */
     39 function generate_block_asset_handle( $block_name, $field_name ) {
     40 	if ( 0 === strpos( $block_name, 'core/' ) ) {
     41 		$asset_handle = str_replace( 'core/', 'wp-block-', $block_name );
     42 		if ( 0 === strpos( $field_name, 'editor' ) ) {
     43 			$asset_handle .= '-editor';
     44 		}
     45 		return $asset_handle;
     46 	}
     47 
     48 	$field_mappings = array(
     49 		'editorScript' => 'editor-script',
     50 		'script'       => 'script',
     51 		'editorStyle'  => 'editor-style',
     52 		'style'        => 'style',
     53 	);
     54 	return str_replace( '/', '-', $block_name ) .
     55 		'-' . $field_mappings[ $field_name ];
     56 }
     57 
     58 /**
     59  * Finds a script handle for the selected block metadata field. It detects
     60  * when a path to file was provided and finds a corresponding asset file
     61  * with details necessary to register the script under automatically
     62  * generated handle name. It returns unprocessed script handle otherwise.
     63  *
     64  * @since 5.5.0
     65  *
     66  * @param array  $metadata   Block metadata.
     67  * @param string $field_name Field name to pick from metadata.
     68  * @return string|false Script handle provided directly or created through
     69  *                      script's registration, or false on failure.
     70  */
     71 function register_block_script_handle( $metadata, $field_name ) {
     72 	if ( empty( $metadata[ $field_name ] ) ) {
     73 		return false;
     74 	}
     75 	$script_handle = $metadata[ $field_name ];
     76 	$script_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
     77 	if ( $script_handle === $script_path ) {
     78 		return $script_handle;
     79 	}
     80 
     81 	$script_handle     = generate_block_asset_handle( $metadata['name'], $field_name );
     82 	$script_asset_path = realpath(
     83 		dirname( $metadata['file'] ) . '/' .
     84 		substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
     85 	);
     86 	if ( ! file_exists( $script_asset_path ) ) {
     87 		_doing_it_wrong(
     88 			__FUNCTION__,
     89 			sprintf(
     90 				/* translators: 1: Field name, 2: Block name. */
     91 				__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.' ),
     92 				$field_name,
     93 				$metadata['name']
     94 			),
     95 			'5.5.0'
     96 		);
     97 		return false;
     98 	}
     99 	$script_asset = require $script_asset_path;
    100 	$result       = wp_register_script(
    101 		$script_handle,
    102 		plugins_url( $script_path, $metadata['file'] ),
    103 		$script_asset['dependencies'],
    104 		$script_asset['version']
    105 	);
    106 	if ( ! $result ) {
    107 		return false;
    108 	}
    109 
    110 	if ( ! empty( $metadata['textdomain'] ) ) {
    111 		wp_set_script_translations( $script_handle, $metadata['textdomain'] );
    112 	}
    113 
    114 	return $script_handle;
    115 }
    116 
    117 /**
    118  * Finds a style handle for the block metadata field. It detects when a path
    119  * to file was provided and registers the style under automatically
    120  * generated handle name. It returns unprocessed style handle otherwise.
    121  *
    122  * @since 5.5.0
    123  *
    124  * @param array  $metadata   Block metadata.
    125  * @param string $field_name Field name to pick from metadata.
    126  * @return string|false Style handle provided directly or created through
    127  *                      style's registration, or false on failure.
    128  */
    129 function register_block_style_handle( $metadata, $field_name ) {
    130 	if ( empty( $metadata[ $field_name ] ) ) {
    131 		return false;
    132 	}
    133 	$is_core_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], ABSPATH . WPINC );
    134 	if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) {
    135 		return false;
    136 	}
    137 
    138 	// Check whether styles should have a ".min" suffix or not.
    139 	$suffix = SCRIPT_DEBUG ? '' : '.min';
    140 
    141 	$style_handle = $metadata[ $field_name ];
    142 	$style_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
    143 
    144 	if ( $style_handle === $style_path && ! $is_core_block ) {
    145 		return $style_handle;
    146 	}
    147 
    148 	$style_uri = plugins_url( $style_path, $metadata['file'] );
    149 	if ( $is_core_block ) {
    150 		$style_path = "style$suffix.css";
    151 		$style_uri  = includes_url( 'blocks/' . str_replace( 'core/', '', $metadata['name'] ) . "/style$suffix.css" );
    152 	}
    153 
    154 	$style_handle   = generate_block_asset_handle( $metadata['name'], $field_name );
    155 	$block_dir      = dirname( $metadata['file'] );
    156 	$style_file     = realpath( "$block_dir/$style_path" );
    157 	$has_style_file = false !== $style_file;
    158 	$version        = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
    159 	$style_uri      = $has_style_file ? $style_uri : false;
    160 	$result         = wp_register_style(
    161 		$style_handle,
    162 		$style_uri,
    163 		array(),
    164 		$version
    165 	);
    166 	if ( file_exists( str_replace( '.css', '-rtl.css', $style_file ) ) ) {
    167 		wp_style_add_data( $style_handle, 'rtl', 'replace' );
    168 	}
    169 	if ( $has_style_file ) {
    170 		wp_style_add_data( $style_handle, 'path', $style_file );
    171 	}
    172 
    173 	$rtl_file = str_replace( "$suffix.css", "-rtl$suffix.css", $style_file );
    174 	if ( is_rtl() && file_exists( $rtl_file ) ) {
    175 		wp_style_add_data( $style_handle, 'path', $rtl_file );
    176 	}
    177 
    178 	return $result ? $style_handle : false;
    179 }
    180 
    181 /**
    182  * Registers a block type from the metadata stored in the `block.json` file.
    183  *
    184  * @since 5.5.0
    185  *
    186  * @param string $file_or_folder Path to the JSON file with metadata definition for
    187  *                               the block or path to the folder where the `block.json` file is located.
    188  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
    189  *                               of `WP_Block_Type`. See WP_Block_Type::__construct() for information
    190  *                               on accepted arguments. Default empty array.
    191  * @return WP_Block_Type|false The registered block type on success, or false on failure.
    192  */
    193 function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
    194 	$filename      = 'block.json';
    195 	$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
    196 		trailingslashit( $file_or_folder ) . $filename :
    197 		$file_or_folder;
    198 	if ( ! file_exists( $metadata_file ) ) {
    199 		return false;
    200 	}
    201 
    202 	$metadata = json_decode( file_get_contents( $metadata_file ), true );
    203 	if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
    204 		return false;
    205 	}
    206 	$metadata['file'] = $metadata_file;
    207 
    208 	/**
    209 	 * Filters the metadata provided for registering a block type.
    210 	 *
    211 	 * @since 5.7.0
    212 	 *
    213 	 * @param array $metadata Metadata for registering a block type.
    214 	 */
    215 	$metadata = apply_filters( 'block_type_metadata', $metadata );
    216 
    217 	// Add `style` and `editor_style` for core blocks if missing.
    218 	if ( ! empty( $metadata['name'] ) && 0 === strpos( $metadata['name'], 'core/' ) ) {
    219 		$block_name = str_replace( 'core/', '', $metadata['name'] );
    220 
    221 		if ( ! isset( $metadata['style'] ) ) {
    222 			$metadata['style'] = "wp-block-$block_name";
    223 		}
    224 		if ( ! isset( $metadata['editorStyle'] ) ) {
    225 			$metadata['editorStyle'] = "wp-block-{$block_name}-editor";
    226 		}
    227 	}
    228 
    229 	$settings          = array();
    230 	$property_mappings = array(
    231 		'title'           => 'title',
    232 		'category'        => 'category',
    233 		'parent'          => 'parent',
    234 		'icon'            => 'icon',
    235 		'description'     => 'description',
    236 		'keywords'        => 'keywords',
    237 		'attributes'      => 'attributes',
    238 		'providesContext' => 'provides_context',
    239 		'usesContext'     => 'uses_context',
    240 		'supports'        => 'supports',
    241 		'styles'          => 'styles',
    242 		'example'         => 'example',
    243 		'apiVersion'      => 'api_version',
    244 	);
    245 
    246 	foreach ( $property_mappings as $key => $mapped_key ) {
    247 		if ( isset( $metadata[ $key ] ) ) {
    248 			$value = $metadata[ $key ];
    249 			if ( empty( $metadata['textdomain'] ) ) {
    250 				$settings[ $mapped_key ] = $value;
    251 				continue;
    252 			}
    253 			$textdomain = $metadata['textdomain'];
    254 			switch ( $key ) {
    255 				case 'title':
    256 				case 'description':
    257 					// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain
    258 					$settings[ $mapped_key ] = translate_with_gettext_context( $value, sprintf( 'block %s', $key ), $textdomain );
    259 					break;
    260 				case 'keywords':
    261 					$settings[ $mapped_key ] = array();
    262 					if ( ! is_array( $value ) ) {
    263 						continue 2;
    264 					}
    265 
    266 					foreach ( $value as $keyword ) {
    267 						// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
    268 						$settings[ $mapped_key ][] = translate_with_gettext_context( $keyword, 'block keyword', $textdomain );
    269 					}
    270 
    271 					break;
    272 				case 'styles':
    273 					$settings[ $mapped_key ] = array();
    274 					if ( ! is_array( $value ) ) {
    275 						continue 2;
    276 					}
    277 
    278 					foreach ( $value as $style ) {
    279 						if ( ! empty( $style['label'] ) ) {
    280 							// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
    281 							$style['label'] = translate_with_gettext_context( $style['label'], 'block style label', $textdomain );
    282 						}
    283 						$settings[ $mapped_key ][] = $style;
    284 					}
    285 
    286 					break;
    287 				default:
    288 					$settings[ $mapped_key ] = $value;
    289 			}
    290 		}
    291 	}
    292 
    293 	if ( ! empty( $metadata['editorScript'] ) ) {
    294 		$settings['editor_script'] = register_block_script_handle(
    295 			$metadata,
    296 			'editorScript'
    297 		);
    298 	}
    299 
    300 	if ( ! empty( $metadata['script'] ) ) {
    301 		$settings['script'] = register_block_script_handle(
    302 			$metadata,
    303 			'script'
    304 		);
    305 	}
    306 
    307 	if ( ! empty( $metadata['editorStyle'] ) ) {
    308 		$settings['editor_style'] = register_block_style_handle(
    309 			$metadata,
    310 			'editorStyle'
    311 		);
    312 	}
    313 
    314 	if ( ! empty( $metadata['style'] ) ) {
    315 		$settings['style'] = register_block_style_handle(
    316 			$metadata,
    317 			'style'
    318 		);
    319 	}
    320 
    321 	/**
    322 	 * Filters the settings determined from the block type metadata.
    323 	 *
    324 	 * @since 5.7.0
    325 	 *
    326 	 * @param array $settings Array of determined settings for registering a block type.
    327 	 * @param array $metadata Metadata provided for registering a block type.
    328 	 */
    329 	$settings = apply_filters(
    330 		'block_type_metadata_settings',
    331 		array_merge(
    332 			$settings,
    333 			$args
    334 		),
    335 		$metadata
    336 	);
    337 
    338 	return WP_Block_Type_Registry::get_instance()->register(
    339 		$metadata['name'],
    340 		$settings
    341 	);
    342 }
    343 
    344 /**
    345  * Registers a block type. The recommended way is to register a block type using
    346  * the metadata stored in the `block.json` file.
    347  *
    348  * @since 5.0.0
    349  * @since 5.8.0 First param accepts a path to the `block.json` file.
    350  *
    351  * @param string|WP_Block_Type $block_type Block type name including namespace, or alternatively
    352  *                                         a path to the JSON file with metadata definition for the block,
    353  *                                         or a path to the folder where the `block.json` file is located,
    354  *                                         or a complete WP_Block_Type instance.
    355  *                                         In case a WP_Block_Type is provided, the $args parameter will be ignored.
    356  * @param array                $args       Optional. Array of block type arguments. Accepts any public property
    357  *                                         of `WP_Block_Type`. See WP_Block_Type::__construct() for information
    358  *                                         on accepted arguments. Default empty array.
    359  *
    360  * @return WP_Block_Type|false The registered block type on success, or false on failure.
    361  */
    362 function register_block_type( $block_type, $args = array() ) {
    363 	if ( is_string( $block_type ) && file_exists( $block_type ) ) {
    364 		return register_block_type_from_metadata( $block_type, $args );
    365 	}
    366 
    367 	return WP_Block_Type_Registry::get_instance()->register( $block_type, $args );
    368 }
    369 
    370 /**
    371  * Unregisters a block type.
    372  *
    373  * @since 5.0.0
    374  *
    375  * @param string|WP_Block_Type $name Block type name including namespace, or alternatively
    376  *                                   a complete WP_Block_Type instance.
    377  * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
    378  */
    379 function unregister_block_type( $name ) {
    380 	return WP_Block_Type_Registry::get_instance()->unregister( $name );
    381 }
    382 
    383 /**
    384  * Determine whether a post or content string has blocks.
    385  *
    386  * This test optimizes for performance rather than strict accuracy, detecting
    387  * the pattern of a block but not validating its structure. For strict accuracy,
    388  * you should use the block parser on post content.
    389  *
    390  * @since 5.0.0
    391  *
    392  * @see parse_blocks()
    393  *
    394  * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object.
    395  *                                      Defaults to global $post.
    396  * @return bool Whether the post has blocks.
    397  */
    398 function has_blocks( $post = null ) {
    399 	if ( ! is_string( $post ) ) {
    400 		$wp_post = get_post( $post );
    401 		if ( $wp_post instanceof WP_Post ) {
    402 			$post = $wp_post->post_content;
    403 		}
    404 	}
    405 
    406 	return false !== strpos( (string) $post, '<!-- wp:' );
    407 }
    408 
    409 /**
    410  * Determine whether a $post or a string contains a specific block type.
    411  *
    412  * This test optimizes for performance rather than strict accuracy, detecting
    413  * whether the block type exists but not validating its structure and not checking
    414  * reusable blocks. For strict accuracy, you should use the block parser on post content.
    415  *
    416  * @since 5.0.0
    417  *
    418  * @see parse_blocks()
    419  *
    420  * @param string                  $block_name Full block type to look for.
    421  * @param int|string|WP_Post|null $post       Optional. Post content, post ID, or post object.
    422  *                                            Defaults to global $post.
    423  * @return bool Whether the post content contains the specified block.
    424  */
    425 function has_block( $block_name, $post = null ) {
    426 	if ( ! has_blocks( $post ) ) {
    427 		return false;
    428 	}
    429 
    430 	if ( ! is_string( $post ) ) {
    431 		$wp_post = get_post( $post );
    432 		if ( $wp_post instanceof WP_Post ) {
    433 			$post = $wp_post->post_content;
    434 		}
    435 	}
    436 
    437 	/*
    438 	 * Normalize block name to include namespace, if provided as non-namespaced.
    439 	 * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
    440 	 * their serialized names.
    441 	 */
    442 	if ( false === strpos( $block_name, '/' ) ) {
    443 		$block_name = 'core/' . $block_name;
    444 	}
    445 
    446 	// Test for existence of block by its fully qualified name.
    447 	$has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' );
    448 
    449 	if ( ! $has_block ) {
    450 		/*
    451 		 * If the given block name would serialize to a different name, test for
    452 		 * existence by the serialized form.
    453 		 */
    454 		$serialized_block_name = strip_core_block_namespace( $block_name );
    455 		if ( $serialized_block_name !== $block_name ) {
    456 			$has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' );
    457 		}
    458 	}
    459 
    460 	return $has_block;
    461 }
    462 
    463 /**
    464  * Returns an array of the names of all registered dynamic block types.
    465  *
    466  * @since 5.0.0
    467  *
    468  * @return string[] Array of dynamic block names.
    469  */
    470 function get_dynamic_block_names() {
    471 	$dynamic_block_names = array();
    472 
    473 	$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
    474 	foreach ( $block_types as $block_type ) {
    475 		if ( $block_type->is_dynamic() ) {
    476 			$dynamic_block_names[] = $block_type->name;
    477 		}
    478 	}
    479 
    480 	return $dynamic_block_names;
    481 }
    482 
    483 /**
    484  * Given an array of attributes, returns a string in the serialized attributes
    485  * format prepared for post content.
    486  *
    487  * The serialized result is a JSON-encoded string, with unicode escape sequence
    488  * substitution for characters which might otherwise interfere with embedding
    489  * the result in an HTML comment.
    490  *
    491  * This function must produce output that remains in sync with the output of
    492  * the serializeAttributes JavaScript function in the block editor in order
    493  * to ensure consistent operation between PHP and JavaScript.
    494  *
    495  * @since 5.3.1
    496  *
    497  * @param array $block_attributes Attributes object.
    498  * @return string Serialized attributes.
    499  */
    500 function serialize_block_attributes( $block_attributes ) {
    501 	$encoded_attributes = wp_json_encode( $block_attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
    502 	$encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
    503 	$encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
    504 	$encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
    505 	$encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
    506 	// Regex: /\\"/
    507 	$encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
    508 
    509 	return $encoded_attributes;
    510 }
    511 
    512 /**
    513  * Returns the block name to use for serialization. This will remove the default
    514  * "core/" namespace from a block name.
    515  *
    516  * @since 5.3.1
    517  *
    518  * @param string $block_name Original block name.
    519  * @return string Block name to use for serialization.
    520  */
    521 function strip_core_block_namespace( $block_name = null ) {
    522 	if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
    523 		return substr( $block_name, 5 );
    524 	}
    525 
    526 	return $block_name;
    527 }
    528 
    529 /**
    530  * Returns the content of a block, including comment delimiters.
    531  *
    532  * @since 5.3.1
    533  *
    534  * @param string|null $block_name       Block name. Null if the block name is unknown,
    535  *                                      e.g. Classic blocks have their name set to null.
    536  * @param array       $block_attributes Block attributes.
    537  * @param string      $block_content    Block save content.
    538  * @return string Comment-delimited block content.
    539  */
    540 function get_comment_delimited_block_content( $block_name, $block_attributes, $block_content ) {
    541 	if ( is_null( $block_name ) ) {
    542 		return $block_content;
    543 	}
    544 
    545 	$serialized_block_name = strip_core_block_namespace( $block_name );
    546 	$serialized_attributes = empty( $block_attributes ) ? '' : serialize_block_attributes( $block_attributes ) . ' ';
    547 
    548 	if ( empty( $block_content ) ) {
    549 		return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
    550 	}
    551 
    552 	return sprintf(
    553 		'<!-- wp:%s %s-->%s<!-- /wp:%s -->',
    554 		$serialized_block_name,
    555 		$serialized_attributes,
    556 		$block_content,
    557 		$serialized_block_name
    558 	);
    559 }
    560 
    561 /**
    562  * Returns the content of a block, including comment delimiters, serializing all
    563  * attributes from the given parsed block.
    564  *
    565  * This should be used when preparing a block to be saved to post content.
    566  * Prefer `render_block` when preparing a block for display. Unlike
    567  * `render_block`, this does not evaluate a block's `render_callback`, and will
    568  * instead preserve the markup as parsed.
    569  *
    570  * @since 5.3.1
    571  *
    572  * @param WP_Block_Parser_Block $block A single parsed block object.
    573  * @return string String of rendered HTML.
    574  */
    575 function serialize_block( $block ) {
    576 	$block_content = '';
    577 
    578 	$index = 0;
    579 	foreach ( $block['innerContent'] as $chunk ) {
    580 		$block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] );
    581 	}
    582 
    583 	if ( ! is_array( $block['attrs'] ) ) {
    584 		$block['attrs'] = array();
    585 	}
    586 
    587 	return get_comment_delimited_block_content(
    588 		$block['blockName'],
    589 		$block['attrs'],
    590 		$block_content
    591 	);
    592 }
    593 
    594 /**
    595  * Returns a joined string of the aggregate serialization of the given parsed
    596  * blocks.
    597  *
    598  * @since 5.3.1
    599  *
    600  * @param WP_Block_Parser_Block[] $blocks Parsed block objects.
    601  * @return string String of rendered HTML.
    602  */
    603 function serialize_blocks( $blocks ) {
    604 	return implode( '', array_map( 'serialize_block', $blocks ) );
    605 }
    606 
    607 /**
    608  * Filters and sanitizes block content to remove non-allowable HTML from
    609  * parsed block attribute values.
    610  *
    611  * @since 5.3.1
    612  *
    613  * @param string         $text              Text that may contain block content.
    614  * @param array[]|string $allowed_html      An array of allowed HTML elements
    615  *                                          and attributes, or a context name
    616  *                                          such as 'post'.
    617  * @param string[]       $allowed_protocols Array of allowed URL protocols.
    618  * @return string The filtered and sanitized content result.
    619  */
    620 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
    621 	$result = '';
    622 
    623 	$blocks = parse_blocks( $text );
    624 	foreach ( $blocks as $block ) {
    625 		$block   = filter_block_kses( $block, $allowed_html, $allowed_protocols );
    626 		$result .= serialize_block( $block );
    627 	}
    628 
    629 	return $result;
    630 }
    631 
    632 /**
    633  * Filters and sanitizes a parsed block to remove non-allowable HTML from block
    634  * attribute values.
    635  *
    636  * @since 5.3.1
    637  *
    638  * @param WP_Block_Parser_Block $block             The parsed block object.
    639  * @param array[]|string        $allowed_html      An array of allowed HTML
    640  *                                                 elements and attributes, or a
    641  *                                                 context name such as 'post'.
    642  * @param string[]              $allowed_protocols Allowed URL protocols.
    643  * @return array The filtered and sanitized block object result.
    644  */
    645 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
    646 	$block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
    647 
    648 	if ( is_array( $block['innerBlocks'] ) ) {
    649 		foreach ( $block['innerBlocks'] as $i => $inner_block ) {
    650 			$block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
    651 		}
    652 	}
    653 
    654 	return $block;
    655 }
    656 
    657 /**
    658  * Filters and sanitizes a parsed block attribute value to remove non-allowable
    659  * HTML.
    660  *
    661  * @since 5.3.1
    662  *
    663  * @param string[]|string $value             The attribute value to filter.
    664  * @param array[]|string  $allowed_html      An array of allowed HTML elements
    665  *                                           and attributes, or a context name
    666  *                                           such as 'post'.
    667  * @param string[]        $allowed_protocols Array of allowed URL protocols.
    668  * @return string[]|string The filtered and sanitized result.
    669  */
    670 function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
    671 	if ( is_array( $value ) ) {
    672 		foreach ( $value as $key => $inner_value ) {
    673 			$filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
    674 			$filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
    675 
    676 			if ( $filtered_key !== $key ) {
    677 				unset( $value[ $key ] );
    678 			}
    679 
    680 			$value[ $filtered_key ] = $filtered_value;
    681 		}
    682 	} elseif ( is_string( $value ) ) {
    683 		return wp_kses( $value, $allowed_html, $allowed_protocols );
    684 	}
    685 
    686 	return $value;
    687 }
    688 
    689 /**
    690  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
    691  *
    692  * As the excerpt should be a small string of text relevant to the full post content,
    693  * this function renders the blocks that are most likely to contain such text.
    694  *
    695  * @since 5.0.0
    696  *
    697  * @param string $content The content to parse.
    698  * @return string The parsed and filtered content.
    699  */
    700 function excerpt_remove_blocks( $content ) {
    701 	$allowed_inner_blocks = array(
    702 		// Classic blocks have their blockName set to null.
    703 		null,
    704 		'core/freeform',
    705 		'core/heading',
    706 		'core/html',
    707 		'core/list',
    708 		'core/media-text',
    709 		'core/paragraph',
    710 		'core/preformatted',
    711 		'core/pullquote',
    712 		'core/quote',
    713 		'core/table',
    714 		'core/verse',
    715 	);
    716 
    717 	$allowed_wrapper_blocks = array(
    718 		'core/columns',
    719 		'core/column',
    720 		'core/group',
    721 	);
    722 
    723 	/**
    724 	 * Filters the list of blocks that can be used as wrapper blocks, allowing
    725 	 * excerpts to be generated from the `innerBlocks` of these wrappers.
    726 	 *
    727 	 * @since 5.8.0
    728 	 *
    729 	 * @param array $allowed_wrapper_blocks The list of allowed wrapper blocks.
    730 	 */
    731 	$allowed_wrapper_blocks = apply_filters( 'excerpt_allowed_wrapper_blocks', $allowed_wrapper_blocks );
    732 
    733 	$allowed_blocks = array_merge( $allowed_inner_blocks, $allowed_wrapper_blocks );
    734 
    735 	/**
    736 	 * Filters the list of blocks that can contribute to the excerpt.
    737 	 *
    738 	 * If a dynamic block is added to this list, it must not generate another
    739 	 * excerpt, as this will cause an infinite loop to occur.
    740 	 *
    741 	 * @since 5.0.0
    742 	 *
    743 	 * @param array $allowed_blocks The list of allowed blocks.
    744 	 */
    745 	$allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks );
    746 	$blocks         = parse_blocks( $content );
    747 	$output         = '';
    748 
    749 	foreach ( $blocks as $block ) {
    750 		if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
    751 			if ( ! empty( $block['innerBlocks'] ) ) {
    752 				if ( in_array( $block['blockName'], $allowed_wrapper_blocks, true ) ) {
    753 					$output .= _excerpt_render_inner_blocks( $block, $allowed_blocks );
    754 					continue;
    755 				}
    756 
    757 				// Skip the block if it has disallowed or nested inner blocks.
    758 				foreach ( $block['innerBlocks'] as $inner_block ) {
    759 					if (
    760 						! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) ||
    761 						! empty( $inner_block['innerBlocks'] )
    762 					) {
    763 						continue 2;
    764 					}
    765 				}
    766 			}
    767 
    768 			$output .= render_block( $block );
    769 		}
    770 	}
    771 
    772 	return $output;
    773 }
    774 
    775 /**
    776  * Render inner blocks from the allowed wrapper blocks
    777  * for generating an excerpt.
    778  *
    779  * @since 5.8
    780  * @access private
    781  *
    782  * @param array $parsed_block   The parsed block.
    783  * @param array $allowed_blocks The list of allowed inner blocks.
    784  * @return string The rendered inner blocks.
    785  */
    786 function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) {
    787 	$output = '';
    788 
    789 	foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
    790 		if ( ! in_array( $inner_block['blockName'], $allowed_blocks, true ) ) {
    791 			continue;
    792 		}
    793 
    794 		if ( empty( $inner_block['innerBlocks'] ) ) {
    795 			$output .= render_block( $inner_block );
    796 		} else {
    797 			$output .= _excerpt_render_inner_blocks( $inner_block, $allowed_blocks );
    798 		}
    799 	}
    800 
    801 	return $output;
    802 }
    803 
    804 /**
    805  * Renders a single block into a HTML string.
    806  *
    807  * @since 5.0.0
    808  *
    809  * @global WP_Post  $post     The post to edit.
    810  *
    811  * @param array $parsed_block A single parsed block object.
    812  * @return string String of rendered HTML.
    813  */
    814 function render_block( $parsed_block ) {
    815 	global $post;
    816 
    817 	/**
    818 	 * Allows render_block() to be short-circuited, by returning a non-null value.
    819 	 *
    820 	 * @since 5.1.0
    821 	 *
    822 	 * @param string|null $pre_render   The pre-rendered content. Default null.
    823 	 * @param array       $parsed_block The block being rendered.
    824 	 */
    825 	$pre_render = apply_filters( 'pre_render_block', null, $parsed_block );
    826 	if ( ! is_null( $pre_render ) ) {
    827 		return $pre_render;
    828 	}
    829 
    830 	$source_block = $parsed_block;
    831 
    832 	/**
    833 	 * Filters the block being rendered in render_block(), before it's processed.
    834 	 *
    835 	 * @since 5.1.0
    836 	 *
    837 	 * @param array $parsed_block The block being rendered.
    838 	 * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
    839 	 */
    840 	$parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
    841 
    842 	$context = array();
    843 
    844 	if ( $post instanceof WP_Post ) {
    845 		$context['postId'] = $post->ID;
    846 
    847 		/*
    848 		 * The `postType` context is largely unnecessary server-side, since the ID
    849 		 * is usually sufficient on its own. That being said, since a block's
    850 		 * manifest is expected to be shared between the server and the client,
    851 		 * it should be included to consistently fulfill the expectation.
    852 		 */
    853 		$context['postType'] = $post->post_type;
    854 	}
    855 
    856 	/**
    857 	 * Filters the default context provided to a rendered block.
    858 	 *
    859 	 * @since 5.5.0
    860 	 *
    861 	 * @param array $context      Default context.
    862 	 * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
    863 	 */
    864 	$context = apply_filters( 'render_block_context', $context, $parsed_block );
    865 
    866 	$block = new WP_Block( $parsed_block, $context );
    867 
    868 	return $block->render();
    869 }
    870 
    871 /**
    872  * Parses blocks out of a content string.
    873  *
    874  * @since 5.0.0
    875  *
    876  * @param string $content Post content.
    877  * @return array[] Array of parsed block objects.
    878  */
    879 function parse_blocks( $content ) {
    880 	/**
    881 	 * Filter to allow plugins to replace the server-side block parser
    882 	 *
    883 	 * @since 5.0.0
    884 	 *
    885 	 * @param string $parser_class Name of block parser class.
    886 	 */
    887 	$parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );
    888 
    889 	$parser = new $parser_class();
    890 	return $parser->parse( $content );
    891 }
    892 
    893 /**
    894  * Parses dynamic blocks out of `post_content` and re-renders them.
    895  *
    896  * @since 5.0.0
    897  *
    898  * @param string $content Post content.
    899  * @return string Updated post content.
    900  */
    901 function do_blocks( $content ) {
    902 	$blocks = parse_blocks( $content );
    903 	$output = '';
    904 
    905 	foreach ( $blocks as $block ) {
    906 		$output .= render_block( $block );
    907 	}
    908 
    909 	// If there are blocks in this content, we shouldn't run wpautop() on it later.
    910 	$priority = has_filter( 'the_content', 'wpautop' );
    911 	if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
    912 		remove_filter( 'the_content', 'wpautop', $priority );
    913 		add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
    914 	}
    915 
    916 	return $output;
    917 }
    918 
    919 /**
    920  * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards,
    921  * for subsequent `the_content` usage.
    922  *
    923  * @access private
    924  *
    925  * @since 5.0.0
    926  *
    927  * @param string $content The post content running through this filter.
    928  * @return string The unmodified content.
    929  */
    930 function _restore_wpautop_hook( $content ) {
    931 	$current_priority = has_filter( 'the_content', '_restore_wpautop_hook' );
    932 
    933 	add_filter( 'the_content', 'wpautop', $current_priority - 1 );
    934 	remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority );
    935 
    936 	return $content;
    937 }
    938 
    939 /**
    940  * Returns the current version of the block format that the content string is using.
    941  *
    942  * If the string doesn't contain blocks, it returns 0.
    943  *
    944  * @since 5.0.0
    945  *
    946  * @param string $content Content to test.
    947  * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise.
    948  */
    949 function block_version( $content ) {
    950 	return has_blocks( $content ) ? 1 : 0;
    951 }
    952 
    953 /**
    954  * Registers a new block style.
    955  *
    956  * @since 5.3.0
    957  *
    958  * @param string $block_name       Block type name including namespace.
    959  * @param array  $style_properties Array containing the properties of the style name,
    960  *                                 label, style (name of the stylesheet to be enqueued),
    961  *                                 inline_style (string containing the CSS to be added).
    962  * @return bool True if the block style was registered with success and false otherwise.
    963  */
    964 function register_block_style( $block_name, $style_properties ) {
    965 	return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties );
    966 }
    967 
    968 /**
    969  * Unregisters a block style.
    970  *
    971  * @since 5.3.0
    972  *
    973  * @param string $block_name       Block type name including namespace.
    974  * @param string $block_style_name Block style name.
    975  * @return bool True if the block style was unregistered with success and false otherwise.
    976  */
    977 function unregister_block_style( $block_name, $block_style_name ) {
    978 	return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name );
    979 }
    980 
    981 /**
    982  * Checks whether the current block type supports the feature requested.
    983  *
    984  * @since 5.8.0
    985  *
    986  * @param WP_Block_Type $block_type Block type to check for support.
    987  * @param string        $feature    Name of the feature to check support for.
    988  * @param mixed         $default    Fallback value for feature support, defaults to false.
    989  *
    990  * @return boolean                  Whether or not the feature is supported.
    991  */
    992 function block_has_support( $block_type, $feature, $default = false ) {
    993 	$block_support = $default;
    994 	if ( $block_type && property_exists( $block_type, 'supports' ) ) {
    995 		$block_support = _wp_array_get( $block_type->supports, $feature, $default );
    996 	}
    997 
    998 	return true === $block_support || is_array( $block_support );
    999 }
   1000 
   1001 /**
   1002  * Converts typography keys declared under `supports.*` to `supports.typography.*`.
   1003  *
   1004  * Displays a `_doing_it_wrong()` notice when a block using the older format is detected.
   1005  *
   1006  * @since 5.8.0
   1007  *
   1008  * @param array $metadata Metadata for registering a block type.
   1009  * @return array Filtered metadata for registering a block type.
   1010  */
   1011 function wp_migrate_old_typography_shape( $metadata ) {
   1012 	if ( ! isset( $metadata['supports'] ) ) {
   1013 		return $metadata;
   1014 	}
   1015 
   1016 	$typography_keys = array(
   1017 		'__experimentalFontFamily',
   1018 		'__experimentalFontStyle',
   1019 		'__experimentalFontWeight',
   1020 		'__experimentalLetterSpacing',
   1021 		'__experimentalTextDecoration',
   1022 		'__experimentalTextTransform',
   1023 		'fontSize',
   1024 		'lineHeight',
   1025 	);
   1026 
   1027 	foreach ( $typography_keys as $typography_key ) {
   1028 		$support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null );
   1029 
   1030 		if ( null !== $support_for_key ) {
   1031 			_doing_it_wrong(
   1032 				'register_block_type_from_metadata()',
   1033 				sprintf(
   1034 					/* translators: 1: Block type, 2: Typography supports key, e.g: fontSize, lineHeight, etc. 3: block.json, 4: Old metadata key, 5: New metadata key. */
   1035 					__( 'Block "%1$s" is declaring %2$s support in %3$s file under %4$s. %2$s support is now declared under %5$s.' ),
   1036 					$metadata['name'],
   1037 					"<code>$typography_key</code>",
   1038 					'<code>block.json</code>',
   1039 					"<code>supports.$typography_key</code>",
   1040 					"<code>supports.typography.$typography_key</code>"
   1041 				),
   1042 				'5.8.0'
   1043 			);
   1044 
   1045 			_wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key );
   1046 			unset( $metadata['supports'][ $typography_key ] );
   1047 		}
   1048 	}
   1049 
   1050 	return $metadata;
   1051 }
   1052 
   1053 /**
   1054  * Helper function that constructs a WP_Query args array from
   1055  * a `Query` block properties.
   1056  *
   1057  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
   1058  *
   1059  * @since 5.8.0
   1060  *
   1061  * @param WP_Block $block Block instance.
   1062  * @param int      $page  Current query's page.
   1063  *
   1064  * @return array Returns the constructed WP_Query arguments.
   1065  */
   1066 function build_query_vars_from_query_block( $block, $page ) {
   1067 	$query = array(
   1068 		'post_type'    => 'post',
   1069 		'order'        => 'DESC',
   1070 		'orderby'      => 'date',
   1071 		'post__not_in' => array(),
   1072 	);
   1073 
   1074 	if ( isset( $block->context['query'] ) ) {
   1075 		if ( ! empty( $block->context['query']['postType'] ) ) {
   1076 			$post_type_param = $block->context['query']['postType'];
   1077 			if ( is_post_type_viewable( $post_type_param ) ) {
   1078 				$query['post_type'] = $post_type_param;
   1079 			}
   1080 		}
   1081 		if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
   1082 			$sticky = get_option( 'sticky_posts' );
   1083 			if ( 'only' === $block->context['query']['sticky'] ) {
   1084 				$query['post__in'] = $sticky;
   1085 			} else {
   1086 				$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
   1087 			}
   1088 		}
   1089 		if ( ! empty( $block->context['query']['exclude'] ) ) {
   1090 			$excluded_post_ids     = array_map( 'intval', $block->context['query']['exclude'] );
   1091 			$excluded_post_ids     = array_filter( $excluded_post_ids );
   1092 			$query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids );
   1093 		}
   1094 		if (
   1095 			isset( $block->context['query']['perPage'] ) &&
   1096 			is_numeric( $block->context['query']['perPage'] )
   1097 		) {
   1098 			$per_page = absint( $block->context['query']['perPage'] );
   1099 			$offset   = 0;
   1100 
   1101 			if (
   1102 				isset( $block->context['query']['offset'] ) &&
   1103 				is_numeric( $block->context['query']['offset'] )
   1104 			) {
   1105 				$offset = absint( $block->context['query']['offset'] );
   1106 			}
   1107 
   1108 			$query['offset']         = ( $per_page * ( $page - 1 ) ) + $offset;
   1109 			$query['posts_per_page'] = $per_page;
   1110 		}
   1111 		if ( ! empty( $block->context['query']['categoryIds'] ) ) {
   1112 			$term_ids              = array_map( 'intval', $block->context['query']['categoryIds'] );
   1113 			$term_ids              = array_filter( $term_ids );
   1114 			$query['category__in'] = $term_ids;
   1115 		}
   1116 		if ( ! empty( $block->context['query']['tagIds'] ) ) {
   1117 			$term_ids         = array_map( 'intval', $block->context['query']['tagIds'] );
   1118 			$term_ids         = array_filter( $term_ids );
   1119 			$query['tag__in'] = $term_ids;
   1120 		}
   1121 		if (
   1122 			isset( $block->context['query']['order'] ) &&
   1123 				in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
   1124 		) {
   1125 			$query['order'] = strtoupper( $block->context['query']['order'] );
   1126 		}
   1127 		if ( isset( $block->context['query']['orderBy'] ) ) {
   1128 			$query['orderby'] = $block->context['query']['orderBy'];
   1129 		}
   1130 		if (
   1131 			isset( $block->context['query']['author'] ) &&
   1132 			(int) $block->context['query']['author'] > 0
   1133 		) {
   1134 			$query['author'] = (int) $block->context['query']['author'];
   1135 		}
   1136 		if ( ! empty( $block->context['query']['search'] ) ) {
   1137 			$query['s'] = $block->context['query']['search'];
   1138 		}
   1139 	}
   1140 	return $query;
   1141 }