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 );
+ }
}