Commit c6fb53c52f6 for woocommerce

commit c6fb53c52f63e28f6bea1f8c2fb6ecdc1c5d0d64
Author: Rob Pugh <52668747+robertbpugh@users.noreply.github.com>
Date:   Thu Oct 22 10:13:33 2026 -0300

    [Email Editor] Fix serif and monospace site fonts showing as Arial (#70096)

    * Fix serif and monospace site fonts turning into Arial in emails

    convert_to_email_safe_font() fell back to Arial for every font it
    couldn't match, so serif and monospace site fonts lost their style, and
    its substring match read a plain "serif" stack as Arial because Arial's
    stack contains "sans-serif". Match whole family names, then use the
    first generic family in the stack, as CSS would: Georgia for serif,
    Courier New for monospace, Arial for sans-serif or none.

    Also unquote the Tahoma, Verdana and Arvo stacks in theme.json, which CSS
    read as one family name with no fallback.

    * Add changelog entry for the email font fallback fix

    * Check every font in an email font stack, not only the first

    A stack such as "'Cardo', Georgia" fell back to Arial because only its
    first name was compared with the email-safe fonts. Walk the stack once,
    as CSS does, and stop at the first email-safe font or generic family.
    Map cursive to Comic Sans MS, the one casual email-safe font.

    * Say what merchants see in the email font changelog entry

    * Name the font stack test and changelog after what they now cover

    * Fold the generic font families into the existing font alias map

diff --git a/packages/php/email-editor/changelog/fix-email-safe-font-generic-family b/packages/php/email-editor/changelog/fix-email-safe-font-generic-family
new file mode 100644
index 00000000000..80e953d2d52
--- /dev/null
+++ b/packages/php/email-editor/changelog/fix-email-safe-font-generic-family
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show serif, monospace and cursive site fonts in emails as Georgia, Courier New and Comic Sans MS instead of Arial, and stop the Tahoma, Verdana and Arvo fonts falling back to the mail app's default font.
diff --git a/packages/php/email-editor/src/Engine/class-site-style-sync-controller.php b/packages/php/email-editor/src/Engine/class-site-style-sync-controller.php
index 0a48b9bc370..20a0f2ddbdf 100644
--- a/packages/php/email-editor/src/Engine/class-site-style-sync-controller.php
+++ b/packages/php/email-editor/src/Engine/class-site-style-sync-controller.php
@@ -434,44 +434,69 @@ class Site_Style_Sync_Controller {

 		// Map common web fonts to email-safe alternatives.
 		$font_map = array(
-			'helvetica' => $email_safe_fonts['arial'], // Arial fallback.
-			'times'     => $email_safe_fonts['georgia'], // Georgia fallback.
-			'courier'   => $email_safe_fonts['courier-new'], // Courier New.
-			'trebuchet' => $email_safe_fonts['trebuchet-ms'],
+			'helvetica'     => $email_safe_fonts['arial'], // Arial fallback.
+			'times'         => $email_safe_fonts['georgia'], // Georgia fallback.
+			'courier'       => $email_safe_fonts['courier-new'], // Courier New.
+			'trebuchet'     => $email_safe_fonts['trebuchet-ms'],
+			// Generic families, so serif and monospace fonts don't all turn into Arial.
+			// `fantasy` has no email-safe match, so it falls through to Arial.
+			'serif'         => $email_safe_fonts['georgia'],
+			'ui-serif'      => $email_safe_fonts['georgia'],
+			'monospace'     => $email_safe_fonts['courier-new'],
+			'ui-monospace'  => $email_safe_fonts['courier-new'],
+			'cursive'       => $email_safe_fonts['comic-sans-ms'],
+			'sans-serif'    => $email_safe_fonts['arial'],
+			'ui-sans-serif' => $email_safe_fonts['arial'],
+			'system-ui'     => $email_safe_fonts['arial'],
 		);

 		$email_safe_fonts = array_merge( $email_safe_fonts, $font_map );

 		$get_font_family = function ( $font_name ) use ( $email_safe_fonts ) {
-			$font_name_lower = strtolower( $font_name );
-
 			// First check for match in the email-safe slug.
-			if ( isset( $email_safe_fonts[ $font_name_lower ] ) ) {
-				return $email_safe_fonts[ $font_name_lower ];
+			if ( isset( $email_safe_fonts[ $font_name ] ) ) {
+				return $email_safe_fonts[ $font_name ];
 			}

-			// If no match in the slug, check for match in the font family name.
-			foreach ( $email_safe_fonts as $safe_font_slug => $safe_font ) {
-				if ( stripos( $safe_font, $font_name_lower ) !== false ) {
+			// If no match in the slug, look for the same family name in a stack.
+			// Whole names only: a substring match finds "serif" inside Arial's "sans-serif".
+			foreach ( $email_safe_fonts as $safe_font ) {
+				if ( in_array( $font_name, $this->get_font_names( $safe_font ), true ) ) {
 					return $safe_font;
 				}
 			}
 			return null;
 		};

-		// Check if it's already an email-safe font.
-		$font_family_array = explode( ',', $font_family );
-		$first_font        = trim( $font_family_array[0] );
-		$first_font        = trim( $first_font, "\"'" );
-		$safe_font_family  = $get_font_family( $first_font );
-		if ( $safe_font_family ) {
-			return $safe_font_family;
+		// Walk the stack as CSS would: the first email-safe font or generic family wins.
+		foreach ( $this->get_font_names( $font_family ) as $font_name ) {
+			$safe_font_family = $get_font_family( $font_name );
+			if ( $safe_font_family ) {
+				return $safe_font_family;
+			}
 		}

 		// Default to arial font if no match found.
 		return $email_safe_fonts['arial'];
 	}

+	/**
+	 * Split a CSS font-family stack into lowercase family names without quotes.
+	 *
+	 * @param string $font_family CSS font-family value.
+	 * @return string[] Family names in stack order.
+	 */
+	private function get_font_names( string $font_family ): array {
+		$names = array();
+		foreach ( explode( ',', $font_family ) as $name ) {
+			$name = strtolower( trim( trim( $name ), "\"'" ) );
+			if ( '' !== $name ) {
+				$names[] = $name;
+			}
+		}
+		return $names;
+	}
+
 	/**
 	 * Convert size value to px format with optional fallback
 	 *
diff --git a/packages/php/email-editor/src/Engine/theme.json b/packages/php/email-editor/src/Engine/theme.json
index e9c6e3f12cf..bd4e1135ee6 100644
--- a/packages/php/email-editor/src/Engine/theme.json
+++ b/packages/php/email-editor/src/Engine/theme.json
@@ -98,7 +98,7 @@
         {
           "name": "Tahoma",
           "slug": "tahoma",
-          "fontFamily": "'Tahoma, Verdana, Segoe, sans-serif'"
+          "fontFamily": "Tahoma, Verdana, Segoe, sans-serif"
         },
         {
           "name": "Times New Roman",
@@ -113,12 +113,12 @@
         {
           "name": "Verdana",
           "slug": "verdana",
-          "fontFamily": "'Verdana, Geneva, sans-serif'"
+          "fontFamily": "Verdana, Geneva, sans-serif"
         },
         {
           "name": "Arvo",
           "slug": "arvo",
-          "fontFamily": "'arvo, courier, georgia, serif'"
+          "fontFamily": "arvo, courier, georgia, serif"
         },
         {
           "name": "Lato",
diff --git a/packages/php/email-editor/tests/integration/Engine/Site_Style_Sync_Controller_Test.php b/packages/php/email-editor/tests/integration/Engine/Site_Style_Sync_Controller_Test.php
index 83950ff9f21..1ee2c6fc1e3 100644
--- a/packages/php/email-editor/tests/integration/Engine/Site_Style_Sync_Controller_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Site_Style_Sync_Controller_Test.php
@@ -1255,4 +1255,69 @@ class Site_Style_Sync_Controller_Test extends \Email_Editor_Integration_Test_Cas
 		$this->assertArrayHasKey( 'typography', $synced_data['styles'] );
 		$this->assertEquals( '24px', $synced_data['styles']['typography']['fontSize'] );
 	}
+
+	/**
+	 * Data provider for fonts that only a later name in their stack can place.
+	 *
+	 * @return array Test cases with input font family and expected output.
+	 */
+	public function font_stack_fallback_data_provider(): array {
+		$arial       = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
+		$georgia     = "Georgia, Times, 'Times New Roman', serif";
+		$courier_new = "'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace";
+		$comic_sans  = "'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif";
+		return array(
+			'unknown font before a safe font' => array( "'Cardo', Georgia", $georgia ),
+			'unknown cursive font'            => array( '"Dancing Script", cursive', $comic_sans ),
+			'unknown fantasy font'            => array( 'Papyrus, fantasy', $arial ),
+			'unknown serif font'              => array( '"Gentium Plus", serif', $georgia ),
+			'unknown ui-serif font'           => array( "'Newsreader', ui-serif, serif", $georgia ),
+			'unknown monospace font'          => array( "'IBM Plex Mono', monospace", $courier_new ),
+			'unknown ui-monospace font'       => array( 'Fira Code, ui-monospace', $courier_new ),
+			'unknown sans-serif font'         => array( "'Figtree', sans-serif", $arial ),
+			'unknown font with no generic'    => array( 'CustomWebFont', $arial ),
+			'stack that starts with serif'    => array( 'serif', $georgia ),
+			'stack that starts with mono'     => array( 'monospace', $courier_new ),
+			'sans-serif before serif'         => array( 'CustomFont, sans-serif, serif', $arial ),
+			'system-ui before monospace'      => array( 'CustomFont, system-ui, monospace', $arial ),
+			'serif before sans-serif'         => array( 'CustomFont, serif, sans-serif', $georgia ),
+		);
+	}
+
+	/**
+	 * Test that an unknown font falls back to the next usable name in its stack.
+	 *
+	 * @testdox Should use the first email-safe font or generic family in the stack.
+	 * @dataProvider font_stack_fallback_data_provider
+	 *
+	 * @param string $input_font    The input font family stack.
+	 * @param string $expected_font The expected email-safe font family output.
+	 */
+	public function test_convert_to_email_safe_font_walks_the_stack( string $input_font, string $expected_font ): void {
+		$reflection = new \ReflectionClass( $this->controller );
+		$method     = $reflection->getMethod( 'convert_to_email_safe_font' );
+		$method->setAccessible( true );
+
+		$this->assertEquals( $expected_font, $method->invoke( $this->controller, $input_font ) );
+	}
+
+	/**
+	 * Test that every email-safe font stack is a list of families, not one quoted name.
+	 *
+	 * @testdox Should not wrap a whole email-safe font stack in quotes.
+	 */
+	public function test_email_safe_font_stacks_are_not_quoted_as_one_family(): void {
+		foreach ( $this->controller->get_email_safe_fonts() as $slug => $font_family ) {
+			foreach ( explode( ',', $font_family ) as $name ) {
+				$name = trim( $name );
+				// Each name is bare or wrapped in its own quotes; a stray quote means
+				// the quotes span several names, which CSS reads as one family.
+				$this->assertMatchesRegularExpression(
+					'/^([^\'"]+|\'[^\']+\'|"[^"]+")$/',
+					$name,
+					"The $slug stack quotes a name that isn't a single family: $name"
+				);
+			}
+		}
+	}
 }