* @param int $post_id Attachment post ID.
public function media_row_con( $post_id ) {
$att_info = wp_get_attachment_metadata( $post_id );
if ( empty( $att_info['file'] ) ) {
$short_path = $att_info['file'];
$size_meta = get_post_meta( $post_id, Img_Optm::DB_SIZE, true );
if ( $size_meta && ! empty( $size_meta['ori_saved'] ) ) {
$percent = (int) ceil( ( (int) $size_meta['ori_saved'] * 100 ) / max( 1, (int) $size_meta['ori_total'] ) );
$extension = pathinfo( $short_path, PATHINFO_EXTENSION );
$bk_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.' . $extension;
$bk_optm_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.optm.' . $extension;
$link = Utility::build_url( Router::ACTION_IMG_OPTM, 'orig' . $post_id );
if ( $this->info( $bk_file, $post_id ) ) {
$curr_status = esc_html__( '(optm)', 'litespeed-cache' );
$desc = esc_attr__( 'Currently using optimized version of file.', 'litespeed-cache' ) . ' ' . esc_attr__( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' );
} elseif ( $this->info( $bk_optm_file, $post_id ) ) {
$cls .= ' litespeed-warning';
$curr_status = esc_html__( '(non-optm)', 'litespeed-cache' );
$desc = esc_attr__( 'Currently using original (unoptimized) version of file.', 'litespeed-cache' ) . ' ' . esc_attr__( 'Click to switch to optimized version.', 'litespeed-cache' );
esc_html__( 'Original file reduced by %1$s (%2$s)', 'litespeed-cache' ),
Utility::real_size( $size_meta['ori_saved'] )
esc_html__( 'Orig saved %s', 'litespeed-cache' ),
' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>',
' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s">%2$s</span>',
esc_attr__( 'Using optimized version of file. ', 'litespeed-cache' ) . ' ' . esc_attr__( 'No backup of original file exists.', 'litespeed-cache' ),
esc_html__( '(optm)', 'litespeed-cache' )
} elseif ( $size_meta && 0 === (int) $size_meta['ori_saved'] ) {
echo wp_kses( GUI::pie_tiny( 0, 24, esc_html__( 'Congratulation! Your file was already optimized', 'litespeed-cache' ), 'left' ), GUI::allowed_svg_tags() );
esc_html__( 'Orig %s', 'litespeed-cache' ),
'<span class="litespeed-desc">' . esc_html__( '(no savings)', 'litespeed-cache' ) . '</span>'
echo esc_html__( 'Orig', 'litespeed-cache' ) . '<span class="litespeed-left10">—</span>';
if ( $size_meta && $this->webp_support( true ) && ! empty( $size_meta[ $this->_sys_format . '_saved' ] ) ) {
$is_avif = 'avif' === $this->_sys_format;
$size_meta_saved = $size_meta[ $this->_sys_format . '_saved' ];
$size_meta_total = $size_meta[ $this->_sys_format . '_total' ];
$percent = ceil( ( $size_meta_saved * 100 ) / max( 1, $size_meta_total ) );
$link = Utility::build_url( Router::ACTION_IMG_OPTM, $this->_sys_format . $post_id );
if ( $this->info( $short_path . '.' . $this->_sys_format, $post_id ) ) {
$curr_status = esc_html__( '(optm)', 'litespeed-cache' );
? esc_attr__( 'Currently using optimized version of AVIF file.', 'litespeed-cache' )
: esc_attr__( 'Currently using optimized version of WebP file.', 'litespeed-cache' );
$desc .= ' ' . esc_attr__( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' );
} elseif ( $this->info( $short_path . '.optm.' . $this->_sys_format, $post_id ) ) {
$cls .= ' litespeed-warning';
$curr_status = esc_html__( '(non-optm)', 'litespeed-cache' );
? esc_attr__( 'Currently using original (unoptimized) version of AVIF file.', 'litespeed-cache' )
: esc_attr__( 'Currently using original (unoptimized) version of WebP file.', 'litespeed-cache' );
$desc .= ' ' . esc_attr__( 'Click to switch to optimized version.', 'litespeed-cache' );
$is_avif ? esc_html__( 'AVIF file reduced by %1$s (%2$s)', 'litespeed-cache' ) : esc_html__( 'WebP file reduced by %1$s (%2$s)', 'litespeed-cache' ),
Utility::real_size( $size_meta_saved )
$is_avif ? esc_html__( 'AVIF saved %s', 'litespeed-cache' ) : esc_html__( 'WebP saved %s', 'litespeed-cache' ),
'<span>' . esc_html( $percent ) . '%</span>'
' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>',
' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s %2$s">%3$s</span>',
esc_attr__( 'Using optimized version of file. ', 'litespeed-cache' ),
$is_avif ? esc_attr__( 'No backup of unoptimized AVIF file exists.', 'litespeed-cache' ) : esc_attr__( 'No backup of unoptimized WebP file exists.', 'litespeed-cache' ),
esc_html__( '(optm)', 'litespeed-cache' )
echo esc_html( $this->next_gen_image_title() ) . '<span class="litespeed-left10">—</span>';
'<div class="row-actions"><span class="delete"><a href="%1$s" class="">%2$s</a></span></div>',
esc_url( Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, [ 'id' => $post_id ] ) ),
esc_html__( 'Restore from backup', 'litespeed-cache' )
* NOTE: this is not used because it has to be after admin_init.
* @return array $sizes Data for all currently-registered image sizes.
public function get_image_sizes() {
global $_wp_additional_image_sizes;
foreach ( get_intermediate_image_sizes() as $_size ) {
if ( in_array( $_size, [ 'thumbnail', 'medium', 'medium_large', 'large' ], true ) ) {
$sizes[ $_size ]['width'] = get_option( $_size . '_size_w' );
$sizes[ $_size ]['height'] = get_option( $_size . '_size_h' );
$sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' );
} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
'width' => $_wp_additional_image_sizes[ $_size ]['width'],
'height' => $_wp_additional_image_sizes[ $_size ]['height'],
'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
* Exclude role from optimization filter.
* @param bool $sys_level Return system-level format if true.
* @return string Next-gen format name or empty string.
public function webp_support( $sys_level = false ) {
return $this->_sys_format;
return $this->_format; // User level next gen support.
* Detect if browser supports next-gen format.
private function _browser_support_next_gen() {
$accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) : '';
if ( false !== strpos( $accept, 'image/' . $this->_sys_format ) ) {
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
$user_agents = [ 'chrome-lighthouse', 'googlebot', 'page speed' ];
foreach ( $user_agents as $user_agent ) {
if ( false !== stripos( $ua, $user_agent ) ) {
if ( preg_match( '/iPhone OS (\d+)_/i', $ua, $matches ) ) {
if ( $matches[1] >= 14 ) {
if ( preg_match( '/Macintosh.+Version\/([0-9.]+)/i', $ua, $matches ) ) {
if ( version_compare( $matches[1], '16.4', '>=' ) ) {
if ( preg_match( '/Firefox\/(\d+)/i', $ua, $matches ) ) {
if ( $matches[1] >= 65 ) {
* Get next gen image title.
public function next_gen_image_title() {
if ( 2 === $this->conf( Base::O_IMG_OPTM_WEBP ) ) {
* NOTE: As this is after cache finalized, can NOT set any cache control anymore.
* Only do for main page. Do NOT do for esi or dynamic content.
* @param string $content Final buffer.
* @return string The buffer.
public function finalize( $content ) {
if ( defined( 'LITESPEED_NO_LAZY' ) ) {
self::debug2( 'bypass: NO_LAZY const' );
if ( ! defined( 'LITESPEED_IS_HTML' ) ) {
self::debug2( 'bypass: Not frontend HTML type' );
if ( ! Control::is_cacheable() ) {
self::debug( 'bypass: Not cacheable' );
self::debug( 'finalize' );
$this->content = $content;
* Run lazyload replacement for images in buffer.
private function _finalize() {
* Use webp for optimized images.
if ( $this->webp_support() ) {
$this->content = $this->_replace_buffer_img_webp( $this->content );
* Check if URI is excluded.
$excludes = $this->conf( Base::O_MEDIA_LAZY_URI_EXC );
if ( ! defined( 'LITESPEED_GUEST_OPTM' ) ) {
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
$result = $request_uri ? Utility::str_hit_array( $request_uri, $excludes ) : false;
self::debug( 'bypass lazyload: hit URI Excludes setting: ' . $result );
$cfg_lazy = ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_LAZY ) ) && ! $this->cls( 'Metabox' )->setting( 'litespeed_no_image_lazy' );
$cfg_iframe_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_IFRAME_LAZY );
$cfg_js_delay = defined( 'LITESPEED_GUEST_OPTM' ) || 2 === $this->conf( Base::O_OPTM_JS_DEFER );
$cfg_trim_noscript = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_NOSCRIPT_RM );
$cfg_vpi = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_VPI );
$this->_parse_img_for_preload();
add_filter( 'litespeed_media_lazy_img_excludes', [ $this->cls( 'Metabox' ), 'lazy_img_excludes' ] );
list( $src_list, $html_list, $placeholder_list ) = $this->_parse_img();
$html_list_ori = $html_list;
self::debug( 'lazyload disabled' );
$__placeholder = Placeholder::cls();
foreach ( $html_list as $k => $v ) {
$size = $placeholder_list[ $k ];
$html_list[ $k ] = $__placeholder->replace( $v, $src, $size );
$this->content = str_replace( $html_list_ori, $html_list, $this->content );
if ( $cfg_iframe_lazy ) {
$html_list = $this->_parse_iframe();
$html_list_ori = $html_list;
foreach ( $html_list as $k => $v ) {
$snippet = $cfg_trim_noscript ? '' : '<noscript>' . $v . '</noscript>';
$v = str_replace( ' src=', ' data-litespeed-src=', $v );
$v = str_replace( ' src=', ' data-src=', $v );
$v = str_replace( '<iframe ', '<iframe data-lazyloaded="1" src="about:blank" ', $v );
$snippet = $v . $snippet;
$html_list[ $k ] = $snippet;
$this->content = str_replace( $html_list_ori, $html_list, $this->content );
// Include lazyload lib js and init lazyload.
if ( $cfg_lazy || $cfg_iframe_lazy ) {
$lazy_lib = '<script data-no-optimize="1">window.lazyLoadOptions=Object.assign({},{threshold:' . apply_filters( 'litespeed_lazyload_threshold', 300 ) . '},window.lazyLoadOptions||{});' . File::read( LSCWP_DIR . self::LIB_FILE_IMG_LAZYLOAD ) . '</script>';
if ( ! defined( 'LITESPEED_JS_DELAY_LIB_LOADED' ) ) {
define( 'LITESPEED_JS_DELAY_LIB_LOADED', true );
$lazy_lib .= '<script data-no-optimize="1">' . File::read( LSCWP_DIR . Optimize::LIB_FILE_JS_DELAY ) . '</script>';
$this->content = str_replace( '</body>', $lazy_lib . '</body>', $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 7.6 - Added attributes fetchpriority="high" and decode="sync" for VPI images.
private function _parse_img_for_preload() {
$is_mobile = $this->_separate_mobile();
$vpi_files = $this->cls( 'Metabox' )->setting( $is_mobile ? VPI::POST_META_MOBILE : VPI::POST_META );
$vpi_files = Utility::sanitize_lines( $vpi_files, 'basename' );
if ( ! $this->content ) {
$content = preg_replace( [ '#<!--.*-->#sU', '#<noscript([^>]*)>.*</noscript>#isU' ], '', $this->content );
preg_match_all('#<img\s+([^>]+)/?>#isU', $content, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$attrs = Utility::parse_attr($match[1]);
if ( empty( $attrs['src'] ) ) {
if ( false !== strpos( $attrs['src'], 'base64' ) || 0 === strpos( $attrs['src'], 'data:' ) ) {
self::debug2( 'lazyload bypassed base64 img' );
if ( false !== strpos( $attrs['src'], '{' ) ) {
self::debug2( 'image src has {} ' . $attrs['src'] );
// If the src contains VPI filename, then preload it.
if ( ! Utility::str_hit_array( $attrs['src'], $vpi_files ) ) {
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().
$attrs[ 'fetchpriority' ] = 'high';
$attrs[ 'decoding' ] = 'sync';
// create html with new attributes.
foreach ( $attrs as $k => $attr ) {
$new_html[] = $k . '="' . $attr . '"';
$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_replace );
* @return array{0:array,1:array,2:array} All the src & related raw html list with placeholders.
private function _parse_img() {
* @since 2.7.1 Changed to array.
$excludes = apply_filters( 'litespeed_media_lazy_img_excludes', $this->conf( Base::O_MEDIA_LAZY_EXC ) );