diff --git a/plugins/auto-sizes/hooks.php b/plugins/auto-sizes/hooks.php index 5ad0921a13..3f9419b2fe 100644 --- a/plugins/auto-sizes/hooks.php +++ b/plugins/auto-sizes/hooks.php @@ -31,6 +31,8 @@ function auto_sizes_render_generator(): void { add_filter( 'render_block_core/image', 'auto_sizes_filter_image_tag', 10, 3 ); add_filter( 'render_block_core/cover', 'auto_sizes_filter_image_tag', 10, 3 ); add_filter( 'render_block_core/post-featured-image', 'auto_sizes_filter_image_tag', 10, 3 ); +add_filter( 'render_block_core/group', 'auto_sizes_add_background_image_data_attributes', 5, 2 ); +add_filter( 'render_block_core/cover', 'auto_sizes_add_background_image_data_attributes', 5, 2 ); add_filter( 'get_block_type_uses_context', 'auto_sizes_filter_uses_context', 10, 2 ); add_filter( 'render_block_context', 'auto_sizes_filter_render_block_context', 10, 3 ); // @codeCoverageIgnoreEnd diff --git a/plugins/auto-sizes/includes/improve-calculate-sizes.php b/plugins/auto-sizes/includes/improve-calculate-sizes.php index ef2bdd8d63..e55c3882d4 100644 --- a/plugins/auto-sizes/includes/improve-calculate-sizes.php +++ b/plugins/auto-sizes/includes/improve-calculate-sizes.php @@ -402,3 +402,62 @@ function auto_sizes_get_featured_image_attachment_id( int $post_id ): int { return (int) get_post_thumbnail_id( $post_id ); } + +/** + * Adds data attributes for background image attachment ID to Group and Cover blocks. + * + * This exposes the attachment ID from block attributes as data attributes on the HTML element, + * allowing Image Prioritizer to access this information without using attachment_url_to_postid(). + * This is particularly important for Cover blocks that may use sizes other than "full". + * + * @since n.e.x.t + * + * @param string|mixed $content The block content about to be rendered. + * @param array{ attrs?: array, blockName?: string } $parsed_block The parsed block. + * @return string The updated block content. + */ +function auto_sizes_add_background_image_data_attributes( $content, array $parsed_block ): string { + if ( ! is_string( $content ) || '' === $content ) { + return ''; + } + + $block_name = $parsed_block['blockName'] ?? ''; + $attrs = $parsed_block['attrs'] ?? array(); + $attachment_id = null; + $image_url = null; + + // Extract background image data based on block type. + if ( 'core/cover' === $block_name ) { + $attachment_id = $attrs['id'] ?? null; + $image_url = $attrs['url'] ?? null; + } elseif ( 'core/group' === $block_name ) { + $attachment_id = $attrs['style']['background']['backgroundImage']['id'] ?? null; + $image_url = $attrs['style']['background']['backgroundImage']['url'] ?? null; + } + + // Normalize attachment ID type if possible. + if ( isset( $attachment_id ) && is_numeric( $attachment_id ) ) { + $attachment_id = (int) $attachment_id; + } + + // Validate extracted data and update the element with background image. + if ( + isset( $attachment_id, $image_url ) && + is_int( $attachment_id ) && + $attachment_id > 0 && + '' !== $image_url && + is_array( wp_get_attachment_metadata( $attachment_id ) ) + ) { + $processor = new WP_HTML_Tag_Processor( $content ); + while ( $processor->next_tag() ) { + $style = $processor->get_attribute( 'style' ); + if ( is_string( $style ) && str_contains( $style, 'background-image:' ) && str_contains( $style, $image_url ) ) { + $processor->set_attribute( 'data-bg-attachment-id', (string) $attachment_id ); + $content = $processor->get_updated_html(); + break; + } + } + } + + return $content; +} diff --git a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php index 12dfb8f9f9..55731cfb5f 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php @@ -92,6 +92,9 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $this->add_image_preload_link( $context->link_collection, $group, $background_image_url ); } + // Reduce the background image size if URL Metrics are available. + $this->reduce_background_image_size( $background_image_url, $context ); + $this->lazy_load_bg_images( $context ); return true; @@ -238,4 +241,40 @@ private function lazy_load_bg_images( OD_Tag_Visitor_Context $context ): void { $this->added_lazy_assets = true; } } + + /** + * Reduces background image size by choosing one that fits the element dimensions more closely. + * + * This is similar to how VIDEO poster images are optimized in the Video Tag Visitor. + * + * @since n.e.x.t + * + * @param non-empty-string $background_image_url Background image URL. + * @param OD_Tag_Visitor_Context $context Tag visitor context, with the cursor currently at an element with a background image. + */ + private function reduce_background_image_size( string $background_image_url, OD_Tag_Visitor_Context $context ): void { + $processor = $context->processor; + + $max_element_width = $this->get_max_element_width( $context ); + + // If the element wasn't present in any URL Metrics gathered for desktop, then abort downsizing the background image. + if ( null === $max_element_width ) { + return; + } + + // Try to get the attachment ID from the data attribute (populated via filter from block attributes). + $attachment_id = $processor->get_attribute( 'data-bg-attachment-id' ); + + if ( is_numeric( $attachment_id ) && $attachment_id > 0 ) { + $smaller_image_url = wp_get_attachment_image_url( (int) $attachment_id, array( (int) $max_element_width, 0 ) ); + if ( is_string( $smaller_image_url ) && $smaller_image_url !== $background_image_url ) { + // Replace the background image URL in the style attribute. + $style = $processor->get_attribute( 'style' ); + if ( is_string( $style ) ) { + $updated_style = str_replace( $background_image_url, $smaller_image_url, $style ); + $processor->set_attribute( 'style', $updated_style ); + } + } + } + } } diff --git a/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php index ca35567b13..8f4af2bf39 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php @@ -67,4 +67,33 @@ protected function get_attribute_value( OD_HTML_Tag_Processor $processor, string } return $value; } + + /** + * Obtains maximum width of the element from the URL Metrics group with the widest viewport width. + * + * This would be the desktop group. This prevents the situation where if URL Metrics have only so far been + * gathered for mobile viewports that an excessively-small image would end up getting served to the first + * desktop visitor. + * + * @since n.e.x.t + * + * @param OD_Tag_Visitor_Context $context Tag visitor context. + * @return float|null Maximum element width, or null if element not found. + */ + protected function get_max_element_width( OD_Tag_Visitor_Context $context ): ?float { + $xpath = $context->processor->get_xpath(); + $max_element_width = null; + + foreach ( $context->url_metric_group_collection->get_last_group() as $url_metric ) { + foreach ( $url_metric->get_elements() as $element ) { + if ( $element->get_xpath() === $xpath ) { + $width = $element->get_bounding_client_rect()['width']; + $max_element_width = null === $max_element_width ? $width : max( $max_element_width, $width ); + break; // Move on to the next URL Metric. + } + } + } + + return $max_element_width; + } } diff --git a/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php index 8e5a6a6c74..463a9e13b0 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php @@ -93,25 +93,10 @@ private function get_poster( OD_Tag_Visitor_Context $context ): ?string { private function reduce_poster_image_size( string $poster, OD_Tag_Visitor_Context $context ): void { $processor = $context->processor; - $xpath = $processor->get_xpath(); - - /* - * Obtain maximum width of the element exclusively from the URL Metrics group with the widest viewport width, - * which would be desktop. This prevents the situation where if URL Metrics have only so far been gathered for - * mobile viewports that an excessively-small poster would end up getting served to the first desktop visitor. - */ - $max_element_width = 0; - foreach ( $context->url_metric_group_collection->get_last_group() as $url_metric ) { - foreach ( $url_metric->get_elements() as $element ) { - if ( $element->get_xpath() === $xpath ) { - $max_element_width = max( $max_element_width, $element->get_bounding_client_rect()['width'] ); - break; // Move on to the next URL Metric. - } - } - } + $max_element_width = $this->get_max_element_width( $context ); // If the element wasn't present in any URL Metrics gathered for desktop, then abort downsizing the poster. - if ( 0 === $max_element_width ) { + if ( null === $max_element_width ) { return; }