e; $type = false; if ( 'image/webp' !== wp_get_image_mime( $filename ) ) { return compact( 'width', 'height', 'type' ); } $magic = file_get_contents( $filename, false, null, 0, 40 ); if ( false === $magic ) { return compact( 'width', 'height', 'type' ); } // Make sure we got enough bytes. if ( strlen( $magic ) < 40 ) { return compact( 'width', 'height', 'type' ); } /* * The headers are a little different for each of the three formats. * Header values based on WebP docs, see https://developers.google.com/speed/webp/docs/riff_container. */ switch ( substr( $magic, 12, 4 ) ) { // Lossy WebP. case 'VP8 ': $parts = unpack( 'v2', substr( $magic, 26, 4 ) ); $width = (int) ( $parts[1] & 0x3FFF ); $height = (int) ( $parts[2] & 0x3FFF ); $type = 'lossy'; break; // Lossless WebP. case 'VP8L': $parts = unpack( 'C4', substr( $magic, 21, 4 ) ); $width = (int) ( $parts[1] | ( ( $parts[2] & 0x3F ) << 8 ) ) + 1; $height = (int) ( ( ( $parts[2] & 0xC0 ) >> 6 ) | ( $parts[3] << 2 ) | ( ( $parts[4] & 0x03 ) << 10 ) ) + 1; $type = 'lossless'; break; // Animated/alpha WebP. case 'VP8X': // Pad 24-bit int. $width = unpack( 'V', substr( $magic, 24, 3 ) . "\x00" ); $width = (int) ( $width[1] & 0xFFFFFF ) + 1; // Pad 24-bit int. $height = unpack( 'V', substr( $magic, 27, 3 ) . "\x00" ); $height = (int) ( $height[1] & 0xFFFFFF ) + 1; $type = 'animated-alpha'; break; } return compact( 'width', 'height', 'type' ); } /** * Gets loading optimization attributes. * * This function returns an array of attributes that should be merged into the given attributes array to optimize * loading performance. Potential attributes returned by this function are: * - `loading` attribute with a value of "lazy" * - `fetchpriority` attribute with a value of "high" * * If any of these attributes are already present in the given attributes, they will not be modified. Note that no * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case * both attributes are present with those values. * * @since 6.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the tag. * @param string $context Context for the element for which the loading optimization attribute is requested. * @return array Loading optimization attributes. */ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) { global $wp_query; /* * Closure for postprocessing logic. * It is here to avoid duplicate logic in many places below, without having * to introduce a very specific private global function. */ $postprocess = static function( $loading_attributes, $with_fetchpriority = false ) use ( $tag_name, $attr, $context ) { // Potentially add `fetchpriority="high"`. if ( $with_fetchpriority ) { $loading_attributes = wp_maybe_add_fetchpriority_high_attr( $loading_attributes, $tag_name, $attr ); } // Potentially strip `loading="lazy"` if the feature is disabled. if ( isset( $loading_attributes['loading'] ) && ! wp_lazy_loading_enabled( $tag_name, $context ) ) { unset( $loading_attributes['loading'] ); } return $loading_attributes; }; // Closure to increase media count for images with certain minimum threshold, mostly used for header images. $maybe_increase_content_media_count = static function() use ( $attr ) { /** This filter is documented in wp-includes/media.php */ $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 ); // Images with a certain minimum size in the header of the page are also counted towards the threshold. if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) { wp_increase_content_media_count(); } }; $loading_attrs = array(); /* * Skip lazy-loading for the overall block template, as it is handled more granularly. * The skip is also applicable for `fetchpriority`. */ if ( 'template' === $context ) { return $loading_attrs; } // For now this function only supports images and iframes. if ( 'img' !== $tag_name && 'iframe' !== $tag_name ) { return $loading_attrs; } // For any resources, width and height must be provided, to avoid layout shifts. if ( ! isset( $attr['width'], $attr['height'] ) ) { return $loading_attrs; } if ( isset( $attr['loading'] ) ) { /* * While any `loading` value could be set in `$loading_attrs`, for * consistency we only do it for `loading="lazy"` since that is the * only possible value that WordPress core would apply on its own. */ if ( 'lazy' === $attr['loading'] ) { $loading_attrs['loading'] = 'lazy'; if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) { _doing_it_wrong( __FUNCTION__, __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ), '6.3.0' ); } } return $postprocess( $loading_attrs, true ); } // An image with `fetchpriority="high"` cannot be assigned `loading="lazy"` at the same time. if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) { return $postprocess( $loading_attrs, true ); } /* * Do not lazy-load images in the header block template part, as they are likely above the fold. * For classic themes, this is handled in the condition below using the 'get_header' action. */ $header_area = WP_TEMPLATE_PART_AREA_HEADER; if ( "template_part_{$header_area}" === $context ) { // Increase media count if there are images in header above a certian minimum size threshold. $maybe_increase_content_media_count(); return $postprocess( $loading_attrs, true ); } // The custom header image is always expected to be in the header. if ( 'get_header_image_tag' === $context ) { // Increase media count if there are images in header above a certian minimum size threshold. $maybe_increase_content_media_count(); return $postprocess( $loading_attrs, true ); } // Special handling for programmatically created image tags. if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context || 'widget_media_image' === $context ) { /* * Skip programmatically created images within post content as they need to be handled together with the other * images within the post content. * Without this clause, they would already be considered below which skews the image count and can result in * the first post content image being lazy-loaded or an image further down the page being marked as a high * priority. */ if ( doing_filter( 'the_content' ) ) { return $loading_attrs; } // Conditionally skip lazy-loading on images before the loop. if ( // Only apply for main query but before the loop. $wp_query->before_loop && $wp_query->is_main_query() /* * Any image before the loop, but after the header has started should not be lazy-loaded, * except when the footer has already started which can happen when the current template * does not include any loop. */ && did_action( 'get_header' ) && ! did_action( 'get_footer' ) ) { // Increase media count if there are images in header above a certian minimum size threshold. $maybe_increase_content_media_count(); return $postprocess( $loading_attrs, true ); } } /* * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded, * as they are likely above the fold. Shortcodes are processed after content images, so if * thresholds haven't already been met, apply the same logic to those as well. */ if ( 'the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context ) { // Only elements within the main query loop have special handling. if ( is_admin() || ! in_the_loop() || ! is_main_query() ) { $loading_attrs['loading'] = 'lazy'; return $postprocess( $loading_attrs, false ); } // Increase the counter since this is a main query content element. $content_media_count = wp_increase_content_media_count(); // If the count so far is below the threshold, `loading` attribute is omitted. if ( $content_media_count <= wp_omit_loading_attr_threshold() ) { // The first largest image will still get `fetchpriority='high'`. return $postprocess( $loading_attrs, true ); } } // Lazy-load by default for any unknown context. $loading_attrs['loading'] = 'lazy'; return $postprocess( $loading_attrs, false ); } /** * Gets the threshold for how many of the first content media elements to not lazy-load. * * This function runs the {@see 'wp_omit_loading_attr_threshold'} filter, which uses a default threshold value of 3. * The filter is only run once per page load, unless the `$force` parameter is used. * * @since 5.9.0 * * @param bool $force Optional. If set to true, the filter will be (re-)applied even if it already has been before. * Default false. * @return int The number of content media elements to not lazy-load. */ function wp_omit_loading_attr_threshold( $force = false ) { static $omit_threshold; // This function may be called multiple times. Run the filter only once per page load. if ( ! isset( $omit_threshold ) || $force ) { /** * Filters the threshold for how many of the first content media elements to not lazy-load. * * For these first content media elements, the `loading` attribute will be omitted. By default, this is the case * for only the very first content media element. * * @since 5.9.0 * @since 6.3.0 The default threshold was changed from 1 to 3. * * @param int $omit_threshold The number of media elements where the `loading` attribute will not be added. Default 3. */ $omit_threshold = apply_filters( 'wp_omit_loading_attr_threshold', 3 ); } return $omit_threshold; } /** * Increases an internal content media count variable. * * @since 5.9.0 * @access private * * @param int $amount Optional. Amount to increase by. Default 1. * @return int The latest content media count, after the increase. */ function wp_increase_content_media_count( $amount = 1 ) { static $content_media_count = 0; $content_media_count += $amount; return $content_media_count; } /** * Determines whether to add `fetchpriority='high'` to loading attributes. * * @since 6.3.0 * @access private * * @param array $loading_attrs Array of the loading optimization attributes for the element. * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the element. * @return array Updated loading optimization attributes for the element. */ function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) { // For now, adding `fetchpriority="high"` is only supported for images. if ( 'img' !== $tag_name ) { return $loading_attrs; } if ( isset( $attr['fetchpriority'] ) ) { /* * While any `fetchpriority` value could be set in `$loading_attrs`, * for consistency we only do it for `fetchpriority="high"` since that * is the only possible value that WordPress core would apply on its * own. */ if ( 'high' === $attr['fetchpriority'] ) { $loading_attrs['fetchpriority'] = 'high'; wp_high_priority_element_flag( false ); } return $loading_attrs; } // Lazy-loading and `fetchpriority="high"` are mutually exclusive. if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) { return $loading_attrs; } if ( ! wp_high_priority_element_flag() ) { return $loading_attrs; } /** * Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image. * * @since 6.3.0 * * @param int $threshold Minimum square-pixels threshold. Default 50000. */ $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 ); if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) { $loading_attrs['fetchpriority'] = 'high'; wp_high_priority_element_flag( false ); } return $loading_attrs; } /** * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`. * * @since 6.3.0 * @access private * * @param bool $value Optional. Used to change the static variable. Default null. * @return bool Returns true if high-priority element was marked already, otherwise false. */ function wp_high_priority_element_flag( $value = null ) { static $high_priority_element = true; if ( is_bool( $value ) ) { $high_priority_element = $value; } return $high_priority_element; }