diff --git a/php/class-delivery.php b/php/class-delivery.php
index ea597a1a..61f14fa3 100644
--- a/php/class-delivery.php
+++ b/php/class-delivery.php
@@ -1006,6 +1006,38 @@ public function get_media_tags( $content, $tags = 'img|video' ) {
return $media;
}
+ /**
+ * Get the image size dimensions from the tags if available.
+ *
+ * @param array $tags The media tags found in the content.
+ * @param array $relation The relation data for the media item.
+ *
+ * @return array An array with width and height, or empty if not found.
+ */
+ private function get_image_size_within_tags( $tags, $relation ) {
+ // If we don't have a post ID, we can't find a size, so return empty.
+ if ( empty( $relation['post_id'] ) ) {
+ return array();
+ }
+ $post_id = intval( $relation['post_id'] );
+
+ // Look through the tags to find one that matches our post ID and has a size slug, then get the size from that slug.
+ foreach ( $tags as $set ) {
+ // If this set doesn't have a size slug, skip.
+ if ( empty( $set['atts']['data-size-slug'] ) ) {
+ continue;
+ }
+
+ // If this tag's post ID matches our relation's post ID, get the size from the slug and return it.
+ if ( $post_id === $set['id'] ) {
+ $size = $this->media->get_size_from_slug( $set['atts']['data-size-slug'] );
+ return ( empty( $size ) ) ? array() : $size; // Return the size if found, otherwise return empty array.
+ }
+ }
+
+ return array();
+ }
+
/**
* Convert media tags from Local to Cloudinary, and register with String_Replace.
*
@@ -1025,7 +1057,12 @@ public function convert_tags( $content, $context = 'view' ) {
}
$tags = $this->get_media_tags( $content, 'img|video|article|source' );
- $tags = array_map( array( $this, 'parse_element' ), $tags );
+ $tags = array_map(
+ function ( $tag ) use ( $content ) {
+ return $this->parse_element( $tag, $content );
+ },
+ $tags
+ );
$tags = array_filter( $tags );
$replacements = array();
@@ -1082,7 +1119,9 @@ public function convert_tags( $content, $context = 'view' ) {
$public_id = ! is_admin() ? $relation['public_id'] . '.' . $relation['format'] : null;
// Get merged transformations including overlays.
$merged_transformations = Relate::get_transformations( $relation['post_id'], true );
- $cloudinary_url = $this->media->cloudinary_url( $relation['post_id'], array(), $merged_transformations, $public_id );
+
+ $size = $this->get_image_size_within_tags( $tags, $relation );
+ $cloudinary_url = $this->media->cloudinary_url( $relation['post_id'], $size, $merged_transformations, $public_id );
if ( empty( $cloudinary_url ) ) {
continue;
}
@@ -1220,6 +1259,15 @@ protected function standardize_tag( $tag_element ) {
$size = $has_wp_size;
}
}
+
+ // Retrieve size from the parent figure class of the image if it exists.
+ if ( ! empty( $tag_element['atts']['data-size-slug'] ) && 'img' === $tag_element['tag'] ) {
+ $slug_size = $this->media->get_size_from_slug( $tag_element['atts']['data-size-slug'] );
+ if ( ! empty( $slug_size ) ) {
+ $size = $slug_size;
+ }
+ }
+
// Unset srcset and sizes.
unset( $tag_element['atts']['srcset'], $tag_element['atts']['sizes'] );
@@ -1437,10 +1485,11 @@ public function rebuild_tag( $tag_element ) {
* Parse an html element into tag, and attributes.
*
* @param string $element The HTML element.
+ * @param string $content Optional full HTML content for parent context.
*
* @return array|null
*/
- public function parse_element( $element ) {
+ public function parse_element( $element, $content = '' ) {
/**
* Filter to skip parsing an element.
*
@@ -1551,6 +1600,21 @@ public function parse_element( $element ) {
if ( in_array( $tag_element['tag'], array( 'img', 'source' ), true ) ) {
// Check if this is a crop or a scale.
$has_size = $this->media->get_size_from_url( $this->sanitize_url( $raw_url ) );
+
+ // If no size found in URL, try to extract from parent figure element so we can apply cropping if needed.
+ if ( empty( $has_size ) && $has_sized_transformation ) {
+ $size_slug = $this->media->get_size_slug_from_parent_figure_class( $tag_element['original'], $content );
+
+ if ( ! empty( $size_slug ) ) {
+ $has_size = $this->media->get_size_from_slug( $size_slug );
+ if ( ! empty( $has_size ) ) {
+ $attributes['data-size-slug'] = $size_slug;
+ $tag_element['width'] = $has_size[0];
+ $tag_element['height'] = $has_size[1];
+ }
+ }
+ }
+
if ( ! empty( $has_size ) && ! empty( $item['height'] ) ) {
$file_ratio = round( $has_size[0] / $has_size[1], 2 );
$original_ratio = round( $item['width'] / $item['height'], 2 );
diff --git a/php/class-media.php b/php/class-media.php
index d034e210..4b1aedd6 100644
--- a/php/class-media.php
+++ b/php/class-media.php
@@ -3267,4 +3267,63 @@ public function upgrade_settings( $previous_version, $new_version ) {
$setting->save_value();
}
}
+
+
+ /**
+ * Get the size dimensions based on the size slug.
+ *
+ * Uses WordPress core API to retrieve registered image subsizes, which handles both
+ * built-in sizes (thumbnail, medium, large, etc.) and custom registered sizes.
+ *
+ * @param string $size_slug The WordPress size slug (e.g., 'thumbnail', 'medium', 'large').
+ *
+ * @return array|null An array with width and height, or null if not found.
+ */
+ public function get_size_from_slug( $size_slug ) {
+ // Use WordPress core API to get all registered image subsizes.
+ $image_subsizes = wp_get_registered_image_subsizes();
+
+ // Check if the requested size exists in registered subsizes.
+ if ( ! isset( $image_subsizes[ $size_slug ] ) ) {
+ return null;
+ }
+
+ $size_data = $image_subsizes[ $size_slug ];
+
+ // Return width and height if both are present and valid.
+ if ( ! empty( $size_data['width'] ) && ! empty( $size_data['height'] ) ) {
+ return array( (int) $size_data['width'], (int) $size_data['height'] );
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Extract WordPress image size from parent figure element.
+ *
+ * @param string $element The img tag element.
+ * @param string $content The full HTML content.
+ *
+ * @return string|null The WordPress size slug (e.g., 'thumbnail', 'medium'), or null if not found.
+ */
+ public function get_size_slug_from_parent_figure_class( $element, $content ) {
+ // If content is empty, we can't find a parent figure, so return null.
+ if ( empty( $content ) ) {
+ return null;
+ }
+
+ // Escape the element for use in regex.
+ $escaped_element = preg_quote( $element, '#' );
+
+ // Pattern: ...img element...[optional figcaption] .
+ $pattern = '#]*class="[^"]*\bsize-(\w+)\b[^"]*"[^>]*>.*?' . $escaped_element . '.*?#is';
+
+ // Look for the parent figure tag that contains this img element.
+ if ( preg_match( $pattern, $content, $matches ) ) {
+ return $matches[1];
+ }
+
+ return null;
+ }
}