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