Commit b302da82e1 for woocommerce

commit b302da82e12e40b56dff74a569d992517ba7617c
Author: Tony Arcangelini <33258733+arcangelini@users.noreply.github.com>
Date:   Fri Nov 28 16:49:32 2025 +0100

    Email Editor: fix slow image rendering (#62118)

    Co-authored-by: github-actions <github-actions@github.com>

diff --git a/packages/php/email-editor/changelog/62118-fix-email-editor-image-rendering-lag b/packages/php/email-editor/changelog/62118-fix-email-editor-image-rendering-lag
new file mode 100644
index 0000000000..b95a47303d
--- /dev/null
+++ b/packages/php/email-editor/changelog/62118-fix-email-editor-image-rendering-lag
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Email Editor: retrieve image width in a more efficient manner.
\ No newline at end of file
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
index 11111c1ddb..af09f8077b 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
@@ -98,20 +98,62 @@ class Image extends Abstract_Block_Renderer {
 		// Can't determine any width let's go with 100%.
 		if ( ! isset( $parsed_block['email_attrs']['width'] ) ) {
 			$parsed_block['attrs']['width'] = '100%';
+			return $parsed_block;
 		}
 		$max_width = Styles_Helper::parse_value( $parsed_block['email_attrs']['width'] );

+		$image_size = null;
+
 		if ( $image_url ) {
-			$upload_dir = wp_upload_dir();
-			$image_path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $image_url );
-			$image_size = wp_getimagesize( $image_path );
-
-			$image_size = $image_size ? $image_size[0] : $max_width;
-			$width      = min( $image_size, $max_width );
-		} else {
-			$width = $max_width;
+			// Try to extract width from URL query parameter if it exists.
+			$parsed_url = wp_parse_url( $image_url );
+			if ( isset( $parsed_url['query'] ) ) {
+				parse_str( $parsed_url['query'], $query_params );
+				if ( isset( $query_params['w'] ) && is_numeric( $query_params['w'] ) && $query_params['w'] > 0 ) {
+					$image_size = (int) $query_params['w'];
+				}
+			}
+
+			// Next we check the attachment data if it has an ID.
+			if ( ! isset( $image_size ) ) {
+				$attachment_id = $parsed_block['attrs']['id'] ?? null;
+				if ( $attachment_id ) {
+					$size_slug = $parsed_block['attrs']['sizeSlug'] ?? 'large';
+
+					// Check the metadata first.
+					$metadata = wp_get_attachment_metadata( $attachment_id );
+					if ( $metadata ) {
+						if ( isset( $metadata['sizes'][ $size_slug ]['width'] ) ) {
+							$image_size = (int) $metadata['sizes'][ $size_slug ]['width'];
+						} elseif ( 'full' === $size_slug && isset( $metadata['width'] ) ) {
+							$image_size = (int) $metadata['width'];
+						}
+					}
+
+					// Try to get dimensions from wp_get_attachment_image_src if metadata didn't have it.
+					if ( ! isset( $image_size ) ) {
+						$image_src = wp_get_attachment_image_src( $attachment_id, $size_slug );
+						if ( $image_src && isset( $image_src[1] ) ) {
+							$image_size = (int) $image_src[1];
+						}
+					}
+				}
+			}
+
+			// Fallback to wp_getimagesize if we still don't have a size.
+			if ( ! isset( $image_size ) ) {
+				$upload_dir = wp_upload_dir();
+				$image_path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $image_url );
+				$result     = wp_getimagesize( $image_path );
+				if ( $result ) {
+					$image_size = (int) $result[0];
+				}
+			}
 		}

+		// Use the found image size or fall back to max_width.
+		$width = isset( $image_size ) ? min( $image_size, $max_width ) : $max_width;
+
 		$parsed_block['attrs']['width'] = "{$width}px";
 		return $parsed_block;
 	}
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Image_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Image_Test.php
index 3f63d2a550..9682a3e9bb 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Image_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Image_Test.php
@@ -268,4 +268,108 @@ class Image_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringNotContainsString( 'javascript:', $rendered );
 		$this->assertStringNotContainsString( 'alert("xss")', $rendered );
 	}
