Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks
Gallery::apply_aspect_ratio_crop
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{}
Hooks from the method
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() 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 "&", 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();
}