As the sun rises and the forest mist clears, and the clouds return and the caves darken, these changes of light and shadow are the morning and evening in the mountains. Wildflowers bloom with their subtle fragrance, fine trees flourish with their dense shade, the wind and frost are pure and clean, and the water recedes to reveal the rocks—these are the four seasons in the mountains. Going out in the morning and returning in the evening, the scenery of the four seasons is different, and the joy is endless.至于负者歌于途,行者休于树,前者呼,后者应,伛偻提携,往来而不绝者,滁人游也。临溪而渔,溪深而鱼肥,酿泉为酒,泉香而酒洌,山肴野蔌,杂然而前陈者,太守宴也。宴酣之乐,非丝非竹,射者中,弈者胜,觥筹交错,起坐而喧哗者,众宾欢也。苍颜白发,颓然乎其间者,太守醉也。
<?php
/**
* Image optimization management trait
*
* @package LiteSpeed
* @since 7.8
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit();
/**
* Trait Img_Optm_Manage
*
* Handles image optimization management operations: clean, destroy, rescan, backup, batch_switch, etc.
*/
trait Img_Optm_Manage {
/**
* Check if an attachment has optimization records
*
* @since 7.8
* @access public
* @param int $post_id The attachment post ID.
* @param array|null $metadata Optional. Attachment metadata for file path check.
* @return bool True if has optimization records.
*/
public function has_optm_record( $post_id, $metadata = null ) {
global $wpdb;
if ( ! $post_id ) {
return false;
}
// Check post meta
if ( get_post_meta( $post_id, self::DB_SIZE, true ) || get_post_meta( $post_id, self::DB_SET, true ) ) {
return true;
}
// Check img_optm table (legacy)
if ( $this->__data->tb_exist( 'img_optm' ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM `$this->_table_img_optm` WHERE post_id = %d", $post_id ) );
if ( $count > 0 ) {
return true;
}
}
// Check img_optming table
if ( $this->__data->tb_exist( 'img_optming' ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE post_id = %d", $post_id ) );
if ( $count > 0 ) {
return true;
}
}
// Check if optimized files exist (.webp, .avif)
if ( null === $metadata ) {
$metadata = wp_get_attachment_metadata( $post_id );
}
if ( ! empty( $metadata['file'] ) ) {
$short_file_path = $metadata['file'];
if ( $this->__media->info( $short_file_path . '.webp', $post_id ) ) {
return true;
}
if ( $this->__media->info( $short_file_path . '.avif', $post_id ) ) {
return true;
}
}
return false;
}
/**
* Clean up all unfinished queue locally and to Cloud server
*
* @since 2.1.2
* @access public
*/
public function clean() {
global $wpdb;
// Reset img_optm table's queue
if ( $this->__data->tb_exist( 'img_optming' ) ) {
// Get min post id to mark
$q = "SELECT MIN(post_id) FROM `$this->_table_img_optming`";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$min_pid = $wpdb->get_var( $q ) - 1;
if ( $this->_summary['next_post_id'] > $min_pid ) {
$this->_summary['next_post_id'] = $min_pid;
self::save_summary();
}
$q = "DELETE FROM `$this->_table_img_optming`";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $q );
}
$msg = __( 'Cleaned up unfinished data successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Reset image counter
*
* @since 7.0
* @access private
*/
private function _reset_counter() {
self::debug( 'reset image optm counter' );
$this->_summary['next_post_id'] = 0;
self::save_summary();
$this->clean();
$msg = __( 'Reset image optimization counter successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Destroy all optimized images
*
* @since 3.0
* @access private
*/
private function _destroy() {
global $wpdb;
self::debug( 'executing DESTROY process' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0;
/**
* Limit images each time before redirection to fix Out of memory issue. #665465
*
* @since 2.9.8
*/
// Start deleting files
$limit = apply_filters( 'litespeed_imgoptm_destroy_max_rows', 500 );
$img_q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
ORDER BY a.ID
LIMIT %d,%d
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $q );
$i = 0;
foreach ( $list as $v ) {
if ( ! $v->post_id ) {
continue;
}
$meta_value = $this->_parse_wp_meta_value( $v );
if ( ! $meta_value ) {
continue;
}
++$i;
$this->tmp_pid = $v->post_id;
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_destroy_optm_file( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
array_map( [ $this, '_destroy_optm_file' ], $meta_value['sizes'] );
}
}
self::debug( 'batch switched images total: ' . $i );
++$offset;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) );
if ( $to_be_continued ) {
// Check if post_id is beyond next_post_id
self::debug( '[next_post_id] ' . $this->_summary['next_post_id'] . ' [cursor post id] ' . $to_be_continued->post_id );
if ( $to_be_continued->post_id <= $this->_summary['next_post_id'] ) {
self::debug( 'redirecting to next' );
return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_DESTROY );
}
self::debug( '🎊 Finished destroying' );
}
// Delete postmeta info
$q = "DELETE FROM `$wpdb->postmeta` WHERE meta_key = %s";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, self::DB_SIZE ) );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, self::DB_SET ) );
// Delete img_optm table
$this->__data->tb_del( 'img_optm' );
$this->__data->tb_del( 'img_optming' );
// Clear options table summary info
self::delete_option( '_summary' );
self::delete_option( self::DB_NEED_PULL );
$msg = __( 'Destroy all optimization data successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Destroy optm file
*
* @since 3.0
* @access private
* @param array $meta_value The meta value array containing file info.
* @param bool $is_ori_file Whether this is the original file.
*/
private function _destroy_optm_file( $meta_value, $is_ori_file = false ) {
$short_file_path = $meta_value['file'];
if ( ! $is_ori_file ) {
$short_file_path = $this->tmp_path . $short_file_path;
}
self::debug( 'deleting ' . $short_file_path );
// del webp
$this->__media->info( $short_file_path . '.webp', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.webp', $this->tmp_pid );
$this->__media->info( $short_file_path . '.optm.webp', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.optm.webp', $this->tmp_pid );
// del avif
$this->__media->info( $short_file_path . '.avif', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.avif', $this->tmp_pid );
$this->__media->info( $short_file_path . '.optm.avif', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.optm.avif', $this->tmp_pid );
$extension = pathinfo( $short_file_path, PATHINFO_EXTENSION );
$local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 );
$bk_file = $local_filename . '.bk.' . $extension;
$bk_optm_file = $local_filename . '.bk.optm.' . $extension;
// del optimized ori
if ( $this->__media->info( $bk_file, $this->tmp_pid ) ) {
self::debug( 'deleting optim ori' );
$this->__media->del( $short_file_path, $this->tmp_pid );
$this->__media->rename( $bk_file, $short_file_path, $this->tmp_pid );
}
$this->__media->info( $bk_optm_file, $this->tmp_pid ) && $this->__media->del( $bk_optm_file, $this->tmp_pid );
}
/**
* Rescan to find new generated images
*
* @since 1.6.7
* @access private
*/
private function _rescan() {
// phpcs:ignore Squiz.PHP.NonExecutableCode
exit( 'tobedone' );
// phpcs:disable Squiz.PHP.NonExecutableCode
global $wpdb;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0;
$limit = 500;
self::debug( 'rescan images' );
// Get images
$q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->posts` a, `$wpdb->postmeta` b
WHERE a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
AND a.ID = b.post_id
AND b.meta_key = '_wp_attachment_metadata'
ORDER BY a.ID
LIMIT %d, %d
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $wpdb->prepare( $q, $offset * $limit, $limit + 1 ) ); // last one is the seed for next batch
if ( ! $list ) {
$msg = __( 'Rescanned successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
self::debug( 'rescan bypass: no gathered image found' );
return;
}
if ( count( $list ) === $limit + 1 ) {
$to_be_continued = true;
array_pop( $list ); // last one is the seed for next round, discard here.
} else {
$to_be_continued = false;
}
// Prepare post_ids to inquery gathered images
$pid_set = [];
$scanned_list = [];
foreach ( $list as $v ) {
$meta_value = $this->_parse_wp_meta_value( $v );
if ( ! $meta_value ) {
continue;
}
$scanned_list[] = [
'pid' => $v->post_id,
'meta' => $meta_value,
];
$pid_set[] = $v->post_id;
}
// Build gathered images
$q = "SELECT src, post_id FROM `$this->_table_img_optm` WHERE post_id IN (" . implode( ',', array_fill( 0, count( $pid_set ), '%d' ) ) . ')';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $wpdb->prepare( $q, $pid_set ) );
foreach ( $list as $v ) {
$this->_existed_src_list[] = $v->post_id . '.' . $v->src;
}
// Find new images
foreach ( $scanned_list as $v ) {
$meta_value = $v['meta'];
// Parse all child src and put them into $this->_img_in_queue, missing ones to $this->_img_in_queue_missed
$this->tmp_pid = $v['pid'];
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_append_img_queue( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) {
$this->_append_img_queue( $img_size, false, $img_size_name );
}
}
}
self::debug( 'rescanned [img] ' . count( $this->_img_in_queue ) );
$count = count( $this->_img_in_queue );
if ( $count > 0 ) {
// Save to DB
$this->_save_raw();
}
if ( $to_be_continued ) {
return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_RESCAN );
}
$msg = $count ? sprintf( __( 'Rescanned %d images successfully.', 'litespeed-cache' ), $count ) : __( 'Rescanned successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
// phpcs:enable Squiz.PHP.NonExecutableCode
}
/**
* Calculate bkup original images storage
*
* @since 2.2.6
* @access private
*/
private function _calc_bkup() {
global $wpdb;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0;
$limit = 500;
if ( ! $offset ) {
$this->_summary['bk_summary'] = [
'date' => time(),
'count' => 0,
'sum' => 0,
];
}
$img_q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
ORDER BY a.ID
LIMIT %d,%d
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $q );
foreach ( $list as $v ) {
if ( ! $v->post_id ) {
continue;
}
$meta_value = $this->_parse_wp_meta_value( $v );
if ( ! $meta_value ) {
continue;
}
$this->tmp_pid = $v->post_id;
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_get_bk_size( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
array_map( [ $this, '_get_bk_size' ], $meta_value['sizes'] );
}
}
$this->_summary['bk_summary']['date'] = time();
self::save_summary();
self::debug( '_calc_bkup total: ' . $this->_summary['bk_summary']['count'] . ' [size] ' . $this->_summary['bk_summary']['sum'] );
++$offset;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) );
if ( $to_be_continued ) {
return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_CALC_BKUP );
}
$msg = __( 'Calculated backups successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Calculate single size
*
* @since 2.2.6
* @access private
* @param array $meta_value The meta value array containing file info.
* @param bool $is_ori_file Whether this is the original file.
*/
private function _get_bk_size( $meta_value, $is_ori_file = false ) {
$short_file_path = $meta_value['file'];
if ( ! $is_ori_file ) {
$short_file_path = $this->tmp_path . $short_file_path;
}
$extension = pathinfo( $short_file_path, PATHINFO_EXTENSION );
$local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 );
$bk_file = $local_filename . '.bk.' . $extension;
$img_info = $this->__media->info( $bk_file, $this->tmp_pid );
if ( ! $img_info ) {
return;
}
++$this->_summary['bk_summary']['count'];
$this->_summary['bk_summary']['sum'] += $img_info['size'];
}
/**
* Delete bkup original images storage
*
* @since 2.5
* @access public
*/
public function rm_bkup() {
global $wpdb;
if ( ! $this->__data->tb_exist( 'img_optming' ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0;
$limit = 500;
if ( empty( $this->_summary['rmbk_summary'] ) ) {
$this->_summary['rmbk_summary'] = [
'date' => time(),
'count' => 0,
'sum' => 0,
];
}
$img_q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
ORDER BY a.ID
LIMIT %d,%d
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $q );
foreach ( $list as $v ) {
if ( ! $v->post_id ) {
continue;
}
$meta_value = $this->_parse_wp_meta_value( $v );
if ( ! $meta_value ) {
continue;
}
$this->tmp_pid = $v->post_id;
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_del_bk_file( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
array_map( [ $this, '_del_bk_file' ], $meta_value['sizes'] );
}
}
$this->_summary['rmbk_summary']['date'] = time();
self::save_summary();
self::debug( 'rm_bkup total: ' . $this->_summary['rmbk_summary']['count'] . ' [size] ' . $this->_summary['rmbk_summary']['sum'] );
++$offset;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) );
if ( $to_be_continued ) {
return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_RM_BKUP );
}
$msg = __( 'Removed backups successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Delete single file
*
* @since 2.5
* @access private
* @param array $meta_value The meta value array containing file info.
* @param bool $is_ori_file Whether this is the original file.
*/
private function _del_bk_file( $meta_value, $is_ori_file = false ) {
$short_file_path = $meta_value['file'];
if ( ! $is_ori_file ) {
$short_file_path = $this->tmp_path . $short_file_path;
}
$extension = pathinfo( $short_file_path, PATHINFO_EXTENSION );
$local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 );
$bk_file = $local_filename . '.bk.' . $extension;
$img_info = $this->__media->info( $bk_file, $this->tmp_pid );
if ( ! $img_info ) {
return;
}
++$this->_summary['rmbk_summary']['count'];
$this->_summary['rmbk_summary']['sum'] += $img_info['size'];
$this->__media->del( $bk_file, $this->tmp_pid );
}
/**
* Count images
*
* @since 1.6
* @access public
* @return array Image count data.
*/
public function img_count() {
global $wpdb;
$q = "SELECT count(*)
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$groups_all = $wpdb->get_var( $q );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$groups_new = $wpdb->get_var( $q . ' AND ID>' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID' );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$groups_done = $wpdb->get_var( $q . ' AND ID<=' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID' );
$q = "SELECT b.post_id
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
ORDER BY a.ID DESC
LIMIT 1
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$max_id = $wpdb->get_var( $q );
$count_list = [
'max_id' => $max_id,
'groups_all' => $groups_all,
'groups_new' => $groups_new,
'groups_done' => $groups_done,
];
// images count from work table
if ( $this->__data->tb_exist( 'img_optming' ) ) {
$q = "SELECT COUNT(DISTINCT post_id),COUNT(*) FROM `$this->_table_img_optming` WHERE optm_status = %d";
$groups_to_check = [ self::STATUS_RAW, self::STATUS_REQUESTED, self::STATUS_NOTIFIED, self::STATUS_ERR_FETCH ];
foreach ( $groups_to_check as $v ) {
$count_list[ 'img.' . $v ] = 0;
$count_list[ 'group.' . $v ] = 0;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
list( $count_list[ 'group.' . $v ], $count_list[ 'img.' . $v ] ) = $wpdb->get_row( $wpdb->prepare( $q, $v ), ARRAY_N );
}
}
return $count_list;
}
/**
* Check if fetch cron is running
*
* @since 1.6.2
* @access public
* @param bool $bool_res Whether to return boolean result.
* @return bool|array Boolean result or array with last run time and status.
*/
public function cron_running( $bool_res = true ) {
$last_run = ! empty( $this->_summary['last_pull'] ) ? $this->_summary['last_pull'] : 0;
$is_running = $last_run && time() - $last_run < 120;
if ( $bool_res ) {
return $is_running;
}
return [ $last_run, $is_running ];
}
/**
* Update fetch cron timestamp tag
*
* @since 1.6.2
* @access private
* @param bool $done Whether the cron job is done.
*/
private function _update_cron_running( $done = false ) {
$this->_summary['last_pull'] = time();
if ( $done ) {
// Only update cron tag when its from the active running cron
if ( $this->_cron_ran ) {
// Rollback for next running
$this->_summary['last_pull'] -= 120;
} else {
return;
}
}
self::save_summary();
$this->_cron_ran = true;
}
/**
* Batch switch images to ori/optm version
*
* @since 1.6.2
* @access public
* @param string $type The switch type (batch_switch_ori or batch_switch_optm).
*/
public function batch_switch( $type ) {
if ( defined( 'LITESPEED_CLI' ) || wp_doing_cron() ) {
$offset = 0;
while ( 'done' !== $offset ) {
Admin_Display::info( "Starting switch to $type [offset] $offset" );
$offset = $this->_batch_switch( $type, $offset );
}
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0;
$new_offset = $this->_batch_switch( $type, $offset );
if ( 'done' !== $new_offset ) {
return Router::self_redirect( Router::ACTION_IMG_OPTM, $type );
}
}
$msg = __( 'Switched images successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Switch images per offset
*
* @since 1.6.2
* @access private
* @param string $type The switch type.
* @param int $offset The current offset.
* @return int|string Next offset or 'done'.
*/
private function _batch_switch( $type, $offset ) {
global $wpdb;
$limit = 500;
$this->tmp_type = $type;
$img_q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->posts` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
WHERE b.meta_key = '_wp_attachment_metadata'
AND a.post_type = 'attachment'
AND a.post_status = 'inherit'
AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
ORDER BY a.ID
LIMIT %d,%d
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $q );
$i = 0;
foreach ( $list as $v ) {
if ( ! $v->post_id ) {
continue;
}
$meta_value = $this->_parse_wp_meta_value( $v );
if ( ! $meta_value ) {
continue;
}
++$i;
$this->tmp_pid = $v->post_id;
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_switch_bk_file( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
array_map( [ $this, '_switch_bk_file' ], $meta_value['sizes'] );
}
}
self::debug( 'batch switched images total: ' . $i . ' [type] ' . $type );
++$offset;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) );
if ( $to_be_continued ) {
return $offset;
}
return 'done';
}
/**
* Switch backup file between original and optimized
*
* @since 1.6.2
* @access private
* @param array $meta_value The meta value array containing file info.
* @param bool $is_ori_file Whether this is the original file.
*/
private function _switch_bk_file( $meta_value, $is_ori_file = false ) {
$short_file_path = $meta_value['file'];
if ( ! $is_ori_file ) {
$short_file_path = $this->tmp_path . $short_file_path;
}
$extension = pathinfo( $short_file_path, PATHINFO_EXTENSION );
$local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 );
$bk_file = $local_filename . '.bk.' . $extension;
$bk_optm_file = $local_filename . '.bk.optm.' . $extension;
// self::debug('_switch_bk_file ' . $bk_file . ' [type] ' . $this->tmp_type);
// switch to ori
if ( self::TYPE_BATCH_SWITCH_ORI === $this->tmp_type || 'orig' === $this->tmp_type ) {
// self::debug('switch to orig ' . $bk_file);
if ( ! $this->__media->info( $bk_file, $this->tmp_pid ) ) {
return;
}
$this->__media->rename( $local_filename . '.' . $extension, $bk_optm_file, $this->tmp_pid );
$this->__media->rename( $bk_file, $local_filename . '.' . $extension, $this->tmp_pid );
} elseif ( self::TYPE_BATCH_SWITCH_OPTM === $this->tmp_type || 'optm' === $this->tmp_type ) {
// switch to optm
// self::debug('switch to optm ' . $bk_file);
if ( ! $this->__media->info( $bk_optm_file, $this->tmp_pid ) ) {
return;
}
$this->__media->rename( $local_filename . '.' . $extension, $bk_file, $this->tmp_pid );
$this->__media->rename( $bk_optm_file, $local_filename . '.' . $extension, $this->tmp_pid );
}
}
/**
* Switch image between original one and optimized one
*
* @since 1.6.2
* @access private
* @param string $type The switch type (webpXXX, avifXXX, or origXXX where XXX is the post ID).
*/
private function _switch_optm_file( $type ) {
Admin_Display::success( __( 'Switched to optimized file successfully.', 'litespeed-cache' ) );
return;
// phpcs:disable Squiz.PHP.NonExecutableCode
global $wpdb;
$pid = substr( $type, 4 );
$switch_type = substr( $type, 0, 4 );
$q = "SELECT src,post_id FROM `$this->_table_img_optm` WHERE post_id = %d AND optm_status = %d";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $wpdb->prepare( $q, [ $pid, self::STATUS_PULLED ] ) );
$msg = 'Unknown Msg';
foreach ( $list as $v ) {
// to switch webp file
if ( 'webp' === $switch_type ) {
if ( $this->__media->info( $v->src . '.webp', $v->post_id ) ) {
$this->__media->rename( $v->src . '.webp', $v->src . '.optm.webp', $v->post_id );
self::debug( 'Disabled WebP: ' . $v->src );
$msg = __( 'Disabled WebP file successfully.', 'litespeed-cache' );
} elseif ( $this->__media->info( $v->src . '.optm.webp', $v->post_id ) ) {
$this->__media->rename( $v->src . '.optm.webp', $v->src . '.webp', $v->post_id );
self::debug( 'Enable WebP: ' . $v->src );
$msg = __( 'Enabled WebP file successfully.', 'litespeed-cache' );
}
} elseif ( 'avif' === $switch_type ) {
// to switch avif file
if ( $this->__media->info( $v->src . '.avif', $v->post_id ) ) {
$this->__media->rename( $v->src . '.avif', $v->src . '.optm.avif', $v->post_id );
self::debug( 'Disabled AVIF: ' . $v->src );
$msg = __( 'Disabled AVIF file successfully.', 'litespeed-cache' );
} elseif ( $this->__media->info( $v->src . '.optm.avif', $v->post_id ) ) {
$this->__media->rename( $v->src . '.optm.avif', $v->src . '.avif', $v->post_id );
self::debug( 'Enable AVIF: ' . $v->src );
$msg = __( 'Enabled AVIF file successfully.', 'litespeed-cache' );
}
} else {
// to switch original file
$extension = pathinfo( $v->src, PATHINFO_EXTENSION );
$local_filename = substr( $v->src, 0, -strlen( $extension ) - 1 );
$bk_file = $local_filename . '.bk.' . $extension;
$bk_optm_file = $local_filename . '.bk.optm.' . $extension;
// revert ori back
if ( $this->__media->info( $bk_file, $v->post_id ) ) {
$this->__media->rename( $v->src, $bk_optm_file, $v->post_id );
$this->__media->rename( $bk_file, $v->src, $v->post_id );
self::debug( 'Restore original img: ' . $bk_file );
$msg = __( 'Restored original file successfully.', 'litespeed-cache' );
} elseif ( $this->__media->info( $bk_optm_file, $v->post_id ) ) {
$this->__media->rename( $v->src, $bk_file, $v->post_id );
$this->__media->rename( $bk_optm_file, $v->src, $v->post_id );
self::debug( 'Switch to optm img: ' . $v->src );
$msg = __( 'Switched to optimized file successfully.', 'litespeed-cache' );
}
}
}
Admin_Display::success( $msg );
// phpcs:enable Squiz.PHP.NonExecutableCode
}
/**
* Delete one optm data and recover original file
*
* @since 2.4.2
* @access public
* @param int $post_id The post ID to reset.
* @param bool $silent Whether to suppress success message. Default false.
*/
public function reset_row( $post_id, $silent = false ) {
global $wpdb;
if ( ! $post_id ) {
return;
}
self::debug( '_reset_row [pid] ' . $post_id );
// TODO: Load image sub files
$img_q = "SELECT b.post_id, b.meta_value
FROM `$wpdb->postmeta` b
WHERE b.post_id =%d AND b.meta_key = '_wp_attachment_metadata'";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = $wpdb->prepare( $img_q, [ $post_id ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$v = $wpdb->get_row( $q );
$meta_value = $this->_parse_wp_meta_value( $v );
if ( $meta_value ) {
$this->tmp_pid = $v->post_id;
$this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
$this->_destroy_optm_file( $meta_value, true );
if ( ! empty( $meta_value['sizes'] ) ) {
array_map( [ $this, '_destroy_optm_file' ], $meta_value['sizes'] );
}
}
delete_post_meta( $post_id, self::DB_SIZE );
delete_post_meta( $post_id, self::DB_SET );
// Delete records from img_optm and img_optming tables
if ( $this->__data->tb_exist( 'img_optm' ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optm` WHERE post_id = %d", $post_id ) );
}
if ( $this->__data->tb_exist( 'img_optming' ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optming` WHERE post_id = %d", $post_id ) );
}
if ( ! $silent ) {
$msg = __( 'Reset the optimized data successfully.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
}
/**
* Show an image's optm status
*
* @since 1.6.5
* @access public
* @return array Response data with image info.
*/
public function check_img() {
global $wpdb;
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$pid = isset( $_POST['data'] ) ? absint( wp_unslash( $_POST['data'] ) ) : 0;
self::debug( 'Check image [ID] ' . $pid );
$data = [];
$data['img_count'] = $this->img_count();
$data['optm_summary'] = self::get_summary();
$data['_wp_attached_file'] = get_post_meta( $pid, '_wp_attached_file', true );
$data['_wp_attachment_metadata'] = get_post_meta( $pid, '_wp_attachment_metadata', true );
// Get img_optm data
$q = "SELECT * FROM `$this->_table_img_optm` WHERE post_id = %d";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $wpdb->prepare( $q, $pid ) );
$img_data = [];
if ( $list ) {
foreach ( $list as $v ) {
$img_data[] = [
'id' => $v->id,
'optm_status' => $v->optm_status,
'src' => $v->src,
'srcpath_md5' => $v->srcpath_md5,
'src_md5' => $v->src_md5,
'server_info' => $v->server_info,
];
}
}
$data['img_data'] = $img_data;
return [
'_res' => 'ok',
'data' => $data,
];
}
}