settings = Settings::get_instance();
$this->helper = Lazy_Load_Helper::get_instance();
$this->array_utils = new Array_Utils();
$this->upload_dir = new Upload_Dir();
$this->url_utils = new Url_Utils();
}
public function should_transform() {
return ! $this->helper->should_skip_lazyload();
}
public function transform_page( $page ) {
$this->transform_image_elements( $page );
if ( ! $this->helper->is_format_excluded( 'iframe' ) ) {
$this->transform_iframes( $page );
}
}
/**
* @param $page Page
*
* @return void
*/
private function transform_iframes( $page ) {
foreach ( $page->get_iframe_elements() as $iframe_element ) {
$this->transform_iframe( $iframe_element );
}
}
/**
* @param Element $iframe_element
*
* @return void
*/
private function transform_iframe( Element $iframe_element ) {
$src_attribute = $iframe_element->get_attribute( 'src' );
if ( ! $src_attribute ) {
return;
}
$original_src_url = $src_attribute->get_value();
$original_iframe_markup = $iframe_element->get_markup();
if (
$this->is_element_excluded( $iframe_element )
|| $this->is_iframe_skipped_through_filter( $original_src_url, $original_iframe_markup )
) {
return;
}
if ( esc_url_raw( $original_src_url ) !== $original_src_url ) {
return;
}
if ( $this->helper->should_lazy_load_embed_video() ) {
$lazy_load_video = new Lazy_Load_Video_Embed( $original_src_url, $iframe_element );
if ( $lazy_load_video->can_lazy_load() ) {
$lazy_load_video->transform();
if ( $this->helper->is_noscript_fallback_enabled() ) {
$iframe_element->set_postfix( "" );
}
return;
}
}
if ( $this->helper->is_native_lazy_loading_enabled() ) {
if ( ! $this->element_has_native_lazy_load_attribute( $iframe_element ) ) {
$this->add_native_lazy_loading_attribute( $iframe_element );
}
return;
}
$this->update_iframe_element_attributes_for_lazy_load( $iframe_element );
}
private function update_iframe_element_attributes_for_lazy_load( Element $iframe_element ) {
$this->remove_native_lazy_loading_attribute( $iframe_element );
$this->update_element_attributes_for_lazy_load( $iframe_element, array( 'src' ) );
$iframe_element->add_attribute( new Element_Attribute( 'data-load-mode', '1' ) );
}
private function update_element_attributes_for_lazy_load( Element $element, $replace_attributes ) {
$this->replace_attributes_with_data_attributes( $element, $replace_attributes );
// We are adding a new src below, the original src is gone because we replaced it.
$element->add_attribute( new Element_Attribute( 'src', self::TEMP_SRC ) );
$this->add_lazy_load_class( $element );
}
private function element_has_native_lazy_load_attribute( Element $element ) {
return $element->has_attribute( 'loading' );
}
private function is_element_excluded( Element $element ) {
return $this->is_high_priority_element( $element )
|| $element->is_lcp()
|| $this->element_has_excluded_keywords( $element );
}
private function element_has_excluded_keywords( Element $element ) {
$keyword_exclusions = $this->keyword_exclusions();
if ( ! $keyword_exclusions->has_excluded_keywords() ) {
return false;
}
return $keyword_exclusions->is_markup_excluded( $element->get_markup() )
|| $keyword_exclusions->is_id_attribute_excluded( $element->get_attribute_value( 'id' ) )
|| $keyword_exclusions->is_class_attribute_excluded( $element->get_attribute_value( 'class' ) );
}
private function is_iframe_skipped_through_filter( $src, $iframe ) {
return apply_filters( 'smush_skip_iframe_from_lazy_load', false, $src, $iframe );
}
private function get_lazy_load_options() {
if ( ! $this->lazy_load_options ) {
$setting = $this->settings->get_setting( 'wp-smush-lazy_load' );
$this->lazy_load_options = empty( $setting ) ? array() : $setting;
}
return $this->lazy_load_options;
}
private function get_excluded_keywords() {
if ( ! $this->excluded_keywords ) {
$this->excluded_keywords = $this->prepare_excluded_keywords();
}
return $this->excluded_keywords;
}
private function prepare_excluded_keywords() {
$default_exclude_keywords = $this->get_default_excluded_keywords();
$exclude_keywords = $this->helper->get_excluded_classes();// @since 3.13.0 used excluded ids, classes as excluded keywords.
$exclude_keywords = array_merge(
$default_exclude_keywords,
$exclude_keywords
);
return apply_filters( 'wp_smush_lazyload_excluded_keywords', array_unique( $exclude_keywords ) );
}
private function replace_attributes_with_data_attributes( Element $element, $attribute_names ) {
foreach ( $attribute_names as $attribute_name ) {
$this->replace_attribute_with_data_attribute( $element, $attribute_name );
}
}
/**
* @param Element $element
* @param $original_attribute_name
*
* @return void
*/
private function replace_attribute_with_data_attribute( Element $element, $original_attribute_name ) {
$attribute = $element->get_attribute( $original_attribute_name );
if ( $attribute ) {
$original_value = $attribute->get_value();
$data_attribute = new Element_Attribute( "data-$original_attribute_name", $original_value );
$element->replace_attribute( $original_attribute_name, $data_attribute );
}
}
private function get_default_excluded_keywords() {
return array(
'data-lazyload=',
'soliloquy-preload', // Soliloquy slider.
'no-lazyload', // Internal class to skip images.
'data-src=',
'data-no-lazy=',
'base64,R0lGOD',
'data-lazy-original=',
'data-lazy-src=',
'data-lazysrc=',
'data-bgposition=',
'fullurl=',
'jetpack-lazy-image',
'lazy-slider-img=',
'data-srcset=',
'class="ls-l',
'class="ls-bg',
'soliloquy-image',
'swatch-img',
'data-height-percentage',
'data-large_image',
'avia-bg-style-fixed',
'data-skip-lazy',
'skip-lazy',
'image-compare__',
'gform_ajax_frame',
'recaptcha/api/',
'google_ads_iframe_',
);
}
private function is_high_priority_element( Element $element ) {
/**
* An image should not be lazy-loaded and marked as high priority at the same time.
*
* @see wp_img_tag_add_loading_optimization_attrs()
*/
$fetch_priority = $element->get_attribute_value( 'fetchpriority' );
return $fetch_priority === 'high';
}
/**
* @param Element $element
*
* @return void
*/
private function add_native_lazy_loading_attribute( Element $element ) {
$element->add_attribute( new Element_Attribute( 'loading', 'lazy' ) );
}
private function remove_native_lazy_loading_attribute( Element $element ) {
$native_lazyload_attr = $element->get_attribute( 'loading' );
if ( ! empty( $native_lazyload_attr ) ) {
$element->remove_attribute( $native_lazyload_attr );
}
}
private function transform_image_elements( Page $page ) {
/**
* The following is being done in addition to the separate LCP_Transform just to save an extra re-parse in the transformer {@see Transformer::transform_content()}.
* TODO: Remove this when re-parsing after every transform is not necessary.
*/
if ( $this->settings->is_lcp_preload_enabled() ) {
$lcp_transform = new LCP_Transform();
$lcp_transform->transform_page( $page );
}
foreach ( $page->get_composite_elements() as $composite_element ) {
if ( ! $this->is_composite_element_excluded( $composite_element ) ) {
$this->transform_elements( $composite_element->get_elements() );
}
}
$this->transform_elements( $page->get_elements() );
}
private function transform_image_element( Element $element ) {
if ( $element->get_tag() === 'source' ) {
$this->maybe_lazy_load_source_element( $element );
} else {
$attributes_updated = $this->maybe_lazy_load_image_element( $element );
if ( ! $attributes_updated ) {
$this->maybe_lazy_load_background( $element );
}
}
}
private function maybe_lazy_load_source_element( Element $element ) {
$srcset_attribute = $element->get_attribute( 'srcset' );
if ( ! $srcset_attribute || empty( $srcset_attribute->get_image_urls() ) ) {
return false;
}
$srcset_url = $srcset_attribute->get_single_image_url();
$srcset_image_url = $srcset_url->get_absolute_url();
$srcset_extension = $srcset_url->get_ext();
$original_markup = $element->get_markup();
if (
! $srcset_image_url
|| ! $this->helper->is_image_extension_supported( $srcset_extension, $srcset_image_url )
|| $this->is_element_excluded( $element )
|| $this->is_image_element_skipped_through_filter( $srcset_image_url, $original_markup )
|| $this->helper->is_native_lazy_loading_enabled()
) {
return false;
}
$this->remove_native_lazy_loading_attribute( $element );
$this->replace_attributes_with_data_attributes( $element, array(
'src',
'srcset',
'sizes',
) );
return true;
}
private function maybe_lazy_load_image_element( Element $element ) {
$src_attribute = $element->get_attribute( 'src' );
if ( ! $src_attribute ) {
return false;
}
$src_image_url = ! empty( $src_attribute->get_single_image_url() )
? $src_attribute->get_single_image_url()->get_absolute_url()
: $src_attribute->get_value();
$src_extension = ! empty( $src_attribute->get_single_image_url() )
? $src_attribute->get_single_image_url()->get_ext()
: '';
$original_markup = $element->get_markup();
if (
! $src_image_url
|| ! $this->helper->is_image_extension_supported( $src_extension, $src_image_url )
|| $this->is_element_excluded( $element )
|| $this->is_image_element_skipped_through_filter( $src_image_url, $original_markup )
) {
return false;
}
$is_tag_supported = in_array( $element->get_tag(), $this->get_lazy_load_image_tag_names(), true );
if ( ! $is_tag_supported ) {
return false;
}
if ( $this->helper->is_native_lazy_loading_enabled() ) {
if ( ! $this->element_has_native_lazy_load_attribute( $element ) ) {
$this->add_native_lazy_loading_attribute( $element );
}
} else {
$this->remove_native_lazy_loading_attribute( $element );
$this->update_element_attributes_for_lazy_load( $element, array(
'src',
'srcset',
'sizes',
) );
$this->set_placeholder_width_and_height_in_style_attribute( $element, $src_image_url );
if ( $element->is_image_element() && $this->helper->is_noscript_fallback_enabled() ) {
// TODO: Remove the duplicate