Commit 4e135705da for woocommerce

commit 4e135705da3e2802be67eb918fca0d27671dc5a4
Author: Jaclyn Chen <watertranquil@gmail.com>
Date:   Fri May 23 14:53:30 2025 +0800

    [Woo POS] Display POS store name in email header HTML (#58124)

    * Extract blog name usage from `email-header` template to a variable default to blog name and set to POS store name in POS completed order email.

    * Apply the same changes to POS refunded email to display POS store name in the email header.

    * Add changelog.

    * WC_Email_Header_Template_Test: explicitly set `email_heading` variable for the template in test cases to fix unit test errors.

    * Replace `woocommerce_email_header` shared template usage with custom action for POS emails.

    * Remove comment on a removed parameter.

diff --git a/plugins/woocommerce/changelog/display-pos-store-name-in-email-header-html b/plugins/woocommerce/changelog/display-pos-store-name-in-email-header-html
new file mode 100644
index 0000000000..d1b378cb3b
--- /dev/null
+++ b/plugins/woocommerce/changelog/display-pos-store-name-in-email-header-html
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Display Point of Sale (POS) store name in the header HTML for POS emails by extracting blog name usage in the email header template to a variable.
diff --git a/plugins/woocommerce/includes/class-wc-emails.php b/plugins/woocommerce/includes/class-wc-emails.php
index 122c5c3d94..b090930be7 100644
--- a/plugins/woocommerce/includes/class-wc-emails.php
+++ b/plugins/woocommerce/includes/class-wc-emails.php
@@ -327,7 +327,13 @@ class WC_Emails {
 	 * @param mixed $email_heading Heading for the email.
 	 */
 	public function email_header( $email_heading ) {
-		wc_get_template( 'emails/email-header.php', array( 'email_heading' => $email_heading ) );
+		wc_get_template(
+			'emails/email-header.php',
+			array(
+				'email_heading' => $email_heading,
+				'store_name'    => get_bloginfo( 'name', 'display' ),
+			)
+		);
 	}

 	/**
diff --git a/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-completed-order.php b/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-completed-order.php
index b30eb577ab..a47a2e3f31 100644
--- a/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-completed-order.php
+++ b/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-completed-order.php
@@ -115,6 +115,7 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Completed_Order', false ) ) :
 		 */
 		public function get_content_html() {
 			$this->add_pos_customizations();
+			add_action( 'woocommerce_pos_email_header', array( $this, 'email_header' ) );
 			$content = wc_get_template_html(
 				$this->template_html,
 				array(
@@ -132,6 +133,7 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Completed_Order', false ) ) :
 				)
 			);
 			$this->remove_pos_customizations();
+			remove_action( 'woocommerce_pos_email_header', array( $this, 'email_header' ) );
 			return $content;
 		}

@@ -256,6 +258,23 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Completed_Order', false ) ) :
 			remove_filter( 'woocommerce_get_order_item_totals', array( $this, 'order_item_totals' ), 10 );
 		}

+		/**
+		 * Get the email header.
+		 *
+		 * @param mixed $email_heading Heading for the email.
+		 *
+		 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
+		 */
+		public function email_header( $email_heading ) {
+			wc_get_template(
+				'emails/email-header.php',
+				array(
+					'email_heading' => $email_heading,
+					'store_name'    => $this->get_pos_store_name(),
+				)
+			);
+		}
+
 		/**
 		 * Add unit price to order item meta start position.
 		 *
diff --git a/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-refunded-order.php b/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-refunded-order.php
index d6b7153e0f..e8f26d8b80 100644
--- a/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-refunded-order.php
+++ b/plugins/woocommerce/includes/emails/class-wc-email-customer-pos-refunded-order.php
@@ -225,6 +225,7 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Refunded_Order', false ) ) :
 		 */
 		public function get_content_html() {
 			$this->add_pos_customizations();
+			add_action( 'woocommerce_pos_email_header', array( $this, 'email_header' ) );
 			$content = wc_get_template_html(
 				$this->template_html,
 				array(
@@ -245,6 +246,7 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Refunded_Order', false ) ) :
 				)
 			);
 			$this->remove_pos_customizations();
+			remove_action( 'woocommerce_pos_email_header', array( $this, 'email_header' ) );
 			return $content;
 		}

@@ -387,6 +389,23 @@ if ( ! class_exists( 'WC_Email_Customer_POS_Refunded_Order', false ) ) :
 			remove_filter( 'woocommerce_get_order_item_totals', array( $this, 'order_item_totals' ), 10 );
 		}

