Skip to content

Commit

Permalink
Block Supports: Re-use instance of Tag Processor when adding layout c…
Browse files Browse the repository at this point in the history
…lasses.

In #45364 (WordPress/wordpress-develop#3976) the Block Supports was extended to
add layout class names using the HTML API, new in WordPress 6.2. The initial
patch opened up two opportunities to refine the code, however:

 - There are multiple instances of the `WP_HTML_Tag_Processor` created when a
   single one suffices. (There is an exception in that a second processor is
   necessary in order to find an inner block wrapper).

 - The code relies on the incidental fact that searching by a whitespace-separated
   list of class names works if the class names in the target tag are in the same
   order.

In this patch the use of the HTML API is refactored to address these opportunities
and clean up a few places where there could be stronger consistency with other use
patterns of the HTML API:

 - Multiple instances of the Tag Processor have been combined to remove overhead,
   extra variables, and code confusion. The new flow is more linear throughout the
   function instead of branching.

 - Updated HTML is returned via `get_updated_html()` instead of casting to a string.

 - The matching logic to find the inner block wrapper has been commented and the
   condition uses the null-coalescing operator now that WordPress requires PHP 7.0+.

 - When attempting to find the inner block wrapper at the end, a custom comparison
   is made against the `class` attribute instead of relying on `next_tag()` to find
   a tag with the given set of class names.

The last refactor is important as a preliminary step to WordPress/wordpress-develop#5096
where `has_class()` and `class_list()` methods are being introduced to the Tag Processor.
In that patch the implicit functionality of matching `'class_name' => 'more than one class'`
is removed since that's not a single class name, but many.
  • Loading branch information
dmsnell committed Aug 30, 2023
1 parent 22d24f3 commit 7d86181
Showing 1 changed file with 87 additions and 44 deletions.
131 changes: 87 additions & 44 deletions lib/block-supports/layout.php
Expand Up @@ -533,15 +533,13 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$support_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
$has_child_layout = isset( $block['attrs']['style']['layout']['selfStretch'] );

if ( ! $support_layout
&& ! $has_child_layout ) {
if ( ! $support_layout && ! $has_child_layout ) {
return $block_content;
}

$outer_class_names = array();

if ( $has_child_layout && ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] || 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) ) {

$container_content_class = wp_unique_id( 'wp-container-content-' );

$child_layout_styles = array();
Expand Down Expand Up @@ -572,15 +570,28 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
);

$outer_class_names[] = $container_content_class;
}

// Prep the processor for modifying the block output.
$processor = new WP_HTML_Tag_Processor( $block_content );

// Having no tags implies there are no tags onto which to add class names.
if ( ! $processor->next_tag() ) {
return $block_content;
}

// Return early if only child layout exists.
/*
* Return early if only child layout exists.
*
* In other words, if there is nothing more complicated, add
* the wrapper class names to the first HTML tag inside the
* block's rendered HTML.
*/
if ( ! $support_layout && ! empty( $outer_class_names ) ) {
$content = new WP_HTML_Tag_Processor( $block_content );
$content->next_tag();
$content->add_class( implode( ' ', $outer_class_names ) );
return (string) $content;
foreach ( $outer_class_names as $class_name ) {
$processor->add_class( $class_name );
}
return $processor->get_updated_html();
}

$global_settings = gutenberg_get_global_settings();
Expand All @@ -590,7 +601,6 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$class_names = array();
$layout_definitions = gutenberg_get_layout_definitions();
$container_class = wp_unique_id( 'wp-container-' );
$layout_classname = '';