+
+	/**
+	 * Test it extracts width from URL query parameter
+	 */
+	public function testItExtractsWidthFromUrlQueryParameter(): void {
+		$image_content = '
+			<figure class="wp-block-image alignleft size-full is-style-default">
+				<img src="https://test.com/wp-content/uploads/2023/05/image.jpg?w=500" alt="" style="" srcset="https://test.com/wp-content/uploads/2023/05/image.jpg 1000w"/>
+			</figure>
+		';
+		$parsed_image  = $this->parsed_image;
+		unset( $parsed_image['attrs']['width'] ); // Remove width to test fallback logic.
+		$parsed_image['email_attrs']['width'] = '600px'; // Set max width.
+		$parsed_image['innerHTML']            = $image_content;
+
+		$rendered = $this->image_renderer->render( $image_content, $parsed_image, $this->rendering_context );
+
+		// Should use width from URL parameter (500px), which is less than max (600px).
+		$this->assertStringContainsString( 'width="500"', $rendered );
+		$this->assertStringContainsString( 'width:500px;', $rendered );
+	}
+
+	/**
+	 * Test it respects max width when URL parameter is larger
+	 */
+	public function testItRespectsMaxWidthWhenUrlParameterIsLarger(): void {
+		$image_content = '
+			<figure class="wp-block-image alignleft size-full is-style-default">
+				<img src="https://test.com/wp-content/uploads/2023/05/image.jpg?w=800" alt="" style="" srcset="https://test.com/wp-content/uploads/2023/05/image.jpg 1000w"/>
+			</figure>
+		';
+		$parsed_image  = $this->parsed_image;
+		unset( $parsed_image['attrs']['width'] ); // Remove width to test fallback logic.
+		$parsed_image['email_attrs']['width'] = '600px'; // Set max width.
+		$parsed_image['innerHTML']            = $image_content;
+
+		$rendered = $this->image_renderer->render( $image_content, $parsed_image, $this->rendering_context );
+
+		// Should use max width (600px) when URL parameter (800px) is larger.
+		$this->assertStringContainsString( 'width="600"', $rendered );
+		$this->assertStringContainsString( 'width:600px;', $rendered );
+	}
+
+	/**
+	 * Test it falls back to 100% when no width information is available
+	 */
+	public function testItFallsBackTo100PercentWhenNoWidthInfoAvailable(): void {
+		$image_content = '
+			<figure class="wp-block-image alignleft size-full is-style-default">
+				<img src="https://test.com/wp-content/uploads/2023/05/image.jpg" alt="" style="" srcset=""/>
+			</figure>
+		';
+		$parsed_image  = $this->parsed_image;
+		unset( $parsed_image['attrs']['width'] ); // Remove width to test fallback logic.
+		unset( $parsed_image['email_attrs']['width'] ); // Remove email_attrs width to trigger 100% fallback.
+		$parsed_image['innerHTML'] = $image_content;
+
+		$rendered = $this->image_renderer->render( $image_content, $parsed_image, $this->rendering_context );
+
+		// Should fall back to 100% width when no width information is available.
+		$this->assertStringContainsString( 'width:100%;', $rendered );
+	}
+
+	/**
+	 * Test it ignores invalid URL width parameters
+	 */
+	public function testItIgnoresInvalidUrlWidthParameters(): void {
+		$image_content = '
+			<figure class="wp-block-image alignleft size-full is-style-default">
+				<img src="https://test.com/wp-content/uploads/2023/05/image.jpg?w=invalid" alt="" style="" srcset=""/>
+			</figure>
+		';
+		$parsed_image  = $this->parsed_image;
+		unset( $parsed_image['attrs']['width'] ); // Remove width to test fallback logic.
+		$parsed_image['email_attrs']['width'] = '600px'; // Set max width.
+		$parsed_image['innerHTML']            = $image_content;
+
+		$rendered = $this->image_renderer->render( $image_content, $parsed_image, $this->rendering_context );
+
+		// Should fall back to max width when URL parameter is invalid.
+		$this->assertStringContainsString( 'width="600"', $rendered );
+		$this->assertStringContainsString( 'width:600px;', $rendered );
+	}
+
+	/**
+	 * Test it ignores negative or zero width parameters
+	 */
+	public function testItIgnoresNegativeOrZeroWidthParameters(): void {
+		$image_content = '
+			<figure class="wp-block-image alignleft size-full is-style-default">
+				<img src="https://test.com/wp-content/uploads/2023/05/image.jpg?w=0" alt="" style="" srcset=""/>
+			</figure>
+		';
+		$parsed_image  = $this->parsed_image;
+		unset( $parsed_image['attrs']['width'] ); // Remove width to test fallback logic.
+		$parsed_image['email_attrs']['width'] = '600px'; // Set max width.
+		$parsed_image['innerHTML']            = $image_content;
+
+		$rendered = $this->image_renderer->render( $image_content, $parsed_image, $this->rendering_context );
+
+		// Should fall back to max width when URL parameter is 0 or negative.
+		$this->assertStringContainsString( 'width="600"', $rendered );
+		$this->assertStringContainsString( 'width:600px;', $rendered );
+	}
 }