+		/**
+		 * Get the email header.
+		 *
+		 * @param mixed $email_heading Heading for the email.
+		 *
+		 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
+		 */
+		public function email_header( $email_heading ) {
+			wc_get_template(
+				'emails/email-header.php',
+				array(
+					'email_heading' => $email_heading,
+					'store_name'    => $this->get_pos_store_name(),
+				)
+			);
+		}
+
 		/**
 		 * Add unit price to order item meta start position.
 		 *
diff --git a/plugins/woocommerce/templates/emails/customer-pos-completed-order.php b/plugins/woocommerce/templates/emails/customer-pos-completed-order.php
index ba3f417d6c..ad4a3e8fd7 100644
--- a/plugins/woocommerce/templates/emails/customer-pos-completed-order.php
+++ b/plugins/woocommerce/templates/emails/customer-pos-completed-order.php
@@ -26,10 +26,10 @@ $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improveme
 /**
  * Hook for the woocommerce_email_header.
  *
- * @hooked WC_Emails::email_header() Output the email header
- * @since 3.7.0
+ * @hooked WC_Email_Customer_POS_*::email_header() Output the email header
+ * @since 10.0.0
  */
-do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
+do_action( 'woocommerce_pos_email_header', $email_heading, $email ); ?>

 <div class="email-introduction">
 <p>
diff --git a/plugins/woocommerce/templates/emails/customer-pos-refunded-order.php b/plugins/woocommerce/templates/emails/customer-pos-refunded-order.php
index 4dc4e25090..b7d914c090 100644
--- a/plugins/woocommerce/templates/emails/customer-pos-refunded-order.php
+++ b/plugins/woocommerce/templates/emails/customer-pos-refunded-order.php
@@ -24,10 +24,10 @@ $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improveme
 /**
  * Hook for the woocommerce_email_header.
  *
- * @hooked WC_Emails::email_header() Output the email header
- * @since 3.7.0
+ * @hooked WC_Email_Customer_POS_*::email_header() Output the email header
+ * @since 10.0.0
  */
-do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
+do_action( 'woocommerce_pos_email_header', $email_heading, $email ); ?>

 <div class="email-introduction">
 <p>
diff --git a/plugins/woocommerce/templates/emails/email-header.php b/plugins/woocommerce/templates/emails/email-header.php
index 62a386cf78..5cf59a53ac 100644
--- a/plugins/woocommerce/templates/emails/email-header.php
+++ b/plugins/woocommerce/templates/emails/email-header.php
@@ -12,16 +12,17 @@
  *
  * @see     https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates\Emails
- * @version 9.8.0
+ * @version 10.0.0
  */

 use Automattic\WooCommerce\Utilities\FeaturesUtil;

 if ( ! defined( 'ABSPATH' ) ) {
-	exit; // Exit if accessed directly
+	exit; // Exit if accessed directly.
 }

 $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improvements' );
+$store_name                 = $store_name ?? get_bloginfo( 'name', 'display' );

 ?>
 <!DOCTYPE html>
@@ -29,7 +30,7 @@ $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improveme
 	<head>
 		<meta http-equiv="Content-Type" content="text/html; charset=<?php bloginfo( 'charset' ); ?>" />
 		<meta content="width=device-width, initial-scale=1.0" name="viewport">
-		<title><?php echo get_bloginfo( 'name', 'display' ); ?></title>
+		<title><?php echo esc_html( $store_name ); ?></title>
 	</head>
 	<body <?php echo is_rtl() ? 'rightmargin' : 'leftmargin'; ?>="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
 		<table width="100%" id="outer_wrapper">
