*/ namespace LiteSpeed; defined( 'WPINC' ) || exit; class Media extends Root { const LIB_FILE_IMG_LAZYLOAD = 'assets/js/lazyload.min.js'; private $content; private $_wp_upload_dir; /** * Init * * @since 1.4 */ public function __construct() { Debug2::debug2( '[Media] init' ); $this->_wp_upload_dir = wp_upload_dir(); } /** * Init optm features * * @since 3.0 * @access public */ public function init() { if ( is_admin() ) { return; } // Due to ajax call doesn't send correct accept header, have to limit webp to HTML only if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) { if ( $this->webp_support() ) { // Hook to srcset if ( function_exists( 'wp_calculate_image_srcset' ) ) { add_filter( 'wp_calculate_image_srcset', array( $this, 'webp_srcset' ), 988 ); } // Hook to mime icon // add_filter( 'wp_get_attachment_image_src', array( $this, 'webp_attach_img_src' ), 988 );// todo: need to check why not // add_filter( 'wp_get_attachment_url', array( $this, 'webp_url' ), 988 ); // disabled to avoid wp-admin display } } /** * Replace gravatar * @since 3.0 */ $this->cls( 'Avatar' ); add_filter( 'litespeed_buffer_finalize', array( $this, 'finalize' ), 4 ); } /** * Adjust WP default JPG quality * * @since 3.0 * @access public */ public function adjust_jpg_quality( $quality ) { $v = $this->conf( Base::O_IMG_OPTM_JPG_QUALITY ); if ( $v ) { return $v; } return $quality; } /** * Register admin menu * * @since 1.6.3 * @access public */ public function after_admin_init() { /** * JPG quality control * @since 3.0 */ add_filter( 'jpeg_quality', array( $this, 'adjust_jpg_quality' ) ); add_filter( 'manage_media_columns', array( $this, 'media_row_title' ) ); add_filter( 'manage_media_custom_column', array( $this, 'media_row_actions' ), 10, 2 ); add_action( 'litespeed_media_row', array( $this, 'media_row_con' ) ); // Hook to attachment delete action add_action( 'delete_attachment', __CLASS__ . '::delete_attachment' ); } /** * Media delete action hook * * @since 2.4.3 * @access public */ public static function delete_attachment( $post_id ) { if ( ! Data::cls()->tb_exist( 'img_optm' ) ) { return; } Debug2::debug( '[Media] delete_attachment [pid] ' . $post_id ); Img_Optm::cls()->reset_row( $post_id ); } /** * Return media file info if exists * * This is for remote attachment plugins * * @since 2.9.8 * @access public */ public function info( $short_file_path, $post_id ) { $real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path; if ( file_exists( $real_file ) ) { return array( 'url' => $this->_wp_upload_dir[ 'baseurl' ] . '/' . $short_file_path, 'md5' => md5_file( $real_file ), 'size' => filesize( $real_file ), ); } /** * WP Stateless compatibility #143 https://github.com/litespeedtech/lscache_wp/issues/143 * @since 2.9.8 * @return array( 'url', 'md5', 'size' ) */ $info = apply_filters( 'litespeed_media_info', array(), $short_file_path, $post_id ); if ( ! empty( $info[ 'url' ] ) && ! empty( $info[ 'md5' ] ) && ! empty( $info[ 'size' ] ) ) { return $info; } return false; } /** * Delete media file * * @since 2.9.8 * @access public */ public function del( $short_file_path, $post_id ) { $real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path; if ( file_exists( $real_file ) ) { unlink( $real_file ); Debug2::debug( '[Media] deleted ' . $real_file ); } do_action( 'litespeed_media_del', $short_file_path, $post_id ); } /** * Rename media file * * @since 2.9.8 * @access public */ public function rename( $short_file_path, $short_file_path_new, $post_id ) { $real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path; $real_file_new = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path_new; if ( file_exists( $real_file ) ) { rename( $real_file, $real_file_new ); Debug2::debug( '[Media] renamed ' . $real_file . ' to ' . $real_file_new ); } do_action( 'litespeed_media_rename', $short_file_path, $short_file_path_new, $post_id ); } /** * Media Admin Menu -> Image Optimization Column Title * * @since 1.6.3 * @access public */ public function media_row_title( $posts_columns ) { $posts_columns[ 'imgoptm' ] = __( 'LiteSpeed Optimization', 'litespeed-cache' ); return $posts_columns; } /** * Media Admin Menu -> Image Optimization Column * * @since 1.6.2 * @access public */ public function media_row_actions( $column_name, $post_id ) { if ( $column_name !== 'imgoptm' ) { return; } do_action( 'litespeed_media_row', $post_id ); } /** * Display image optm info * * @since 3.0 */ public function media_row_con( $post_id ) { $att_info = wp_get_attachment_metadata( $post_id ); if ( empty( $att_info[ 'file' ] ) ) { return; } $short_path = $att_info[ 'file' ]; $size_meta = get_post_meta( $post_id, Img_Optm::DB_SIZE, true ); echo '
'; // Original image info if ( $size_meta && ! empty ( $size_meta[ 'ori_saved' ] ) ) { $percent = ceil( $size_meta[ 'ori_saved' ] * 100 / $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 ); $desc = false; $cls = ''; if ( $this->info( $bk_file, $post_id ) ) { $curr_status = __( '(optm)', 'litespeed-cache' ); $desc = __( 'Currently using optimized version of file.', 'litespeed-cache' ) . ' ' . __( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' ); } elseif ( $this->info( $bk_optm_file, $post_id ) ) { $cls .= ' litespeed-warning'; $curr_status = __( '(non-optm)', 'litespeed-cache' ); $desc = __( 'Currently using original (unoptimized) version of file.', 'litespeed-cache' ) . ' ' . __( 'Click to switch to optimized version.', 'litespeed-cache' ); } echo GUI::pie_tiny( $percent, 24, sprintf( __( 'Original file reduced by %1$s (%2$s)', 'litespeed-cache' ), $percent . '%', Utility::real_size( $size_meta[ 'ori_saved' ] ) ) , 'left' ); echo sprintf( __( 'Orig saved %s', 'litespeed-cache' ), $percent . '%' ); if ( $desc ) { echo sprintf( ' %4$s', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' %2$s', __( 'Using optimized version of file. ', 'litespeed-cache' ) . ' ' . __( 'No backup of original file exists.', 'litespeed-cache' ), __( '(optm)', 'litespeed-cache' ) ); } } elseif ( $size_meta && $size_meta[ 'ori_saved' ] === 0 ){ echo GUI::pie_tiny( 0, 24, __( 'Congratulation! Your file was already optmized', 'litespeed-cache' ), 'left' ); echo sprintf( __( 'Orig %s', 'litespeed-cache' ), '' . __( '(no savings)', 'litespeed-cache' ) . '' ); } else { echo __( 'Orig', 'litespeed-cache' ) . '—'; } echo '
'; echo ''; // WebP info if ( $size_meta && ! empty ( $size_meta[ 'webp_saved' ] ) ) { $percent = ceil( $size_meta[ 'webp_saved' ] * 100 / $size_meta[ 'webp_total' ] ); $link = Utility::build_url( Router::ACTION_IMG_OPTM, 'webp' . $post_id ); $desc = false; $cls = ''; if ( $this->info( $short_path . '.webp', $post_id ) ) { $curr_status = __( '(optm)', 'litespeed-cache' ); $desc = __( 'Currently using optimized version of WebP file.', 'litespeed-cache' ) . ' ' . __( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' ); } elseif ( $this->info( $short_path . '.optm.webp', $post_id ) ) { $cls .= ' litespeed-warning'; $curr_status = __( '(non-optm)', 'litespeed-cache' ); $desc = __( 'Currently using original (unoptimized) version of WebP file.', 'litespeed-cache' ) . ' ' . __( 'Click to switch to optimized version.', 'litespeed-cache' ); } echo GUI::pie_tiny( $percent, 24, sprintf( __( 'WebP file reduced by %1$s (%2$s)', 'litespeed-cache' ), $percent . '%', Utility::real_size( $size_meta[ 'webp_saved' ] ) ) , 'left' ); echo sprintf( __( 'WebP saved %s', 'litespeed-cache' ), $percent . '%' ); if ( $desc ) { echo sprintf( ' %4$s', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' %2$s', __( 'Using optimized version of file. ', 'litespeed-cache' ) . ' ' . __( 'No backup of unoptimized WebP file exists.', 'litespeed-cache' ), __( '(optm)', 'litespeed-cache' ) ); } } else { echo __( 'WebP', 'litespeed-cache' ) . '—'; } echo '
'; // Delete row btn if ( $size_meta ) { echo sprintf( '', Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, array( 'id' => $post_id ) ), __( 'Restore from backup', 'litespeed-cache' ) ); echo ''; } } /** * Get wp size info * * NOTE: this is not used because it has to be after admin_init * * @since 1.6.2 * @return array $sizes Data for all currently-registered image sizes. */ public function get_image_sizes() { global $_wp_additional_image_sizes; $sizes = array(); foreach ( get_intermediate_image_sizes() as $_size ) { if ( in_array( $_size, array( 'thumbnail', 'medium', 'medium_large', 'large' ) ) ) { $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 ] ) ) { $sizes[ $_size ] = array( 'width' => $_wp_additional_image_sizes[ $_size ][ 'width' ], 'height' => $_wp_additional_image_sizes[ $_size ][ 'height' ], 'crop' => $_wp_additional_image_sizes[ $_size ][ 'crop' ] ); } } return $sizes; } /** * Exclude role from optimization filter * * @since 1.6.2 * @access public */ public function webp_support() { if ( ! empty( $_SERVER[ 'HTTP_ACCEPT' ] ) && strpos( $_SERVER[ 'HTTP_ACCEPT' ], 'image/webp' ) !== false ) { return true; } if ( ! empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) ) { if ( strpos( $_SERVER[ 'HTTP_USER_AGENT' ], 'Page Speed' ) !== false ) { return true; } if ( preg_match( "/iPhone OS (\d+)_/i", $_SERVER[ 'HTTP_USER_AGENT' ], $matches ) ) { $lscwp_ios_version = $matches[1]; if ($lscwp_ios_version >= 14){ return true; } } } return false; } /** * Run lazy load process * 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. * * @since 1.4 * @access public * @return string The buffer */ public function finalize( $content ) { if ( defined( 'LITESPEED_NO_LAZY' ) ) { Debug2::debug2( '[Media] bypass: NO_LAZY const' ); return $content; } if ( ! defined( 'LITESPEED_IS_HTML' ) ) { Debug2::debug2( '[Media] bypass: Not frontend HTML type' ); return $content; } if ( ! Control::is_cacheable() ) { Debug2::debug( '[Media] bypass: Not cacheable' ); return $content; } Debug2::debug( '[Media] finalize' ); $this->content = $content; $this->_finalize(); return $this->content; } /** * Run lazyload replacement for images in buffer * * @since 1.4 * @access private */ private function _finalize() { /** * Use webp for optimized images * @since 1.6.2 */ if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) && $this->webp_support() ) { $this->content = $this->_replace_buffer_img_webp( $this->content ); } /** * Check if URI is excluded * @since 3.0 */ $excludes = $this->conf( Base::O_MEDIA_LAZY_URI_EXC ); if ( ! defined( 'LITESPEED_GUEST_OPTM' ) ) { $result = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes ); if ( $result ) { Debug2::debug( '[Media] bypass lazyload: hit URI Excludes setting: ' . $result ); return; } } $cfg_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_LAZY ); $cfg_iframe_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_IFRAME_LAZY ); $cfg_js_delay = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_JS_DEFER ) == 2; $cfg_trim_noscript = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_NOSCRIPT_RM ); if ( $cfg_lazy ) { list( $src_list, $html_list, $placeholder_list ) = $this->_parse_img(); $html_list_ori = $html_list; } // image lazy load if ( $cfg_lazy ) { $__placeholder = Placeholder::cls(); foreach ( $html_list as $k => $v ) { $size = $placeholder_list[ $k ]; $src = $src_list[ $k ]; $html_list[ $k ] = $__placeholder->replace( $v, $src, $size ); } } if ( $cfg_lazy ) { $this->content = str_replace( $html_list_ori, $html_list, $this->content ); } // iframe lazy load if ( $cfg_iframe_lazy ) { $html_list = $this->_parse_iframe(); $html_list_ori = $html_list; foreach ( $html_list as $k => $v ) { $snippet = $cfg_trim_noscript ? '' : ''; if ( $cfg_js_delay ) { $v = str_replace( ' src=', ' data-litespeed-src=', $v ); } else { $v = str_replace( ' src=', ' data-src=', $v ); } $v = str_replace( '