Commit b58c5035665 for woocommerce
commit b58c5035665ae35b8bfa53b0ff4016eec0c14fb9
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri Jul 17 16:02:58 2026 +0200
Make images in Featured Product and Featured Category blocks responsive (#66466)
* Add changelog
* Make images in Featured Product and Featured Category blocks responsive
* Update PHPStan baseline
* Simplify code
* Make get_item_image_id() concrete
* Override responsive image sizes when Image Fit is None
* Add fall back in case get_item_image_id is not implemented
diff --git a/plugins/woocommerce/changelog/fix-make-featured-product-category-images-responsive b/plugins/woocommerce/changelog/fix-make-featured-product-category-images-responsive
new file mode 100644
index 00000000000..677f5100ff4
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-make-featured-product-category-images-responsive
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Make images in Featured Product and Featured Category blocks responsive
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 8265f01ccb0..a6847dfae7e 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -50859,12 +50859,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/FeaturedProduct.php
- -
- message: '#^Parameter \#1 \$attachment_id of function wp_get_attachment_image_url expects int, string given\.$#'
- identifier: argument.type
- count: 2
- path: src/Blocks/BlockTypes/FeaturedProduct.php
-
-
message: '#^Parameter \#1 \$variation of function wc_get_formatted_variation expects array\|WC_Product_Variation, WC_Product given\.$#'
identifier: argument.type
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedCategory.php b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedCategory.php
index 325a07f3c63..9cef5d7fff1 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedCategory.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedCategory.php
@@ -57,6 +57,16 @@ class FeaturedCategory extends FeaturedItem {
return $category->name;
}
+ /**
+ * Returns the featured category image attachment ID.
+ *
+ * @param \WP_Term $category Term object.
+ * @return int
+ */
+ protected function get_item_image_id( $category ) {
+ return (int) get_term_meta( $category->term_id, 'thumbnail_id', true );
+ }
+
/**
* Returns the featured category image URL.
*
@@ -65,14 +75,13 @@ class FeaturedCategory extends FeaturedItem {
* @return string
*/
protected function get_item_image( $category, $size = 'full' ) {
- $image = '';
- $image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
+ $image_id = $this->get_item_image_id( $category );
if ( $image_id ) {
- $image = wp_get_attachment_image_url( $image_id, $size );
+ return wp_get_attachment_image_url( $image_id, $size );
}
- return $image;
+ return '';
}
/**
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php
index 166fc2c212e..295d6d40fd0 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php
@@ -203,6 +203,22 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
*/
abstract protected function get_item_image( $item, $size = 'full' );
+ /**
+ * Returns the featured item image attachment ID.
+ *
+ * Note: Ideally, this method would be declared as abstract.
+ * However, it remains a concrete method returning 0 to preserve legacy
+ * compatibility with existing child classes that may not implement it.
+ * See:
+ * https://github.com/woocommerce/woocommerce/pull/66466#discussion_r3559124282
+ *
+ * @param \WP_Term|\WC_Product $item Item object.
+ * @return int
+ */
+ protected function get_item_image_id( $item ) {
+ return 0;
+ }
+
/**
* Renders the featured item attributes.
*
@@ -231,8 +247,6 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
$attributes['height'] = $attributes['height'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );
- $image_url = esc_url( $this->get_image_url( $attributes, $item ) );
-
$styles = $this->get_styles( $attributes );
$classes = $this->get_classes( $attributes );
@@ -241,9 +255,9 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
$output .= $this->render_overlay( $attributes );
if ( ! $attributes['isRepeated'] && ! $attributes['hasParallax'] ) {
- $output .= $this->render_image( $attributes, $item, $image_url );
+ $output .= $this->render_image( $attributes, $item );
} else {
- $output .= $this->render_bg_image( $attributes, $image_url );
+ $output .= $this->render_bg_image( $attributes, $item );
}
if ( isset( $aria_label ) && ! empty( $aria_label ) ) {
@@ -268,6 +282,21 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
return $output;
}
+ /**
+ * Returns the image size slug for the featured item image.
+ *
+ * @param array $attributes Block attributes. Default empty array.
+ * @return string
+ */
+ private function get_image_size( $attributes ) {
+ $image_size = 'large';
+ if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
+ $image_size = 'full';
+ }
+
+ return $image_size;
+ }
+
/**
* Returns the url the item's image
*
@@ -277,10 +306,7 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
* @return string
*/
private function get_image_url( $attributes, $item ) {
- $image_size = 'large';
- if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
- $image_size = 'full';
- }
+ $image_size = $this->get_image_size( $attributes );
if ( $attributes['mediaId'] ) {
return wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
@@ -292,13 +318,14 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
/**
* Renders the featured image as a div background.
*
- * @param array $attributes Block attributes. Default empty array.
- * @param string $image_url Item image url.
+ * @param array $attributes Block attributes. Default empty array.
+ * @param \WC_Product|\WP_Term $item Item object.
*
* @return string
*/
- private function render_bg_image( $attributes, $image_url ) {
- $styles = $this->get_bg_styles( $attributes, $image_url );
+ private function render_bg_image( $attributes, $item ) {
+ $image_url = $this->get_image_url( $attributes, $item );
+ $styles = $this->get_bg_styles( $attributes, $image_url );
$classes = [ "wc-block-{$this->block_name}__background-image" ];
@@ -350,12 +377,11 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
*
* @param array $attributes Block attributes. Default empty array.
* @param \WC_Product|\WP_Term $item Item object.
- * @param string $image_url Item image url.
*
* @return string
*/
- private function render_image( $attributes, $item, string $image_url ) {
- $style = sprintf( 'object-fit: %s;', esc_attr( $attributes['imageFit'] ) );
+ private function render_image( $attributes, $item ) {
+ $style = sprintf( 'object-fit: %s;', $attributes['imageFit'] );
$img_alt = $attributes['alt'] ?: $this->get_item_title( $item );
if ( $this->hasFocalPoint( $attributes ) ) {
@@ -366,6 +392,34 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
);
}
+ $image_size = $this->get_image_size( $attributes );
+
+ // When imageFit is not "cover", the image uses object-fit: none, so it is
+ // not scaled to the block — it renders at its natural pixel size. Before
+ // this block used responsive images, a single large URL was always loaded,
+ // which was typically bigger than the block and got clipped by overflow:
+ // hidden, making the block look filled. wp_get_attachment_image() picks a
+ // smaller srcset candidate on narrow viewports by default, leaving empty
+ // space around the image. Override sizes so the browser still selects the
+ // full image width and the previous appearance is preserved.
+ if ( 'cover' === $attributes['imageFit'] ) {
+ $image_id = empty( $attributes['mediaId'] ) ?
+ $this->get_item_image_id( $item ) :
+ absint( $attributes['mediaId'] );
+
+ if ( $image_id ) {
+ $attr = array(
+ 'alt' => $img_alt,
+ 'class' => "wc-block-{$this->block_name}__background-image",
+ 'style' => $style,
+ );
+
+ return wp_get_attachment_image( $image_id, $image_size, false, $attr );
+ }
+ }
+
+ $image_url = $this->get_item_image( $item, $image_size );
+
if ( ! empty( $image_url ) ) {
return sprintf(
'<img alt="%1$s" class="wc-block-%2$s__background-image" src="%3$s" style="%4$s" />',
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedProduct.php b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedProduct.php
index dd2a4075183..ec91d0adc20 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedProduct.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedProduct.php
@@ -42,6 +42,28 @@ class FeaturedProduct extends FeaturedItem {
return $product->get_title();
}
+ /**
+ * Returns the featured product image attachment ID.
+ *
+ * @param \WC_Product $product Product object.
+ * @return int
+ */
+ protected function get_item_image_id( $product ) {
+ $image_id = $product->get_image_id();
+ if ( $image_id ) {
+ return (int) $image_id;
+ }
+
+ if ( $product->get_parent_id() ) {
+ $parent_product = wc_get_product( $product->get_parent_id() );
+ if ( $parent_product ) {
+ return (int) $parent_product->get_image_id();
+ }
+ }
+
+ return 0;
+ }
+
/**
* Returns the featured product image URL.
*
@@ -50,17 +72,13 @@ class FeaturedProduct extends FeaturedItem {
* @return string
*/
protected function get_item_image( $product, $size = 'full' ) {
- $image = '';
- if ( $product->get_image_id() ) {
- $image = wp_get_attachment_image_url( $product->get_image_id(), $size );
- } elseif ( $product->get_parent_id() ) {
- $parent_product = wc_get_product( $product->get_parent_id() );
- if ( $parent_product ) {
- $image = wp_get_attachment_image_url( $parent_product->get_image_id(), $size );
- }
+ $image_id = $this->get_item_image_id( $product );
+
+ if ( $image_id ) {
+ return wp_get_attachment_image_url( $image_id, $size );
}
- return $image;
+ return '';
}
/**