Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks

Gallery::apply_aspect_ratio_crop │ private │ WC 1.0

Apply an aspect-ratio crop to a sanitized <img> tag.

Email clients can't crop client-side reliably (object-fit/aspect-ratio are unsupported in Gmail), so the only way to truly honor the crop everywhere is to serve an already-cropped image file. This method exposes the woocommerce_email_editor_gallery_cropped_image_url filter so integrations (e.g. Jetpack/Photon on WordPress.com) can rewrite the image URL to a server-cropped version. When that happens, the image is given concrete width/height dimensions so it renders correctly even in clients without CSS crop support.

When no integration crops the URL (e.g. self-hosted sites with no image CDN), the method falls back to inline aspect-ratio + object-fit: cover CSS. Clients that support it (Apple Mail, iOS Mail, modern webmail) render the crop; the rest fall back to the natural aspect ratio, matching the previous behavior with no regression.

Method of the class: Gallery{}

Returns

string. Image HTML with the crop applied, or the input unchanged when no valid ratio.

Usage

// private - for code of main (parent) class only
$result = $this->apply_aspect_ratio_crop( $img_html, ?string $aspect_ratio, $cell_width, $image_attrs ): string;
$img_html(string) (required)
Sanitized <img> HTML.
?string $aspect_ratio(required)
.
$cell_width(int)
Estimated display width of the gallery cell in px.
$image_attrs(array)
Parsed attributes of the core/image block (id, sizeSlug, ...).
Default: array()

Gallery::apply_aspect_ratio_crop() code WC 11.1.2

private function apply_aspect_ratio_crop( string $img_html, ?string $aspect_ratio, int $cell_width = 0, array $image_attrs = array() ): string {
	if ( null === $aspect_ratio || '' === $img_html ) {
		return $img_html;
	}

	// Only accept simple numeric ratios such as "1", "1.5" or "4/3" to avoid injecting anything unexpected.
	$aspect_ratio = trim( $aspect_ratio );
	$ratio_value  = $this->parse_aspect_ratio( $aspect_ratio );
	if ( null === $ratio_value ) {
		return $img_html;
	}

	$html = new \WP_HTML_Tag_Processor( $img_html );
	if ( ! $html->next_tag( array( 'tag_name' => 'img' ) ) ) {
		return $img_html;
	}

	// Derive the target display dimensions from the cell width and the requested ratio. Clamp the
	// height to at least 1px so a very wide ratio can't round down to a 0-height (unusable) crop.
	$width  = $cell_width > 0 ? $cell_width : 0;
	$height = $width > 0 ? max( 1, (int) round( $width / $ratio_value ) ) : 0;

	// get_attribute() can return a string, null (absent), or bool true (valueless attribute);
	// coerce anything that isn't a real URL string to an empty string.
	$src_attribute = $html->get_attribute( 'src' );
	$image_url     = is_string( $src_attribute ) ? $src_attribute : '';

	/**
	 * Filters the URL of an image inside an email gallery so integrations can serve a
	 * server-side-cropped file that honors the block's aspect ratio.
	 *
	 * Email can't crop client-side reliably, so returning an already-cropped URL (e.g. an image
	 * CDN URL with resize/crop parameters) is the only way to honor the crop in every client.
	 * Return the URL unchanged to leave the image uncropped (the renderer then falls back to
	 * best-effort CSS cropping).
	 *
	 * @param string $image_url    The original image URL.
	 * @param string $aspect_ratio The requested aspect ratio (e.g. "1", "4/3").
	 * @param int    $width        Target display width of the image in px (0 if unknown).
	 * @param int    $height       Target display height derived from the aspect ratio in px (0 if unknown).
	 * @param array  $image_attrs  Parsed attributes of the core/image block (id, sizeSlug, ...).
	 */
	$filtered_url = apply_filters( 'woocommerce_email_editor_gallery_cropped_image_url', $image_url, $aspect_ratio, $width, $height, $image_attrs );

	// Extensions can return anything (arrays, WP_Error, objects). A crop happened only when an
	// integration returned a *different*, non-empty string. Compare the raw filter result against
	// the original src here, BEFORE escaping: get_attribute() returns the entity-decoded src (raw
	// "&"), while esc_url() re-encodes "&" to "&#038;", so comparing an escaped URL against the
	// decoded original would misclassify any image whose src has a multi-param query string (e.g.
	// "?w=600&h=600", common with image CDNs) as server-cropped.
	$is_server_cropped = is_string( $filtered_url ) && '' !== $image_url && '' !== $filtered_url && $filtered_url !== $image_url;

	// Escape for output only after the decision. If esc_url() reduces a hostile/invalid crop URL
	// to an empty string, fall through to the CSS branch rather than emit an empty src.
	$cropped_url = $is_server_cropped ? esc_url( (string) $filtered_url ) : '';

	// These crop styles are appended after Html_Processing_Helper::sanitize_image_html() has run
	// (its style allowlist would otherwise strip object-fit), so they intentionally bypass that
	// sanitizer. Only the regex-validated $aspect_ratio may be interpolated here — every other
	// token is a literal. Do not add dynamic values to $crop_styles without validating them.
	if ( '' !== $cropped_url ) {
		// The file is already cropped to the requested ratio, so we can give it concrete
		// dimensions. This renders the crop correctly even in clients without CSS crop support.
		$html->set_attribute( 'src', $cropped_url );
		if ( $width > 0 && $height > 0 ) {
			$html->set_attribute( 'width', esc_attr( (string) $width ) );
			$html->set_attribute( 'height', esc_attr( (string) $height ) );
		}
		$crop_styles = sprintf( 'aspect-ratio: %s; object-fit: cover; width: 100%%; height: auto; max-width: 100%%; display: block;', $aspect_ratio );
	} else {
		// No server-side crop available: fall back to best-effort CSS cropping. We don't stamp
		// crop dimensions here so the natural image isn't distorted in clients that ignore
		// object-fit. normalize_image_for_email() may still clamp an oversized width downstream,
		// but it scales the height with it, preserving the natural ratio rather than the crop.
		$crop_styles = sprintf( 'aspect-ratio: %s; object-fit: cover; width: 100%%; height: auto; display: block;', $aspect_ratio );
	}

	/** @var string $existing_style */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
	$existing_style = $html->get_attribute( 'style' ) ?? '';
	$existing_style = '' !== $existing_style ? ( rtrim( $existing_style, ';' ) . '; ' ) : '';
	$html->set_attribute( 'style', esc_attr( $existing_style . $crop_styles ) );

	return $html->get_updated_html();
}