Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion lib/compat/wordpress-7.1/block-bindings.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @subpackage Block Bindings
*/

// The following filter can be removed once the minimum required WordPress version is 7.1 or newer.
// The following filters can be removed once the minimum required WordPress version is 7.1 or newer.
add_filter(
'block_bindings_supported_attributes',
function ( $attributes, $block_type ) {
Expand All @@ -20,6 +20,16 @@ function ( $attributes, $block_type ) {
2
);

add_filter(
'block_bindings_supported_attributes_core/media-text',
function ( $supported_attributes ) {
$supported_attributes[] = 'mediaUrl';
$supported_attributes[] = 'mediaAlt';
$supported_attributes[] = 'mediaId';
return $supported_attributes;
}
);

/*
* On WordPress versions before 7.1, `WP_Block::replace_html()` lacks the
* inner-blocks fix (wordpress-develop#12113), so binding a List Item's content
Expand Down
92 changes: 91 additions & 1 deletion packages/block-library/src/media-text/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,97 @@
*/
function render_block_core_media_text( $attributes, $content ) {
if ( false === $attributes['useFeaturedImage'] ) {
return $content;
$media_bindings = $attributes['metadata']['bindings'] ?? array();
if ( empty( $media_bindings['mediaUrl'] ) ) {
return $content;
}

/*
* The `mediaUrl` and `mediaAlt` attributes are bound to a block bindings
* source. `WP_Block::replace_html()` cannot match the block's descendant
* selectors (e.g. `figure img`), so update the media element markup here
* with the resolved values.
*/
$media_url = $attributes['mediaUrl'] ?? null;
if ( '' === $media_url ) {
return $content;
}

$media_type = $attributes['mediaType'] ?? 'image';
$is_video = 'video' === $media_type;
$media_alt = $attributes['mediaAlt'] ?? '';
$has_media_on_right = 'right' === ( $attributes['mediaPosition'] ?? null );
$figure_query = array(
'tag_name' => 'figure',
'class_name' => 'wp-block-media-text__media',
);

/*
* Locate this block's media figure, accounting for `media-position: right`
* where the media column follows the content column. Returns a processor
* positioned on the figure, or null if no media figure is found.
*/
$find_media_figure = static function ( $block_content ) use ( $has_media_on_right, $figure_query ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
$found = false;
while ( $processor->next_tag( $figure_query ) ) {
$found = true;
$processor->set_bookmark( 'media_figure' );
if ( ! $has_media_on_right ) {
break;
}
}
if ( ! $found || ! $processor->seek( 'media_figure' ) ) {
return null;
}
return $processor;
};

$media_processor = $find_media_figure( $content );
if ( null === $media_processor ) {
return $content;
}

if ( $media_processor->next_tag( array( 'tag_name' => $is_video ? 'video' : 'img' ) ) ) {
// Update an existing media element in the saved markup.
$media_processor->set_attribute( 'src', $media_url );
if ( ! $is_video && isset( $media_bindings['mediaAlt'] ) ) {
$media_processor->set_attribute( 'alt', $media_alt );
}

return $media_processor->get_updated_html();
}

// The media figure is empty (e.g. the pattern was saved without an image),
// so build the media element from the bound attributes and insert it.
$media_processor->seek( 'media_figure' );
$unique_id = 'wp-block-media-text__media-' . wp_unique_id();
$media_processor->set_attribute( 'id', $unique_id );

$media_tag = $is_video
? '<video controls src="' . esc_url( $media_url ) . '"></video>'
: '<img src="' . esc_url( $media_url ) . '"' . ( isset( $media_bindings['mediaAlt'] ) ? ' alt="' . esc_attr( $media_alt ) . '"' : '' ) . ' />';

$content = $media_processor->get_updated_html();
// Insert the media element right after the figure opening tag, then remove
// the temporary id used to target it.
$content = preg_replace(
'/(<figure\s+id="' . preg_quote( $unique_id, '/' ) . '"\s+class="wp-block-media-text__media"\s*>)/',
'$1' . $media_tag,
$content
);

$cleanup_processor = new WP_HTML_Tag_Processor( $content );
if ( $cleanup_processor->next_tag(
array(
'tag_name' => 'figure',
'id' => $unique_id,
)
) ) {
$cleanup_processor->remove_attribute( 'id' );
}

return $cleanup_processor->get_updated_html();
}

if ( in_the_loop() ) {
Expand Down
7 changes: 6 additions & 1 deletion packages/block-library/src/media-text/media-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ function MediaContainer( props, ref ) {
const { toggleSelection } = useDispatch( blockEditorStore );

if ( mediaUrl || featuredImageURL || useFeaturedImage ) {
// `mediaType` may be unset when the media comes from a block binding
// (e.g. a pattern override) rather than a media library selection.
// Default to rendering an image so the bound media is visible in the editor.
const mediaTypeToRender = mediaType || 'image';

const onResizeStart = () => {
toggleSelection( false );
};
Expand Down Expand Up @@ -207,7 +212,7 @@ function MediaContainer( props, ref ) {
toggleUseFeaturedImage={ toggleUseFeaturedImage }
useFeaturedImage={ useFeaturedImage }
/>
{ ( mediaTypeRenderers[ mediaType ] || noop )() }
{ ( mediaTypeRenderers[ mediaTypeToRender ] || noop )() }
{ isTemporaryMedia && <Spinner /> }
{ ! useFeaturedImage && <PlaceholderContainer { ...props } /> }
{ ! featuredImageURL && useFeaturedImage && (
Expand Down
127 changes: 127 additions & 0 deletions phpunit/block-bindings-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,133 @@ public function test_update_block_with_value_from_source_image_placeholder() {
);
}

/**
* Tests if the block content is updated with the value returned by the source
* for the Media & Text block, whose supported attributes are added via the
* `block_bindings_supported_attributes_core/media-text` filter.
*
* @covers ::register_block_bindings_source
*/
public function test_update_block_with_value_from_source_media_text() {
$get_value_callback = function ( $source_args, $block_instance, $attribute_name ) {
if ( 'mediaUrl' === $attribute_name ) {
return 'https://example.com/updated-image.jpg';
}
if ( 'mediaAlt' === $attribute_name ) {
return 'Updated alt text';
}
};

register_block_bindings_source(
self::SOURCE_NAME,
array(
'label' => self::SOURCE_LABEL,
'get_value_callback' => $get_value_callback,
)
);

$this->assertContains(
'mediaUrl',
get_block_bindings_supported_attributes( 'core/media-text' ),
'The mediaUrl attribute should be supported for block bindings.'
);
$this->assertContains(
'mediaAlt',
get_block_bindings_supported_attributes( 'core/media-text' ),
'The mediaAlt attribute should be supported for block bindings.'
);
$this->assertContains(
'mediaId',
get_block_bindings_supported_attributes( 'core/media-text' ),
'The mediaId attribute should be supported for block bindings.'
);

$block_content = <<<HTML
<!-- wp:media-text {"metadata":{"bindings":{"mediaUrl":{"source":"test/source"},"mediaAlt":{"source":"test/source"}}},"mediaId":11,"mediaType":"image","mediaUrl":"https://example.com/original.jpg"} -->
<div class="wp-block-media-text is-stacked-on-mobile"><figure class="wp-block-media-text__media"><img class="wp-image-11 size-full" src="https://example.com/original.jpg" alt="original alt"/></figure><div class="wp-block-media-text__content"><p></p></div></div>
<!-- /wp:media-text -->
HTML;
$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$this->assertSame(
'https://example.com/updated-image.jpg',
$block->attributes['mediaUrl'],
"The 'mediaUrl' attribute should be updated with the value returned by the source."
);
$this->assertSame(
'Updated alt text',
$block->attributes['mediaAlt'],
"The 'mediaAlt' attribute should be updated with the value returned by the source."
);
$this->assertStringContainsString(
'<img class="wp-image-11 size-full" src="https://example.com/updated-image.jpg" alt="Updated alt text"/>',
$result,
'The block content should be updated with the value returned by the source.'
);
}

public function data_update_block_with_value_from_source_media_text_empty_figure() {
return array(
'media on the left' => array(
'left',
'<div class="wp-block-media-text is-stacked-on-mobile"><figure class="wp-block-media-text__media"></figure><div class="wp-block-media-text__content"><p></p></div></div>',
),
'media on the right' => array(
'right',
'<div class="wp-block-media-text has-media-on-the-right is-stacked-on-mobile"><div class="wp-block-media-text__content"><p></p></div><figure class="wp-block-media-text__media"></figure></div>',
),
);
}

/**
* Tests that a Media & Text block saved without media renders the image
* bound via block bindings, for both media positions.
*
* @covers ::register_block_bindings_source
*
* @dataProvider data_update_block_with_value_from_source_media_text_empty_figure
*/
public function test_update_block_with_value_from_source_media_text_empty_figure( $media_position, $block_markup ) {
$get_value_callback = function ( $source_args, $block_instance, $attribute_name ) {
if ( 'mediaUrl' === $attribute_name ) {
return 'https://example.com/updated-image.jpg';
}
if ( 'mediaAlt' === $attribute_name ) {
return 'Updated alt text';
}
};

register_block_bindings_source(
self::SOURCE_NAME,
array(
'label' => self::SOURCE_LABEL,
'get_value_callback' => $get_value_callback,
)
);

$block_content = <<<HTML
<!-- wp:media-text {"metadata":{"bindings":{"mediaUrl":{"source":"test/source"},"mediaAlt":{"source":"test/source"}}},"mediaPosition":"{$media_position}"} -->
{$block_markup}
<!-- /wp:media-text -->
HTML;
$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$this->assertStringContainsString(
'<img src="https://example.com/updated-image.jpg" alt="Updated alt text" />',
$result,
'The block should render the bound image in an empty media figure.'
);
$this->assertStringNotContainsString(
'id="wp-block-media-text__media-',
$result,
'The temporary figure id used to insert the media should be removed.'
);
}

/**
* Tests if the `__default` attribute is replaced with real attributes for
* pattern overrides.
Expand Down
Loading