Commit 7e71bb02267 for woocommerce

commit 7e71bb0226772c7fffc6eefe3bb39a9db286f363
Author: Pavel Dohnal <pavel.dohnal@automattic.com>
Date:   Mon Mar 9 09:50:22 2026 +0100

    Make email header image clickable in classic email templates (#63559)

diff --git a/plugins/woocommerce/changelog/63559-add-WOOPLUG-6239-clickable-email-header-image b/plugins/woocommerce/changelog/63559-add-WOOPLUG-6239-clickable-email-header-image
new file mode 100644
index 00000000000..6e0e9d8862b
--- /dev/null
+++ b/plugins/woocommerce/changelog/63559-add-WOOPLUG-6239-clickable-email-header-image
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Make email header image/logo clickable with a link to the store homepage in classic email templates. Adds `woocommerce_email_header_image_url` filter for customization.
\ No newline at end of file
diff --git a/plugins/woocommerce/templates/emails/email-header.php b/plugins/woocommerce/templates/emails/email-header.php
index ea18593f21c..a66cadf2375 100644
--- a/plugins/woocommerce/templates/emails/email-header.php
+++ b/plugins/woocommerce/templates/emails/email-header.php
@@ -12,7 +12,7 @@
  *
  * @see     https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates\Emails
- * @version 10.4.0
+ * @version 10.7.0
  */

 use Automattic\WooCommerce\Utilities\FeaturesUtil;
@@ -24,6 +24,16 @@ if ( ! defined( 'ABSPATH' ) ) {
 $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improvements' );
 $store_name                 = $store_name ?? get_bloginfo( 'name', 'display' );

+/**
+ * Filter the URL used for the email header image/logo link.
+ *
+ * Return an empty string to disable the link.
+ *
+ * @since 10.7.0
+ * @param string $url The URL to link to. Defaults to the site home URL.
+ */
+$header_image_url = apply_filters( 'woocommerce_email_header_image_url', home_url() );
+
 ?>
 <!DOCTYPE html>
 <html <?php language_attributes(); ?>>
@@ -60,7 +70,16 @@ $store_name                 = $store_name ?? get_bloginfo( 'name', 'display' );
 												<td id="template_header_image">
 													<?php
 													if ( $img ) {
-														echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" /></p>';
+														$image_html = '<img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" />';
+														if ( $header_image_url ) {
+															// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $image_html is built from esc_url() and esc_attr().
+															echo '<p style="margin-top:0;"><a href="' . esc_url( $header_image_url ) . '" style="display: inline-block; text-decoration: none;" target="_blank">' . $image_html . '</a></p>';
+														} else {
+															// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+															echo '<p style="margin-top:0;">' . $image_html . '</p>';
+														}
+													} elseif ( $header_image_url ) {
+														echo '<p class="email-logo-text"><a href="' . esc_url( $header_image_url ) . '" style="color: inherit; text-decoration: none;" target="_blank">' . esc_html( $store_name ) . '</a></p>';
 													} else {
 														echo '<p class="email-logo-text">' . esc_html( $store_name ) . '</p>';
 													}
@@ -72,7 +91,14 @@ $store_name                 = $store_name ?? get_bloginfo( 'name', 'display' );
 										<div id="template_header_image">
 											<?php
 											if ( $img ) {
-												echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" /></p>';
+												$image_html = '<img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" />';
+												if ( $header_image_url ) {
+													// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $image_html is built from esc_url() and esc_attr().
+													echo '<p style="margin-top:0;"><a href="' . esc_url( $header_image_url ) . '" style="display: inline-block; text-decoration: none;" target="_blank">' . $image_html . '</a></p>';
+												} else {
+													// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+													echo '<p style="margin-top:0;">' . $image_html . '</p>';
+												}
 											}
 											?>
 										</div>
diff --git a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-header-template-test.php b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-header-template-test.php
index 036a7ef79fb..d76cac1469f 100644
--- a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-header-template-test.php
+++ b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-header-template-test.php
@@ -7,6 +7,17 @@ declare( strict_types = 1 );
  * @covers `email-header.php` template
  */
 class WC_Email_Header_Template_Test extends \WC_Unit_Test_Case {
+
+	/**
+	 * Tear down after each test.
+	 */
+	public function tearDown(): void {
+		parent::tearDown();
+		delete_option( 'woocommerce_email_header_image' );
+		update_option( 'woocommerce_feature_email_improvements_enabled', 'no' );
+		remove_all_filters( 'woocommerce_email_header_image_url' );
+	}
+
 	/**
 	 * @testdox Email header template includes blog name when store name is not set.
 	 */
@@ -41,4 +52,133 @@ class WC_Email_Header_Template_Test extends \WC_Unit_Test_Case {
 		$this->assertStringContainsString( '<title>Another store</title>', $content );
 		$this->assertStringNotContainsString( '<title>Online Store</title>', $content );
 	}
+
+	/**
+	 * @testdox Header image is wrapped in a link to home_url() when improvements are disabled.
+	 */
+	public function test_header_image_wrapped_in_link() {
+		update_option( 'woocommerce_email_header_image', 'https://example.com/logo.png' );
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( '<a ', $content );
+		$this->assertStringContainsString( 'target="_blank"', $content );
+		$this->assertStringContainsString( 'src="https://example.com/logo.png"', $content );
+	}
+
+	/**
+	 * @testdox Header image is wrapped in a link to home_url() when improvements are enabled.
+	 */
+	public function test_header_image_wrapped_in_link_with_improvements_enabled() {
+		update_option( 'woocommerce_feature_email_improvements_enabled', 'yes' );
+		update_option( 'woocommerce_email_header_image', 'https://example.com/logo.png' );
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( '<a ', $content );
+		$this->assertStringContainsString( 'target="_blank"', $content );
+		$this->assertStringContainsString( 'src="https://example.com/logo.png"', $content );
+	}
+
+	/**
+	 * @testdox Text fallback is wrapped in a link when improvements are enabled and no image is set.
+	 */
+	public function test_text_fallback_wrapped_in_link_with_improvements_enabled() {
+		update_option( 'woocommerce_feature_email_improvements_enabled', 'yes' );
+		update_option( 'blogname', 'My Store' );
+		delete_option( 'woocommerce_email_header_image' );
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( 'email-logo-text', $content );
+		$this->assertStringContainsString( '<a href="' . esc_url( home_url() ) . '"', $content );
+		$this->assertStringContainsString( 'My Store</a>', $content );
+	}
+
+	/**
+	 * @testdox No text fallback or link is rendered when improvements are disabled and no image is set.
+	 */
+	public function test_no_text_fallback_without_improvements() {
+		delete_option( 'woocommerce_email_header_image' );
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringNotContainsString( 'email-logo-text', $content );
+		$this->assertStringNotContainsString( '<a href="' . esc_url( home_url() ) . '" style="color: inherit', $content );
+	}
+
+	/**
+	 * @testdox Header image URL is filterable via woocommerce_email_header_image_url.
+	 */
+	public function test_header_image_url_is_filterable() {
+		update_option( 'woocommerce_email_header_image', 'https://example.com/logo.png' );
+		add_filter(
+			'woocommerce_email_header_image_url',
+			function () {
+				return 'https://custom-url.com/shop';
+			}
+		);
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( '<a href="https://custom-url.com/shop"', $content );
+	}
+
+	/**
+	 * @testdox No link wraps image when filter returns empty string and improvements are enabled.
+	 */
+	public function test_no_link_when_filter_returns_empty_with_improvements_enabled() {
+		update_option( 'woocommerce_feature_email_improvements_enabled', 'yes' );
+		update_option( 'woocommerce_email_header_image', 'https://example.com/logo.png' );
+		add_filter(
+			'woocommerce_email_header_image_url',
+			function () {
+				return '';
+			}
+		);
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( '<img src="https://example.com/logo.png"', $content );
+		$this->assertStringNotContainsString( '<a href=', $content );
+	}
+
+	/**
+	 * @testdox Text fallback has no link when filter returns empty string and improvements are enabled.
+	 */
+	public function test_text_fallback_no_link_when_filter_returns_empty_with_improvements_enabled() {
+		update_option( 'woocommerce_feature_email_improvements_enabled', 'yes' );
+		update_option( 'blogname', 'My Store' );
+		delete_option( 'woocommerce_email_header_image' );
+		add_filter(
+			'woocommerce_email_header_image_url',
+			function () {
+				return '';
+			}
+		);
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( 'email-logo-text', $content );
+		$this->assertStringContainsString( 'My Store', $content );
+		$this->assertStringNotContainsString( '<a href=', $content );
+	}
+
+	/**
+	 * @testdox No link is rendered when filter returns empty string.
+	 */
+	public function test_no_link_when_filter_returns_empty() {
+		update_option( 'woocommerce_email_header_image', 'https://example.com/logo.png' );
+		add_filter(
+			'woocommerce_email_header_image_url',
+			function () {
+				return '';
+			}
+		);
+
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test' ) );
+
+		$this->assertStringContainsString( '<img src="https://example.com/logo.png"', $content );
+		$this->assertStringNotContainsString( '<a href=', $content );
+	}
 }