wp/wp-includes/blocks.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   326 	}
   326 	}
   327 
   327 
   328 	$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 ) );
   329 	$style_uri       = get_block_asset_url( $style_path_norm );
   329 	$style_uri       = get_block_asset_url( $style_path_norm );
   330 
   330 
   331 	$version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
   331 	$block_version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
   332 	$result  = wp_register_style(
   332 	$version       = $style_path_norm && defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? filemtime( $style_path_norm ) : $block_version;
       
   333 	$result        = wp_register_style(
   333 		$style_handle_name,
   334 		$style_handle_name,
   334 		$style_uri,
   335 		$style_uri,
   335 		array(),
   336 		array(),
   336 		$version
   337 		$version
   337 	);
   338 	);
   374 
   375 
   375 	return $i18n_block_schema;
   376 	return $i18n_block_schema;
   376 }
   377 }
   377 
   378 
   378 /**
   379 /**
       
   380  * Registers all block types from a block metadata collection.
       
   381  *
       
   382  * This can either reference a previously registered metadata collection or, if the `$manifest` parameter is provided,
       
   383  * register the metadata collection directly within the same function call.
       
   384  *
       
   385  * @since 6.8.0
       
   386  * @see wp_register_block_metadata_collection()
       
   387  * @see register_block_type_from_metadata()
       
   388  *
       
   389  * @param string $path     The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ).
       
   390  * @param string $manifest Optional. The absolute path to the manifest file containing the metadata collection, in
       
   391  *                         order to register the collection. If this parameter is not provided, the `$path` parameter
       
   392  *                         must reference a previously registered block metadata collection.
       
   393  */
       
   394 function wp_register_block_types_from_metadata_collection( $path, $manifest = '' ) {
       
   395 	if ( $manifest ) {
       
   396 		wp_register_block_metadata_collection( $path, $manifest );
       
   397 	}
       
   398 
       
   399 	$block_metadata_files = WP_Block_Metadata_Registry::get_collection_block_metadata_files( $path );
       
   400 	foreach ( $block_metadata_files as $block_metadata_file ) {
       
   401 		register_block_type_from_metadata( $block_metadata_file );
       
   402 	}
       
   403 }
       
   404 
       
   405 /**
       
   406  * Registers a block metadata collection.
       
   407  *
       
   408  * This function allows core and third-party plugins to register their block metadata
       
   409  * collections in a centralized location. Registering collections can improve performance
       
   410  * by avoiding multiple reads from the filesystem and parsing JSON.
       
   411  *
       
   412  * @since 6.7.0
       
   413  *
       
   414  * @param string $path     The base path in which block files for the collection reside.
       
   415  * @param string $manifest The path to the manifest file for the collection.
       
   416  */
       
   417 function wp_register_block_metadata_collection( $path, $manifest ) {
       
   418 	WP_Block_Metadata_Registry::register_collection( $path, $manifest );
       
   419 }
       
   420 
       
   421 /**
   379  * Registers a block type from the metadata stored in the `block.json` file.
   422  * Registers a block type from the metadata stored in the `block.json` file.
   380  *
   423  *
   381  * @since 5.5.0
   424  * @since 5.5.0
   382  * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields.
   425  * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields.
   383  * @since 5.9.0 Added support for `variations` and `viewScript` fields.
   426  * @since 5.9.0 Added support for `variations` and `viewScript` fields.
   384  * @since 6.1.0 Added support for `render` field.
   427  * @since 6.1.0 Added support for `render` field.
   385  * @since 6.3.0 Added `selectors` field.
   428  * @since 6.3.0 Added `selectors` field.
   386  * @since 6.4.0 Added support for `blockHooks` field.
   429  * @since 6.4.0 Added support for `blockHooks` field.
   387  * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields.
   430  * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields.
       
   431  * @since 6.7.0 Allow PHP filename as `variations` argument.
   388  *
   432  *
   389  * @param string $file_or_folder Path to the JSON file with metadata definition for
   433  * @param string $file_or_folder Path to the JSON file with metadata definition for
   390  *                               the block or path to the folder where the `block.json` file is located.
   434  *                               the block or path to the folder where the `block.json` file is located.
   391  *                               If providing the path to a JSON file, the filename must end with `block.json`.
   435  *                               If providing the path to a JSON file, the filename must end with `block.json`.
   392  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
   436  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
   399 	 * Get an array of metadata from a PHP file.
   443 	 * Get an array of metadata from a PHP file.
   400 	 * This improves performance for core blocks as it's only necessary to read a single PHP file
   444 	 * 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.
   445 	 * 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.
   446 	 * Using a static variable ensures that the metadata is only read once per request.
   403 	 */
   447 	 */
   404 	static $core_blocks_meta;
   448 
   405 	if ( ! $core_blocks_meta ) {
   449 	$file_or_folder = wp_normalize_path( $file_or_folder );
   406 		$core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php';
       
   407 	}
       
   408 
   450 
   409 	$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
   451 	$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
   410 		trailingslashit( $file_or_folder ) . 'block.json' :
   452 		trailingslashit( $file_or_folder ) . 'block.json' :
   411 		$file_or_folder;
   453 		$file_or_folder;
   412 
   454 
   413 	$is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC );
   455 	$is_core_block        = str_starts_with( $file_or_folder, wp_normalize_path( 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 );
   456 	$metadata_file_exists = $is_core_block || file_exists( $metadata_file );
   416 	if ( ! $metadata_file_exists && empty( $args['name'] ) ) {
   457 	$registry_metadata    = WP_Block_Metadata_Registry::get_metadata( $file_or_folder );
   417 		return false;
   458 
   418 	}
   459 	if ( $registry_metadata ) {
   419 
   460 		$metadata = $registry_metadata;
   420 	// Try to get metadata from the static cache for core blocks.
   461 	} elseif ( $metadata_file_exists ) {
   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 ) );
   462 		$metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
       
   463 	} else {
       
   464 		$metadata = array();
   432 	}
   465 	}
   433 
   466 
   434 	if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
   467 	if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
   435 		return false;
   468 		return false;
   436 	}
   469 	}
   520 				return ob_get_clean();
   553 				return ob_get_clean();
   521 			};
   554 			};
   522 		}
   555 		}
   523 	}
   556 	}
   524 
   557 
       
   558 	// If `variations` is a string, it's the name of a PHP file that
       
   559 	// generates the variations.
       
   560 	if ( ! empty( $metadata['variations'] ) && is_string( $metadata['variations'] ) ) {
       
   561 		$variations_path = wp_normalize_path(
       
   562 			realpath(
       
   563 				dirname( $metadata['file'] ) . '/' .
       
   564 				remove_block_asset_path_prefix( $metadata['variations'] )
       
   565 			)
       
   566 		);
       
   567 		if ( $variations_path ) {
       
   568 			/**
       
   569 			 * Generates the list of block variations.
       
   570 			 *
       
   571 			 * @since 6.7.0
       
   572 			 *
       
   573 			 * @return string Returns the list of block variations.
       
   574 			 */
       
   575 			$settings['variation_callback'] = static function () use ( $variations_path ) {
       
   576 				$variations = require $variations_path;
       
   577 				return $variations;
       
   578 			};
       
   579 			// The block instance's `variations` field is only allowed to be an array
       
   580 			// (of known block variations). We unset it so that the block instance will
       
   581 			// provide a getter that returns the result of the `variation_callback` instead.
       
   582 			unset( $settings['variations'] );
       
   583 		}
       
   584 	}
       
   585 
   525 	$settings = array_merge( $settings, $args );
   586 	$settings = array_merge( $settings, $args );
   526 
   587 
   527 	$script_fields = array(
   588 	$script_fields = array(
   528 		'editorScript' => 'editor_script_handles',
   589 		'editorScript' => 'editor_script_handles',
   529 		'script'       => 'script_handles',
   590 		'script'       => 'script_handles',
   878 	 *
   939 	 *
   879 	 * @param string[]                        $hooked_block_types The list of hooked block types.
   940 	 * @param string[]                        $hooked_block_types The list of hooked block types.
   880 	 * @param string                          $relative_position  The relative position of the hooked blocks.
   941 	 * @param string                          $relative_position  The relative position of the hooked blocks.
   881 	 *                                                            Can be one of 'before', 'after', 'first_child', or 'last_child'.
   942 	 *                                                            Can be one of 'before', 'after', 'first_child', or 'last_child'.
   882 	 * @param string                          $anchor_block_type  The anchor block type.
   943 	 * @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,
   944 	 * @param WP_Block_Template|WP_Post|array $context            The block template, template part, post object,
   884 	 *                                                            or pattern that the anchor block belongs to.
   945 	 *                                                            or pattern that the anchor block belongs to.
   885 	 */
   946 	 */
   886 	$hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context );
   947 	$hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context );
   887 
   948 
   888 	$markup = '';
   949 	$markup = '';
   901 		 *
   962 		 *
   902 		 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
   963 		 * @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.
   964 		 * @param string                          $hooked_block_type   The hooked block type name.
   904 		 * @param string                          $relative_position   The relative position of the hooked block.
   965 		 * @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.
   966 		 * @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,
   967 		 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, post object,
   907 		 *                                                             or pattern that the anchor block belongs to.
   968 		 *                                                             or pattern that the anchor block belongs to.
   908 		 */
   969 		 */
   909 		$parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
   970 		$parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
   910 
   971 
   911 		/**
   972 		/**
   917 		 *
   978 		 *
   918 		 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
   979 		 * @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.
   980 		 * @param string                          $hooked_block_type   The hooked block type name.
   920 		 * @param string                          $relative_position   The relative position of the hooked block.
   981 		 * @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.
   982 		 * @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,
   983 		 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, post object,
   923 		 *                                                             or pattern that the anchor block belongs to.
   984 		 *                                                             or pattern that the anchor block belongs to.
   924 		 */
   985 		 */
   925 		$parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
   986 		$parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context );
   926 
   987 
   927 		if ( null === $parsed_hooked_block ) {
   988 		if ( null === $parsed_hooked_block ) {
  1004 
  1065 
  1005 /**
  1066 /**
  1006  * Runs the hooked blocks algorithm on the given content.
  1067  * Runs the hooked blocks algorithm on the given content.
  1007  *
  1068  *
  1008  * @since 6.6.0
  1069  * @since 6.6.0
       
  1070  * @since 6.7.0 Injects the `theme` attribute into Template Part blocks, even if no hooked blocks are registered.
       
  1071  * @since 6.8.0 Have the `$context` parameter default to `null`, in which case `get_post()` will be called to use the current post as context.
  1009  * @access private
  1072  * @access private
  1010  *
  1073  *
  1011  * @param string                          $content  Serialized content.
  1074  * @param string                               $content  Serialized content.
  1012  * @param WP_Block_Template|WP_Post|array $context  A block template, template part, `wp_navigation` post object,
  1075  * @param WP_Block_Template|WP_Post|array|null $context  A block template, template part, post object, or pattern
  1013  *                                                  or pattern that the blocks belong to.
  1076  *                                                       that the blocks belong to. If set to `null`, `get_post()`
  1014  * @param callable                        $callback A function that will be called for each block to generate
  1077  *                                                       will be called to use the current post as context.
  1015  *                                                  the markup for a given list of blocks that are hooked to it.
  1078  *                                                       Default: `null`.
  1016  *                                                  Default: 'insert_hooked_blocks'.
  1079  * @param callable                             $callback A function that will be called for each block to generate
       
  1080  *                                                       the markup for a given list of blocks that are hooked to it.
       
  1081  *                                                       Default: 'insert_hooked_blocks'.
  1017  * @return string The serialized markup.
  1082  * @return string The serialized markup.
  1018  */
  1083  */
  1019 function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) {
  1084 function apply_block_hooks_to_content( $content, $context = null, $callback = 'insert_hooked_blocks' ) {
       
  1085 	// Default to the current post if no context is provided.
       
  1086 	if ( null === $context ) {
       
  1087 		$context = get_post();
       
  1088 	}
       
  1089 
  1020 	$hooked_blocks = get_hooked_blocks();
  1090 	$hooked_blocks = get_hooked_blocks();
  1021 	if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
  1091 
  1022 		return $content;
  1092 	$before_block_visitor = '_inject_theme_attribute_in_template_part_block';
  1023 	}
  1093 	$after_block_visitor  = null;
  1024 
  1094 	if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
  1025 	$blocks = parse_blocks( $content );
  1095 		$before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback );
  1026 
  1096 		$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $context, $callback );
  1027 	$before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback );
  1097 	}
  1028 	$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $context, $callback );
  1098 
  1029 
  1099 	$block_allows_multiple_instances = array();
  1030 	return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
  1100 	/*
  1031 }
  1101 	 * Remove hooked blocks from `$hooked_block_types` if they have `multiple` set to false and
  1032 
  1102 	 * are already present in `$content`.
  1033 /**
  1103 	 */
  1034  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
  1104 	foreach ( $hooked_blocks as $anchor_block_type => $relative_positions ) {
  1035  *
  1105 		foreach ( $relative_positions as $relative_position => $hooked_block_types ) {
  1036  * @since 6.6.0
  1106 			foreach ( $hooked_block_types as $index => $hooked_block_type ) {
       
  1107 				$hooked_block_type_definition =
       
  1108 					WP_Block_Type_Registry::get_instance()->get_registered( $hooked_block_type );
       
  1109 
       
  1110 				$block_allows_multiple_instances[ $hooked_block_type ] =
       
  1111 					block_has_support( $hooked_block_type_definition, 'multiple', true );
       
  1112 
       
  1113 				if (
       
  1114 					! $block_allows_multiple_instances[ $hooked_block_type ] &&
       
  1115 					has_block( $hooked_block_type, $content )
       
  1116 				) {
       
  1117 					unset( $hooked_blocks[ $anchor_block_type ][ $relative_position ][ $index ] );
       
  1118 				}
       
  1119 			}
       
  1120 			if ( empty( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) ) {
       
  1121 				unset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] );
       
  1122 			}
       
  1123 		}
       
  1124 		if ( empty( $hooked_blocks[ $anchor_block_type ] ) ) {
       
  1125 			unset( $hooked_blocks[ $anchor_block_type ] );
       
  1126 		}
       
  1127 	}
       
  1128 
       
  1129 	/*
       
  1130 	 * We also need to cover the case where the hooked block is not present in
       
  1131 	 * `$content` at first and we're allowed to insert it once -- but not again.
       
  1132 	 */
       
  1133 	$suppress_single_instance_blocks = static function ( $hooked_block_types ) use ( &$block_allows_multiple_instances, $content ) {
       
  1134 		static $single_instance_blocks_present_in_content = array();
       
  1135 		foreach ( $hooked_block_types as $index => $hooked_block_type ) {
       
  1136 			if ( ! isset( $block_allows_multiple_instances[ $hooked_block_type ] ) ) {
       
  1137 				$hooked_block_type_definition =
       
  1138 					WP_Block_Type_Registry::get_instance()->get_registered( $hooked_block_type );
       
  1139 
       
  1140 				$block_allows_multiple_instances[ $hooked_block_type ] =
       
  1141 					block_has_support( $hooked_block_type_definition, 'multiple', true );
       
  1142 			}
       
  1143 
       
  1144 			if ( $block_allows_multiple_instances[ $hooked_block_type ] ) {
       
  1145 				continue;
       
  1146 			}
       
  1147 
       
  1148 			// The block doesn't allow multiple instances, so we need to check if it's already present.
       
  1149 			if (
       
  1150 				in_array( $hooked_block_type, $single_instance_blocks_present_in_content, true ) ||
       
  1151 				has_block( $hooked_block_type, $content )
       
  1152 			) {
       
  1153 				unset( $hooked_block_types[ $index ] );
       
  1154 			} else {
       
  1155 				// We can insert the block once, but need to remember not to insert it again.
       
  1156 				$single_instance_blocks_present_in_content[] = $hooked_block_type;
       
  1157 			}
       
  1158 		}
       
  1159 		return $hooked_block_types;
       
  1160 	};
       
  1161 	add_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX );
       
  1162 	$content = traverse_and_serialize_blocks(
       
  1163 		parse_blocks( $content ),
       
  1164 		$before_block_visitor,
       
  1165 		$after_block_visitor
       
  1166 	);
       
  1167 	remove_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX );
       
  1168 
       
  1169 	return $content;
       
  1170 }
       
  1171 
       
  1172 /**
       
  1173  * Run the Block Hooks algorithm on a post object's content.
       
  1174  *
       
  1175  * This function is different from `apply_block_hooks_to_content` in that
       
  1176  * it takes ignored hooked block information from the post's metadata into
       
  1177  * account. This ensures that any blocks hooked as first or last child
       
  1178  * of the block that corresponds to the post type are handled correctly.
       
  1179  *
       
  1180  * @since 6.8.0
  1037  * @access private
  1181  * @access private
  1038  *
  1182  *
  1039  * @param string $serialized_block The serialized markup of a block and its inner blocks.
  1183  * @param string       $content  Serialized content.
  1040  * @return string The serialized markup of the inner blocks.
  1184  * @param WP_Post|null $post     A post object that the content belongs to. If set to `null`,
  1041  */
  1185  *                               `get_post()` will be called to use the current post as context.
  1042 function remove_serialized_parent_block( $serialized_block ) {
  1186  *                               Default: `null`.
  1043 	$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
  1187  * @param callable     $callback A function that will be called for each block to generate
  1044 	$end   = strrpos( $serialized_block, '<!--' );
  1188  *                               the markup for a given list of blocks that are hooked to it.
  1045 	return substr( $serialized_block, $start, $end - $start );
  1189  *                               Default: 'insert_hooked_blocks'.
  1046 }
  1190  * @return string The serialized markup.
  1047 
  1191  */
  1048 /**
  1192 function apply_block_hooks_to_content_from_post_object( $content, $post = null, $callback = 'insert_hooked_blocks' ) {
  1049  * Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content.
  1193 	// Default to the current post if no context is provided.
  1050  * Currently only supports `wp_navigation` post types.
  1194 	if ( null === $post ) {
  1051  *
  1195 		$post = get_post();
  1052  * @since 6.6.0
  1196 	}
  1053  * @access private
  1197 
  1054  *
  1198 	if ( ! $post instanceof WP_Post ) {
  1055  * @param stdClass $post Post object.
  1199 		return apply_block_hooks_to_content( $content, $post, $callback );
  1056  * @return stdClass The updated post object.
  1200 	}
  1057  */
  1201 
  1058 function update_ignored_hooked_blocks_postmeta( $post ) {
       
  1059 	/*
  1202 	/*
  1060 	 * In this scenario the user has likely tried to create a navigation via the REST API.
  1203 	 * If the content was created using the classic editor or using a single Classic block
  1061 	 * In which case we won't have a post ID to work with and store meta against.
  1204 	 * (`core/freeform`), it might not contain any block markup at all.
       
  1205 	 * However, we still might need to inject hooked blocks in the first child or last child
       
  1206 	 * positions of the parent block. To be able to apply the Block Hooks algorithm, we wrap
       
  1207 	 * the content in a `core/freeform` wrapper block.
  1062 	 */
  1208 	 */
  1063 	if ( empty( $post->ID ) ) {
  1209 	if ( ! has_blocks( $content ) ) {
  1064 		return $post;
  1210 		$original_content = $content;
  1065 	}
  1211 
  1066 
  1212 		$content_wrapped_in_classic_block = get_comment_delimited_block_content(
  1067 	/*
  1213 			'core/freeform',
  1068 	 * Skip meta generation when consumers intentionally update specific Navigation fields
  1214 			array(),
  1069 	 * and omit the content update.
  1215 			$content
  1070 	 */
  1216 		);
  1071 	if ( ! isset( $post->post_content ) ) {
  1217 
  1072 		return $post;
  1218 		$content = $content_wrapped_in_classic_block;
  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 	}
  1219 	}
  1081 
  1220 
  1082 	$attributes = array();
  1221 	$attributes = array();
  1083 
  1222 
       
  1223 	// If context is a post object, `ignoredHookedBlocks` information is stored in its post meta.
  1084 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
  1224 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
  1085 	if ( ! empty( $ignored_hooked_blocks ) ) {
  1225 	if ( ! empty( $ignored_hooked_blocks ) ) {
  1086 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
  1226 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
  1087 		$attributes['metadata'] = array(
  1227 		$attributes['metadata'] = array(
  1088 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
  1228 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
  1089 		);
  1229 		);
  1090 	}
  1230 	}
  1091 
  1231 
       
  1232 	/*
       
  1233 	 * We need to wrap the content in a temporary wrapper block with that metadata
       
  1234 	 * so the Block Hooks algorithm can insert blocks that are hooked as first or last child
       
  1235 	 * of the wrapper block.
       
  1236 	 * To that end, we need to determine the wrapper block type based on the post type.
       
  1237 	 */
       
  1238 	if ( 'wp_navigation' === $post->post_type ) {
       
  1239 		$wrapper_block_type = 'core/navigation';
       
  1240 	} elseif ( 'wp_block' === $post->post_type ) {
       
  1241 		$wrapper_block_type = 'core/block';
       
  1242 	} else {
       
  1243 		$wrapper_block_type = 'core/post-content';
       
  1244 	}
       
  1245 
       
  1246 	$content = get_comment_delimited_block_content(
       
  1247 		$wrapper_block_type,
       
  1248 		$attributes,
       
  1249 		$content
       
  1250 	);
       
  1251 
       
  1252 	/*
       
  1253 	 * We need to avoid inserting any blocks hooked into the `before` and `after` positions
       
  1254 	 * of the temporary wrapper block that we create to wrap the content.
       
  1255 	 * See https://core.trac.wordpress.org/ticket/63287 for more details.
       
  1256 	 */
       
  1257 	$suppress_blocks_from_insertion_before_and_after_wrapper_block = static function ( $hooked_block_types, $relative_position, $anchor_block_type ) use ( $wrapper_block_type ) {
       
  1258 		if (
       
  1259 			$wrapper_block_type === $anchor_block_type &&
       
  1260 			in_array( $relative_position, array( 'before', 'after' ), true )
       
  1261 		) {
       
  1262 			return array();
       
  1263 		}
       
  1264 		return $hooked_block_types;
       
  1265 	};
       
  1266 
       
  1267 	// Apply Block Hooks.
       
  1268 	add_filter( 'hooked_block_types', $suppress_blocks_from_insertion_before_and_after_wrapper_block, PHP_INT_MAX, 3 );
       
  1269 	$content = apply_block_hooks_to_content( $content, $post, $callback );
       
  1270 	remove_filter( 'hooked_block_types', $suppress_blocks_from_insertion_before_and_after_wrapper_block, PHP_INT_MAX );
       
  1271 
       
  1272 	// Finally, we need to remove the temporary wrapper block.
       
  1273 	$content = remove_serialized_parent_block( $content );
       
  1274 
       
  1275 	// If we wrapped the content in a `core/freeform` block, we also need to remove that.
       
  1276 	if ( ! empty( $content_wrapped_in_classic_block ) ) {
       
  1277 		/*
       
  1278 		 * We cannot simply use remove_serialized_parent_block() here,
       
  1279 		 * as that function assumes that the block wrapper is at the top level.
       
  1280 		 * However, there might now be a hooked block inserted next to it
       
  1281 		 * (as first or last child of the parent).
       
  1282 		 */
       
  1283 		$content = str_replace( $content_wrapped_in_classic_block, $original_content, $content );
       
  1284 	}
       
  1285 
       
  1286 	return $content;
       
  1287 }
       
  1288 
       
  1289 /**
       
  1290  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
       
  1291  *
       
  1292  * @since 6.6.0
       
  1293  * @access private
       
  1294  *
       
  1295  * @param string $serialized_block The serialized markup of a block and its inner blocks.
       
  1296  * @return string The serialized markup of the inner blocks.
       
  1297  */
       
  1298 function remove_serialized_parent_block( $serialized_block ) {
       
  1299 	$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
       
  1300 	$end   = strrpos( $serialized_block, '<!--' );
       
  1301 	return substr( $serialized_block, $start, $end - $start );
       
  1302 }
       
  1303 
       
  1304 /**
       
  1305  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the wrapper block.
       
  1306  *
       
  1307  * @since 6.7.0
       
  1308  * @access private
       
  1309  *
       
  1310  * @see remove_serialized_parent_block()
       
  1311  *
       
  1312  * @param string $serialized_block The serialized markup of a block and its inner blocks.
       
  1313  * @return string The serialized markup of the wrapper block.
       
  1314  */
       
  1315 function extract_serialized_parent_block( $serialized_block ) {
       
  1316 	$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
       
  1317 	$end   = strrpos( $serialized_block, '<!--' );
       
  1318 	return substr( $serialized_block, 0, $start ) . substr( $serialized_block, $end );
       
  1319 }
       
  1320 
       
  1321 /**
       
  1322  * Updates the wp_postmeta with the list of ignored hooked blocks
       
  1323  * where the inner blocks are stored as post content.
       
  1324  *
       
  1325  * @since 6.6.0
       
  1326  * @since 6.8.0 Support non-`wp_navigation` post types.
       
  1327  * @access private
       
  1328  *
       
  1329  * @param stdClass $post Post object.
       
  1330  * @return stdClass The updated post object.
       
  1331  */
       
  1332 function update_ignored_hooked_blocks_postmeta( $post ) {
       
  1333 	/*
       
  1334 	 * In this scenario the user has likely tried to create a new post object via the REST API.
       
  1335 	 * In which case we won't have a post ID to work with and store meta against.
       
  1336 	 */
       
  1337 	if ( empty( $post->ID ) ) {
       
  1338 		return $post;
       
  1339 	}
       
  1340 
       
  1341 	/*
       
  1342 	 * Skip meta generation when consumers intentionally update specific fields
       
  1343 	 * and omit the content update.
       
  1344 	 */
       
  1345 	if ( ! isset( $post->post_content ) ) {
       
  1346 		return $post;
       
  1347 	}
       
  1348 
       
  1349 	/*
       
  1350 	 * Skip meta generation if post type is not set.
       
  1351 	 */
       
  1352 	if ( ! isset( $post->post_type ) ) {
       
  1353 		return $post;
       
  1354 	}
       
  1355 
       
  1356 	$attributes = array();
       
  1357 
       
  1358 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1359 	if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1360 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
       
  1361 		$attributes['metadata'] = array(
       
  1362 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
       
  1363 		);
       
  1364 	}
       
  1365 
       
  1366 	if ( 'wp_navigation' === $post->post_type ) {
       
  1367 		$wrapper_block_type = 'core/navigation';
       
  1368 	} elseif ( 'wp_block' === $post->post_type ) {
       
  1369 		$wrapper_block_type = 'core/block';
       
  1370 	} else {
       
  1371 		$wrapper_block_type = 'core/post-content';
       
  1372 	}
       
  1373 
  1092 	$markup = get_comment_delimited_block_content(
  1374 	$markup = get_comment_delimited_block_content(
  1093 		'core/navigation',
  1375 		$wrapper_block_type,
  1094 		$attributes,
  1376 		$attributes,
  1095 		$post->post_content
  1377 		$post->post_content
  1096 	);
  1378 	);
  1097 
  1379 
  1098 	$serialized_block = apply_block_hooks_to_content( $markup, get_post( $post->ID ), 'set_ignored_hooked_blocks_metadata' );
  1380 	$existing_post = get_post( $post->ID );
       
  1381 	// Merge the existing post object with the updated post object to pass to the block hooks algorithm for context.
       
  1382 	$context          = (object) array_merge( (array) $existing_post, (array) $post );
       
  1383 	$context          = new WP_Post( $context ); // Convert to WP_Post object.
       
  1384 	$serialized_block = apply_block_hooks_to_content( $markup, $context, 'set_ignored_hooked_blocks_metadata' );
  1099 	$root_block       = parse_blocks( $serialized_block )[0];
  1385 	$root_block       = parse_blocks( $serialized_block )[0];
  1100 
  1386 
  1101 	$ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
  1387 	$ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
  1102 		? $root_block['attrs']['metadata']['ignoredHookedBlocks']
  1388 		? $root_block['attrs']['metadata']['ignoredHookedBlocks']
  1103 		: array();
  1389 		: array();
  1106 		$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
  1392 		$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
  1107 		if ( ! empty( $existing_ignored_hooked_blocks ) ) {
  1393 		if ( ! empty( $existing_ignored_hooked_blocks ) ) {
  1108 			$existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
  1394 			$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 ) );
  1395 			$ignored_hooked_blocks          = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
  1110 		}
  1396 		}
  1111 		update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
  1397 
       
  1398 		if ( ! isset( $post->meta_input ) ) {
       
  1399 			$post->meta_input = array();
       
  1400 		}
       
  1401 		$post->meta_input['_wp_ignored_hooked_blocks'] = json_encode( $ignored_hooked_blocks );
  1112 	}
  1402 	}
  1113 
  1403 
  1114 	$post->post_content = remove_serialized_parent_block( $serialized_block );
  1404 	$post->post_content = remove_serialized_parent_block( $serialized_block );
  1115 	return $post;
  1405 	return $post;
  1116 }
  1406 }
  1137 
  1427 
  1138 	return $markup;
  1428 	return $markup;
  1139 }
  1429 }
  1140 
  1430 
  1141 /**
  1431 /**
  1142  * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
  1432  * Hooks into the REST API response for the Posts endpoint and adds the first and last inner blocks.
  1143  *
  1433  *
  1144  * @since 6.6.0
  1434  * @since 6.6.0
       
  1435  * @since 6.8.0 Support non-`wp_navigation` post types.
  1145  *
  1436  *
  1146  * @param WP_REST_Response $response The response object.
  1437  * @param WP_REST_Response $response The response object.
  1147  * @param WP_Post          $post     Post object.
  1438  * @param WP_Post          $post     Post object.
  1148  * @return WP_REST_Response The response object.
  1439  * @return WP_REST_Response The response object.
  1149  */
  1440  */
  1150 function insert_hooked_blocks_into_rest_response( $response, $post ) {
  1441 function insert_hooked_blocks_into_rest_response( $response, $post ) {
  1151 	if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
  1442 	if ( empty( $response->data['content']['raw'] ) ) {
  1152 		return $response;
  1443 		return $response;
  1153 	}
  1444 	}
  1154 
  1445 
  1155 	$attributes            = array();
  1446 	$response->data['content']['raw'] = apply_block_hooks_to_content_from_post_object(
  1156 	$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
  1447 		$response->data['content']['raw'],
  1157 	if ( ! empty( $ignored_hooked_blocks ) ) {
  1448 		$post,
  1158 		$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
  1449 		'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
  1159 		$attributes['metadata'] = array(
  1450 	);
  1160 			'ignoredHookedBlocks' => $ignored_hooked_blocks,
  1451 
  1161 		);
  1452 	// If the rendered content was previously empty, we leave it like that.
  1162 	}
  1453 	if ( empty( $response->data['content']['rendered'] ) ) {
  1163 	$content = get_comment_delimited_block_content(
  1454 		return $response;
  1164 		'core/navigation',
  1455 	}
  1165 		$attributes,
  1456 
       
  1457 	// `apply_block_hooks_to_content` is called above. Ensure it is not called again as a filter.
       
  1458 	$priority = has_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object' );
       
  1459 	if ( false !== $priority ) {
       
  1460 		remove_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
       
  1461 	}
       
  1462 
       
  1463 	/** This filter is documented in wp-includes/post-template.php */
       
  1464 	$response->data['content']['rendered'] = apply_filters(
       
  1465 		'the_content',
  1166 		$response->data['content']['raw']
  1466 		$response->data['content']['raw']
  1167 	);
  1467 	);
  1168 
  1468 
  1169 	$content = apply_block_hooks_to_content( $content, $post );
  1469 	// Restore the filter if it was set initially.
  1170 
  1470 	if ( false !== $priority ) {
  1171 	// Remove mock Navigation block wrapper.
  1471 		add_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
  1172 	$content = remove_serialized_parent_block( $content );
  1472 	}
  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 
  1473 
  1179 	return $response;
  1474 	return $response;
  1180 }
  1475 }
  1181 
  1476 
  1182 /**
  1477 /**
  1191  * @since 6.4.0
  1486  * @since 6.4.0
  1192  * @since 6.5.0 Added $callback argument.
  1487  * @since 6.5.0 Added $callback argument.
  1193  * @access private
  1488  * @access private
  1194  *
  1489  *
  1195  * @param array                           $hooked_blocks An array of blocks hooked to another given block.
  1490  * @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,
  1491  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, post object,
  1197  *                                                       or pattern that the blocks belong to.
  1492  *                                                       or pattern that the blocks belong to.
  1198  * @param callable                        $callback      A function that will be called for each block to generate
  1493  * @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.
  1494  *                                                       the markup for a given list of blocks that are hooked to it.
  1200  *                                                       Default: 'insert_hooked_blocks'.
  1495  *                                                       Default: 'insert_hooked_blocks'.
  1201  * @return callable A function that returns the serialized markup for the given block,
  1496  * @return callable A function that returns the serialized markup for the given block,
  1248  * @since 6.4.0
  1543  * @since 6.4.0
  1249  * @since 6.5.0 Added $callback argument.
  1544  * @since 6.5.0 Added $callback argument.
  1250  * @access private
  1545  * @access private
  1251  *
  1546  *
  1252  * @param array                           $hooked_blocks An array of blocks hooked to another block.
  1547  * @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,
  1548  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, post object,
  1254  *                                                       or pattern that the blocks belong to.
  1549  *                                                       or pattern that the blocks belong to.
  1255  * @param callable                        $callback      A function that will be called for each block to generate
  1550  * @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.
  1551  *                                                       the markup for a given list of blocks that are hooked to it.
  1257  *                                                       Default: 'insert_hooked_blocks'.
  1552  *                                                       Default: 'insert_hooked_blocks'.
  1258  * @return callable A function that returns the serialized markup for the given block,
  1553  * @return callable A function that returns the serialized markup for the given block,
  1377  * instead preserve the markup as parsed.
  1672  * instead preserve the markup as parsed.
  1378  *
  1673  *
  1379  * @since 5.3.1
  1674  * @since 5.3.1
  1380  *
  1675  *
  1381  * @param array $block {
  1676  * @param array $block {
  1382  *     A representative array of a single parsed block object. See WP_Block_Parser_Block.
  1677  *     An associative array of a single parsed block object. See WP_Block_Parser_Block.
  1383  *
  1678  *
  1384  *     @type string   $blockName    Name of block.
  1679  *     @type string   $blockName    Name of block.
  1385  *     @type array    $attrs        Attributes from block comment delimiters.
  1680  *     @type array    $attrs        Attributes from block comment delimiters.
  1386  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1681  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1387  *                                  have the same structure as this one.
  1682  *                                  have the same structure as this one.
  1418  *
  1713  *
  1419  * @param array[] $blocks {
  1714  * @param array[] $blocks {
  1420  *     Array of block structures.
  1715  *     Array of block structures.
  1421  *
  1716  *
  1422  *     @type array ...$0 {
  1717  *     @type array ...$0 {
  1423  *         A representative array of a single parsed block object. See WP_Block_Parser_Block.
  1718  *         An associative array of a single parsed block object. See WP_Block_Parser_Block.
  1424  *
  1719  *
  1425  *         @type string   $blockName    Name of block.
  1720  *         @type string   $blockName    Name of block.
  1426  *         @type array    $attrs        Attributes from block comment delimiters.
  1721  *         @type array    $attrs        Attributes from block comment delimiters.
  1427  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1722  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1428  *                                      have the same structure as this one.
  1723  *                                      have the same structure as this one.
  1460  * @since 6.4.0
  1755  * @since 6.4.0
  1461  * @access private
  1756  * @access private
  1462  *
  1757  *
  1463  * @see serialize_block()
  1758  * @see serialize_block()
  1464  *
  1759  *
  1465  * @param array    $block         A representative array of a single parsed block object. See WP_Block_Parser_Block.
  1760  * @param array    $block         An associative 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.
  1761  * @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.
  1762  *                                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.
  1763  *                                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.
  1764  * @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.
  1765  *                                It is called with the following arguments: &$block, $parent_block, $next_block.
  1635  */
  1930  */
  1636 function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) {
  1931 function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) {
  1637 	$result       = '';
  1932 	$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.
  1933 	$parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference.
  1639 
  1934 
       
  1935 	$pre_callback_is_callable  = is_callable( $pre_callback );
       
  1936 	$post_callback_is_callable = is_callable( $post_callback );
       
  1937 
  1640 	foreach ( $blocks as $index => $block ) {
  1938 	foreach ( $blocks as $index => $block ) {
  1641 		if ( is_callable( $pre_callback ) ) {
  1939 		if ( $pre_callback_is_callable ) {
  1642 			$prev = 0 === $index
  1940 			$prev = 0 === $index
  1643 				? null
  1941 				? null
  1644 				: $blocks[ $index - 1 ];
  1942 				: $blocks[ $index - 1 ];
  1645 
  1943 
  1646 			$result .= call_user_func_array(
  1944 			$result .= call_user_func_array(
  1647 				$pre_callback,
  1945 				$pre_callback,
  1648 				array( &$block, &$parent_block, $prev )
  1946 				array( &$block, &$parent_block, $prev )
  1649 			);
  1947 			);
  1650 		}
  1948 		}
  1651 
  1949 
  1652 		if ( is_callable( $post_callback ) ) {
  1950 		if ( $post_callback_is_callable ) {
  1653 			$next = count( $blocks ) - 1 === $index
  1951 			$next = count( $blocks ) - 1 === $index
  1654 				? null
  1952 				? null
  1655 				: $blocks[ $index + 1 ];
  1953 				: $blocks[ $index + 1 ];
  1656 
  1954 
  1657 			$post_markup = call_user_func_array(
  1955 			$post_markup = call_user_func_array(
  1942  * @since 5.0.0
  2240  * @since 5.0.0
  1943  *
  2241  *
  1944  * @global WP_Post $post The post to edit.
  2242  * @global WP_Post $post The post to edit.
  1945  *
  2243  *
  1946  * @param array $parsed_block {
  2244  * @param array $parsed_block {
  1947  *     A representative array of the block being rendered. See WP_Block_Parser_Block.
  2245  *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  1948  *
  2246  *
  1949  *     @type string   $blockName    Name of block.
  2247  *     @type string   $blockName    Name of block.
  1950  *     @type array    $attrs        Attributes from block comment delimiters.
  2248  *     @type array    $attrs        Attributes from block comment delimiters.
  1951  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2249  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1952  *                                  have the same structure as this one.
  2250  *                                  have the same structure as this one.
  1966 	 * @since 5.1.0
  2264 	 * @since 5.1.0
  1967 	 * @since 5.9.0 The `$parent_block` parameter was added.
  2265 	 * @since 5.9.0 The `$parent_block` parameter was added.
  1968 	 *
  2266 	 *
  1969 	 * @param string|null   $pre_render   The pre-rendered content. Default null.
  2267 	 * @param string|null   $pre_render   The pre-rendered content. Default null.
  1970 	 * @param array         $parsed_block {
  2268 	 * @param array         $parsed_block {
  1971 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
  2269 	 *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  1972 	 *
  2270 	 *
  1973 	 *     @type string   $blockName    Name of block.
  2271 	 *     @type string   $blockName    Name of block.
  1974 	 *     @type array    $attrs        Attributes from block comment delimiters.
  2272 	 *     @type array    $attrs        Attributes from block comment delimiters.
  1975 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2273 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1976 	 *                                  have the same structure as this one.
  2274 	 *                                  have the same structure as this one.
  1992 	 *
  2290 	 *
  1993 	 * @since 5.1.0
  2291 	 * @since 5.1.0
  1994 	 * @since 5.9.0 The `$parent_block` parameter was added.
  2292 	 * @since 5.9.0 The `$parent_block` parameter was added.
  1995 	 *
  2293 	 *
  1996 	 * @param array         $parsed_block {
  2294 	 * @param array         $parsed_block {
  1997 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
  2295 	 *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  1998 	 *
  2296 	 *
  1999 	 *     @type string   $blockName    Name of block.
  2297 	 *     @type string   $blockName    Name of block.
  2000 	 *     @type array    $attrs        Attributes from block comment delimiters.
  2298 	 *     @type array    $attrs        Attributes from block comment delimiters.
  2001 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2299 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2002 	 *                                  have the same structure as this one.
  2300 	 *                                  have the same structure as this one.
  2040 	 * @since 5.5.0
  2338 	 * @since 5.5.0
  2041 	 * @since 5.9.0 The `$parent_block` parameter was added.
  2339 	 * @since 5.9.0 The `$parent_block` parameter was added.
  2042 	 *
  2340 	 *
  2043 	 * @param array         $context      Default context.
  2341 	 * @param array         $context      Default context.
  2044 	 * @param array         $parsed_block {
  2342 	 * @param array         $parsed_block {
  2045 	 *     A representative array of the block being rendered. See WP_Block_Parser_Block.
  2343 	 *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  2046 	 *
  2344 	 *
  2047 	 *     @type string   $blockName    Name of block.
  2345 	 *     @type string   $blockName    Name of block.
  2048 	 *     @type array    $attrs        Attributes from block comment delimiters.
  2346 	 *     @type array    $attrs        Attributes from block comment delimiters.
  2049 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2347 	 *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2050 	 *                                  have the same structure as this one.
  2348 	 *                                  have the same structure as this one.
  2069  * @param string $content Post content.
  2367  * @param string $content Post content.
  2070  * @return array[] {
  2368  * @return array[] {
  2071  *     Array of block structures.
  2369  *     Array of block structures.
  2072  *
  2370  *
  2073  *     @type array ...$0 {
  2371  *     @type array ...$0 {
  2074  *         A representative array of a single parsed block object. See WP_Block_Parser_Block.
  2372  *         An associative array of a single parsed block object. See WP_Block_Parser_Block.
  2075  *
  2373  *
  2076  *         @type string   $blockName    Name of block.
  2374  *         @type string   $blockName    Name of block.
  2077  *         @type array    $attrs        Attributes from block comment delimiters.
  2375  *         @type array    $attrs        Attributes from block comment delimiters.
  2078  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2376  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2079  *                                      have the same structure as this one.
  2377  *                                      have the same structure as this one.
  2104  *
  2402  *
  2105  * @param string $content Post content.
  2403  * @param string $content Post content.
  2106  * @return string Updated post content.
  2404  * @return string Updated post content.
  2107  */
  2405  */
  2108 function do_blocks( $content ) {
  2406 function do_blocks( $content ) {
  2109 	$blocks = parse_blocks( $content );
  2407 	$blocks                = parse_blocks( $content );
  2110 	$output = '';
  2408 	$top_level_block_count = count( $blocks );
  2111 
  2409 	$output                = '';
  2112 	foreach ( $blocks as $block ) {
  2410 
  2113 		$output .= render_block( $block );
  2411 	/**
       
  2412 	 * Parsed blocks consist of a list of top-level blocks. Those top-level
       
  2413 	 * blocks may themselves contain nested inner blocks. However, every
       
  2414 	 * top-level block is rendered independently, meaning there are no data
       
  2415 	 * dependencies between them.
       
  2416 	 *
       
  2417 	 * Ideally, therefore, the parser would only need to parse one complete
       
  2418 	 * top-level block at a time, render it, and move on. Unfortunately, this
       
  2419 	 * is not possible with {@see \parse_blocks()} because it must parse the
       
  2420 	 * entire given document at once.
       
  2421 	 *
       
  2422 	 * While the current implementation prevents this optimization, it’s still
       
  2423 	 * possible to reduce the peak memory use when calls to `render_block()`
       
  2424 	 * on those top-level blocks are memory-heavy (which many of them are).
       
  2425 	 * By setting each parsed block to `NULL` after rendering it, any memory
       
  2426 	 * allocated during the render will be freed and reused for the next block.
       
  2427 	 * Before making this change, that memory was retained and would lead to
       
  2428 	 * out-of-memory crashes for certain posts that now run with this change.
       
  2429 	 */
       
  2430 	for ( $i = 0; $i < $top_level_block_count; $i++ ) {
       
  2431 		$output      .= render_block( $blocks[ $i ] );
       
  2432 		$blocks[ $i ] = null;
  2114 	}
  2433 	}
  2115 
  2434 
  2116 	// If there are blocks in this content, we shouldn't run wpautop() on it later.
  2435 	// If there are blocks in this content, we shouldn't run wpautop() on it later.
  2117 	$priority = has_filter( 'the_content', 'wpautop' );
  2436 	$priority = has_filter( 'the_content', 'wpautop' );
  2118 	if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
  2437 	if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
  2275  *
  2594  *
  2276  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
  2595  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
  2277  *
  2596  *
  2278  * @since 5.8.0
  2597  * @since 5.8.0
  2279  * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
  2598  * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
       
  2599  * @since 6.7.0 Added support for the `format` property in query.
  2280  *
  2600  *
  2281  * @param WP_Block $block Block instance.
  2601  * @param WP_Block $block Block instance.
  2282  * @param int      $page  Current query's page.
  2602  * @param int      $page  Current query's page.
  2283  *
  2603  *
  2284  * @return array Returns the constructed WP_Query arguments.
  2604  * @return array Returns the constructed WP_Query arguments.
  2287 	$query = array(
  2607 	$query = array(
  2288 		'post_type'    => 'post',
  2608 		'post_type'    => 'post',
  2289 		'order'        => 'DESC',
  2609 		'order'        => 'DESC',
  2290 		'orderby'      => 'date',
  2610 		'orderby'      => 'date',
  2291 		'post__not_in' => array(),
  2611 		'post__not_in' => array(),
       
  2612 		'tax_query'    => array(),
  2292 	);
  2613 	);
  2293 
  2614 
  2294 	if ( isset( $block->context['query'] ) ) {
  2615 	if ( isset( $block->context['query'] ) ) {
  2295 		if ( ! empty( $block->context['query']['postType'] ) ) {
  2616 		if ( ! empty( $block->context['query']['postType'] ) ) {
  2296 			$post_type_param = $block->context['query']['postType'];
  2617 			$post_type_param = $block->context['query']['postType'];
  2308 				 *
  2629 				 *
  2309 				 * @see https://core.trac.wordpress.org/ticket/28099
  2630 				 * @see https://core.trac.wordpress.org/ticket/28099
  2310 				 */
  2631 				 */
  2311 				$query['post__in']            = ! empty( $sticky ) ? $sticky : array( 0 );
  2632 				$query['post__in']            = ! empty( $sticky ) ? $sticky : array( 0 );
  2312 				$query['ignore_sticky_posts'] = 1;
  2633 				$query['ignore_sticky_posts'] = 1;
  2313 			} else {
  2634 			} elseif ( 'exclude' === $block->context['query']['sticky'] ) {
  2314 				$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
  2635 				$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
       
  2636 			} elseif ( 'ignore' === $block->context['query']['sticky'] ) {
       
  2637 				$query['ignore_sticky_posts'] = 1;
  2315 			}
  2638 			}
  2316 		}
  2639 		}
  2317 		if ( ! empty( $block->context['query']['exclude'] ) ) {
  2640 		if ( ! empty( $block->context['query']['exclude'] ) ) {
  2318 			$excluded_post_ids     = array_map( 'intval', $block->context['query']['exclude'] );
  2641 			$excluded_post_ids     = array_map( 'intval', $block->context['query']['exclude'] );
  2319 			$excluded_post_ids     = array_filter( $excluded_post_ids );
  2642 			$excluded_post_ids     = array_filter( $excluded_post_ids );
  2336 			$query['offset']         = ( $per_page * ( $page - 1 ) ) + $offset;
  2659 			$query['offset']         = ( $per_page * ( $page - 1 ) ) + $offset;
  2337 			$query['posts_per_page'] = $per_page;
  2660 			$query['posts_per_page'] = $per_page;
  2338 		}
  2661 		}
  2339 		// Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
  2662 		// Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
  2340 		if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
  2663 		if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
  2341 			$tax_query = array();
  2664 			$tax_query_back_compat = array();
  2342 			if ( ! empty( $block->context['query']['categoryIds'] ) ) {
  2665 			if ( ! empty( $block->context['query']['categoryIds'] ) ) {
  2343 				$tax_query[] = array(
  2666 				$tax_query_back_compat[] = array(
  2344 					'taxonomy'         => 'category',
  2667 					'taxonomy'         => 'category',
  2345 					'terms'            => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
  2668 					'terms'            => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
  2346 					'include_children' => false,
  2669 					'include_children' => false,
  2347 				);
  2670 				);
  2348 			}
  2671 			}
  2349 			if ( ! empty( $block->context['query']['tagIds'] ) ) {
  2672 			if ( ! empty( $block->context['query']['tagIds'] ) ) {
  2350 				$tax_query[] = array(
  2673 				$tax_query_back_compat[] = array(
  2351 					'taxonomy'         => 'post_tag',
  2674 					'taxonomy'         => 'post_tag',
  2352 					'terms'            => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
  2675 					'terms'            => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
  2353 					'include_children' => false,
  2676 					'include_children' => false,
  2354 				);
  2677 				);
  2355 			}
  2678 			}
  2356 			$query['tax_query'] = $tax_query;
  2679 			$query['tax_query'] = array_merge( $query['tax_query'], $tax_query_back_compat );
  2357 		}
  2680 		}
  2358 		if ( ! empty( $block->context['query']['taxQuery'] ) ) {
  2681 		if ( ! empty( $block->context['query']['taxQuery'] ) ) {
  2359 			$query['tax_query'] = array();
  2682 			$tax_query = array();
  2360 			foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
  2683 			foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
  2361 				if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
  2684 				if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
  2362 					$query['tax_query'][] = array(
  2685 					$tax_query[] = array(
  2363 						'taxonomy'         => $taxonomy,
  2686 						'taxonomy'         => $taxonomy,
  2364 						'terms'            => array_filter( array_map( 'intval', $terms ) ),
  2687 						'terms'            => array_filter( array_map( 'intval', $terms ) ),
  2365 						'include_children' => false,
  2688 						'include_children' => false,
  2366 					);
  2689 					);
  2367 				}
  2690 				}
  2368 			}
  2691 			}
  2369 		}
  2692 			$query['tax_query'] = array_merge( $query['tax_query'], $tax_query );
       
  2693 		}
       
  2694 		if ( ! empty( $block->context['query']['format'] ) && is_array( $block->context['query']['format'] ) ) {
       
  2695 			$formats = $block->context['query']['format'];
       
  2696 			/*
       
  2697 			 * Validate that the format is either `standard` or a supported post format.
       
  2698 			 * - First, add `standard` to the array of valid formats.
       
  2699 			 * - Then, remove any invalid formats.
       
  2700 			 */
       
  2701 			$valid_formats = array_merge( array( 'standard' ), get_post_format_slugs() );
       
  2702 			$formats       = array_intersect( $formats, $valid_formats );
       
  2703 
       
  2704 			/*
       
  2705 			 * The relation needs to be set to `OR` since the request can contain
       
  2706 			 * two separate conditions. The user may be querying for items that have
       
  2707 			 * either the `standard` format or a specific format.
       
  2708 			 */
       
  2709 			$formats_query = array( 'relation' => 'OR' );
       
  2710 
       
  2711 			/*
       
  2712 			 * The default post format, `standard`, is not stored in the database.
       
  2713 			 * If `standard` is part of the request, the query needs to exclude all post items that
       
  2714 			 * have a format assigned.
       
  2715 			 */
       
  2716 			if ( in_array( 'standard', $formats, true ) ) {
       
  2717 				$formats_query[] = array(
       
  2718 					'taxonomy' => 'post_format',
       
  2719 					'field'    => 'slug',
       
  2720 					'operator' => 'NOT EXISTS',
       
  2721 				);
       
  2722 				// Remove the `standard` format, since it cannot be queried.
       
  2723 				unset( $formats[ array_search( 'standard', $formats, true ) ] );
       
  2724 			}
       
  2725 			// Add any remaining formats to the formats query.
       
  2726 			if ( ! empty( $formats ) ) {
       
  2727 				// Add the `post-format-` prefix.
       
  2728 				$terms           = array_map(
       
  2729 					static function ( $format ) {
       
  2730 						return "post-format-$format";
       
  2731 					},
       
  2732 					$formats
       
  2733 				);
       
  2734 				$formats_query[] = array(
       
  2735 					'taxonomy' => 'post_format',
       
  2736 					'field'    => 'slug',
       
  2737 					'terms'    => $terms,
       
  2738 					'operator' => 'IN',
       
  2739 				);
       
  2740 			}
       
  2741 
       
  2742 			/*
       
  2743 			 * Add `$formats_query` to `$query`, as long as it contains more than one key:
       
  2744 			 * If `$formats_query` only contains the initial `relation` key, there are no valid formats to query,
       
  2745 			 * and the query should not be modified.
       
  2746 			 */
       
  2747 			if ( count( $formats_query ) > 1 ) {
       
  2748 				// Enable filtering by both post formats and other taxonomies by combining them with `AND`.
       
  2749 				if ( empty( $query['tax_query'] ) ) {
       
  2750 					$query['tax_query'] = $formats_query;
       
  2751 				} else {
       
  2752 					$query['tax_query'] = array(
       
  2753 						'relation' => 'AND',
       
  2754 						$query['tax_query'],
       
  2755 						$formats_query,
       
  2756 					);
       
  2757 				}
       
  2758 			}
       
  2759 		}
       
  2760 
  2370 		if (
  2761 		if (
  2371 			isset( $block->context['query']['order'] ) &&
  2762 			isset( $block->context['query']['order'] ) &&
  2372 				in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
  2763 				in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
  2373 		) {
  2764 		) {
  2374 			$query['order'] = strtoupper( $block->context['query']['order'] );
  2765 			$query['order'] = strtoupper( $block->context['query']['order'] );
  2389 		}
  2780 		}
  2390 		if ( ! empty( $block->context['query']['search'] ) ) {
  2781 		if ( ! empty( $block->context['query']['search'] ) ) {
  2391 			$query['s'] = $block->context['query']['search'];
  2782 			$query['s'] = $block->context['query']['search'];
  2392 		}
  2783 		}
  2393 		if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
  2784 		if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
  2394 			$query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) );
  2785 			$query['post_parent__in'] = array_unique( array_map( 'intval', $block->context['query']['parents'] ) );
  2395 		}
  2786 		}
  2396 	}
  2787 	}
  2397 
  2788 
  2398 	/**
  2789 	/**
  2399 	 * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
  2790 	 * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
  2509 				$max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages;
  2900 				$max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages;
  2510 				if ( 0 !== $max_num_pages ) {
  2901 				if ( 0 !== $max_num_pages ) {
  2511 					$comment_args['paged'] = $max_num_pages;
  2902 					$comment_args['paged'] = $max_num_pages;
  2512 				}
  2903 				}
  2513 			}
  2904 			}
  2514 			// Set the `cpage` query var to ensure the previous and next pagination links are correct
       
  2515 			// when inheriting the Discussion Settings.
       
  2516 			if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) {
       
  2517 				set_query_var( 'cpage', $comment_args['paged'] );
       
  2518 			}
       
  2519 		}
  2905 		}
  2520 	}
  2906 	}
  2521 
  2907 
  2522 	return $comment_args;
  2908 	return $comment_args;
  2523 }
  2909 }