Skip to content

Commit d03544d

Browse files
committed
Block Supports: Backport update from Gutenberg
See WordPress/gutenberg#54075 Refactors block supports to use a single Tag Processor instance and to only send a single class at a time to `add_class()`.
1 parent a0cce39 commit d03544d

1 file changed

Lines changed: 108 additions & 44 deletions

File tree

src/wp-includes/block-supports/layout.php

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -547,29 +547,30 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false
547547
* @return string Filtered block content.
548548
*/
549549
function wp_render_layout_support_flag( $block_content, $block ) {
550-
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
551-
$support_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false );
552-
$has_child_layout = isset( $block['attrs']['style']['layout']['selfStretch'] );
550+
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
551+
$block_supports_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false );
552+
$layout_from_parent = $block['attrs']['style']['layout']['selfStretch'] ?? null;
553553

554-
if ( ! $support_layout && ! $has_child_layout ) {
554+
if ( ! $block_supports_layout && ! $layout_from_parent ) {
555555
return $block_content;
556556
}
557+
557558
$outer_class_names = array();
558559

559-
if ( $has_child_layout && ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] || 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) ) {
560+
if ( 'fixed' === $layout_from_parent || 'fill' === $layout_from_parent ) {
560561
$container_content_class = wp_unique_id( 'wp-container-content-' );
561562

562563
$child_layout_styles = array();
563564

564-
if ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
565+
if ( 'fixed' === $layout_from_parent && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
565566
$child_layout_styles[] = array(
566567
'selector' => ".$container_content_class",
567568
'declarations' => array(
568569
'flex-basis' => $block['attrs']['style']['layout']['flexSize'],
569570
'box-sizing' => 'border-box',
570571
),
571572
);
572-
} elseif ( 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) {
573+
} elseif ( 'fill' === $layout_from_parent ) {
573574
$child_layout_styles[] = array(
574575
'selector' => ".$container_content_class",
575576
'declarations' => array(
@@ -589,12 +590,25 @@ function wp_render_layout_support_flag( $block_content, $block ) {
589590
$outer_class_names[] = $container_content_class;
590591
}
591592

592-
// Return early if only child layout exists.
593-
if ( ! $support_layout && ! empty( $outer_class_names ) ) {
594-
$content = new WP_HTML_Tag_Processor( $block_content );
595-
$content->next_tag();
596-
$content->add_class( implode( ' ', $outer_class_names ) );
597-
return (string) $content;
593+
// Prep the processor for modifying the block output.
594+
$processor = new WP_HTML_Tag_Processor( $block_content );
595+
596+
// Having no tags implies there are no tags onto which to add class names.
597+
if ( ! $processor->next_tag() ) {
598+
return $block_content;
599+
}
600+
601+
/*
602+
* A block may not support layout but still be affected by a parent block's layout.
603+
*
604+
* In these cases add the appropriate class names and then return early; there's
605+
* no need to investigate on this block whether additional layout constraints apply.
606+
*/
607+
if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) {
608+
foreach ( $outer_class_names as $class_name ) {
609+
$processor->add_class( $class_name );
610+
}
611+
return $processor->get_updated_html();
598612
}
599613

600614
$global_settings = wp_get_global_settings();
@@ -604,7 +618,6 @@ function wp_render_layout_support_flag( $block_content, $block ) {
604618
$class_names = array();
605619
$layout_definitions = wp_get_layout_definitions();
606620
$container_class = wp_unique_id( 'wp-container-' );
607-
$layout_classname = '';
608621

609622
// Set the correct layout type for blocks using legacy content width.
610623
if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
@@ -702,49 +715,100 @@ function wp_render_layout_support_flag( $block_content, $block ) {
702715
$block_name = explode( '/', $block['blockName'] );
703716
$class_names[] = 'wp-block-' . end( $block_name ) . '-' . $layout_classname;
704717

705-
$content_with_outer_classnames = '';
706-
718+
// Add classes to the outermost HTML tag if necessary.
707719
if ( ! empty( $outer_class_names ) ) {
708-
$content_with_outer_classnames = new WP_HTML_Tag_Processor( $block_content );
709-
$content_with_outer_classnames->next_tag();
710720
foreach ( $outer_class_names as $outer_class_name ) {
711-
$content_with_outer_classnames->add_class( $outer_class_name );
721+
$processor->add_class( $outer_class_name );
712722
}
713-
714-
$content_with_outer_classnames = (string) $content_with_outer_classnames;
715723
}
716724

717725
/**
718-
* The first chunk of innerContent contains the block markup up until the inner blocks start.
719-
* This targets the opening tag of the inner blocks wrapper, which is the last tag in that chunk.
720-
*/
721-
$inner_content_classnames = '';
722-
723-
if ( isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) && count( $block['innerContent'] ) > 1 ) {
724-
$tags = new WP_HTML_Tag_Processor( $block['innerContent'][0] );
725-
$last_classnames = '';
726-
while ( $tags->next_tag() ) {
727-
$last_classnames = $tags->get_attribute( 'class' );
726+
* Attempts to refer to the inner-block wrapping element by its class attribute.
727+
*
728+
* When examining a block's inner content, if a block has inner blocks, then
729+
* the first content item will likely be a text (HTML) chunk immediately
730+
* preceding the inner blocks. The last HTML tag in that chunk would then be
731+
* an opening tag for an element that wraps the inner blocks.
732+
*
733+
* There's no reliable way to associate this wrapper in $block_content because
734+
* it may have changed during the rendering pipeline (as inner contents is
735+
* provided before rendering) and through previous filters. In many cases,
736+
* however, the `class` attribute will be a good-enough identifier, so this
737+
* code finds the last tag in that chunk and stores the `class` attribute
738+
* so that it can be used later when working through the rendered block output
739+
* to identify the wrapping element and add the remaining class names to it.
740+
*
741+
* It's also possible that no inner block wrapper even exists. If that's the
742+
* case this code could apply the class names to an invalid element.
743+
*
744+
* Example:
745+
*
746+
* $block['innerBlocks'] = array( $list_item );
747+
* $block['innerContent'] = array( '<ul class="list-wrapper is-unordered">', null, '</ul>' );
748+
*
749+
* // After rendering, the initial contents may have been modified by other renderers or filters.
750+
* $block_content = <<<HTML
751+
* <figure>
752+
* <ul class="annotated-list list-wrapper is-unordered">
753+
* <li>Code</li>
754+
* </ul><figcaption>It's a list!</figcaption>
755+
* </figure>
756+
* HTML;
757+
*
758+
* Although it is possible that the original block-wrapper classes are changed in $block_content
759+
* from how they appear in $block['innerContent'], it's likely that the original class attributes
760+
* are still present in the wrapper as they are in this example. Frequently, additional classes
761+
* will also be present; rarely should classes be removed.
762+
*
763+
* @TODO: Find a better way to match the first inner block. If it's possible to identify where the
764+
* first inner block starts, then it will be possible to find the last tag before it starts
765+
* and then that tag, if an opening tag, can be solidly identified as a wrapping element.
766+
* Can some unique value or class or ID be added to the inner blocks when they process
767+
* so that they can be extracted here safely without guessing? Can the block rendering function
768+
* return information about where the rendered inner blocks start?
769+
*
770+
* @var string|null
771+
*/
772+
$inner_block_wrapper_classes = null;
773+
$first_chunk = $block['innerContent'][0] ?? null;
774+
if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
775+
$first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
776+
while ( $first_chunk_processor->next_tag() ) {
777+
$class_attribute = $first_chunk_processor->get_attribute( 'class' );
778+
if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
779+
$inner_block_wrapper_classes = $class_attribute;
780+
}
728781
}
729-
730-
$inner_content_classnames = (string) $last_classnames;
731782
}
732783

733-
$content = $content_with_outer_classnames ? new WP_HTML_Tag_Processor( $content_with_outer_classnames ) : new WP_HTML_Tag_Processor( $block_content );
734-
735-
if ( $inner_content_classnames ) {
736-
$content->next_tag( array( 'class_name' => $inner_content_classnames ) );
737-
foreach ( $class_names as $class_name ) {
738-
$content->add_class( $class_name );
784+
/*
785+
* If necessary, advance to what is likely to be an inner block wrapper tag.
786+
*
787+
* This advances until it finds the first tag containing the original class
788+
* attribute from above. If none is found it will scan to the end of the block
789+
* and fail to add any class names.
790+
*
791+
* If there is no block wrapper it won't advance at all, in which case the
792+
* class names will be added to the first and outermost tag of the block.
793+
* For cases where this outermost tag is the only tag surrounding inner
794+
* blocks then the outer wrapper and inner wrapper are the same.
795+
*/
796+
do {
797+
if ( ! $inner_block_wrapper_classes ) {
798+
break;
739799
}
740-
} else {
741-
$content->next_tag();
742-
foreach ( $class_names as $class_name ) {
743-
$content->add_class( $class_name );
800+
801+
if ( false !== strpos( $processor->get_attribute( 'class' ), $inner_block_wrapper_classes ) ) {
802+
break;
744803
}
804+
} while ( $processor->next_tag() );
805+
806+
// Add the remaining class names.
807+
foreach ( $class_names as $class_name ) {
808+
$processor->add_class( $class_name );
745809
}
746810

747-
return (string) $content;
811+
return $processor->get_updated_html();
748812
}
749813

750814
// Register the block support.

0 commit comments

Comments
 (0)