Commit 25f31872565 for woocommerce
commit 25f3187256500aef460a9247b03b73a71819bfaf
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date: Tue Jul 21 11:49:56 2026 +0200
Add placeholder image source filtering (#66717)
* Fix placeholder image source filtering
* Add changelog entry for placeholder image filtering
* Preserve responsive placeholder markup
* Fix placeholder image source linting
diff --git a/plugins/woocommerce/changelog/fix-placeholder-img-src-filter b/plugins/woocommerce/changelog/fix-placeholder-img-src-filter
new file mode 100644
index 00000000000..311519e66d2
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-placeholder-img-src-filter
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Ensure the woocommerce_placeholder_img_src filter applies to attachment-based product placeholders.
diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php
index e3f39cdf942..56a7c3dab08 100644
--- a/plugins/woocommerce/includes/wc-product-functions.php
+++ b/plugins/woocommerce/includes/wc-product-functions.php
@@ -444,8 +444,15 @@ function wc_placeholder_img_src( $size = 'woocommerce_thumbnail' ) {
* @return string
*/
function wc_placeholder_img( $size = 'woocommerce_thumbnail', $attr = '' ) {
- $dimensions = wc_get_image_size( $size );
- $placeholder_image = get_option( 'woocommerce_placeholder_image', 0 );
+ $dimensions = wc_get_image_size( $size );
+ $placeholder_image = get_option( 'woocommerce_placeholder_image', 0 );
+ $use_attachment_image = wp_attachment_is_image( $placeholder_image );
+ $image = null;
+
+ if ( $use_attachment_image && has_filter( 'woocommerce_placeholder_img_src' ) ) {
+ $image = wc_placeholder_img_src( $size );
+ $use_attachment_image = wp_get_attachment_image_url( $placeholder_image, $size ) === $image;
+ }
$default_attr = array(
'class' => 'woocommerce-placeholder wp-post-image',
@@ -454,7 +461,7 @@ function wc_placeholder_img( $size = 'woocommerce_thumbnail', $attr = '' ) {
$attr = wp_parse_args( $attr, $default_attr );
- if ( wp_attachment_is_image( $placeholder_image ) ) {
+ if ( $use_attachment_image ) {
$image_html = wp_get_attachment_image(
$placeholder_image,
$size,
@@ -462,7 +469,8 @@ function wc_placeholder_img( $size = 'woocommerce_thumbnail', $attr = '' ) {
$attr
);
} else {
- $image = wc_placeholder_img_src( $size );
+ // A changed source cannot use the attachment's srcset, as the browser could select it instead.
+ $image = $image ?? wc_placeholder_img_src( $size );
$hwstring = image_hwstring( $dimensions['width'], $dimensions['height'] );
$attributes = array();
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/product/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/product/functions.php
index 4acae40fb66..ac1cbbfb5d5 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/product/functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/product/functions.php
@@ -1015,6 +1015,72 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
$this->assertStringContainsString( 'class="custom-class"', wc_placeholder_img( 'woocommerce_thumbnail', $attr ) );
}
+ /**
+ * @testdox Should use the filtered placeholder source for an attachment placeholder.
+ */
+ public function test_wc_placeholder_img_uses_filtered_src_for_attachment_placeholder() {
+ $option_name = 'woocommerce_placeholder_image';
+ $original_placeholder_image = get_option( $option_name, false );
+ $placeholder_image_id = self::factory()->attachment->create(
+ array(
+ 'file' => 'placeholder.png',
+ 'guid' => 'https://example.com/wp-content/uploads/placeholder.png',
+ 'post_mime_type' => 'image/png',
+ )
+ );
+ $placeholder_src = 'https://example.com/custom-placeholder.png';
+ $filter = function () use ( $placeholder_src ) {
+ return $placeholder_src;
+ };
+ $passthrough_filter = function ( $src ) {
+ return $src;
+ };
+
+ wp_update_attachment_metadata(
+ $placeholder_image_id,
+ array(
+ 'width' => 186,
+ 'height' => 144,
+ 'file' => 'placeholder.png',
+ )
+ );
+ update_option( $option_name, $placeholder_image_id );
+ add_filter( 'woocommerce_placeholder_img_src', $filter );
+
+ try {
+ $image_html = wc_placeholder_img();
+
+ $this->assertStringContainsString( 'src="' . $placeholder_src . '"', $image_html );
+ $this->assertStringNotContainsString( 'srcset=', $image_html );
+
+ remove_filter( 'woocommerce_placeholder_img_src', $filter );
+ add_filter( 'woocommerce_placeholder_img_src', $passthrough_filter );
+
+ $this->assertSame(
+ wp_get_attachment_image(
+ $placeholder_image_id,
+ 'woocommerce_thumbnail',
+ false,
+ array(
+ 'class' => 'woocommerce-placeholder wp-post-image',
+ 'alt' => __( 'Placeholder', 'woocommerce' ),
+ )
+ ),
+ wc_placeholder_img()
+ );
+ } finally {
+ remove_filter( 'woocommerce_placeholder_img_src', $filter );
+ remove_filter( 'woocommerce_placeholder_img_src', $passthrough_filter );
+ wp_delete_attachment( $placeholder_image_id, true );
+
+ if ( false === $original_placeholder_image ) {
+ delete_option( $option_name );
+ } else {
+ update_option( $option_name, $original_placeholder_image );
+ }
+ }
+ }
+
/**
* Test wc_get_product_types().
*