', $lazy_lib . '', $this->content );
}
}
/**
* Parse img src for VPI preload only.
* Note: Didn't reuse the _parse_img() because it contains replacement logic which is not needed for preload.
*
* @since 6.2
* @since 7.6 - Added attributes fetchpriority="high" and decode="sync" for VPI images.
* @return void
*/
private function _parse_img_for_preload() {
// Load VPI setting.
$is_mobile = $this->_separate_mobile();
$vpi_files = $this->cls( 'Metabox' )->setting( $is_mobile ? VPI::POST_META_MOBILE : VPI::POST_META );
if ( $vpi_files ) {
$vpi_files = Utility::sanitize_lines( $vpi_files, 'basename' );
}
if ( ! $vpi_files ) {
return;
}
if ( ! $this->content ) {
return;
}
$content = preg_replace( array( '##sU', '##isU' ), '', $this->content );
if ( ! $content ) {
return;
}
$vpi_fp_search = [];
$vpi_fp_replace = [];
preg_match_all('#]+)/?>#isU', $content, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$attrs = Utility::parse_attr($match[1]);
if ( empty( $attrs['src'] ) ) {
continue;
}
if ( false !== strpos( $attrs['src'], 'base64' ) || 0 === strpos( $attrs['src'], 'data:' ) ) {
self::debug2( 'lazyload bypassed base64 img' );
continue;
}
if ( false !== strpos( $attrs['src'], '{' ) ) {
self::debug2( 'image src has {} ' . $attrs['src'] );
continue;
}
// If the src contains VPI filename, then preload it.
if ( ! Utility::str_hit_array( $attrs['src'], $vpi_files ) ) {
continue;
}
self::debug2( 'VPI preload found and matched: ' . $attrs['src'] );
$this->_vpi_preload_list[] = $attrs['src'];
// Add attributes fetchpriority="high" and decode="sync"
// after WP 6.3.0 use: wp_img_tag_add_loading_optimization_attrs().
$new_html = [];
$attrs[ 'fetchpriority' ] = 'high';
$attrs[ 'decoding' ] = 'sync';
// create html with new attributes.
foreach ( $attrs as $k => $attr ) {
$new_html[] = $k . '="' . $attr . '"';
}
if ( $new_html ) {
$vpi_fp_search[] = $match[1];
$vpi_fp_replace[] = implode( ' ', $new_html);
}
}
// if VPI fetchpriority changes, do the replacement
if ( $vpi_fp_search && $vpi_fp_replace ) {
$this->content = str_replace( $vpi_fp_search, $vpi_fp_replace, $this->content );
}
unset( $vpi_fp_search );
unset( $vpi_fp_replace );
}
/**
* Parse img src.
*
* @since 1.4
* @access private
* @return array{0:array,1:array,2:array} All the src & related raw html list with placeholders.
*/
private function _parse_img() {
/**
* Exclude list.
*
* @since 1.5
* @since 2.7.1 Changed to array.
*/
$excludes = apply_filters( 'litespeed_media_lazy_img_excludes', $this->conf( Base::O_MEDIA_LAZY_EXC ) );
$cls_excludes = apply_filters( 'litespeed_media_lazy_img_cls_excludes', $this->conf( Base::O_MEDIA_LAZY_CLS_EXC ) );
$cls_excludes[] = 'skip-lazy'; // https://core.trac.wordpress.org/ticket/44427
$src_list = [];
$html_list = [];
$placeholder_list = [];
$content = preg_replace(
array(
'##sU',
'##isU',
'##isU', // Remove script to avoid false matches and warnings, when image size detection is turned ON.
),
'',
$this->content
);
/**
* Exclude parent classes.
*
* @since 3.0
*/
$parent_cls_exc = apply_filters( 'litespeed_media_lazy_img_parent_cls_excludes', $this->conf( Base::O_MEDIA_LAZY_PARENT_CLS_EXC ) );
if ( $parent_cls_exc ) {
self::debug2( 'Lazyload Class excludes', $parent_cls_exc );
foreach ( $parent_cls_exc as $v ) {
$content = preg_replace('#<(\w+) [^>]*class=(\'|")[^\'"]*' . preg_quote($v, '#') . '[^\'"]*\2[^>]*>.*\1>#sU', '', $content);
}
}
preg_match_all( '#
]+)/?>#isU', $content, $matches, PREG_SET_ORDER );
foreach ( $matches as $match ) {
$attrs = Utility::parse_attr( $match[1] );
if ( empty( $attrs['src'] ) ) {
continue;
}
/**
* Add src validation to bypass base64 img src.
*
* @since 1.6
*/
if ( false !== strpos( $attrs['src'], 'base64' ) || 0 === strpos( $attrs['src'], 'data:' ) ) {
self::debug2( 'lazyload bypassed base64 img' );
continue;
}
self::debug2( 'lazyload found: ' . $attrs['src'] );
if (
! empty( $attrs['data-no-lazy'] ) ||
! empty( $attrs['data-skip-lazy'] ) ||
! empty( $attrs['data-lazyloaded'] ) ||
! empty( $attrs['data-src'] ) ||
! empty( $attrs['data-srcset'] )
) {
self::debug2( 'bypassed' );
continue;
}
$hit = ! empty( $attrs['class'] ) ? Utility::str_hit_array( $attrs['class'], $cls_excludes ) : false;
if ( $hit ) {
self::debug2( 'lazyload image cls excludes [hit] ' . $hit );
continue;
}
/**
* Exclude from lazyload by setting.
*
* @since 1.5
*/
if ( $excludes && Utility::str_hit_array( $attrs['src'], $excludes ) ) {
self::debug2( 'lazyload image exclude ' . $attrs['src'] );
continue;
}
/**
* Excludes invalid image src from buddypress avatar crop.
*
* @see https://wordpress.org/support/topic/lazy-load-breaking-buddypress-upload-avatar-feature
* @since 3.0
*/
if ( false !== strpos( $attrs['src'], '{' ) ) {
self::debug2( 'image src has {} ' . $attrs['src'] );
continue;
}
// to avoid multiple replacement.
if ( in_array( $match[0], $html_list, true ) ) {
continue;
}
// Add missing dimensions.
if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_ADD_MISSING_SIZES ) ) {
if ( ! apply_filters( 'litespeed_media_add_missing_sizes', true ) ) {
self::debug2( 'add_missing_sizes bypassed via litespeed_media_add_missing_sizes filter' );
} elseif ( empty( $attrs['width'] ) || 'auto' === $attrs['width'] || empty( $attrs['height'] ) || 'auto' === $attrs['height'] ) {
self::debug( '⚠️ Missing sizes for image [src] ' . $attrs['src'] );
$dimensions = $this->_detect_dimensions( $attrs['src'] );
if ( $dimensions ) {
$ori_width = $dimensions[0];
$ori_height = $dimensions[1];
// Calculate height based on width.
if ( ! empty( $attrs['width'] ) && 'auto' !== $attrs['width'] ) {
$ori_height = (int) ( ( $ori_height * (int) $attrs['width'] ) / max( 1, $ori_width ) );
} elseif ( ! empty( $attrs['height'] ) && 'auto' !== $attrs['height'] ) {
$ori_width = (int) ( ( $ori_width * (int) $attrs['height'] ) / max( 1, $ori_height ) );
}
$attrs['width'] = $ori_width;
$attrs['height'] = $ori_height;
$new_html = preg_replace( '#\s+(width|height)=(["\'])[^\2]*?\2#', '', $match[0] );
$new_html = preg_replace(
'#
content = str_replace( $match[0], $new_html, $this->content );
$match[0] = $new_html;
}
}
}
$placeholder = false;
if ( ! empty( $attrs['width'] ) && 'auto' !== $attrs['width'] && ! empty( $attrs['height'] ) && 'auto' !== $attrs['height'] ) {
$placeholder = (int) $attrs['width'] . 'x' . (int) $attrs['height'];
}
$src_list[] = $attrs['src'];
$html_list[] = $match[0];
$placeholder_list[] = $placeholder;
}
return array( $src_list, $html_list, $placeholder_list );
}
/**
* Detect the original sizes.
*
* @since 4.0
*
* @param string $src Source URL/path.
* @return array|false getimagesize array or false.
*/
private function _detect_dimensions( $src ) {
$pathinfo = Utility::is_internal_file( $src );
if ( $pathinfo ) {
$src = $pathinfo[0];
} elseif ( apply_filters( 'litespeed_media_ignore_remote_missing_sizes', false ) ) {
return false;
}
if ( 0 === strpos( $src, '//' ) ) {
$src = 'https:' . $src;
}
try {
$sizes = getimagesize( $src );
} catch ( \Exception $e ) {
return false;
}
if ( ! empty( $sizes[0] ) && ! empty( $sizes[1] ) ) {
return $sizes;
}
return false;
}
/**
* Parse iframe src.
*
* @since 1.4
* @access private
* @return array All the related raw html list (full