wp/wp-includes/blocks.php
changeset 9 177826044cd9
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     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  * Registers a block type.
       
    12  *
       
    13  * @since 5.0.0
       
    14  *
       
    15  * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
       
    16  *                                   complete WP_Block_Type instance. In case a WP_Block_Type
       
    17  *                                   is provided, the $args parameter will be ignored.
       
    18  * @param array                $args {
       
    19  *     Optional. Array of block type arguments. Any arguments may be defined, however the
       
    20  *     ones described below are supported by default. Default empty array.
       
    21  *
       
    22  *     @type callable $render_callback Callback used to render blocks of this block type.
       
    23  * }
       
    24  * @return WP_Block_Type|false The registered block type on success, or false on failure.
       
    25  */
       
    26 function register_block_type( $name, $args = array() ) {
       
    27 	return WP_Block_Type_Registry::get_instance()->register( $name, $args );
       
    28 }
       
    29 
       
    30 /**
       
    31  * Unregisters a block type.
       
    32  *
       
    33  * @since 5.0.0
       
    34  *
       
    35  * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
       
    36  *                                   complete WP_Block_Type instance.
       
    37  * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
       
    38  */
       
    39 function unregister_block_type( $name ) {
       
    40 	return WP_Block_Type_Registry::get_instance()->unregister( $name );
       
    41 }
       
    42 
       
    43 /**
       
    44  * Determine whether a post or content string has blocks.
       
    45  *
       
    46  * This test optimizes for performance rather than strict accuracy, detecting
       
    47  * the pattern of a block but not validating its structure. For strict accuracy,
       
    48  * you should use the block parser on post content.
       
    49  *
       
    50  * @since 5.0.0
       
    51  * @see parse_blocks()
       
    52  *
       
    53  * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
       
    54  * @return bool Whether the post has blocks.
       
    55  */
       
    56 function has_blocks( $post = null ) {
       
    57 	if ( ! is_string( $post ) ) {
       
    58 		$wp_post = get_post( $post );
       
    59 		if ( $wp_post instanceof WP_Post ) {
       
    60 			$post = $wp_post->post_content;
       
    61 		}
       
    62 	}
       
    63 
       
    64 	return false !== strpos( (string) $post, '<!-- wp:' );
       
    65 }
       
    66 
       
    67 /**
       
    68  * Determine whether a $post or a string contains a specific block type.
       
    69  *
       
    70  * This test optimizes for performance rather than strict accuracy, detecting
       
    71  * the block type exists but not validating its structure. For strict accuracy,
       
    72  * you should use the block parser on post content.
       
    73  *
       
    74  * @since 5.0.0
       
    75  * @see parse_blocks()
       
    76  *
       
    77  * @param string                  $block_type Full Block type to look for.
       
    78  * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
       
    79  * @return bool Whether the post content contains the specified block.
       
    80  */
       
    81 function has_block( $block_type, $post = null ) {
       
    82 	if ( ! has_blocks( $post ) ) {
       
    83 		return false;
       
    84 	}
       
    85 
       
    86 	if ( ! is_string( $post ) ) {
       
    87 		$wp_post = get_post( $post );
       
    88 		if ( $wp_post instanceof WP_Post ) {
       
    89 			$post = $wp_post->post_content;
       
    90 		}
       
    91 	}
       
    92 
       
    93 	return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' );
       
    94 }
       
    95 
       
    96 /**
       
    97  * Returns an array of the names of all registered dynamic block types.
       
    98  *
       
    99  * @since 5.0.0
       
   100  *
       
   101  * @return array Array of dynamic block names.
       
   102  */
       
   103 function get_dynamic_block_names() {
       
   104 	$dynamic_block_names = array();
       
   105 
       
   106 	$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
       
   107 	foreach ( $block_types as $block_type ) {
       
   108 		if ( $block_type->is_dynamic() ) {
       
   109 			$dynamic_block_names[] = $block_type->name;
       
   110 		}
       
   111 	}
       
   112 
       
   113 	return $dynamic_block_names;
       
   114 }
       
   115 
       
   116 /**
       
   117  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
       
   118  *
       
   119  * As the excerpt should be a small string of text relevant to the full post content,
       
   120  * this function renders the blocks that are most likely to contain such text.
       
   121  *
       
   122  * @since 5.0.0
       
   123  *
       
   124  * @param string $content The content to parse.
       
   125  * @return string The parsed and filtered content.
       
   126  */
       
   127 function excerpt_remove_blocks( $content ) {
       
   128 	$allowed_inner_blocks = array(
       
   129 		// Classic blocks have their blockName set to null.
       
   130 		null,
       
   131 		'core/freeform',
       
   132 		'core/heading',
       
   133 		'core/html',
       
   134 		'core/list',
       
   135 		'core/media-text',
       
   136 		'core/paragraph',
       
   137 		'core/preformatted',
       
   138 		'core/pullquote',
       
   139 		'core/quote',
       
   140 		'core/table',
       
   141 		'core/verse',
       
   142 	);
       
   143 
       
   144 	$allowed_blocks = array_merge( $allowed_inner_blocks, array( 'core/columns' ) );
       
   145 
       
   146 	/**
       
   147 	 * Filters the list of blocks that can contribute to the excerpt.
       
   148 	 *
       
   149 	 * If a dynamic block is added to this list, it must not generate another
       
   150 	 * excerpt, as this will cause an infinite loop to occur.
       
   151 	 *
       
   152 	 * @since 5.0.0
       
   153 	 *
       
   154 	 * @param array $allowed_blocks The list of allowed blocks.
       
   155 	 */
       
   156 	$allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks );
       
   157 	$blocks         = parse_blocks( $content );
       
   158 	$output         = '';
       
   159 
       
   160 	foreach ( $blocks as $block ) {
       
   161 		if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
       
   162 			if ( ! empty( $block['innerBlocks'] ) ) {
       
   163 				if ( 'core/columns' === $block['blockName'] ) {
       
   164 					$output .= _excerpt_render_inner_columns_blocks( $block, $allowed_inner_blocks );
       
   165 					continue;
       
   166 				}
       
   167 
       
   168 				// Skip the block if it has disallowed or nested inner blocks.
       
   169 				foreach ( $block['innerBlocks'] as $inner_block ) {
       
   170 					if (
       
   171 						! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) ||
       
   172 						! empty( $inner_block['innerBlocks'] )
       
   173 					) {
       
   174 						continue 2;
       
   175 					}
       
   176 				}
       
   177 			}
       
   178 
       
   179 			$output .= render_block( $block );
       
   180 		}
       
   181 	}
       
   182 
       
   183 	return $output;
       
   184 }
       
   185 
       
   186 /**
       
   187  * Render inner blocks from the `core/columns` block for generating an excerpt.
       
   188  *
       
   189  * @since 5.2.0
       
   190  * @access private
       
   191  *
       
   192  * @param array $columns        The parsed columns block.
       
   193  * @param array $allowed_blocks The list of allowed inner blocks.
       
   194  * @return string The rendered inner blocks.
       
   195  */
       
   196 function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) {
       
   197 	$output = '';
       
   198 
       
   199 	foreach ( $columns['innerBlocks'] as $column ) {
       
   200 		foreach ( $column['innerBlocks'] as $inner_block ) {
       
   201 			if ( in_array( $inner_block['blockName'], $allowed_blocks, true ) && empty( $inner_block['innerBlocks'] ) ) {
       
   202 				$output .= render_block( $inner_block );
       
   203 			}
       
   204 		}
       
   205 	}
       
   206 
       
   207 	return $output;
       
   208 }
       
   209 
       
   210 /**
       
   211  * Renders a single block into a HTML string.
       
   212  *
       
   213  * @since 5.0.0
       
   214  *
       
   215  * @global WP_Post $post The post to edit.
       
   216  *
       
   217  * @param array $block A single parsed block object.
       
   218  * @return string String of rendered HTML.
       
   219  */
       
   220 function render_block( $block ) {
       
   221 	global $post;
       
   222 
       
   223 	/**
       
   224 	 * Allows render_block() to be shortcircuited, by returning a non-null value.
       
   225 	 *
       
   226 	 * @since 5.1.0
       
   227 	 *
       
   228 	 * @param string $pre_render The pre-rendered content. Default null.
       
   229 	 * @param array  $block      The block being rendered.
       
   230 	 */
       
   231 	$pre_render = apply_filters( 'pre_render_block', null, $block );
       
   232 	if ( ! is_null( $pre_render ) ) {
       
   233 		return $pre_render;
       
   234 	}
       
   235 
       
   236 	$source_block = $block;
       
   237 
       
   238 	/**
       
   239 	 * Filters the block being rendered in render_block(), before it's processed.
       
   240 	 *
       
   241 	 * @since 5.1.0
       
   242 	 *
       
   243 	 * @param array $block        The block being rendered.
       
   244 	 * @param array $source_block An un-modified copy of $block, as it appeared in the source content.
       
   245 	 */
       
   246 	$block = apply_filters( 'render_block_data', $block, $source_block );
       
   247 
       
   248 	$block_type    = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
       
   249 	$is_dynamic    = $block['blockName'] && null !== $block_type && $block_type->is_dynamic();
       
   250 	$block_content = '';
       
   251 	$index         = 0;
       
   252 
       
   253 	foreach ( $block['innerContent'] as $chunk ) {
       
   254 		$block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] );
       
   255 	}
       
   256 
       
   257 	if ( ! is_array( $block['attrs'] ) ) {
       
   258 		$block['attrs'] = array();
       
   259 	}
       
   260 
       
   261 	if ( $is_dynamic ) {
       
   262 		$global_post   = $post;
       
   263 		$block_content = $block_type->render( $block['attrs'], $block_content );
       
   264 		$post          = $global_post;
       
   265 	}
       
   266 
       
   267 	/**
       
   268 	 * Filters the content of a single block.
       
   269 	 *
       
   270 	 * @since 5.0.0
       
   271 	 *
       
   272 	 * @param string $block_content The block content about to be appended.
       
   273 	 * @param array  $block         The full block, including name and attributes.
       
   274 	 */
       
   275 	return apply_filters( 'render_block', $block_content, $block );
       
   276 }
       
   277 
       
   278 /**
       
   279  * Parses blocks out of a content string.
       
   280  *
       
   281  * @since 5.0.0
       
   282  *
       
   283  * @param string $content Post content.
       
   284  * @return array Array of parsed block objects.
       
   285  */
       
   286 function parse_blocks( $content ) {
       
   287 	/**
       
   288 	 * Filter to allow plugins to replace the server-side block parser
       
   289 	 *
       
   290 	 * @since 5.0.0
       
   291 	 *
       
   292 	 * @param string $parser_class Name of block parser class.
       
   293 	 */
       
   294 	$parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );
       
   295 
       
   296 	$parser = new $parser_class();
       
   297 	return $parser->parse( $content );
       
   298 }
       
   299 
       
   300 /**
       
   301  * Parses dynamic blocks out of `post_content` and re-renders them.
       
   302  *
       
   303  * @since 5.0.0
       
   304  * @global WP_Post $post The post to edit.
       
   305  *
       
   306  * @param  string $content Post content.
       
   307  * @return string Updated post content.
       
   308  */
       
   309 function do_blocks( $content ) {
       
   310 	$blocks = parse_blocks( $content );
       
   311 	$output = '';
       
   312 
       
   313 	foreach ( $blocks as $block ) {
       
   314 		$output .= render_block( $block );
       
   315 	}
       
   316 
       
   317 	// If there are blocks in this content, we shouldn't run wpautop() on it later.
       
   318 	$priority = has_filter( 'the_content', 'wpautop' );
       
   319 	if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
       
   320 		remove_filter( 'the_content', 'wpautop', $priority );
       
   321 		add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
       
   322 	}
       
   323 
       
   324 	return $output;
       
   325 }
       
   326 
       
   327 /**
       
   328  * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards,
       
   329  * for subsequent `the_content` usage.
       
   330  *
       
   331  * @access private
       
   332  *
       
   333  * @since 5.0.0
       
   334  *
       
   335  * @param string $content The post content running through this filter.
       
   336  * @return string The unmodified content.
       
   337  */
       
   338 function _restore_wpautop_hook( $content ) {
       
   339 	$current_priority = has_filter( 'the_content', '_restore_wpautop_hook' );
       
   340 
       
   341 	add_filter( 'the_content', 'wpautop', $current_priority - 1 );
       
   342 	remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority );
       
   343 
       
   344 	return $content;
       
   345 }
       
   346 
       
   347 /**
       
   348  * Returns the current version of the block format that the content string is using.
       
   349  *
       
   350  * If the string doesn't contain blocks, it returns 0.
       
   351  *
       
   352  * @since 5.0.0
       
   353  *
       
   354  * @param string $content Content to test.
       
   355  * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise.
       
   356  */
       
   357 function block_version( $content ) {
       
   358 	return has_blocks( $content ) ? 1 : 0;
       
   359 }