diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php
index 7fe05b2209c812..584318b51d196f 100644
--- a/packages/block-library/src/image/index.php
+++ b/packages/block-library/src/image/index.php
@@ -28,12 +28,33 @@ function render_block_core_image( $attributes, $content, $block ) {
return '';
}
+ $has_id_binding = isset( $attributes['metadata']['bindings']['id'] ) && isset( $attributes['id'] );
+
+ // Ensure the `wp-image-id` classname on the image block supports block bindings.
+ if ( $has_id_binding ) {
+ // If there's a mismatch with the 'wp-image-' class and the actual id, the id was
+ // probably overridden by block bindings. Update it to the correct value.
+ // See https://github.com/WordPress/gutenberg/issues/62886 for why this is needed.
+ $id = $attributes['id'];
+ $image_classnames = $p->get_attribute( 'class' );
+ $class_with_binding_value = "wp-image-$id";
+ if ( is_string( $image_classnames ) && ! str_contains( $image_classnames, $class_with_binding_value ) ) {
+ $image_classnames = preg_replace( '/wp-image-(\d+)/', $class_with_binding_value, $image_classnames );
+ $p->set_attribute( 'class', $image_classnames );
+ }
+ }
+
+ // For backwards compatibility, the data-id html attribute is only set for
+ // image blocks nested in a gallery. Detect if the image is in a gallery by
+ // checking the data-id attribute.
+ // See the `block_core_gallery_data_id_backcompatibility` function.
if ( isset( $attributes['data-id'] ) ) {
- // Adds the data-id="$id" attribute to the img element to provide backwards
- // compatibility for the Gallery Block, which now wraps Image Blocks within
- // innerBlocks. The data-id attribute is added in a core/gallery
- // `render_block_data` hook.
- $p->set_attribute( 'data-id', $attributes['data-id'] );
+ // If there's a binding for the `id`, the `id` attribute is used for the
+ // value, since `data-id` does not support block bindings.
+ // Else the `data-id` is used for backwards compatibility, since
+ // third parties may be filtering its value.
+ $data_id = $has_id_binding ? $attributes['id'] : $attributes['data-id'];
+ $p->set_attribute( 'data-id', $data_id );
}
$link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none';
diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js
index 3587296a6b2a08..574bc4ead01c89 100644
--- a/test/e2e/specs/editor/various/pattern-overrides.spec.js
+++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js
@@ -18,6 +18,7 @@ test.describe( 'Pattern Overrides', () => {
test.afterEach( async ( { requestUtils } ) => {
await requestUtils.deleteAllBlocks();
+ await requestUtils.deleteAllMedia();
} );
test.afterAll( async ( { requestUtils } ) => {
@@ -795,6 +796,139 @@ test.describe( 'Pattern Overrides', () => {
expect( patternInnerBlocks[ 0 ].attributes.link ).toBe( undefined );
} );
+ test( 'image block classname and data-id attributes contain the correct media ids when used in a gallery', async ( {
+ admin,
+ editor,
+ page,
+ requestUtils,
+ } ) => {
+ // Upload two images, one for the original pattern, one for the override.
+ const { id: originalImageId, source_url: originalImageSrc } =
+ await requestUtils.uploadMedia(
+ path.resolve(
+ process.cwd(),
+ 'test/e2e/assets/10x10_e2e_test_image_z9T8jK.png'
+ )
+ );
+ const { id: overrideImageId, source_url: overrideImageSrc } =
+ await requestUtils.uploadMedia(
+ path.resolve(
+ process.cwd(),
+ 'test/e2e/assets/1024x768_e2e_test_image_size.jpeg'
+ )
+ );
+ const overrideName = 'test';
+
+ // Might be overkill, but check that the ids are actually different.
+ expect( overrideImageId ).not.toBe( originalImageId );
+
+ // Create a pattern with a gallery that has a single image with pattern overrides enabled.
+ // It has media that is not yet uploaded.
+ const { id } = await requestUtils.createBlock( {
+ title: 'Pattern',
+ content: `
+
+`,
+ status: 'publish',
+ } );
+
+ // Insert the pattern into a new post, overriding the image via the pattern block attributes.
+ await admin.createNewPost();
+ const imageAlt = 'Overridden Image';
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: {
+ ref: id,
+ content: {
+ [ overrideName ]: {
+ id: overrideImageId,
+ url: overrideImageSrc,
+ alt: imageAlt,
+ },
+ },
+ },
+ } );
+
+ // Check the image attributes on the frontend.
+ const postId = await editor.publishPost();
+ await page.goto( `/?p=${ postId }` );
+ const imageBlock = page.getByAltText( imageAlt );
+ await expect( imageBlock ).toHaveAttribute(
+ 'data-id',
+ `${ overrideImageId }`
+ );
+ await expect( imageBlock ).toHaveAttribute(
+ 'class',
+ `wp-image-${ overrideImageId }`
+ );
+ } );
+
+ test( 'image block classname contains the correct media id and has no data-id attribute when used as a standalone image', async ( {
+ admin,
+ editor,
+ page,
+ requestUtils,
+ } ) => {
+ // Upload two images, one for the original pattern, one for the override.
+ const { id: originalImageId, source_url: originalImageSrc } =
+ await requestUtils.uploadMedia(
+ path.resolve(
+ process.cwd(),
+ 'test/e2e/assets/10x10_e2e_test_image_z9T8jK.png'
+ )
+ );
+ const { id: overrideImageId, source_url: overrideImageSrc } =
+ await requestUtils.uploadMedia(
+ path.resolve(
+ process.cwd(),
+ 'test/e2e/assets/1024x768_e2e_test_image_size.jpeg'
+ )
+ );
+ const overrideName = 'test';
+
+ // Might be overkill, but check that the ids are actually different.
+ expect( overrideImageId ).not.toBe( originalImageId );
+
+ // Create a pattern with a gallery that has a single image with pattern overrides enabled.
+ // It has media that is not yet uploaded.
+ const { id } = await requestUtils.createBlock( {
+ title: 'Pattern',
+ content: `
+
+`,
+ status: 'publish',
+ } );
+
+ // Insert the pattern into a new post, overriding the image via the pattern block attributes.
+ await admin.createNewPost();
+ const imageAlt = 'Overridden Image';
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: {
+ ref: id,
+ content: {
+ [ overrideName ]: {
+ id: overrideImageId,
+ url: overrideImageSrc,
+ alt: imageAlt,
+ },
+ },
+ },
+ } );
+
+ // Check the image attributes on the frontend.
+ const postId = await editor.publishPost();
+ await page.goto( `/?p=${ postId }` );
+ const imageBlock = page.getByAltText( imageAlt );
+ await expect( imageBlock ).not.toHaveAttribute( 'data-id' );
+ await expect( imageBlock ).toHaveAttribute(
+ 'class',
+ `wp-image-${ overrideImageId }`
+ );
+ } );
+
test( 'blocks with the same name should be synced', async ( {
page,
admin,