// Set the correct layout type for blocks using legacy content width.
if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
Expand All @@ -599,11 +609,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {

$root_padding_aware_alignments = _wp_array_get( $global_settings, array( 'useRootPaddingAwareAlignments' ), false );

if (
$root_padding_aware_alignments &&
isset( $used_layout['type'] ) &&
'constrained' === $used_layout['type']
) {
if ( $root_padding_aware_alignments && isset( $used_layout['type'] ) && 'constrained' === $used_layout['type'] ) {
$class_names[] = 'has-global-padding';
}

Expand Down Expand Up @@ -690,49 +696,86 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$full_block_name = 'core' === $split_block_name[0] ? end( $split_block_name ) : implode( '-', $split_block_name );
$class_names[] = 'wp-block-' . $full_block_name . '-' . $layout_classname;

$content_with_outer_classnames = '';

// Add classes to the outermost HTML tag if necessary.
if ( ! empty( $outer_class_names ) ) {
$content_with_outer_classnames = new WP_HTML_Tag_Processor( $block_content );
$content_with_outer_classnames->next_tag();
foreach ( $outer_class_names as $outer_class_name ) {
$content_with_outer_classnames->add_class( $outer_class_name );
$processor->add_class( $outer_class_name );
}

$content_with_outer_classnames = (string) $content_with_outer_classnames;
}

/**
* The first chunk of innerContent contains the block markup up until the inner blocks start.
* We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk.
*/
$inner_content_classnames = '';

if ( isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) && count( $block['innerContent'] ) > 1 ) {
$tags = new WP_HTML_Tag_Processor( $block['innerContent'][0] );
$last_classnames = '';
while ( $tags->next_tag() ) {
$last_classnames = $tags->get_attribute( 'class' );
* Attempts to refer to the inner-block wrapping element by its class attribute.
*
* When examining a block's inner content, if a block has inner blocks, then
* the first content item will likely be a text (HTML) chunk immediately
* preceding the inner blocks. The last HTML tag in that chunk would then be
* an opening tag for an element that wraps the inner blocks.
*
* There's no reliable way to associate this wrapper in $block_content because
* it may have changed during the rendering pipeline (as inner contents is
* provided before rendering) and through previous filters. In many cases,
* however, the `class` attribute will be a good-enough identifier, so this
* code finds the last tag in that chunk and stores the `class` attribute
* so that it can be used later when working through the rendered block output
* to identify the wrapping element and add the remaining class names to it.
*
* Example:
*
* $block['innerBlocks'] = array( $list_item );
* $block['innerContent'] = array( '<ul class="list-wrapper is-unordered">', null, '</ul>' );
*
* // After rendering, the initial contents may have been modified by other renderers or filters.
* $block_content = <<<HTML
* <figure>
* <ul class="annotated-list list-wrapper is-unordered">
* <li>Code</li>
* </ul><figcaption>It's a list!</figcaption>
* </figure>
* HTML;
*
* Although it is possible that the original block-wrapper classes are changed in $block_content
* from how they appear in $block['innerContent'], it's likely that the original class attributes
* are still present in the wrapper as they are in this example. Frequently, additional classes
* will also be present; rarely should classes be removed.
*
* @TODO: Find a better way to match the first inner block. Can some unique
* value or class or ID be added to the inner blocks when they process
* so that they can be extracted here safely without guessing?
*
* @var string|null
*/
$inner_block_wrapper_classes = null;
$first_chunk = $block['innerContent'][0] ?? null;
if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
$first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
while ( $first_chunk_processor->next_tag() ) {
$class_attribute = $first_chunk_processor->get_attribute( 'class' );
if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
$inner_block_wrapper_classes = $class_attribute;
}
}

$inner_content_classnames = (string) $last_classnames;
}

$content = $content_with_outer_classnames ? new WP_HTML_Tag_Processor( $content_with_outer_classnames ) : new WP_HTML_Tag_Processor( $block_content );

if ( $inner_content_classnames ) {
$content->next_tag( array( 'class_name' => $inner_content_classnames ) );
foreach ( $class_names as $class_name ) {
$content->add_class( $class_name );
/*
* Find where to add the remaining class names. If there was a last tag identified before
* the inner blocks then they belong on that tag. Otherwise, they belong on the outermost tag.
*/
do {
if ( ! $inner_block_wrapper_classes ) {
break;
}
} else {
$content->next_tag();
foreach ( $class_names as $class_name ) {
$content->add_class( $class_name );

if ( false !== strpos( $processor->get_attribute( 'class' ), $inner_block_wrapper_classes ) ) {
break;
}
} while ( $processor->next_tag() );

// Add the remaining class names.
foreach ( $class_names as $class_name ) {
$processor->add_class( $class_name );
}

return (string) $content;
return $processor->get_updated_html();
}

// Register the block support. (overrides core one).
Expand Down

0 comments on commit 7d86181

Please sign in to comment.