@@ -59,9 +60,9 @@ $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improveme
 												<td id="template_header_image">
 													<?php
 													if ( $img ) {
-														echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( get_bloginfo( 'name', 'display' ) ) . '" /></p>';
+														echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" /></p>';
 													} else {
-														echo '<p class="email-logo-text">' . esc_html( get_bloginfo( 'name', 'display' ) ) . '</p>';
+														echo '<p class="email-logo-text">' . esc_html( $store_name ) . '</p>';
 													}
 													?>
 												</td>
@@ -71,7 +72,7 @@ $email_improvements_enabled = FeaturesUtil::feature_is_enabled( 'email_improveme
 										<div id="template_header_image">
 											<?php
 											if ( $img ) {
-												echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( get_bloginfo( 'name', 'display' ) ) . '" /></p>';
+												echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . esc_attr( $store_name ) . '" /></p>';
 											}
 											?>
 										</div>
diff --git a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-completed-order-test.php b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-completed-order-test.php
index 5626576ca3..08d5b2026b 100644
--- a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-completed-order-test.php
+++ b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-completed-order-test.php
@@ -194,6 +194,57 @@ class WC_Email_Customer_POS_Completed_Order_Test extends \WC_Unit_Test_Case {
 		$this->assertStringNotContainsString( __( 'Time of payment:', 'woocommerce' ), $regular_plain_text );
 	}

+	/**
+	 * @testdox POS email includes POS store name in email header HTML while regular email includes blog name.
+	 */
+	public function test_pos_email_includes_pos_store_name_in_email_header_html_while_regular_email_includes_blog_name() {
+		// Initialize WC_Emails to set up actions and filters for email header in regular emails.
+		$emails = new WC_Emails();
+
+		// Given POS store name and blog name.
+		update_option( 'woocommerce_pos_store_name', 'Physical Store' );
+		update_option( 'blogname', 'Online Store' );
+
+		// When getting content from both email classes.
+		$pos_email     = new WC_Email_Customer_POS_Completed_Order();
+		$regular_email = new WC_Email_Customer_Completed_Order();
+
+		// Set the order on both email classes.
+		$pos_email->object     = OrderHelper::create_order();
+		$regular_email->object = OrderHelper::create_order();
+
+		$pos_content     = $pos_email->get_content_html();
+		$regular_content = $regular_email->get_content_html();
+
+		// Then POS email should include POS store name.
+		$this->assertStringContainsString( '<title>Physical Store</title>', $pos_content );
+		$this->assertStringNotContainsString( '<title>Online Store</title>', $pos_content );
+
+		// And regular email should include blog name.
+		$this->assertStringNotContainsString( '<title>Physical Store</title>', $regular_content );
+		$this->assertStringContainsString( '<title>Online Store</title>', $regular_content );
+	}
+
+	/**
+	 * @testdox POS email includes blog name in email header HTML when POS store name is not set.
+	 */
+	public function test_pos_email_header_html_includes_blog_name_when_pos_store_name_is_not_set() {
+		// Given POS store name is not set.
+		delete_option( 'woocommerce_pos_store_name' );
+		update_option( 'blogname', 'Online Store' );
+
+		// When getting content from POS email.
+		$email = new WC_Email_Customer_POS_Completed_Order();
+
+		// Set the order on the email.
+		$email->object = OrderHelper::create_order();
+
+		$content = $email->get_content_html();
+
+		// Then POS email should include blog name.
+		$this->assertStringContainsString( '<title>Online Store</title>', $content );
+	}
+
 	/**
 	 * @testdox get_default_subject includes blog name when POS store name is not set.
 	 */
diff --git a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-refunded-order-test.php b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-refunded-order-test.php
index 016f14c6de..1b4166f984 100644
--- a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-refunded-order-test.php
+++ b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-pos-refunded-order-test.php
@@ -170,6 +170,59 @@ class WC_Email_Customer_POS_Refunded_Order_Test extends \WC_Unit_Test_Case {
 		$this->assertStringNotContainsString( __( 'Time of payment:', 'woocommerce' ), $regular_plain_text );
 	}

+	/**
+	 * @testdox POS email includes POS store name in email header HTML while regular email includes blog name.
+	 */
+	public function test_pos_email_includes_pos_store_name_in_email_header_html_while_regular_email_includes_blog_name() {
+		// Initialize WC_Emails to set up actions and filters for email header in regular emails.
+		$emails = new WC_Emails();
+
+		// Given POS store name and blog name.
+		update_option( 'woocommerce_pos_store_name', 'Physical Store' );
+		update_option( 'blogname', 'Online Store' );
+
+		// When getting content from both email classes.
+		$pos_email     = new WC_Email_Customer_POS_Refunded_Order();
+		$regular_email = new WC_Email_Customer_Refunded_Order();
+
+		// Set the order on both email classes.
+		$pos_email->object     = OrderHelper::create_order();
+		$regular_email->object = OrderHelper::create_order();
+
+		$pos_content     = $pos_email->get_content_html();
+		$regular_content = $regular_email->get_content_html();
+
+		// Then POS email should include POS store name.
+		$this->assertStringContainsString( '<title>Physical Store</title>', $pos_content );
+		$this->assertStringNotContainsString( '<title>Online Store</title>', $pos_content );
+
+		// And regular email should include blog name.
+		$this->assertStringNotContainsString( '<title>Physical Store</title>', $regular_content );
+		$this->assertStringContainsString( '<title>Online Store</title>', $regular_content );
+	}
+
+	/**
+	 * @testdox POS email includes blog name in email header HTML when POS store name is not set.
+	 */
+	public function test_pos_email_header_html_includes_blog_name_when_pos_store_name_is_not_set() {
+		// Given POS store name is not set.
+		delete_option( 'woocommerce_pos_store_name' );
+		update_option( 'blogname', 'Online Store' );
+
+		$emails = new WC_Emails();
+
+		// When getting content from POS email.
+		$email = new WC_Email_Customer_POS_Refunded_Order( $emails );
+
+		// Set the order on the email.
+		$email->object = OrderHelper::create_order();
+
+		$content = $email->get_content_html();
+
+		// Then POS email should include blog name.
+		$this->assertStringContainsString( '<title>Online Store</title>', $content );
+	}
+
 	/**
 	 * @testdox get_default_subject includes blog name when POS store name is not set.
 	 */
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
new file mode 100644
index 0000000000..036a7ef79f
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-header-template-test.php
@@ -0,0 +1,44 @@
+<?php
+declare( strict_types = 1 );
+
+/**
+ * `email-header.php` test.
+ *
+ * @covers `email-header.php` template
+ */
+class WC_Email_Header_Template_Test extends \WC_Unit_Test_Case {
+	/**
+	 * @testdox Email header template includes blog name when store name is not set.
+	 */
+	public function test_html_includes_blog_name_when_store_name_is_not_set() {
+		// Given blog name.
+		update_option( 'blogname', 'Online Store' );
+
+		// When getting content from email header.
+		$content = wc_get_template_html( 'emails/email-header.php', array( 'email_heading' => 'Test email heading' ) );
+
+		// Then email header should include blog name.
+		$this->assertStringContainsString( '<title>Online Store</title>', $content );
+	}
+
+	/**
+	 * @testdox Email header template includes store name, not blog name, when store name is set.
+	 */
+	public function test_html_includes_store_name_when_store_name_is_set() {
+		// Given blog name.
+		update_option( 'blogname', 'Online Store' );
+
+		// When getting content from email header.
+		$content = wc_get_template_html(
+			'emails/email-header.php',
+			array(
+				'email_heading' => 'Test email heading',
+				'store_name'    => 'Another store',
+			)
+		);
+
+		// Then email header should include blog name.
+		$this->assertStringContainsString( '<title>Another store</title>', $content );
+		$this->assertStringNotContainsString( '<title>Online Store</title>', $content );
+	}
+}